diff --git a/.coveralls.yml b/.coveralls.yml
new file mode 100644
index 0000000..b8cb438
--- /dev/null
+++ b/.coveralls.yml
@@ -0,0 +1,3 @@
+service_name: travis-ci
+coverage_clover: tests/_output/coverage.xml
+json_path: tests/_output/coveralls-upload.json
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..436d305
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,35 @@
+# The following files should be ignored:
+# - unencrypted sensitive data
+# - files that are not checked into the code repository
+# - files that are not relevant to the Docker build
+
+.git
+.idea
+bin
+docker-output
+docs
+img
+
+tests/_output
+!tests/_output/.gitignore
+tests/_support/_generated
+!tests/_support/_generated/.gitignore
+
+vendor
+!vendor/autoload.php
+!vendor/ivome/graphql-relay-php/src
+!vendor/webonyx/graphql-php/src
+!vendor/composer
+
+.dockerignore
+.gitignore
+.travis.yml
+CODE_OF_CONDUCT.md
+CONTRIBUTING.md
+Dockerfile*
+ISSUE_TEMPLATE.md
+LICENSE
+PULL_REQUEST_TEMPLATE.md
+README.md
+readme.txt
+run-docker*.sh
diff --git a/.env b/.env
new file mode 100644
index 0000000..ec17662
--- /dev/null
+++ b/.env
@@ -0,0 +1,31 @@
+DB_NAME=wordpress
+DB_HOST=app_db
+DB_USER=wordpress
+DB_PASSWORD=wordpress
+WP_TABLE_PREFIX=wp_
+WP_URL=http://localhost
+WP_DOMAIN=localhost
+ADMIN_EMAIL=admin@example.com
+ADMIN_USERNAME=admin
+ADMIN_PASSWORD=password
+ADMIN_PATH=/wp-admin
+
+TEST_DB_NAME=wpgraphql_acf_tests
+TEST_DB_HOST=127.0.0.1
+TEST_DB_USER=wordpress
+TEST_DB_PASSWORD=wordpress
+TEST_WP_TABLE_PREFIX=wp_
+
+SKIP_DB_CREATE=false
+TEST_WP_ROOT_FOLDER=/tmp/wordpress
+TEST_ADMIN_EMAIL=admin@wp.test
+
+TESTS_DIR=tests
+TESTS_OUTPUT=tests/_output
+TESTS_DATA=tests/_data
+TESTS_SUPPORT=tests/_support
+TESTS_ENVS=tests/_envs
+
+#WPGRAPHQL_VERSION=v1.3.3
+SKIP_TESTS_CLEANUP=1
+SUITES=wpunit
diff --git a/.env.dist b/.env.dist
deleted file mode 100644
index 12c16af..0000000
--- a/.env.dist
+++ /dev/null
@@ -1,23 +0,0 @@
-# Shared
-TEST_DB_NAME="wptests"
-TEST_DB_HOST="127.0.0.1"
-TEST_DB_USER="root"
-TEST_DB_PASSWORD=""
-
-# Install script
-WP_VERSION=latest
-SKIP_DB_CREATE=false
-
-# Codeception
-WP_ROOT_FOLDER="/tmp/wp-graphql-acf/wordpress"
-WP_ADMIN_PATH="/wp-admin"
-DB_NAME="wptests"
-DB_HOST="127.0.0.1"
-DB_USER="root"
-DB_PASSWORD=""
-WP_TABLE_PREFIX="wp_"
-WP_URL="http://wp.test"
-WP_DOMAIN="wp.test"
-ADMIN_EMAIL="admin@wp.test"
-ADMIN_USERNAME="admin"
-ADMIN_PASSWORD="password"
diff --git a/.github/workflows/testing-integration.yml b/.github/workflows/testing-integration.yml
new file mode 100644
index 0000000..38ca063
--- /dev/null
+++ b/.github/workflows/testing-integration.yml
@@ -0,0 +1,112 @@
+name: Testing Integration
+
+on:
+ push:
+ branches:
+ - develop
+ - master
+ pull_request:
+ branches:
+ - develop
+ - master
+ paths:
+ - '**.php'
+ - '!docs/**'
+
+jobs:
+ continuous_integration:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ php: [ '7.3', '7.4' ]
+ wordpress: [ '5.6', '5.5.3', '5.4.2' ]
+ include:
+ - php: '7.4'
+ wordpress: '5.6'
+ coverage: 1
+ - php: '7.4'
+ wordpress: '5.6'
+ WPGRAPHQL_VERSION: 'v1.3.5'
+ - php: '7.4'
+ wordpress: '5.6'
+ WPGRAPHQL_VERSION: 'v1.2.6'
+ - php: '7.4'
+ wordpress: '5.6'
+ WPGRAPHQL_VERSION: 'v1.1.8'
+ - php: '7.4'
+ wordpress: '5.6'
+ WPGRAPHQL_VERSION: 'v1.0.5'
+ - php: '7.4'
+ wordpress: '5.6'
+ WPGRAPHQL_VERSION: 'v0.15.5'
+ - php: '7.4'
+ wordpress: '5.6'
+ WPGRAPHQL_VERSION: 'v0.14.0'
+ - php: '7.4'
+ wordpress: '5.6'
+ WPGRAPHQL_VERSION: 'v0.13.3'
+ - php: '7.4'
+ wordpress: '5.6'
+ WPGRAPHQL_VERSION: 'v0.12.3'
+ - php: '7.4'
+ wordpress: '5.6'
+ WPGRAPHQL_VERSION: 'v0.11.0'
+ - php: '7.4'
+ wordpress: '5.6'
+ WPGRAPHQL_VERSION: 'v0.11.0'
+ - php: '7.4'
+ wordpress: '5.5.3'
+ - php: '7.4'
+ wordpress: '5.4.2'
+ - php: '7.3'
+ wordpress: '5.6'
+ - php: '7.3'
+ wordpress: '5.5.3'
+ - php: '7.3'
+ wordpress: '5.4.2'
+ fail-fast: false
+ name: WordPress ${{ matrix.wordpress }} on PHP ${{ matrix.php }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: json, mbstring, zip, unzip
+
+ - name: Get Composer Cache Directory
+ id: composercache
+ run: echo "::set-output name=dir::$(composer config cache-files-dir)"
+
+ - name: Cache dependencies
+ uses: actions/cache@v2
+ with:
+ path: ${{ steps.composercache.outputs.dir }}
+ key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
+ restore-keys: ${{ runner.os }}-composer-
+
+ - name: Install dependencies
+ run: composer install
+
+ - name: Build "testing" Docker Image
+ env:
+ PHP_VERSION: ${{ matrix.php }}
+ WP_VERSION: ${{ matrix.wordpress }}
+ run: composer build-test
+
+ - name: Run Tests w/ Docker.
+ env:
+ COVERAGE: ${{ matrix.coverage }}
+ USING_XDEBUG: ${{ matrix.coverage }}
+ DEBUG: ${{ matrix.debug }}
+ SKIP_TESTS_CLEANUP: ${{ matrix.coverage }}
+ run: composer run-test
+
+ - name: Push Codecoverage to Coveralls.io
+ if: ${{ matrix.coverage == 1 }}
+ env:
+ COVERALLS_RUN_LOCALLY: 1
+ COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: vendor/bin/php-coveralls -v
diff --git a/.gitignore b/.gitignore
index 75c6b57..e6c29ad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,4 @@ vendor/*
vendor/composer/installed.json
vendor/composer/*/
composer.lock
+.log
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 985814d..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,67 +0,0 @@
-sudo: false
-dist: trusty
-
-language: php
-
-notifications:
- email:
- on_success: never
- on_failure: change
-
-branches:
- only:
- - master
-
-cache:
- directories:
- - vendor
- - $HOME/.composer/cache
-
-matrix:
- include:
- - php: 7.3
- env: WP_VERSION=latest
- - php: 7.2
- env: WP_VERSION=latest
- - php: 7.1
- env: WP_VERSION=latest
- - php: 7.0
- env: WP_VERSION=latest
- - php: 7.0
- env: WP_VERSION=trunk
-
-install:
- - |
- cd $TRAVIS_BUILD_DIR
- curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
- chmod +x wp-cli.phar
- sudo mv wp-cli.phar /usr/local/bin/wp
-
-before_script:
- - export PATH="$HOME/.composer/vendor/bin:$PATH"
- - |
- if [[ ! -z "$WP_VERSION" ]]; then
- cp .env.dist .env
- composer install-wp-tests
- COMPOSER_MEMORY_LIMIT=-1 travis_retry composer install --prefer-source --no-interaction
- fi
- - |
- if [[ "$WP_TRAVISCI" == "phpcs" ]]; then
- COMPOSER_MEMORY_LIMIT=-1 travis_retry composer require \
- squizlabs/php_codesniffer \
- phpcompatibility/phpcompatibility-wp wp-coding-standards/wpcs \
- dealerdirect/phpcodesniffer-composer-installer
- COMPOSER_MEMORY_LIMIT=-1 travis_retry composer install --no-dev
- fi
-
-script:
- - |
- if [[ ! -z "$WP_VERSION" ]]; then
- vendor/bin/codecept run wpunit
- fi
- - |
- if [[ "$WP_TRAVISCI" == "phpcs" ]]; then
- vendor/bin/phpcs \
- wp-graphql-acf.php \
- src/*.php --standard=WordPress
- fi
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
new file mode 100644
index 0000000..720720a
--- /dev/null
+++ b/DEVELOPMENT.md
@@ -0,0 +1,75 @@
+# Using Docker For Local Development
+
+## WordPress Site
+
+The `app` docker image starts a running WordPress site with the local wp-graphql-acf directory installed and activated. Local changes to the source code is immediately reflects in the app.
+
+1. Build the plugin.
+1.1 `composer install` or `docker run -v $PWD:/app composer --ignore-platform-reqs install`
+1. Run `composer build-app` to build the `app` docker image.
+1. Run `composer build-test` to build the `testing` docker image.
+1. Run `composer run-app` to start the WordPress site.
+1. Browse to http://localhost:8091/ to access the running WordPress app.
+1. Browse to http://localhost:8091/wp-admin/ to access the admin dashboard. Username is 'admin'. Password is 'password'.
+
+## Testing Suite
+
+The `testing` docker image starts a running WordPress and runs the codeception test suites.
+
+1. Run `composer build-test` to build the `testing` docker image.
+1. Run `composer run-test` to start the `testing` image and run the codeception tests.
+
+# Using XDebug
+
+## Local WordPress Site With XDebug
+Use an environment variable USING_XDEBUG to start the docker image and WordPress with xdebug configured to use port 9003 to communicated with your IDE.
+
+```
+export USING_XDEBUG=1
+composer run-app
+```
+
+Start the debugger in your IDE. Set breakpoints.
+
+Load the app in http://localhost:8091/.
+
+## Using XDebug With Tests
+
+Use the environment variable USING_XDEBUG to run tests with xdebug configured to use port 9003 to communicated with your IDE.
+
+```
+export USING_XDEBUG=1
+composer run-test
+```
+
+Start the debugger in your IDE. Set breakpoints.
+
+## Configure VSCode IDE Launch File
+
+Create or add the following configuration to your .vscode/launch.json in the root directory. Restart VSCode. Start the debug listener before running the app or testing images.
+
+If you have WordPress core files in a directory for local development, you can add the location to the `pathMappings` for debug step through.
+
+```
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Listen for Xdebug",
+ "type": "php",
+ "request": "launch",
+ "port": 9003,
+ "xdebugSettings": {
+ "max_children": 128,
+ "max_data": 1024,
+ "max_depth": 3,
+ "show_hidden": 1
+ },
+ "pathMappings": {
+ "/var/www/html/wp-content/plugins/wp-graphql-acf": "${workspaceFolder}",
+ "/var/www/html/wp-content/plugins/wp-graphql": "${workspaceFolder}../wp-graphql",
+ "/var/www/html": "${workspaceFolder}/wordpress",
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index da5a21f..bf26740 100644
--- a/README.md
+++ b/README.md
@@ -47,9 +47,11 @@ WPGraphQL for Advanced Custom Fields automatically exposes your ACF fields to th
- [Location Rules](#location-rules)
## Install and Activate
+
WPGraphQL for Advanced Custom Fields is not currently available on the WordPress.org repository, so you must download it from Github, or Composer.
### Installing From Github
+
To install the plugin from Github, you can [download the latest release zip file](https://github.com/wp-graphql/wp-graphql-acf/archive/master.zip), upload the Zip file to your WordPress install, and activate the plugin.
[Click here](https://wordpress.org/support/article/managing-plugins/) to learn more about installing WordPress plugins from a Zip file.
@@ -59,6 +61,7 @@ To install the plugin from Github, you can [download the latest release zip file
`composer require wp-graphql/wp-graphql-acf`
## Dependencies
+
In order to use WPGraphQL for Advanced Custom Fields, you must have [WPGraphQL](https://github.com/wp-graphql/wp-graphql) and [Advanced Custom Fields](https://advancedcustomfields.com) (free or pro) installed and activated.
## Adding Fields to the WPGraphQL Schema
@@ -92,9 +95,10 @@ Setting the value of this field to "Yes" will show the field group in the WPGrap
When registering ACF Fields in PHP, `@todo`
## Supported Fields
+
In order to document interacting with the fields in GraphQL, an example field group has been created with one field of each type.
-To replicate the same field group documented here you can [download the exported field group](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/download/field-group-export.zip) and [import it](https://support.advancedcustomfields.com/forums/topic/importing-exporting-acf-settings/) into your environment.
+To replicate the same field group documented here you can download the [example field group](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/field-group-examples-export.json) and [import it](https://support.advancedcustomfields.com/forums/topic/importing-exporting-acf-settings/) into your environment.
For the sake of documentation, this example field group has the [location rule](#location-rules) set to "Post Type is equal to Post", which will allow for the fields to be entered when creating and editing Posts in the WordPress dashboard, and will expose the fields to the `Post` type in the WPGraphQL Schema.
@@ -116,7 +120,7 @@ This field can be Queried in GraphQL like so:
{
post( id: "acf-example-test" idType: URI ) {
acfDocs {
- textArea
+ text
}
}
}
@@ -129,15 +133,13 @@ and the results of the query would be:
"data": {
"post": {
"acfDocs": {
- "textArea": "Text Area Value"
+ "text": "Text Value"
}
}
}
}
```
-![Text field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/text-field-query.png?raw=true)
-
### Text Area Field
Text Area fields are added to the WPGraphQL Schema as a field with the Type `String`.
@@ -174,14 +176,11 @@ and the results of the query would be:
}
```
-![Text field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/text-field-query.png?raw=true)
-
-
### Number Field
-Number fields are added to the WPGraphQL Schema as a field with the Type `Integer`.
+Number fields are added to the WPGraphQL Schema as a field with the Type `Float`.
-Number fields can be queried and an Integer will be returned.
+Number fields can be queried and a Float will be returned.
Here, we have a Number field named `number` on the Post Edit screen within the "ACF Docs" Field Group.
@@ -213,13 +212,11 @@ and the results of the query would be:
}
```
-![Number field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/number-field-query.png?raw=true)
-
### Range Field
-Range fields are added to the WPGraphQL Schema as a field with the Type `Integer`.
+Range fields are added to the WPGraphQL Schema as a field with the Type `Float`.
-Range fields can be queried and an Integer will be returned.
+Range fields can be queried and a Float will be returned.
Here, we have a Range field named `range` on the Post Edit screen within the "ACF Docs" Field Group.
@@ -251,8 +248,6 @@ and the results of the query would be:
}
```
-![Range field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/range-field-query.png?raw=true)
-
### Email Field
Email fields are added to the WPGraphQL Schema as a field with the Type `String`.
@@ -289,8 +284,6 @@ and the results of the query would be:
}
```
-![Email field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/email-field-query.png?raw=true)
-
### URL Field
Url fields are added to the WPGraphQL Schema as a field with the Type `String`.
@@ -327,8 +320,6 @@ and the results of the query would be:
}
```
-![URL field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/url-field-query.png?raw=true)
-
### Password Field
Password fields are added to the WPGraphQL Schema as a field with the Type `String`.
@@ -365,8 +356,6 @@ and the results of the query would be:
}
```
-![Password field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/password-field-query.png?raw=true)
-
### Image Field
Image fields are added to the WPGraphQL Schema as a field with the Type `MediaItem`.
@@ -384,9 +373,11 @@ This field can be Queried in GraphQL like so:
```graphql
{
post( id: "acf-example-test" idType: URI ) {
- image {
- id
- sourceUrl(size: MEDIUM)
+ acfDocs {
+ image {
+ id
+ sourceUrl(size: MEDIUM)
+ }
}
}
}
@@ -409,8 +400,6 @@ And the results of the query would be:
}
```
-![Image field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/image-field-query.png?raw=true)
-
### File Field
File fields are added to the WPGraphQL Schema as a field with the Type `MediaItem`.
@@ -455,8 +444,6 @@ And the results of the query would be:
}
```
-![File field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/file-field-query.png?raw=true)
-
### WYSIWYG Editor Field
WYSIWYG fields are added to the WPGraphQL Schema as a field with the Type `String`.
@@ -493,9 +480,6 @@ and the results of the query would be:
}
```
-![WYSIWYG field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/wysiwyg-field-query.png?raw=true)
-
-
### oEmbed Field
oEmbed fields are added to the WPGraphQL Schema as a field with the Type `String`.
@@ -504,7 +488,7 @@ oEmbed fields can be queried and a String will be returned.
Here, we have a oEmbed field named `oembed` on the Post Edit screen within the "ACF Docs" Field Group.
-![oEmbed field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/oembed-field-input.png?raw=true)
+![oEmbed field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/oEmbed-field-input.png?raw=true)
This field can be Queried in GraphQL like so:
@@ -516,7 +500,6 @@ This field can be Queried in GraphQL like so:
}
}
}
-
```
and the results of the query would be:
@@ -533,9 +516,6 @@ and the results of the query would be:
}
```
-![oEmbed field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/oembed-field-query.png?raw=true)
-
-
### Gallery Field
Gallery fields are added to the WPGraphQL Schema as a field with the Type of `['list_of' => 'MediaItem']`.
@@ -586,8 +566,6 @@ and the results of the query would be:
}
```
-![Gallery field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/gallery-field-query.png?raw=true)
-
### Select Field
Select fields (when configured to _not_ allow mutliple selections) are added to the WPGraphQL Schema as a field with the Type `String`.
@@ -624,8 +602,6 @@ and the results of the query would be:
}
```
-![Select field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/select-field-query.png?raw=true)
-
### Checkbox Field
Checkbox fields are added to the WPGraphQL Schema as a field with the Type `[ 'list_of' => 'String' ]`.
@@ -664,8 +640,6 @@ and the results of the query would be:
}
```
-![Checkbox field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/checkbox-field-query.png?raw=true)
-
### Radio Button Field
Radio Button fields are added to the WPGraphQL Schema as a field with the Type `String`.
@@ -702,8 +676,6 @@ and the results of the query would be:
}
```
-![Radio Button field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/radio-button-field-query.png?raw=true)
-
### Button Group Field
Button Group fields are added to the WPGraphQL Schema as a field with the Type `String`.
@@ -740,17 +712,15 @@ and the results of the query would be:
}
```
-![Radio Button field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/radio-button-field-query.png?raw=true)
-
### True/False Field
True/False fields are added to the WPGraphQL Schema as a field with the Type `Boolean`.
-True/False fields can be queried and a String will be returned.
+True/False fields can be queried and a Boolean will be returned.
Here, we have a True/False field named `true_false` on the Post Edit screen within the "ACF Docs" Field Group, and "true" is selected.
-![True/False field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/true-false-group-field-input.png?raw=true)
+![True/False field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/true-false-field-input.png?raw=true)
This field can be Queried in GraphQL like so:
@@ -778,8 +748,6 @@ and the results of the query would be:
}
```
-![True/False field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/true-false-field-query.png?raw=true)
-
### Link Field
Link fields are added to the WPGraphQL Schema as a field with the Type `ACF_Link`.
@@ -794,7 +762,7 @@ The available fields on the `ACF_Link` Type are:
Here, we have a Link field named `link` on the Post Edit screen within the "ACF Docs" Field Group.
-![Link field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/link-group-field-input.png?raw=true)
+![Link field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/link-field-input.png?raw=true)
This field can be Queried in GraphQL like so:
@@ -830,8 +798,6 @@ and the results of the query would be:
}
```
-![Link field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/link-field-query.png?raw=true)
-
### Post Object Field
Post Object fields are added to the WPGraphQL Schema as a field with a [Union](https://graphql.org/learn/schema/#union-types) of Possible Types the field is configured to allow.
@@ -848,7 +814,7 @@ Then the Union type for the field will allow `Post` and `Page` types to be retur
![Post Object field Union Possible Types](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/post-object-field-possible-types.png?raw=true)
-Here, we have a Post Object field named `post_object` on the Post Edit screen within the "ACF Docs" Field Group, configured with the Post "Hello World".
+Here, we have a Post Object field named `post_object` on the Post Edit screen within the "ACF Docs" Field Group, configured with the Post "Hello World!".
![Post Object field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/post-object-field-input-post.png?raw=true)
@@ -898,8 +864,6 @@ and the results of the query would be:
}
```
-![Post Object field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/post-object-field-query-post.png?raw=true)
-
If the input of the field was saved as a Page, instead of a Post, like so:
![Post Object field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/post-object-field-input-page.png?raw=true)
@@ -922,8 +886,6 @@ Then the same query above, would return the following results:
}
```
-![Post Object field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/post-object-field-query-page.png?raw=true)
-
Now, if the field were configured to allow multiple values, the field would be added to the Schema as a `listOf`, returning an Array of the Union.
If the field were set with a value of one Page, and one Post, like so:
@@ -956,8 +918,6 @@ Then the results of the same query as above would be:
}
```
-![Post Object field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/post-object-field-query-multi.png?raw=true)
-
### Page Link Field
Page Link fields are added to the WPGraphQL Schema as a field with a [Union](https://graphql.org/learn/schema/#union-types) of Possible Types the field is configured to allow.
@@ -974,7 +934,7 @@ Then the Union type for the field will allow `Post` and `Page` types to be retur
Here, we have a Page Link field named `page_link` on the Post Edit screen within the "ACF Docs" Field Group, and the value is set to the "Sample Page" page.
-![Page Link field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/page-link-field-input-page.png?raw=true)
+![Page Link field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/page-link-field-input.png?raw=true)
This field can be Queried in GraphQL like so:
@@ -1017,11 +977,9 @@ and the results of the query would be:
}
```
-![Page Link field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/link-field-query-page.png?raw=true)
-
Here, we set the value to the "Hello World" Post:
-![Page Link field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/page-link-field-input-post.png?raw=true)
+![Page Link field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/page-link-field-input-2.png?raw=true)
And the results of the same query are now:
@@ -1042,8 +1000,6 @@ And the results of the same query are now:
}
```
-![Page Link field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/link-field-query-post.png?raw=true)
-
### Relationship Field
Relationship fields are added to the WPGraphQL Schema as a field with a [Union](https://graphql.org/learn/schema/#union-types) of Possible Types the field is configured to allow.
@@ -1111,8 +1067,6 @@ and the results of the query would be:
}
```
-![Relationship field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/relationship-field-query.png?raw=true)
-
### Taxonomy Field
The Taxonomy field is added to the GraphQL Schema as a List Of the Taxonomy Type.
@@ -1161,8 +1115,6 @@ and the results of the query would be:
}
```
-![Taxonomy field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/taxonomy-field-query.png?raw=true)
-
### User Field
User fields are added to the WPGraphQL Schema as a field with a User type.
@@ -1207,13 +1159,11 @@ and the response would look like:
}
```
-![User field Query with one selection](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/user-field-query.png?raw=true)
-
If the field is configured to allow multiple selections, it's added to the Schema as a List Of the User type.
-Here, we have a User field named `user` on the Post Edit screen within the "ACF Docs" Field Group, set with the User "jasonbahl" as the value.
+Here, we have a User field named `user` on the Post Edit screen within the "ACF Docs" Field Group, set with the User "jasonbahl" and "WPGraphQL" as the value.
-![User field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/user-field-input-multi.png?raw=true)
+![User field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/user-field-input-multiple.png?raw=true)
and the response to the same query would look like:
@@ -1241,11 +1191,10 @@ and the response to the same query would look like:
}
}
```
-![User field Query with multiple selections](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/user-field-query-multiple.png?raw=true)
### Google Map Field
-Google Map fields are added the WPGraphQL Schema as the `ACF_GoogleMap` Type.
+Google Map fields are added to the WPGraphQL Schema as the `ACF_GoogleMap` Type.
The `ACF_GoogleMap` Type has fields that expose location data. The available fields are:
@@ -1265,7 +1214,7 @@ The `ACF_GoogleMap` Type has fields that expose location data. The available fie
Here, we have a Google Map field named `google_map` on the Post Edit screen within the "ACF Docs" Field Group, set with the Address "1 Infinite Loop, Cupertino, CA 95014, USA" as the value.
-![Google Map field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/google-map-field-input.png?raw=true)
+![Google Map field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/map-field-input.png?raw=true)
This field can be queried in GraphQL like so:
@@ -1285,7 +1234,6 @@ This field can be queried in GraphQL like so:
}
}
}
-
```
and the response would look like:
@@ -1311,8 +1259,6 @@ and the response would look like:
}
```
-![Google Map field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/google-map-field-query.png?raw=true)
-
### Date Picker Field
The Date Picker field is added to the WPGraphQL Schema as field with the Type `String`.
@@ -1349,8 +1295,6 @@ and the result of the query would be:
}
```
-![Date Picker field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/date-picker-field-query.png?raw=true)
-
### Date/Time Picker Field
The Date/Time Picker field is added to the WPGraphQL Schema as field with the Type `String`.
@@ -1387,8 +1331,6 @@ and the result of the query would be:
}
```
-![Date/Time Picker field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/date-time-picker-field-query.png?raw=true)
-
### Time Picker Field
The Time Picker field is added to the WPGraphQL Schema as field with the Type `String`.
@@ -1425,8 +1367,6 @@ and the result of the query would be:
}
```
-![Time Picker field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/time-picker-field-query.png?raw=true)
-
### Color Picker Field
The Color Picker field is added to the WPGraphQL Schema as field with the Type `String`.
@@ -1463,8 +1403,6 @@ and the result of the query would be:
}
```
-![Color Picker field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/color-picker-field-query.png?raw=true)
-
### Message Field
Message fields are not currently supported.
@@ -1517,8 +1455,6 @@ And the results of the query would be:
}
```
-![Group field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/group-field-query.png?raw=true)
-
### Repeater Field
Repeater Fields are added to the Schema as a List Of the Type of group that makes up the fields.
@@ -1583,8 +1519,6 @@ and the results of the query would be:
}
```
-![Repeater field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/repeater-field-query.png?raw=true)
-
### Flexible Content Field
The Flexible Content is a powerful ACF field that allows for groups of fields to be organized into "Layouts".
@@ -1595,7 +1529,7 @@ Flexible Content Fields are added to the WPGraphQL Schema as a List Of [Unions](
The Union for a Flex Field is made up of each Layout in the Flex Field as the possible Types.
-In our example, we've created a Flex Field with 3 layouts named "Layout 1", "Layout 2" and "Layout 3". In the Schema, we can see the Flex Field Union's Possible Types are these 3 layouts.
+In our example, we've created a Flex Field with 3 layouts named "Layout One", "Layout Two" and "Layout Three". In the Schema, we can see the Flex Field Union's Possible Types are these 3 layouts.
![Flex Fields Schema Union Possible Types](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/flex-field-union-possible-types.png?raw=true)
@@ -1603,12 +1537,12 @@ Each of these Layout types will contain the fields defined for the layout and ca
Here's an example of a Flex Field named `flexible_content`, with 3 layouts:
-- Layout 1
+- Layout One
- Text field named "text"
- Text field named "another_text_field"
-- Layout 2
+- Layout Two
- Image field named "image"
-- Layout 3
+- Layout Three
- Gallery field named "gallery"
Above are the possible layouts and their fields. These layouts can be added and arranged in any order. While we, as a GraphQL consumer, don't know ahead of time what order they will be in, we _do_ know what the possibilities are.
@@ -1620,7 +1554,6 @@ Here's an example of a Flex Field named `flexible_content` with the values saved
We can query this field like so:
```graphql
-
{
post(id: "acf-example-test", idType: URI) {
acfDocs {
@@ -1688,8 +1621,6 @@ and the results of the query would be:
}
```
-![Flex field Query](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/flex-field-query.png?raw=true)
-
If we were to re-arrange the layouts, so that the order was "Layout Three", "Layout One", "Layout Two", the results of the query would be:
```json
@@ -1728,8 +1659,6 @@ If we were to re-arrange the layouts, so that the order was "Layout Three", "Lay
}
```
-![Flex field Query 2](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/flex-field-query2.png?raw=true)
-
### Clone Field
The clone field is not fully supported (yet). We plan to support it in the future.
@@ -1775,7 +1704,7 @@ Alternatively, it's you can check the Fields API Reference to learn about exposi
## Location Rules
-Advanced Custom Fields field groups are added to the WordPress dashboard by being assinged "Location Rules".
+Advanced Custom Fields field groups are added to the WordPress dashboard by being assigned "Location Rules".
WPGraphQL for Advanced Custom Fields uses the Location Rules to determine where in the GraphQL Schema the field groups/fields should be added to the Schema.
@@ -1786,6 +1715,7 @@ For example, if a Field Group were assigned to "Post Type is equal to Post", the
@todo: Document supported location rules and how they map from ACF to the WPGraphQL Schema
### Why aren't all location rules supported?
+
You might notice that some location rules don't add fields to the Schema. This is because some location rules are based on context that doesn't exist when the GraphQL Schema is generated.
For example, if you have a location rule to show a field group only on a specific page, how would that be exposed the the Schema? There's no Type in the Schema for just one specific page.
diff --git a/bin/install-test-env.sh b/bin/install-test-env.sh
new file mode 100644
index 0000000..66890d1
--- /dev/null
+++ b/bin/install-test-env.sh
@@ -0,0 +1,194 @@
+#!/usr/bin/env bash
+
+source .env
+
+print_usage_instruction() {
+ echo "Ensure that .env file exist in project root directory exists."
+ echo "And run the following 'composer install-wp-tests' in the project root directory"
+ exit 1
+}
+
+if [[ -z "$TEST_DB_NAME" ]]; then
+ echo "TEST_DB_NAME not found"
+ print_usage_instruction
+else
+ DB_NAME=$TEST_DB_NAME
+fi
+if [[ -z "$TEST_DB_USER" ]]; then
+ echo "TEST_DB_USER not found"
+ print_usage_instruction
+else
+ DB_USER=$TEST_DB_USER
+fi
+
+DB_HOST=${TEST_DB_HOST-localhost}
+DB_PASS=${TEST_DB_PASSWORD-""}
+WP_VERSION=${WP_VERSION-latest}
+TMPDIR=${TMPDIR-/tmp}
+TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//")
+WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib}
+WP_CORE_DIR=${TEST_WP_ROOT_FOLDER-$TMPDIR/wordpress/}
+PLUGIN_DIR=$(pwd)
+DB_SERVE_NAME=${DB_SERVE_NAME-wpgatsby_serve}
+SKIP_DB_CREATE=${SKIP_DB_CREATE-false}
+
+download() {
+ if [ `which curl` ]; then
+ curl -s "$1" > "$2";
+ elif [ `which wget` ]; then
+ wget -nv -O "$2" "$1"
+ fi
+}
+
+if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then
+ WP_BRANCH=${WP_VERSION%\-*}
+ WP_TESTS_TAG="branches/$WP_BRANCH"
+
+elif [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then
+ WP_TESTS_TAG="branches/$WP_VERSION"
+elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then
+ if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
+ # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x
+ WP_TESTS_TAG="tags/${WP_VERSION%??}"
+ else
+ WP_TESTS_TAG="tags/$WP_VERSION"
+ fi
+elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
+ WP_TESTS_TAG="trunk"
+else
+ # http serves a single offer, whereas https serves multiple. we only want one
+ download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json
+ grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json
+ LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//')
+ if [[ -z "$LATEST_VERSION" ]]; then
+ echo "Latest WordPress version could not be found"
+ exit 1
+ fi
+ WP_TESTS_TAG="tags/$LATEST_VERSION"
+fi
+set -ex
+
+install_wp() {
+
+ if [ -d $WP_CORE_DIR ]; then
+ return;
+ fi
+
+ mkdir -p $WP_CORE_DIR
+
+ if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
+ mkdir -p $TMPDIR/wordpress-nightly
+ download https://wordpress.org/nightly-builds/wordpress-latest.zip $TMPDIR/wordpress-nightly/wordpress-nightly.zip
+ unzip -q $TMPDIR/wordpress-nightly/wordpress-nightly.zip -d $TMPDIR/wordpress-nightly/
+ mv $TMPDIR/wordpress-nightly/wordpress/* $WP_CORE_DIR
+ else
+ if [ $WP_VERSION == 'latest' ]; then
+ local ARCHIVE_NAME='latest'
+ elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then
+ # https serves multiple offers, whereas http serves single.
+ download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json
+ if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
+ # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x
+ LATEST_VERSION=${WP_VERSION%??}
+ else
+ # otherwise, scan the releases and get the most up to date minor version of the major release
+ local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'`
+ LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1)
+ fi
+ if [[ -z "$LATEST_VERSION" ]]; then
+ local ARCHIVE_NAME="wordpress-$WP_VERSION"
+ else
+ local ARCHIVE_NAME="wordpress-$LATEST_VERSION"
+ fi
+ else
+ local ARCHIVE_NAME="wordpress-$WP_VERSION"
+ fi
+ download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz
+ tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR
+ fi
+
+ download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php
+}
+
+install_db() {
+
+ if [ ${SKIP_DB_CREATE} = "true" ]; then
+ return 0
+ fi
+
+ # parse DB_HOST for port or socket references
+ local PARTS=(${DB_HOST//\:/ })
+ local DB_HOSTNAME=${PARTS[0]};
+ local DB_SOCK_OR_PORT=${PARTS[1]};
+ local EXTRA=""
+
+ if ! [ -z $DB_HOSTNAME ] ; then
+ if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then
+ EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp"
+ elif ! [ -z $DB_SOCK_OR_PORT ] ; then
+ EXTRA=" --socket=$DB_SOCK_OR_PORT"
+ elif ! [ -z $DB_HOSTNAME ] ; then
+ EXTRA=" --host=$DB_HOSTNAME --protocol=tcp"
+ fi
+ fi
+
+ # create database
+ RESULT=`mysql -u $DB_USER --password="$DB_PASS" --skip-column-names -e "SHOW DATABASES LIKE '$DB_NAME'"$EXTRA`
+ if [ "$RESULT" != $DB_NAME ]; then
+ mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA
+ fi
+}
+
+configure_wordpress() {
+ cd $WP_CORE_DIR
+ wp config create --dbname="$DB_NAME" --dbuser="$DB_USER" --dbpass="$DB_PASS" --dbhost="$DB_HOST" --skip-check --force=true
+ wp core install --url=wp.test --title="WPGraphQL ACF Tests" --admin_user=admin --admin_password=password --admin_email=admin@wp.test
+ wp rewrite structure '/%year%/%monthnum%/%postname%/'
+}
+
+install_acf_pro() {
+ if [ ! -d $WP_CORE_DIR/wp-content/plugins/advanced-custom-fields-pro ]; then
+ echo "Cloning ACF PRO"
+ git clone https://github.com/wp-premium/advanced-custom-fields-pro.git $WP_CORE_DIR/wp-content/plugins/advanced-custom-fields-pro
+ fi
+ echo "Cloning ACF PRO"
+ wp plugin activate advanced-custom-fields-pro
+}
+
+setup_plugin() {
+ # Add this repo as a plugin to the repo
+ if [ ! -d $WP_CORE_DIR/wp-content/plugins/wp-graphql-acf ]; then
+ ln -s $PLUGIN_DIR $WP_CORE_DIR/wp-content/plugins/wp-graphql-acf
+ cd $WP_CORE_DIR/wp-content/plugins
+ pwd
+ ls
+ fi
+
+ cd $WP_CORE_DIR
+
+ wp plugin list
+
+ # Install WPGraphQL
+ wp plugin install wp-graphql
+
+ # Activate WPGraphQL
+ wp plugin activate wp-graphql
+
+ # activate the plugin
+ wp plugin activate wp-graphql-acf
+
+ # List the active plugins
+ wp plugin list
+
+ # Flush the permalinks
+ wp rewrite flush
+
+ # Export the db for codeception to use
+ wp db export $PLUGIN_DIR/tests/_data/dump.sql
+}
+
+install_wp
+install_db
+configure_wordpress
+install_acf_pro
+setup_plugin
diff --git a/bin/install-wp-tests.sh b/bin/install-wp-tests.sh
deleted file mode 100755
index dbc7a55..0000000
--- a/bin/install-wp-tests.sh
+++ /dev/null
@@ -1,196 +0,0 @@
-#!/usr/bin/env bash
-
-source .env
-
-print_usage_instruction() {
- echo "Ensure that .env file exist in project root directory exists."
- echo "And run the following 'composer install-wp-tests' in the project root directory"
- exit 1
-}
-
-if [[ -z "$TEST_DB_NAME" ]]; then
- echo "TEST_DB_NAME not found"
- print_usage_instruction
-else
- DB_NAME=$TEST_DB_NAME
-fi
-if [[ -z "$TEST_DB_USER" ]]; then
- echo "TEST_DB_USER not found"
- print_usage_instruction
-else
- DB_USER=$TEST_DB_USER
-fi
-if [[ -z "$TEST_DB_PASSWORD" ]]; then
- DB_PASS=""
-else
- DB_PASS=$TEST_DB_PASSWORD
-fi
-if [[ -z "$TEST_DB_HOST" ]]; then
- DB_HOST=localhost
-else
- DB_HOST=$TEST_DB_HOST
-fi
-if [ -z "$SKIP_DB_CREATE" ]; then
- SKIP_DB_CREATE=false
-fi
-
-PLUGIN_DIR=$(pwd)
-WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wp-graphql-acf/wordpress-tests-lib}
-WP_CORE_DIR=${WP_CORE_DIR-/tmp/wp-graphql-acf/wordpress/}
-
-download() {
- if [ `which curl` ]; then
- curl -s "$1" > "$2";
- elif [ `which wget` ]; then
- wget -nv -O "$2" "$1"
- fi
-}
-
-if [[ $WP_VERSION =~ [0-9]+\.[0-9]+(\.[0-9]+)? ]]; then
- WP_TESTS_TAG="tags/$WP_VERSION"
-elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
- WP_TESTS_TAG="trunk"
-else
- # http serves a single offer, whereas https serves multiple. we only want one
- download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json
- grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json
- LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//')
- if [[ -z "$LATEST_VERSION" ]]; then
- echo "Latest WordPress version could not be found"
- exit 1
- fi
- WP_TESTS_TAG="tags/$LATEST_VERSION"
-fi
-
-set -ex
-
-install_wp() {
-
- if [ -d $WP_CORE_DIR ]; then
- return;
- fi
-
- mkdir -p $WP_CORE_DIR
-
- if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
- mkdir -p /tmp/wordpress-nightly
- download https://wordpress.org/nightly-builds/wordpress-latest.zip /tmp/wordpress-nightly/wordpress-nightly.zip
- unzip -q /tmp/wordpress-nightly/wordpress-nightly.zip -d /tmp/wordpress-nightly/
- mv /tmp/wordpress-nightly/wordpress/* $WP_CORE_DIR
- else
- if [ $WP_VERSION == 'latest' ]; then
- local ARCHIVE_NAME='latest'
- else
- local ARCHIVE_NAME="wordpress-$WP_VERSION"
- fi
- download https://wordpress.org/${ARCHIVE_NAME}.tar.gz /tmp/wordpress.tar.gz
- tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR
- fi
-
- download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php
-}
-
-install_test_suite() {
- # portable in-place argument for both GNU sed and Mac OSX sed
- if [[ $(uname -s) == 'Darwin' ]]; then
- local ioption='-i .bak'
- else
- local ioption='-i'
- fi
-
- # set up testing suite if it doesn't yet exist
- if [ ! -d $WP_TESTS_DIR ]; then
- # set up testing suite
- mkdir -p $WP_TESTS_DIR
- svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes
- svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data
- fi
-
- if [ ! -f wp-tests-config.php ]; then
- download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php
- # remove all forward slashes in the end
- WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::")
- sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
- sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php
- sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php
- sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
- sed $ioption "s|localhost|$DB_HOST|" "$WP_TESTS_DIR"/wp-tests-config.php
- fi
-
-}
-
-install_db() {
-
- if [ ${SKIP_DB_CREATE} = "true" ]; then
- return 0
- fi
-
- # parse DB_HOST for port or socket references
- local PARTS=(${DB_HOST//\:/ })
- local DB_HOSTNAME=${PARTS[0]};
- local DB_SOCK_OR_PORT=${PARTS[1]};
- local EXTRA=""
-
- if ! [ -z $DB_HOSTNAME ] ; then
- if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then
- EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp"
- elif ! [ -z $DB_SOCK_OR_PORT ] ; then
- EXTRA=" --socket=$DB_SOCK_OR_PORT"
- elif ! [ -z $DB_HOSTNAME ] ; then
- EXTRA=" --host=$DB_HOSTNAME --protocol=tcp"
- fi
- fi
-
- # create database
- mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA
-}
-
-configure_wordpress() {
-
- cd $WP_CORE_DIR
- wp config create --dbname="$DB_NAME" --dbuser="$DB_USER" --dbpass="$DB_PASS" --dbhost="$DB_HOST" --skip-check --force=true
- wp core install --url=wpgraphql.test --title="WPGraphQL ACF Tests" --admin_user=admin --admin_password=password --admin_email=admin@wpgraphql.test
- wp rewrite structure '/%year%/%monthnum%/%postname%/'
-}
-
-install_wpgraphql() {
- if [ ! -d $WP_CORE_DIR/wp-content/plugins/wp-graphql ]; then
- echo "Cloning WPGraphQL"
- git clone https://github.com/wp-graphql/wp-graphql.git $WP_CORE_DIR/wp-content/plugins/wp-graphql
- fi
- echo "Activating WPGraphQL"
- wp plugin activate wp-graphql
-}
-
-install_acf_pro() {
- if [ ! -d $WP_CORE_DIR/wp-content/plugins/advanced-custom-fields-pro ]; then
- echo "Cloning ACF PRO"
- git clone https://github.com/wp-premium/advanced-custom-fields-pro.git $WP_CORE_DIR/wp-content/plugins/advanced-custom-fields-pro
- fi
- echo "Cloning ACF PRO"
- wp plugin activate advanced-custom-fields-pro
-}
-
-activate_plugins() {
-
- # Add this repo as a plugin to the repo
- if [ ! -d $WP_CORE_DIR/wp-content/plugins/wp-graphql-acf ]; then
- ln -s $PLUGIN_DIR $WP_CORE_DIR/wp-content/plugins/wp-graphql-acf
- fi
-
- cd $WP_CORE_DIR
-
- # Flush the permalinks
- wp rewrite flush
-
- # Export the db for codeception to use
- wp db export $PLUGIN_DIR/tests/_data/dump.sql
-}
-
-install_wp
-install_test_suite
-install_db
-configure_wordpress
-install_wpgraphql
-install_acf_pro
-activate_plugins
diff --git a/bin/run-docker.sh b/bin/run-docker.sh
new file mode 100644
index 0000000..d21e8d1
--- /dev/null
+++ b/bin/run-docker.sh
@@ -0,0 +1,84 @@
+#!/usr/bin/env bash
+
+set -eu
+
+##
+# Use this script through Composer scripts in the package.json.
+# To quickly build and run the docker-compose scripts for an app or automated testing
+# run the command below after run `composer install --no-dev` with the respectively
+# flag for what you need.
+##
+print_usage_instructions() {
+ echo "Usage: $0 [build|run] [-c|-a|-t]";
+ echo " Build or run app or testing images."
+ echo " -c Specify as first option with [build] command to build images without cache."
+ echo " -a Spin up a WordPress installation.";
+ echo " -t Run the automated tests.";
+ exit 1
+}
+
+if [ -z "$1" ]; then
+ print_usage_instructions
+fi
+
+BUILD_NO_CACHE=
+
+subcommand=$1; shift
+case "$subcommand" in
+ "build" )
+ while getopts ":cat" opt; do
+ case ${opt} in
+ c )
+ echo "Build without cache"
+ BUILD_NO_CACHE=--no-cache
+ ;;
+ a )
+ echo "Build app"
+ docker build $BUILD_NO_CACHE -f docker/app.Dockerfile \
+ -t wpgraphql-acf-app:latest \
+ --build-arg WP_VERSION=${WP_VERSION-5.4} \
+ --build-arg PHP_VERSION=${PHP_VERSION-7.4} \
+ .
+ ;;
+ t )
+ echo "Build app"
+ docker build $BUILD_NO_CACHE -f docker/app.Dockerfile \
+ -t wpgraphql-acf-app:latest \
+ --build-arg WP_VERSION=${WP_VERSION-5.4} \
+ --build-arg PHP_VERSION=${PHP_VERSION-7.4} \
+ .
+ echo "Build testing"
+ docker build $BUILD_NO_CACHE -f docker/testing.Dockerfile \
+ -t wpgraphql-acf-testing:latest \
+ .
+ ;;
+ \? ) print_usage_instructions;;
+ * ) print_usage_instructions;;
+ esac
+ done
+ shift $((OPTIND -1))
+ ;;
+ "run" )
+ while getopts ":at" opt; do
+ case ${opt} in
+ a )
+ docker-compose up --scale testing=0
+ ;;
+ t )
+ docker-compose run --rm \
+ -e COVERAGE=${COVERAGE-} \
+ -e USING_XDEBUG=${USING_XDEBUG-} \
+ -e DEBUG=${DEBUG-} \
+ -e WPGRAPHQL_VERSION=${WPGRAPHQL_VERSION-} \
+ testing --scale app=0
+ ;;
+ \? ) print_usage_instructions;;
+ * ) print_usage_instructions;;
+ esac
+ done
+ shift $((OPTIND -1))
+ ;;
+
+ \? ) print_usage_instructions;;
+ * ) print_usage_instructions;;
+esac
diff --git a/codeception.dist.yml b/codeception.dist.yml
index e3d4224..23756bd 100644
--- a/codeception.dist.yml
+++ b/codeception.dist.yml
@@ -1,73 +1,85 @@
paths:
- tests: tests
- output: tests/_output
- data: tests/_data
- support: tests/_support
- envs: tests/_envs
+ tests: '%TESTS_DIR%'
+ output: '%TESTS_OUTPUT%'
+ data: '%TESTS_DATA%'
+ support: '%TESTS_SUPPORT%'
+ envs: '%TESTS_ENVS%'
+params:
+ - env
+ - .env
actor_suffix: Tester
settings:
- colors: true
- memory_limit: 1024M
+ colors: true
+ memory_limit: 1024M
coverage:
- enabled: true
- whitelist:
- include:
- - wp-graphql-acf.php
- - access-functions.php
- - src/*.php
+ enabled: true
+ remote: false
+ c3_url: '%WP_URL%/wp-content/plugins/wp-graphql-acf/wp-graphql-acf.php'
+ include:
+ - src/*
+ exclude:
+ - wp-graphql-acf.php
+ - vendor/*
+ show_only_summary: false
extensions:
- enabled:
- - Codeception\Extension\RunFailed
- commands:
- - Codeception\Command\GenerateWPUnit
- - Codeception\Command\GenerateWPRestApi
- - Codeception\Command\GenerateWPRestController
- - Codeception\Command\GenerateWPRestPostTypeController
- - Codeception\Command\GenerateWPAjax
- - Codeception\Command\GenerateWPCanonical
- - Codeception\Command\GenerateWPXMLRPC
-params:
- - .env
+ enabled:
+ - Codeception\Extension\RunFailed
+ commands:
+ - Codeception\Command\GenerateWPUnit
+ - Codeception\Command\GenerateWPRestApi
+ - Codeception\Command\GenerateWPRestController
+ - Codeception\Command\GenerateWPRestPostTypeController
+ - Codeception\Command\GenerateWPAjax
+ - Codeception\Command\GenerateWPCanonical
+ - Codeception\Command\GenerateWPXMLRPC
modules:
- config:
- WPDb:
- dsn: 'mysql:host=%DB_HOST%;dbname=%DB_NAME%'
- user: '%DB_USER%'
- password: '%DB_PASSWORD%'
- populator: 'mysql -u $user -p$password -h $host $dbname < $dump'
- dump: 'tests/_data/dump.sql'
- populate: true
- cleanup: true
- waitlock: 0
- url: '%WP_URL%'
- urlReplacement: true
- tablePrefix: '%WP_TABLE_PREFIX%'
- WPBrowser:
- url: '%WP_URL%'
- wpRootFolder: '%WP_ROOT_FOLDER%'
- adminUsername: '%ADMIN_USERNAME%'
- adminPassword: '%ADMIN_PASSWORD%'
- adminPath: '/wp-admin'
- REST:
- depends: WPBrowser
- url: '%WP_URL%'
- WPFilesystem:
- wpRootFolder: '%WP_ROOT_FOLDER%'
- plugins: '/wp-content/plugins'
- mu-plugins: '/wp-content/mu-plugins'
- themes: '/wp-content/themes'
- uploads: '/wp-content/uploads'
- WPLoader:
- wpRootFolder: '%WP_ROOT_FOLDER%'
- dbName: '%DB_NAME%'
- dbHost: '%DB_HOST%'
- dbUser: '%DB_USER%'
- dbPassword: '%DB_PASSWORD%'
- tablePrefix: '%WP_TABLE_PREFIX%'
- domain: '%WP_DOMAIN%'
- adminEmail: '%ADMIN_EMAIL%'
- title: 'Test'
- plugins: ['wp-graphql/wp-graphql.php', 'wp-graphql-acf/wp-graphql-acf.php', 'advanced-custom-fields-pro/acf.php']
- activatePlugins: ['wp-graphql/wp-graphql.php', 'wp-graphql-acf/wp-graphql-acf.php', 'advanced-custom-fields-pro/acf.php']
- configFile: 'tests/_data/config.php'
-
+ config:
+ WPDb:
+ dsn: 'mysql:host=%DB_HOST%;dbname=%DB_NAME%'
+ user: '%DB_USER%'
+ password: '%DB_PASSWORD%'
+ populator: 'mysql -u $user -p$password -h $host $dbname < $dump'
+ dump: 'tests/_data/dump.sql'
+ populate: false
+ cleanup: true
+ waitlock: 0
+ url: '%WP_URL%'
+ urlReplacement: true
+ tablePrefix: '%WP_TABLE_PREFIX%'
+ WPBrowser:
+ url: '%WP_URL%'
+ wpRootFolder: '%WP_ROOT_FOLDER%'
+ adminUsername: '%ADMIN_USERNAME%'
+ adminPassword: '%ADMIN_PASSWORD%'
+ adminPath: '/wp-admin'
+ cookies: false
+ REST:
+ depends: WPBrowser
+ url: '%WP_URL%'
+ WPFilesystem:
+ wpRootFolder: '%WP_ROOT_FOLDER%'
+ plugins: '/wp-content/plugins'
+ mu-plugins: '/wp-content/mu-plugins'
+ themes: '/wp-content/themes'
+ uploads: '/wp-content/uploads'
+ WPLoader:
+ wpRootFolder: '%WP_ROOT_FOLDER%'
+ dbName: '%DB_NAME%'
+ dbHost: '%DB_HOST%'
+ dbUser: '%DB_USER%'
+ dbPassword: '%DB_PASSWORD%'
+ tablePrefix: '%WP_TABLE_PREFIX%'
+ domain: '%WP_DOMAIN%'
+ adminEmail: '%ADMIN_EMAIL%'
+ title: 'Test'
+ plugins:
+ - wp-graphql-acf/tests/_bootstrap/bootstrap.php
+ - advanced-custom-fields-pro/acf.php
+ - wp-graphql/wp-graphql.php
+ - wp-graphql-acf/wp-graphql-acf.php
+ activatePlugins:
+ - wp-graphql-acf/tests/_bootstrap/bootstrap.php
+ - advanced-custom-fields-pro/acf.php
+ - wp-graphql/wp-graphql.php
+ - wp-graphql-acf/wp-graphql-acf.php
+ configFile: 'tests/_data/config.php'
diff --git a/composer.json b/composer.json
index fb620d4..a5fcb29 100644
--- a/composer.json
+++ b/composer.json
@@ -8,7 +8,8 @@
"email": "jasonbahl@mac.com"
}],
"config": {
- "optimize-autoloader": true
+ "optimize-autoloader": true,
+ "process-timeout": 0
},
"autoload": {
"psr-4": {
@@ -19,16 +20,52 @@
]
},
"scripts": {
- "install-wp-tests": "bash bin/install-wp-tests.sh",
- "test": "vendor/bin/codecept run",
- "functional-test": "vendor/bin/codecept run functional",
- "acceptance-test": "vendor/bin/codecept run acceptance",
- "wpunit-test": "vendor/bin/codecept run wpunit"
+ "install-test-env": "bash bin/install-test-env.sh",
+ "docker-build": "bash bin/run-docker.sh build",
+ "docker-run": "bash bin/run-docker.sh run",
+ "docker-destroy": "docker-compose down",
+ "build-and-run": [
+ "@docker-build",
+ "@docker-run"
+ ],
+ "build-app": "@docker-build -a",
+ "build-test": "@docker-build -t",
+ "run-app": "@docker-run -a",
+ "run-test": "@docker-run -t",
+ "lint": "vendor/bin/phpcs",
+ "phpcs-i": [
+ "php ./vendor/bin/phpcs -i"
+ ],
+ "check-cs": [
+ "php ./vendor/bin/phpcs src"
+ ],
+ "fix-cs": [
+ "php ./vendor/bin/phpcbf src"
+ ],
+ "phpstan": ["phpstan analyze --ansi --memory-limit=1G"]
+ },
+ "require": {
+ "php": "^7"
},
"require-dev": {
- "lucatume/wp-browser": "^2.2.",
- "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
- "wp-coding-standards/wpcs": "^2.1",
- "phpcompatibility/phpcompatibility-wp": "^2.0"
+ "lucatume/wp-browser": "^2.4",
+ "codeception/module-asserts": "^1.0",
+ "codeception/module-phpbrowser": "^1.0",
+ "codeception/module-webdriver": "^1.0",
+ "codeception/module-db": "^1.0",
+ "codeception/module-filesystem": "^1.0",
+ "codeception/module-cli": "^1.0",
+ "codeception/util-universalframework": "^1.0",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1",
+ "wp-coding-standards/wpcs": "2.1.1",
+ "phpcompatibility/phpcompatibility-wp": "2.1.0",
+ "squizlabs/php_codesniffer": "3.5.4",
+ "phpstan/phpstan": "^0.12.64",
+ "szepeviktor/phpstan-wordpress": "^0.7.1",
+ "codeception/module-rest": "^1.2",
+ "wp-graphql/wp-graphql-testcase": "^1.0",
+ "phpunit/phpunit": "9.4.1",
+ "simpod/php-coveralls-mirror": "^3.0",
+ "phpstan/extension-installer": "^1.1"
}
}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..03ea775
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,64 @@
+version: '3.3'
+
+services:
+ app:
+ depends_on:
+ - app_db
+ image: wpgraphql-acf-app:latest
+ volumes:
+ - '.:/var/www/html/wp-content/plugins/wp-graphql-acf'
+ - './.log/app:/var/log/apache2'
+ env_file:
+ - .env
+ environment:
+ WP_URL: 'http://localhost:8091'
+ WP_DOMAIN: 'localhost:8091'
+ DB_HOST: app_db
+ DB_NAME: wordpress
+ DB_USER: wordpress
+ DB_PASSWORD: wordpress
+ WP_DOMAIN: localhost
+ ADMIN_EMAIL: admin@example.com
+ ADMIN_USERNAME: admin
+ ADMIN_PASSWORD: password
+ USING_XDEBUG: ${USING_XDEBUG:-}
+ WPGRAPHQL_VERSION: ${WPGRAPHQL_VERSION:-}
+ ports:
+ - '8091:80'
+ networks:
+ local:
+
+ app_db:
+ image: mysql:5.7
+ environment:
+ MYSQL_ROOT_PASSWORD: root
+ MYSQL_DATABASE: wordpress
+ MYSQL_USER: wordpress
+ MYSQL_PASSWORD: wordpress
+ ports:
+ - '3306'
+ networks:
+ testing:
+ local:
+
+ testing:
+ depends_on:
+ - app_db
+ image: wpgraphql-acf-testing:latest
+ volumes:
+ - '.:/var/www/html/wp-content/plugins/wp-graphql-acf'
+ - './.log/testing:/var/log/apache2'
+ - './codeception.dist.yml:/var/www/html/wp-content/plugins/wp-graphql-acf/codeception.yml'
+ env_file:
+ - .env
+ environment:
+ DB_HOST: app_db
+ WP_URL: 'http://localhost'
+ WP_DOMAIN: 'localhost'
+ SUITES: ${SUITES:-}
+ networks:
+ testing:
+
+networks:
+ local:
+ testing:
diff --git a/docker/app.Dockerfile b/docker/app.Dockerfile
new file mode 100644
index 0000000..4e0872e
--- /dev/null
+++ b/docker/app.Dockerfile
@@ -0,0 +1,79 @@
+###############################################################################
+# Pre-configured WordPress Installation w/ WPGraphQL, WPGraphQL for ACF, ACF Pro #
+# For testing only, use in production not recommended. #
+###############################################################################
+ARG WP_VERSION
+ARG PHP_VERSION
+
+FROM wordpress:${WP_VERSION}-php${PHP_VERSION}-apache
+
+ENV WP_VERSION=${WP_VERSION}
+ENV PHP_VERSION=${PHP_VERSION}
+
+LABEL author=jasonbahl
+LABEL author_uri=https://github.com/jasonbahl
+
+SHELL [ "/bin/bash", "-c" ]
+
+# Install system packages
+RUN apt-get update && \
+ apt-get -y install \
+ # CircleCI depedencies
+ git \
+ ssh \
+ tar \
+ gzip \
+ wget \
+ mariadb-client
+
+# Install Dockerize
+ENV DOCKERIZE_VERSION v0.6.1
+RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
+ && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
+ && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz
+
+# Install WP-CLI
+RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \
+ && chmod +x wp-cli.phar \
+ && mv wp-cli.phar /usr/local/bin/wp
+
+# Set project environmental variables
+ENV WP_ROOT_FOLDER="/var/www/html"
+ENV WORDPRESS_DB_HOST=${DB_HOST}
+ENV WORDPRESS_DB_USER=${DB_USER}
+ENV WORDPRESS_DB_PASSWORD=${DB_PASSWORD}
+ENV WORDPRESS_DB_NAME=${DB_NAME}
+ENV PLUGINS_DIR="${WP_ROOT_FOLDER}/wp-content/plugins"
+ENV PROJECT_DIR="${PLUGINS_DIR}/wp-graphql-acf"
+ENV WPGRAPHQL_VERSION="${WPGRAPHQL_VERSION}"
+
+# Remove exec statement from base entrypoint script.
+RUN sed -i '$d' /usr/local/bin/docker-entrypoint.sh
+
+# Set up Apache
+RUN echo 'ServerName localhost' >> /etc/apache2/apache2.conf
+
+# Custom PHP settings
+RUN echo "upload_max_filesize = 50M" >> /usr/local/etc/php/conf.d/custom.ini \
+ ;
+
+# Install XDebug 3
+RUN echo "Installing XDebug 3 (in disabled state)" \
+ && pecl install xdebug \
+ && mkdir -p /usr/local/etc/php/conf.d/disabled \
+ && echo "zend_extension=xdebug" > /usr/local/etc/php/conf.d/disabled/docker-php-ext-xdebug.ini \
+ && echo "xdebug.mode=develop,debug,coverage" >> /usr/local/etc/php/conf.d/disabled/docker-php-ext-xdebug.ini \
+ && echo "xdebug.start_with_request=yes" >> /usr/local/etc/php/conf.d/disabled/docker-php-ext-xdebug.ini \
+ && echo "xdebug.client_host=host.docker.internal" >> /usr/local/etc/php/conf.d/disabled/docker-php-ext-xdebug.ini \
+ && echo "xdebug.client_port=9003" >> /usr/local/etc/php/conf.d/disabled/docker-php-ext-xdebug.ini \
+ ;
+
+# Set xdebug configuration off by default. See the entrypoint.sh.
+ENV USING_XDEBUG=${USING_XDEBUG}
+
+# Set up entrypoint
+WORKDIR /var/www/html
+COPY docker/app.entrypoint.sh /usr/local/bin/app-entrypoint.sh
+RUN chmod 755 /usr/local/bin/app-entrypoint.sh
+ENTRYPOINT ["app-entrypoint.sh"]
+CMD ["apache2-foreground"]
diff --git a/docker/app.entrypoint.sh b/docker/app.entrypoint.sh
new file mode 100644
index 0000000..1a06d4c
--- /dev/null
+++ b/docker/app.entrypoint.sh
@@ -0,0 +1,86 @@
+#!/bin/bash
+
+if [ "$USING_XDEBUG" == "1" ]; then
+ echo "Enabling XDebug 3"
+ mv /usr/local/etc/php/conf.d/disabled/docker-php-ext-xdebug.ini /usr/local/etc/php/conf.d/
+fi
+
+# Run WordPress docker entrypoint.
+. docker-entrypoint.sh 'apache2'
+
+set +u
+
+# Ensure mysql is loaded
+dockerize -wait tcp://${DB_HOST}:${DB_HOST_PORT:-3306} -timeout 1m
+
+# Config WordPress
+if [ ! -f "${WP_ROOT_FOLDER}/wp-config.php" ]; then
+ wp config create \
+ --path="${WP_ROOT_FOLDER}" \
+ --dbname="${DB_NAME}" \
+ --dbuser="${DB_USER}" \
+ --dbpass="${DB_PASSWORD}" \
+ --dbhost="${DB_HOST}" \
+ --dbprefix="${WP_TABLE_PREFIX}" \
+ --skip-check \
+ --quiet \
+ --allow-root
+fi
+
+# Install WP if not yet installed
+if ! $( wp core is-installed --allow-root ); then
+ wp core install \
+ --path="${WP_ROOT_FOLDER}" \
+ --url="${WP_URL}" \
+ --title='Test' \
+ --admin_user="${ADMIN_USERNAME}" \
+ --admin_password="${ADMIN_PASSWORD}" \
+ --admin_email="${ADMIN_EMAIL}" \
+ --allow-root
+fi
+
+# Install and activate WPGraphQL
+
+echo "wpgraphql version... ${WPGRAPHQL_VERSION}"
+echo "${PLUGINS_DIR}"
+
+if [ ! -f "${PLUGINS_DIR}/wp-graphql/wp-graphql.php" ]; then
+ # WPGRAPHQL_VERSION in format like v1.2.3
+ echo ${WPGRAPHQL_VERSION}
+ if [[ -z ${WPGRAPHQL_VERSION} ]]; then
+ echo "installing latest WPGraphQL from WordPress.org"
+ wp plugin install wp-graphql --activate --allow-root
+ else
+ echo "Installing WPGraphQL from Github"
+ git clone https://github.com/wp-graphql/wp-graphql.git "${PLUGINS_DIR}/wp-graphql"
+ cd "${PLUGINS_DIR}/wp-graphql"
+ echo "checking out WPGraphQL tag/${WPGRAPHQL_VERSION}"
+ git checkout tags/${WPGRAPHQL_VERSION} -b master
+ composer install --no-dev
+ cd ${WP_ROOT_FOLDER}
+ echo "activating WPGraphQL"
+ wp plugin activate wp-graphql --allow-root
+ wp plugin list --allow-root
+ fi
+else
+ wp plugin activate wp-graphql --allow-root
+fi
+
+# Install and activate ACF Pro
+if [ ! -f "${PLUGINS_DIR}/advanced-custom-fields-pro/acf.php" ]; then
+ wp plugin install \
+ https://github.com/wp-premium/advanced-custom-fields-pro/archive/master.zip \
+ --activate --allow-root
+else
+ wp plugin activate advanced-custom-fields-pro --allow-root
+fi
+
+# Install and activate WPGatsby
+wp plugin activate wp-graphql-acf --allow-root
+
+# Set pretty permalinks.
+wp rewrite structure '/%year%/%monthnum%/%postname%/' --allow-root
+
+wp db export "${PROJECT_DIR}/tests/_data/dump.sql" --allow-root
+
+exec "$@"
diff --git a/docker/testing.Dockerfile b/docker/testing.Dockerfile
new file mode 100644
index 0000000..cdec274
--- /dev/null
+++ b/docker/testing.Dockerfile
@@ -0,0 +1,44 @@
+############################################################################
+# Container for running Codeception tests on a WPGraphQL Docker instance. #
+############################################################################
+
+# Using the 'DESIRED_' prefix to avoid confusion with environment variables of the same name.
+FROM wpgraphql-acf-app:latest
+
+LABEL author=jasonbahl
+LABEL author_uri=https://github.com/jasonbahl
+
+SHELL [ "/bin/bash", "-c" ]
+
+# Install php extensions
+RUN docker-php-ext-install pdo_mysql
+
+# Install PCOV
+# This is needed for Codeception / PHPUnit to track code coverage
+RUN apt-get install zip unzip -y \
+ && pecl install pcov
+
+ENV COVERAGE=0
+ENV SUITES=${SUITES:-zz}
+
+# Install composer
+ENV COMPOSER_ALLOW_SUPERUSER=1
+
+RUN curl -sS https://getcomposer.org/installer | php -- \
+ --filename=composer \
+ --install-dir=/usr/local/bin
+
+# Add composer global binaries to PATH
+ENV PATH "$PATH:~/.composer/vendor/bin"
+
+# Configure php
+RUN echo "date.timezone = UTC" >> /usr/local/etc/php/php.ini
+
+# Remove exec statement from base entrypoint script.
+RUN sed -i '$d' /usr/local/bin/app-entrypoint.sh
+
+# Set up entrypoint
+WORKDIR /var/www/html/wp-content/plugins/wp-graphql-acf
+COPY docker/testing.entrypoint.sh /usr/local/bin/testing-entrypoint.sh
+RUN chmod 755 /usr/local/bin/testing-entrypoint.sh
+ENTRYPOINT ["testing-entrypoint.sh"]
diff --git a/docker/testing.entrypoint.sh b/docker/testing.entrypoint.sh
new file mode 100644
index 0000000..d812b6b
--- /dev/null
+++ b/docker/testing.entrypoint.sh
@@ -0,0 +1,141 @@
+#!/bin/bash
+
+# Processes parameters and runs Codeception.
+run_tests() {
+ if [[ -n "$COVERAGE" ]]; then
+ local coverage="--coverage --coverage-xml"
+ fi
+ if [[ -n "$DEBUG" ]]; then
+ local debug="--debug"
+ fi
+
+ local suites=$1
+ if [[ -z "$suites" ]]; then
+ echo "No test suites specified. Must specify variable SUITES."
+ exit 1
+ fi
+
+ for suite in $suites ; do
+ echo "Running Test Suite $suite"
+ vendor/bin/codecept run -c codeception.dist.yml ${suite} ${coverage:-} ${debug:-} --no-exit
+ done
+}
+
+# Exits with a status of 0 (true) if provided version number is higher than proceeding numbers.
+version_gt() {
+ test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1";
+}
+
+write_htaccess() {
+ echo "
+RewriteEngine On
+RewriteBase /
+SetEnvIf Authorization \"(.*)\" HTTP_AUTHORIZATION=\$1
+RewriteRule ^index\.php$ - [L]
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule . /index.php [L]
+" >> ${WP_ROOT_FOLDER}/.htaccess
+}
+
+# Move to WordPress root folder
+workdir="$PWD"
+echo "Moving to WordPress root directory."
+cd ${WP_ROOT_FOLDER}
+
+# Run app entrypoint script.
+. app-entrypoint.sh
+
+write_htaccess
+
+# Return to PWD.
+echo "Moving back to project working directory."
+cd ${workdir}
+
+# Ensure Apache is running
+service apache2 start
+
+# Ensure everything is loaded
+dockerize \
+ -wait tcp://${DB_HOST}:${DB_HOST_PORT:-3306} \
+ -wait ${WP_URL} \
+ -timeout 1m
+
+# Download c3 for testing.
+if [ ! -f "$PROJECT_DIR/c3.php" ]; then
+ echo "Downloading Codeception's c3.php"
+ curl -L 'https://raw.github.com/Codeception/c3/2.0/c3.php' > "$PROJECT_DIR/c3.php"
+fi
+
+# Install dependencies
+echo "Running composer update"
+COMPOSER_MEMORY_LIMIT=-1 composer update
+echo "Running composer install"
+COMPOSER_MEMORY_LIMIT=-1 composer install --no-interaction
+
+# Install pcov/clobber if PHP7.1+
+if version_gt $PHP_VERSION 7.0 && [[ -n "$COVERAGE" ]] && [[ -z "$USING_XDEBUG" ]]; then
+ echo "Using pcov/clobber for codecoverage"
+ docker-php-ext-enable pcov
+ echo "pcov.enabled=1" >> /usr/local/etc/php/conf.d/docker-php-ext-pcov.ini
+ echo "pcov.directory = /var/www/html/wp-content/plugins/wp-graphql" >> /usr/local/etc/php/conf.d/docker-php-ext-pcov.ini
+ COMPOSER_MEMORY_LIMIT=-1 composer require --dev pcov/clobber
+ vendor/bin/pcov clobber
+elif [[ -n "$COVERAGE" ]] && [[ -n "$USING_XDEBUG" ]]; then
+ echo "Using XDebug for codecoverage"
+fi
+
+# Set output permission
+echo "Setting Codeception output directory permissions"
+chmod 777 ${TESTS_OUTPUT}
+
+# Run tests
+run_tests "${SUITES}"
+
+# Remove c3.php
+if [ -f "$PROJECT_DIR/c3.php" ] && [ "$SKIP_TESTS_CLEANUP" != "1" ]; then
+ echo "Removing Codeception's c3.php"
+ rm -rf "$PROJECT_DIR/c3.php"
+fi
+
+# Clean coverage.xml and clean up PCOV configurations.
+if [ -f "${TESTS_OUTPUT}/coverage.xml" ] && [[ -n "$COVERAGE" ]]; then
+ echo 'Cleaning coverage.xml for deployment'.
+ pattern="$PROJECT_DIR/"
+ sed -i "s~$pattern~~g" "$TESTS_OUTPUT"/coverage.xml
+
+ # Remove pcov/clobber
+ if version_gt $PHP_VERSION 7.0 && [[ -z "$SKIP_TESTS_CLEANUP" ]] && [[ -z "$USING_XDEBUG" ]]; then
+ echo 'Removing pcov/clobber.'
+ vendor/bin/pcov unclobber
+ COMPOSER_MEMORY_LIMIT=-1 composer remove --dev pcov/clobber
+ rm /usr/local/etc/php/conf.d/docker-php-ext-pcov.ini
+ fi
+
+fi
+
+if [[ -z "$SKIP_TESTS_CLEANUP" ]]; then
+ echo 'Changing composer configuration in container.'
+ composer config --global discard-changes true
+
+ echo 'Removing devDependencies.'
+ composer install --no-dev -n
+
+ echo 'Removing composer.lock'
+ rm composer.lock
+fi
+
+# Set public test result files permissions.
+if [ -n "$(ls "$TESTS_OUTPUT")" ]; then
+ echo 'Setting result files permissions'.
+ chmod 777 -R "$TESTS_OUTPUT"/*
+fi
+
+
+# Check results and exit accordingly.
+if [ -f "${TESTS_OUTPUT}/failed" ]; then
+ echo "Uh oh, something went wrong."
+ exit 1
+else
+ echo "Woohoo! It's working!"
+fi
diff --git a/docs/field-group-examples-export.json b/docs/field-group-examples-export.json
new file mode 100644
index 0000000..7a0f024
--- /dev/null
+++ b/docs/field-group-examples-export.json
@@ -0,0 +1,883 @@
+[
+ {
+ "key": "group_60468a2b40d13",
+ "title": "ACF Docs",
+ "fields": [
+ {
+ "key": "field_60468a428ad20",
+ "label": "Text",
+ "name": "text",
+ "type": "text",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "default_value": "",
+ "placeholder": "",
+ "prepend": "",
+ "append": "",
+ "maxlength": ""
+ },
+ {
+ "key": "field_60468c101f5bc",
+ "label": "Text Area",
+ "name": "text_area",
+ "type": "textarea",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "default_value": "",
+ "placeholder": "",
+ "maxlength": "",
+ "rows": "",
+ "new_lines": ""
+ },
+ {
+ "key": "field_60468c261f5bd",
+ "label": "Number",
+ "name": "number",
+ "type": "number",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "default_value": "",
+ "placeholder": "",
+ "prepend": "",
+ "append": "",
+ "min": "",
+ "max": "",
+ "step": ""
+ },
+ {
+ "key": "field_60468c7d1f5be",
+ "label": "Range",
+ "name": "range",
+ "type": "range",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "default_value": "",
+ "min": "",
+ "max": "",
+ "step": "",
+ "prepend": "",
+ "append": ""
+ },
+ {
+ "key": "field_60468d7ed5271",
+ "label": "Email",
+ "name": "email",
+ "type": "email",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "default_value": "",
+ "placeholder": "",
+ "prepend": "",
+ "append": ""
+ },
+ {
+ "key": "field_60468db4a3624",
+ "label": "Url",
+ "name": "url",
+ "type": "url",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "default_value": "",
+ "placeholder": ""
+ },
+ {
+ "key": "field_60468dd9a7390",
+ "label": "Password",
+ "name": "password",
+ "type": "password",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "placeholder": "",
+ "prepend": "",
+ "append": ""
+ },
+ {
+ "key": "field_60468e38c3039",
+ "label": "Image",
+ "name": "image",
+ "type": "image",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "return_format": "array",
+ "preview_size": "medium",
+ "library": "all",
+ "min_width": "",
+ "min_height": "",
+ "min_size": "",
+ "max_width": "",
+ "max_height": "",
+ "max_size": "",
+ "mime_types": ""
+ },
+ {
+ "key": "field_6046909b38734",
+ "label": "File",
+ "name": "file",
+ "type": "file",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "return_format": "array",
+ "library": "all",
+ "min_size": "",
+ "max_size": "",
+ "mime_types": ""
+ },
+ {
+ "key": "field_604690bb38735",
+ "label": "Wysiwyg",
+ "name": "wysiwyg",
+ "type": "wysiwyg",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "default_value": "",
+ "tabs": "all",
+ "toolbar": "full",
+ "media_upload": 1,
+ "delay": 0
+ },
+ {
+ "key": "field_604690cd38736",
+ "label": "Oembed",
+ "name": "oembed",
+ "type": "oembed",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "width": "",
+ "height": ""
+ },
+ {
+ "key": "field_6047cac3147ba",
+ "label": "Gallery",
+ "name": "gallery",
+ "type": "gallery",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "return_format": "array",
+ "preview_size": "medium",
+ "insert": "append",
+ "library": "all",
+ "min": "",
+ "max": "",
+ "min_width": "",
+ "min_height": "",
+ "min_size": "",
+ "max_width": "",
+ "max_height": "",
+ "max_size": "",
+ "mime_types": ""
+ },
+ {
+ "key": "field_604690da38737",
+ "label": "Select",
+ "name": "select",
+ "type": "select",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "choices": {
+ "choice_1": "Choice 1",
+ "choice_2": "Choice 2"
+ },
+ "default_value": false,
+ "allow_null": 0,
+ "multiple": 0,
+ "ui": 0,
+ "return_format": "value",
+ "ajax": 0,
+ "placeholder": ""
+ },
+ {
+ "key": "field_60469107346a9",
+ "label": "Checkbox",
+ "name": "checkbox",
+ "type": "checkbox",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "choices": {
+ "choice_1": "Choice 1",
+ "choice_2": "Choice 2"
+ },
+ "allow_custom": 0,
+ "default_value": [],
+ "layout": "vertical",
+ "toggle": 0,
+ "return_format": "value",
+ "save_custom": 0
+ },
+ {
+ "key": "field_6046914753efc",
+ "label": "Radio Button",
+ "name": "radio_button",
+ "type": "radio",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "choices": {
+ "choice_1": "Choice 1",
+ "choice_2": "Choice 2"
+ },
+ "allow_null": 0,
+ "other_choice": 0,
+ "default_value": "",
+ "layout": "vertical",
+ "return_format": "value",
+ "save_other_choice": 0
+ },
+ {
+ "key": "field_6046917b53efd",
+ "label": "Button Group",
+ "name": "button_group",
+ "type": "button_group",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "choices": {
+ "choice_1": "Choice 1",
+ "choice_2": "Choice 2"
+ },
+ "allow_null": 0,
+ "default_value": "",
+ "layout": "horizontal",
+ "return_format": "value"
+ },
+ {
+ "key": "field_604691d753ce6",
+ "label": "True False",
+ "name": "true_false",
+ "type": "true_false",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "message": "",
+ "default_value": 0,
+ "ui": 0,
+ "ui_on_text": "",
+ "ui_off_text": ""
+ },
+ {
+ "key": "field_6046928a53ce7",
+ "label": "Link",
+ "name": "link",
+ "type": "link",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "return_format": "array"
+ },
+ {
+ "key": "field_604692a202533",
+ "label": "Post Object",
+ "name": "post_object",
+ "type": "post_object",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "post_type": [
+ "post",
+ "page"
+ ],
+ "taxonomy": "",
+ "allow_null": 0,
+ "multiple": 1,
+ "return_format": "object",
+ "ui": 1
+ },
+ {
+ "key": "field_60469560e9da6",
+ "label": "Page Link",
+ "name": "page_link",
+ "type": "page_link",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "post_type": [
+ "post",
+ "page"
+ ],
+ "taxonomy": "",
+ "allow_null": 0,
+ "allow_archives": 1,
+ "multiple": 0
+ },
+ {
+ "key": "field_60469ad3e9da7",
+ "label": "Relationship",
+ "name": "relationship",
+ "type": "relationship",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "post_type": [
+ "post",
+ "page"
+ ],
+ "taxonomy": "",
+ "filters": [
+ "search",
+ "post_type",
+ "taxonomy"
+ ],
+ "elements": "",
+ "min": "",
+ "max": "",
+ "return_format": "object"
+ },
+ {
+ "key": "field_60469bf265bd6",
+ "label": "Taxonomy",
+ "name": "taxonomy",
+ "type": "taxonomy",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "taxonomy": "category",
+ "field_type": "checkbox",
+ "add_term": 1,
+ "save_terms": 0,
+ "load_terms": 0,
+ "return_format": "id",
+ "multiple": 0,
+ "allow_null": 0
+ },
+ {
+ "key": "field_60469c1665bd7",
+ "label": "User",
+ "name": "user",
+ "type": "user",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "role": "",
+ "allow_null": 0,
+ "multiple": 1,
+ "return_format": "array"
+ },
+ {
+ "key": "field_60469c2065bd8",
+ "label": "Google Map",
+ "name": "google_map",
+ "type": "google_map",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "center_lat": "",
+ "center_lng": "",
+ "zoom": "",
+ "height": ""
+ },
+ {
+ "key": "field_60469d0dc197a",
+ "label": "Date Picker",
+ "name": "date_picker",
+ "type": "date_picker",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "display_format": "d\/m\/Y",
+ "return_format": "d\/m\/Y",
+ "first_day": 1
+ },
+ {
+ "key": "field_60469d19c197b",
+ "label": "Date Time Picker",
+ "name": "date_time_picker",
+ "type": "date_time_picker",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "display_format": "d\/m\/Y g:i a",
+ "return_format": "d\/m\/Y g:i a",
+ "first_day": 1
+ },
+ {
+ "key": "field_60469d27c197c",
+ "label": "Time Picker",
+ "name": "time_picker",
+ "type": "time_picker",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "display_format": "g:i a",
+ "return_format": "g:i a"
+ },
+ {
+ "key": "field_60469d34c197d",
+ "label": "Color Picker",
+ "name": "color_picker",
+ "type": "color_picker",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "default_value": ""
+ },
+ {
+ "key": "field_60469d5933bce",
+ "label": "Group",
+ "name": "group",
+ "type": "group",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "layout": "block",
+ "sub_fields": [
+ {
+ "key": "field_6047ecb8e5cbc",
+ "label": "Text Field In Group",
+ "name": "text_field_in_group",
+ "type": "text",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "default_value": "",
+ "placeholder": "",
+ "prepend": "",
+ "append": "",
+ "maxlength": ""
+ },
+ {
+ "key": "field_6047eccce5cbd",
+ "label": "Text Area Field In Group",
+ "name": "text_area_field_in_group",
+ "type": "textarea",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "default_value": "",
+ "placeholder": "",
+ "maxlength": "",
+ "rows": "",
+ "new_lines": ""
+ }
+ ]
+ },
+ {
+ "key": "field_6047cb430101c",
+ "label": "Repeater",
+ "name": "repeater",
+ "type": "repeater",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "collapsed": "",
+ "min": 0,
+ "max": 0,
+ "layout": "table",
+ "button_label": "",
+ "sub_fields": [
+ {
+ "key": "field_6047cb620101d",
+ "label": "Text Field In Repeater",
+ "name": "text_field_in_repeater",
+ "type": "text",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "default_value": "",
+ "placeholder": "",
+ "prepend": "",
+ "append": "",
+ "maxlength": ""
+ },
+ {
+ "key": "field_6047cb740101e",
+ "label": "Image Field In Repeater",
+ "name": "image_field_in_repeater",
+ "type": "image",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "return_format": "array",
+ "preview_size": "medium",
+ "library": "all",
+ "min_width": "",
+ "min_height": "",
+ "min_size": "",
+ "max_width": "",
+ "max_height": "",
+ "max_size": "",
+ "mime_types": ""
+ }
+ ]
+ },
+ {
+ "key": "field_6047cb92951ce",
+ "label": "Flexible Content",
+ "name": "flexible_content",
+ "type": "flexible_content",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "layouts": {
+ "layout_6047cb980608a": {
+ "key": "layout_6047cb980608a",
+ "name": "layout_one",
+ "label": "Layout One",
+ "display": "block",
+ "sub_fields": [
+ {
+ "key": "field_6047cc58951cf",
+ "label": "Text",
+ "name": "text",
+ "type": "text",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "default_value": "",
+ "placeholder": "",
+ "prepend": "",
+ "append": "",
+ "maxlength": ""
+ },
+ {
+ "key": "field_6047cc9b951d0",
+ "label": "Another Text Field",
+ "name": "another_text_field",
+ "type": "text",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "default_value": "",
+ "placeholder": "",
+ "prepend": "",
+ "append": "",
+ "maxlength": ""
+ }
+ ],
+ "min": "",
+ "max": ""
+ },
+ "layout_6047eee191715": {
+ "key": "layout_6047eee191715",
+ "name": "layout_two",
+ "label": "Layout Two",
+ "display": "block",
+ "sub_fields": [
+ {
+ "key": "field_6047eeeb91716",
+ "label": "Image",
+ "name": "image",
+ "type": "image",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "return_format": "array",
+ "preview_size": "medium",
+ "library": "all",
+ "min_width": "",
+ "min_height": "",
+ "min_size": "",
+ "max_width": "",
+ "max_height": "",
+ "max_size": "",
+ "mime_types": ""
+ }
+ ],
+ "min": "",
+ "max": ""
+ },
+ "layout_6047eefa91717": {
+ "key": "layout_6047eefa91717",
+ "name": "layout_three",
+ "label": "Layout Three",
+ "display": "block",
+ "sub_fields": [
+ {
+ "key": "field_6047ef0291718",
+ "label": "Gallery",
+ "name": "gallery",
+ "type": "gallery",
+ "instructions": "",
+ "required": 0,
+ "conditional_logic": 0,
+ "wrapper": {
+ "width": "",
+ "class": "",
+ "id": ""
+ },
+ "show_in_graphql": 1,
+ "return_format": "array",
+ "preview_size": "medium",
+ "insert": "append",
+ "library": "all",
+ "min": "",
+ "max": "",
+ "min_width": "",
+ "min_height": "",
+ "min_size": "",
+ "max_width": "",
+ "max_height": "",
+ "max_size": "",
+ "mime_types": ""
+ }
+ ],
+ "min": "",
+ "max": ""
+ }
+ },
+ "button_label": "Add Row",
+ "min": "",
+ "max": ""
+ }
+ ],
+ "location": [
+ [
+ {
+ "param": "post_type",
+ "operator": "==",
+ "value": "post"
+ }
+ ]
+ ],
+ "menu_order": 0,
+ "position": "normal",
+ "style": "default",
+ "label_placement": "top",
+ "instruction_placement": "label",
+ "hide_on_screen": "",
+ "active": true,
+ "description": "ACF Documentation Examples",
+ "show_in_graphql": 1,
+ "graphql_field_name": "acfDocs"
+ }
+]
\ No newline at end of file
diff --git a/readme.txt b/readme.txt
index 7e7458a..0b68998 100644
--- a/readme.txt
+++ b/readme.txt
@@ -4,7 +4,7 @@ Donate link: https://wpgraphql.com/acf
Tags: WPGraphQL, GraphQL, API, Advanced Custom Fields, ACF
Requires at least: 5.0
Tested up to: 5.1.1
-Stable tag: 0.4.1
+Stable tag: 0.5.0
License: GPL-3
License URI: https://www.gnu.org/licenses/gpl-3.0.html
diff --git a/src/class-acf.php b/src/class-acf.php
index c925a25..e7f8e21 100644
--- a/src/class-acf.php
+++ b/src/class-acf.php
@@ -7,6 +7,8 @@
namespace WPGraphQL\ACF;
+use GraphQL\Type\Definition\ResolveInfo;
+
/**
* Final class ACF
*/
@@ -85,11 +87,6 @@ public function __wakeup() {
*/
private function setup_constants() {
- // Plugin version.
- if ( ! defined( 'WPGRAPHQL_ACF_VERSION' ) ) {
- define( 'WPGRAPHQL_ACF_VERSION', '0.3.0' );
- }
-
// Plugin Folder Path.
if ( ! defined( 'WPGRAPHQL_ACF_PLUGIN_DIR' ) ) {
define( 'WPGRAPHQL_ACF_PLUGIN_DIR', plugin_dir_path( __FILE__ . '/..' ) );
@@ -132,6 +129,19 @@ private function actions() {
*/
private function filters() {
+ /**
+ * This filters any field that returns the `ContentTemplate` type
+ * to pass the source node down to the template for added context
+ */
+ add_filter( 'graphql_resolve_field', function( $result, $source, $args, $context, ResolveInfo $info, $type_name, $field_key, $field, $field_resolver ) {
+ if ( isset( $info->returnType ) && strtolower( 'ContentTemplate' ) === strtolower( $info->returnType ) ) {
+ if ( is_array( $result ) && ! isset( $result['node'] ) && ! empty( $source ) ) {
+ $result['node'] = $source;
+ }
+ }
+ return $result;
+ }, 10, 9 );
+
}
/**
diff --git a/src/class-acfsettings.php b/src/class-acfsettings.php
index ae19d78..ff8292e 100644
--- a/src/class-acfsettings.php
+++ b/src/class-acfsettings.php
@@ -19,64 +19,84 @@ class ACF_Settings {
*/
public function init() {
- /**
- * Creates a field group setting to allow a field group to be
- * shown in the GraphQL Schema.
- */
- add_action( 'acf/render_field_group_settings', [ $this, 'add_field_group_settings' ], 10, 1 );
-
/**
* Add settings to individual fields to allow each field granular control
* over how it's shown in the GraphQL Schema
*/
add_action( 'acf/render_field_settings', [ $this, 'add_field_settings' ], 10, 1 );
+ /**
+ * Enqueue scripts to enhance the UI of the ACF Field Group Settings
+ */
+ add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_graphql_acf_scripts' ], 10, 1 );
+
+ /**
+ * Register meta boxes for the ACF Field Group Settings
+ */
+ add_action( 'add_meta_boxes', [ $this, 'register_meta_boxes' ] );
+
+ /**
+ * Register an AJAX action and callback for converting ACF Location rules to GraphQL Types
+ */
+ add_action( 'wp_ajax_get_acf_field_group_graphql_types', [ $this, 'ajax_callback' ] );
+
}
/**
- * Add settings to each field to show in GraphQL
+ * Handle the AJAX callback for converting ACF Location settings to GraphQL Types
*
- * @param array $field The field to add the setting to.
+ * @return void
*/
- public function add_field_settings( $field ) {
+ public function ajax_callback() {
- $supported_fields = Config::get_supported_fields();
+ if ( isset( $_POST['data'] ) ) {
- /**
- * If there are no supported fields, or the field is not supported, don't add a setting field.
- */
- if ( empty( $supported_fields ) || ! is_array( $supported_fields ) || ! in_array( $field['type'], $supported_fields, true ) ) {
- return;
- }
+ $form_data = [];
- /**
- * Render the "show_in_graphql" setting for the field.
- */
- acf_render_field_setting(
- $field,
- [
- 'label' => __( 'Show in GraphQL', 'wp-graphql-acf' ),
- 'instructions' => __( 'Whether the field should be queryable via GraphQL', 'wp-graphql-acf' ),
- 'name' => 'show_in_graphql',
- 'type' => 'true_false',
- 'ui' => 1,
- 'default_value' => 1,
- 'value' => isset( $field['show_in_graphql'] ) ? (bool) $field['show_in_graphql'] : true,
- ],
- true
- );
+ parse_str( $_POST['data'], $form_data );
+
+ if ( empty( $form_data ) || ! isset( $form_data['acf_field_group'] ) ) {
+ wp_send_json( __( 'No form data.', 'wp-graphql-acf' ) );
+ }
+
+ $field_group = isset( $form_data['acf_field_group'] ) ? $form_data['acf_field_group'] : [];
+ $rules = new LocationRules( [ $field_group ] );
+ $rules->determine_location_rules();
+
+ $group_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : $field_group['title'];
+ $group_name = $rules->format_field_name( $group_name );
+ $all_rules = $rules->get_rules();
+ if ( isset( $all_rules[ $group_name ] ) ) {
+ wp_send_json( [ 'graphql_types' => array_values( $all_rules[ $group_name ] ) ] );
+ }
+ wp_send_json( [ 'graphql_types' => null ] );
+ }
+
+ echo __( 'No location rules were found', 'wp-graphql-acf' );
+ wp_die();
}
/**
- * This adds a setting to the ACF Field groups to activate a field group in GraphQL.
+ * Register the GraphQL Settings metabox for the ACF Field Group post type
*
- * If a field group is set to active and is set to "show_in_graphql", the fields in the field
- * group will be exposed to the GraphQL Schema based on the matching location rules.
+ * @return void
+ */
+ public function register_meta_boxes() {
+ add_meta_box( 'wpgraphql-acf-meta-box', __( 'GraphQL', 'wp-graphql-acf' ), [
+ $this,
+ 'display_metabox'
+ ], [ 'acf-field-group' ] );
+ }
+
+ /**
+ * Display the GraphQL Settings Metabox on the Field Group admin page
*
- * @param array $field_group The field group to add settings to.
+ * @param $field_group_post_object
*/
- public function add_field_group_settings( $field_group ) {
+ public function display_metabox( $field_group_post_object ) {
+
+ global $field_group;
/**
* Render a field in the Field Group settings to allow for a Field Group to be shown in GraphQL.
@@ -94,21 +114,118 @@ public function add_field_group_settings( $field_group ) {
);
/**
- * Render a field in the Field Group settings to allow for a Field Group to be shown in GraphQL.
+ * Render a field in the Field Group settings to set the GraphQL field name for the field group.
*/
acf_render_field_wrap(
[
'label' => __( 'GraphQL Field Name', 'acf' ),
- 'instructions' => __( 'The name of the field group in the GraphQL Schema.', 'wp-graphql-acf' ),
+ 'instructions' => __( 'The name of the field group in the GraphQL Schema. Names should not include spaces or special characters. Best practice is to use "camelCase".', 'wp-graphql-acf' ),
'type' => 'text',
'prefix' => 'acf_field_group',
'name' => 'graphql_field_name',
- 'required' => true,
+ 'required' => isset( $field_group['show_in_graphql'] ) ? (bool) $field_group['show_in_graphql'] : false,
'placeholder' => ! empty( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : null,
'value' => ! empty( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : null,
]
);
+ acf_render_field_wrap(
+ [
+ 'label' => __( 'Manually Set GraphQL Types for Field Group', 'acf' ),
+ 'instructions' => __( 'By default, ACF Field groups are added to the GraphQL Schema based on the field group\'s location rules. Checking this box will let you manually control the GraphQL Types the field group should be shown on in the GraphQL Schema using the checkboxes below, and the Location Rules will no longer effect the GraphQL Types.', 'wp-graphql-acf' ),
+ 'type' => 'true_false',
+ 'name' => 'map_graphql_types_from_location_rules',
+ 'prefix' => 'acf_field_group',
+ 'value' => isset( $field_group['map_graphql_types_from_location_rules'] ) ? (bool) $field_group['map_graphql_types_from_location_rules'] : false,
+ 'ui' => 1,
+ ]
+ );
+
+ $choices = Config::get_all_graphql_types();
+ acf_render_field_wrap(
+ [
+ 'label' => __( 'GraphQL Types to Show the Field Group On', 'wp-graphql-acf' ),
+ 'instructions' => __( 'Select the Types in the WPGraphQL Schema to show the fields in this field group on', 'wp-graphql-acf' ),
+ 'type' => 'checkbox',
+ 'prefix' => 'acf_field_group',
+ 'name' => 'graphql_types',
+ 'value' => ! empty( $field_group['graphql_types'] ) ? $field_group['graphql_types'] : [],
+ 'toggle' => true,
+ 'choices' => $choices,
+ ]
+ );
+
+ ?>
+
+
+
+
+ __( 'Show in GraphQL', 'wp-graphql-acf' ),
+ 'instructions' => __( 'Whether the field should be queryable via GraphQL', 'wp-graphql-acf' ),
+ 'name' => 'show_in_graphql',
+ 'type' => 'true_false',
+ 'ui' => 1,
+ 'default_value' => 1,
+ 'value' => isset( $field['show_in_graphql'] ) ? (bool) $field['show_in_graphql'] : true,
+ ],
+ true
+ );
+
+ }
+
+ /**
+ * This enqueues admin script.
+ *
+ * @param string $screen The screen that scripts are being enqueued to
+ *
+ * @return void
+ */
+ public function enqueue_graphql_acf_scripts( string $screen ) {
+ global $post;
+
+ if ( $screen == 'post-new.php' || $screen == 'post.php' ) {
+ if ( 'acf-field-group' === $post->post_type ) {
+ wp_enqueue_script( 'graphql-acf', plugins_url( 'src/js/main.js', dirname( __FILE__ ) ), array(
+ 'jquery',
+ 'acf-input',
+ 'acf-field-group'
+ ) );
+ }
+ }
}
}
diff --git a/src/class-config.php b/src/class-config.php
index 7a6014b..d13003c 100644
--- a/src/class-config.php
+++ b/src/class-config.php
@@ -7,6 +7,7 @@
namespace WPGraphQL\ACF;
+use Exception;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
use WPGraphQL\Data\DataSource;
@@ -16,43 +17,64 @@
use WPGraphQL\Model\Post;
use WPGraphQL\Model\Term;
use WPGraphQL\Model\User;
+use WPGraphQL\Registry\TypeRegistry;
+use WPGraphQL\Utils\Utils;
/**
* Config class.
*/
class Config {
+ /**
+ * @var TypeRegistry
+ */
protected $type_registry;
+ /**
+ * Stores the location rules for back compat
+ * @var array
+ */
+ protected $location_rules = [];
+
/**
* @var array List of field names registered to the Schema
*/
protected $registered_field_names;
+ /**
+ * @var array List of options page slugs registered to the Schema
+ */
+ protected $registered_options_pages = [];
+
/**
* Initialize WPGraphQL to ACF
*
- * @param \WPGraphQL\Registry\TypeRegistry $type_registry Instance of the WPGraphQL TypeRegistry
+ * @param TypeRegistry $type_registry Instance of the WPGraphQL TypeRegistry
+ *
+ * @throws Exception
*/
- public function init( \WPGraphQL\Registry\TypeRegistry $type_registry ) {
+ public function init( TypeRegistry $type_registry ) {
/**
* Set the TypeRegistry
*/
$this->type_registry = $type_registry;
+ $this->register_initial_types();
+
+ /**
+ * Gets the location rules for backward compatibility.
+ *
+ * This allows for ACF Field Groups that were registered before the "graphql_types"
+ * field was respected can still work with the old GraphQL Schema rules that mapped
+ * from the ACF Location rules.
+ */
+ $this->location_rules = $this->get_location_rules();
/**
* Add ACF Fields to GraphQL Types
*/
- $this->add_acf_fields_to_post_object_types();
- $this->add_acf_fields_to_term_objects();
- $this->add_acf_fields_to_comments();
- $this->add_acf_fields_to_menus();
- $this->add_acf_fields_to_menu_items();
- $this->add_acf_fields_to_media_items();
- $this->add_acf_fields_to_individual_posts();
- $this->add_acf_fields_to_users();
- $this->add_acf_fields_to_options_pages();
+ $this->add_options_pages_to_schema();
+ $this->add_acf_fields_to_graphql_types();
// This filter tells WPGraphQL to resolve revision meta for ACF fields from the revision's meta, instead
// of the parent (published post) meta.
@@ -96,6 +118,175 @@ public function init( \WPGraphQL\Registry\TypeRegistry $type_registry ) {
}, 10, 4 );
}
+ /**
+ * Registers initial Types for use with ACF Fields
+ *
+ * @throws Exception
+ */
+ public function register_initial_types() {
+
+ $this->type_registry->register_interface_type(
+ 'AcfFieldGroup',
+ [
+ 'description' => __( 'A Field Group registered by ACF', 'wp-graphql-acf' ),
+ 'fields' => [
+ 'fieldGroupName' => [
+ 'description' => __( 'The name of the ACF Field Group', 'wp-graphql-acf' ),
+ 'type' => 'String',
+ ],
+ ]
+ ]
+ );
+
+ $this->type_registry->register_object_type(
+ 'AcfLink',
+ [
+ 'description' => __( 'ACF Link field', 'wp-graphql-acf' ),
+ 'fields' => [
+ 'url' => [
+ 'type' => 'String',
+ 'description' => __( 'The url of the link', 'wp-graphql-acf' ),
+ ],
+ 'title' => [
+ 'type' => 'String',
+ 'description' => __( 'The title of the link', 'wp-graphql-acf' ),
+ ],
+ 'target' => [
+ 'type' => 'String',
+ 'description' => __( 'The target of the link (_blank, etc)', 'wp-graphql-acf' ),
+ ],
+ ],
+ ]
+ );
+
+ }
+
+
+
+ /**
+ * Gets the location rules
+ * @return array
+ */
+ protected function get_location_rules() {
+
+ $field_groups = acf_get_field_groups();
+ if ( empty( $field_groups ) || ! is_array( $field_groups ) ) {
+ return [];
+ }
+
+ $rules = [];
+
+ // Each field group that doesn't have GraphQL Types explicitly set should get the location
+ // rules interpreted.
+ foreach ( $field_groups as $field_group ) {
+ if ( ! isset( $field_group['graphql_types'] ) || ! is_array( $field_group['graphql_types'] ) ) {
+ $rules[] = $field_group;
+ }
+ }
+
+ if ( empty( $rules ) ) {
+ return [];
+ }
+
+ // If there are field groups with no graphql_types field set, inherit the rules from
+ // ACF Location Rules
+ $rules = new LocationRules();
+ $rules->determine_location_rules();
+ return $rules->get_rules();
+ }
+
+ protected function add_options_pages_to_schema() {
+
+ global $acf_options_page;
+
+ if ( ! isset( $acf_options_page ) ) {
+ return ;
+ }
+
+ /**
+ * Get a list of post types that have been registered to show in graphql
+ */
+ $graphql_options_pages = acf_get_options_pages();
+
+ /**
+ * If there are no post types exposed to GraphQL, bail
+ */
+ if ( empty( $graphql_options_pages ) || ! is_array( $graphql_options_pages ) ) {
+ return;
+ }
+
+ $options_pages_to_register = [];
+
+ /**
+ * Loop over the post types exposed to GraphQL
+ */
+ foreach ( $graphql_options_pages as $options_page_key => $options_page ) {
+ if ( ! isset( $options_page['show_in_graphql'] ) || false === (bool) $options_page['show_in_graphql'] ) {
+ continue;
+ }
+
+ /**
+ * Get options page properties.
+ */
+ $page_title = $options_page['page_title'];
+ $page_slug = $options_page['menu_slug'];
+ $type_name = isset( $options_page['graphql_field_name'] ) ? Utils::format_type_name( $options_page['graphql_field_name'] ) : Utils::format_type_name( $options_page['menu_slug'] );
+
+ $options_pages_to_register[ $type_name ] = [
+ 'title' => $page_title,
+ 'slug' => $page_slug,
+ 'type_name' => $type_name,
+ 'options_page' => $options_page,
+ ];
+
+ }
+
+ if ( is_array( $options_pages_to_register ) && ! empty( $options_pages_to_register ) ) {
+
+ foreach ( $options_pages_to_register as $page_to_register ) {
+
+ $page_title = $page_to_register['title'];
+ $page_slug = $page_to_register['slug'];
+ $type_name = isset( $page_to_register['type_name'] ) ? Utils::format_type_name( $page_to_register['type_name'] ) : Utils::format_type_name( $page_to_register['slug'] );
+ $options_page = $page_to_register['options_page'];
+
+ $this->type_registry->register_object_type( $type_name, [
+ 'description' => sprintf( __( '%s options.', 'wp-graphql-acf' ), $page_title ),
+ 'fields' => [
+ 'pageTitle' => [
+ 'type' => 'String',
+ 'resolve' => function( $source ) use ( $page_title ) {
+ return ! empty( $page_title ) ? $page_title : null;
+ },
+ ],
+ 'pageSlug' => [
+ 'type' => 'String',
+ 'resolve' => function( $source ) use ( $page_slug ) {
+ return ! empty( $page_slug ) ? $page_slug : null;
+ },
+ ],
+ ],
+ ] );
+
+ $field_name = Utils::format_field_name( $type_name );
+
+ $this->type_registry->register_field(
+ 'RootQuery',
+ $field_name,
+ [
+ 'type' => $type_name,
+ 'description' => sprintf( __( '%s options.', 'wp-graphql-acf' ), $page_title ),
+ 'resolve' => function() use ( $options_page ) {
+ return ! empty( $options_page ) ? $options_page : null;
+ }
+ ]
+ );
+
+ }
+ }
+
+ }
+
/**
* Determines whether a field group should be exposed to the GraphQL Schema. By default, field
* groups will not be exposed to GraphQL.
@@ -168,77 +359,6 @@ public static function camel_case( $str, array $no_strip = [] ) {
return $str;
}
- /**
- * Add ACF Fields to Post Object Types.
- *
- * This gets the Post Types that are configured to show_in_graphql and iterates
- * over them to expose ACF Fields to their Type in the GraphQL Schema.
- */
- protected function add_acf_fields_to_post_object_types() {
-
- /**
- * Get a list of post types that have been registered to show in graphql
- */
- $graphql_post_types = get_post_types( [ 'show_in_graphql' => true ] );
-
- /**
- * If there are no post types exposed to GraphQL, bail
- */
- if ( empty( $graphql_post_types ) || ! is_array( $graphql_post_types ) ) {
- return;
- }
-
- /**
- * Loop over the post types exposed to GraphQL
- */
- foreach ( $graphql_post_types as $post_type ) {
-
- /**
- * Get the field groups associated with the post type
- */
- $field_groups = acf_get_field_groups(
- [
- 'post_type' => $post_type,
- ]
- );
-
- /**
- * If there are no field groups for this post type, move on to the next one.
- */
- if ( empty( $field_groups ) || ! is_array( $field_groups ) ) {
- continue;
- }
-
- /**
- * Get the post_type_object
- */
- $post_type_object = get_post_type_object( $post_type );
-
- /**
- * Loop over the field groups for this post type
- */
- foreach ( $field_groups as $field_group ) {
-
- $field_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : Config::camel_case( $field_group['title'] );
-
- $field_group['type'] = 'group';
- $field_group['name'] = $field_name;
- $config = [
- 'name' => $field_name,
- 'description' => $field_group['description'],
- 'acf_field' => $field_group,
- 'acf_field_group' => null,
- 'resolve' => function( $root ) use ( $field_group ) {
- return isset( $root ) ? $root : null;
- }
- ];
-
- $this->register_graphql_field( $post_type_object->graphql_single_name, $field_name, $config );
- }
- }
-
- }
-
/**
* Undocumented function
*
@@ -253,6 +373,10 @@ protected function get_acf_field_value( $root, $acf_field, $format = false ) {
$value = null;
$id = null;
+ if ( is_array( $root ) && isset( $root['node'] ) ) {
+ $id = $root['node']->ID;
+ }
+
if ( is_array( $root ) && ! ( ! empty( $root['type'] ) && 'options_page' === $root['type'] ) ) {
if ( isset( $root[ $acf_field['key'] ] ) ) {
@@ -266,23 +390,24 @@ protected function get_acf_field_value( $root, $acf_field, $format = false ) {
} else {
switch ( true ) {
+
case $root instanceof Term:
- $id = acf_get_term_post_id( $root->taxonomyName, $root->term_id );
+ $id = 'term_' . $root->term_id;
break;
case $root instanceof Post:
- $id = absint( $root->ID );
+ $id = absint( $root->databaseId );
break;
case $root instanceof MenuItem:
$id = absint( $root->menuItemId );
break;
case $root instanceof Menu:
- $id = acf_get_term_post_id( 'nav_menu', $root->menuId );
+ $id = 'term_' . $root->menuId;
break;
case $root instanceof User:
$id = 'user_' . absint( $root->userId );
break;
case $root instanceof Comment:
- $id = 'comment_' . absint( $root->comment_ID );
+ $id = 'comment_' . absint( $root->databaseId );
break;
case is_array( $root ) && ! empty( $root['type'] ) && 'options_page' === $root['type']:
$id = $root['post_id'];
@@ -291,12 +416,15 @@ protected function get_acf_field_value( $root, $acf_field, $format = false ) {
$id = null;
break;
}
+ }
+
+ if ( empty( $value ) ) {
/**
* Filters the root ID, allowing additional Models the ability to provide a way to resolve their ID
*
- * @param int $id The ID of the object. Default null
- * @param mixed $root The Root object being resolved. The ID is typically a property of this object.
+ * @param int $id The ID of the object. Default null
+ * @param mixed $root The Root object being resolved. The ID is typically a property of this object.
*/
$id = apply_filters( 'graphql_acf_get_root_id', $id, $root );
@@ -310,6 +438,10 @@ protected function get_acf_field_value( $root, $acf_field, $format = false ) {
$format = true;
}
+ if ( 'select' === $acf_field['type'] ) {
+ $format = true;
+ }
+
/**
* Check if cloned field and retrieve the key accordingly.
*/
@@ -322,6 +454,7 @@ protected function get_acf_field_value( $root, $acf_field, $format = false ) {
$field_value = get_field( $key, $id, $format );
$value = ! empty( $field_value ) ? $field_value : null;
+
}
/**
@@ -392,13 +525,13 @@ public static function get_supported_fields() {
/**
* Undocumented function
*
- * @param [type] $type_name Undocumented.
- * @param [type] $field_name Undocumented.
- * @param [type] $config Undocumented.
+ * @param string $type_name The name of the GraphQL Type to add the field to.
+ * @param string $field_name The name of the field to add to the GraphQL Type.
+ * @param array $config The GraphQL configuration of the field.
*
* @return mixed
*/
- protected function register_graphql_field( $type_name, $field_name, $config ) {
+ protected function register_graphql_field( string $type_name, string $field_name, array $config ) {
$acf_field = isset( $config['acf_field'] ) ? $config['acf_field'] : null;
$acf_type = isset( $acf_field['type'] ) ? $acf_field['type'] : null;
@@ -406,8 +539,6 @@ protected function register_graphql_field( $type_name, $field_name, $config ) {
return false;
}
-
-
/**
* filter the field config for custom field types
*
@@ -417,7 +548,6 @@ protected function register_graphql_field( $type_name, $field_name, $config ) {
'type' => null,
'resolve' => isset( $config['resolve'] ) && is_callable( $config['resolve'] ) ? $config['resolve'] : function( $root, $args, $context, $info ) use ( $acf_field ) {
$value = $this->get_acf_field_value( $root, $acf_field );
-
return ! empty( $value ) ? $value : null;
},
], $type_name, $field_name, $config );
@@ -466,7 +596,16 @@ protected function register_graphql_field( $type_name, $field_name, $config ) {
* @see: https://github.com/wp-graphql/wp-graphql-acf/issues/25
*/
if ( empty( $acf_field['multiple'] ) ) {
- $field_config['type'] = 'String';
+ if('array' === $acf_field['return_format'] ){
+ $field_config['type'] = [ 'list_of' => 'String' ];
+ $field_config['resolve'] = function( $root ) use ( $acf_field) {
+ $value = $this->get_acf_field_value( $root, $acf_field, true);
+
+ return ! empty( $value ) && is_array( $value ) ? $value : [];
+ };
+ }else{
+ $field_config['type'] = 'String';
+ }
} else {
$field_config['type'] = [ 'list_of' => 'String' ];
$field_config['resolve'] = function( $root ) use ( $acf_field ) {
@@ -479,10 +618,8 @@ protected function register_graphql_field( $type_name, $field_name, $config ) {
case 'radio':
$field_config['type'] = 'String';
break;
- case 'range':
- $field_config['type'] = 'Float';
- break;
case 'number':
+ case 'range':
$field_config['type'] = 'Float';
break;
case 'true_false':
@@ -571,7 +708,7 @@ protected function register_graphql_field( $type_name, $field_name, $config ) {
} else {
$type_names = [];
foreach ( $acf_field['post_type'] as $post_type ) {
- if ( in_array( $post_type, \get_post_types( [ 'show_in_graphql' => true ]), true ) ) {
+ if ( in_array( $post_type, \get_post_types( [ 'show_in_graphql' => true ] ), true ) ) {
$type_names[ $post_type ] = get_post_type_object( $post_type )->graphql_single_name;
}
}
@@ -608,7 +745,7 @@ protected function register_graphql_field( $type_name, $field_name, $config ) {
$return = [];
if ( ! empty( $value ) ) {
if ( is_array( $value ) ) {
- foreach ($value as $id ) {
+ foreach ( $value as $id ) {
$post = get_post( $id );
if ( ! empty( $post ) ) {
$return[] = new Post( $post );
@@ -650,34 +787,7 @@ protected function register_graphql_field( $type_name, $field_name, $config ) {
];
break;
case 'link':
-
- $field_type_name = 'ACF_Link';
- if ( $this->type_registry->get_type( $field_type_name ) == $field_type_name ) {
- $field_config['type'] = $field_type_name;
- break;
- }
-
- register_graphql_object_type(
- $field_type_name,
- [
- 'description' => __( 'ACF Link field', 'wp-graphql-acf' ),
- 'fields' => [
- 'url' => [
- 'type' => 'String',
- 'description' => __( 'The url of the link', 'wp-graphql-acf' ),
- ],
- 'title' => [
- 'type' => 'String',
- 'description' => __( 'The title of the link', 'wp-graphql-acf' ),
- ],
- 'target' => [
- 'type' => 'String',
- 'description' => __( 'The target of the link (_blank, etc)', 'wp-graphql-acf' ),
- ],
- ],
- ]
- );
- $field_config['type'] = $field_type_name;
+ $field_config['type'] = 'AcfLink';
break;
case 'image':
case 'file':
@@ -736,7 +846,7 @@ protected function register_graphql_field( $type_name, $field_name, $config ) {
$return = [];
if ( ! empty( $value ) ) {
if ( is_array( $value ) ) {
- foreach ($value as $id ) {
+ foreach ( $value as $id ) {
$user = get_user_by( 'id', $id );
if ( ! empty( $user ) ) {
$user = new User( $user );
@@ -778,7 +888,7 @@ protected function register_graphql_field( $type_name, $field_name, $config ) {
}
}
- $is_multiple = isset($acf_field['field_type']) && in_array( $acf_field['field_type'], array('checkbox', 'multi_select'));
+ $is_multiple = isset( $acf_field['field_type'] ) && in_array( $acf_field['field_type'], array( 'checkbox', 'multi_select' ) );
$field_config = [
'type' => $is_multiple ? ['list_of' => $type ] : $type,
@@ -805,19 +915,20 @@ protected function register_graphql_field( $type_name, $field_name, $config ) {
$field_config = null;
break;
case 'group':
+
$field_type_name = $type_name . '_' . ucfirst( self::camel_case( $acf_field['name'] ) );
- if ( $this->type_registry->get_type( $field_type_name ) ) {
+ if ( null !== $this->type_registry->get_type( $field_type_name ) ) {
$field_config['type'] = $field_type_name;
break;
}
- register_graphql_object_type(
+ $this->type_registry->register_object_type(
$field_type_name,
[
'description' => __( 'Field Group', 'wp-graphql-acf' ),
+ 'interfaces' => [ 'AcfFieldGroup' ],
'fields' => [
'fieldGroupName' => [
- 'type' => 'String',
'resolve' => function( $source ) use ( $acf_field ) {
return ! empty( $acf_field['name'] ) ? $acf_field['name'] : null;
},
@@ -826,10 +937,10 @@ protected function register_graphql_field( $type_name, $field_name, $config ) {
]
);
-
$this->add_field_group_fields( $acf_field, $field_type_name );
$field_config['type'] = $field_type_name;
+
break;
case 'google_map':
@@ -865,7 +976,7 @@ protected function register_graphql_field( $type_name, $field_name, $config ) {
// ACF 5.8.6 added more data to Google Maps field value
// https://www.advancedcustomfields.com/changelog/
- if (\acf_version_compare(acf_get_db_version(), '>=', '5.8.6')) {
+ if ( \acf_version_compare(acf_get_db_version(), '>=', '5.8.6' ) ) {
$fields += [
'streetName' => [
'type' => 'String',
@@ -940,7 +1051,7 @@ protected function register_graphql_field( $type_name, $field_name, $config ) {
];
}
- register_graphql_object_type(
+ $this->type_registry->register_object_type(
$field_type_name,
[
'description' => __( 'Google Map field', 'wp-graphql-acf' ),
@@ -957,13 +1068,13 @@ protected function register_graphql_field( $type_name, $field_name, $config ) {
break;
}
- register_graphql_object_type(
+ $this->type_registry->register_object_type(
$field_type_name,
[
'description' => __( 'Field Group', 'wp-graphql-acf' ),
+ 'interfaces' => [ 'AcfFieldGroup' ],
'fields' => [
'fieldGroupName' => [
- 'type' => 'String',
'resolve' => function( $source ) use ( $acf_field ) {
return ! empty( $acf_field['name'] ) ? $acf_field['name'] : null;
},
@@ -1037,11 +1148,11 @@ protected function register_graphql_field( $type_name, $field_name, $config ) {
} else {
- register_graphql_object_type( $flex_field_layout_name, [
+ $this->type_registry->register_object_type( $flex_field_layout_name, [
'description' => __( 'Group within the flex field', 'wp-graphql-acf' ),
+ 'interfaces' => [ 'AcfFieldGroup' ],
'fields' => [
'fieldGroupName' => [
- 'type' => 'String',
'resolve' => function( $source ) use ( $flex_field_layout_name ) {
return ! empty( $flex_field_layout_name ) ? $flex_field_layout_name : null;
},
@@ -1058,7 +1169,7 @@ protected function register_graphql_field( $type_name, $field_name, $config ) {
}
}
- register_graphql_union_type( $field_type_name, [
+ $this->type_registry->register_union_type( $field_type_name, [
'typeNames' => $union_types,
'resolveType' => function( $value ) use ( $union_types ) {
return isset( $union_types[ $value['acf_fc_layout'] ] ) ? $this->type_registry->get_type( $union_types[ $value['acf_fc_layout'] ] ) : null;
@@ -1082,7 +1193,6 @@ protected function register_graphql_field( $type_name, $field_name, $config ) {
}
$config = array_merge( $config, $field_config );
-
$this->registered_field_names[] = $acf_field['name'];
return $this->type_registry->register_field( $type_name, $field_name, $config );
}
@@ -1094,7 +1204,7 @@ protected function register_graphql_field( $type_name, $field_name, $config ) {
* @param string $type_name The Type name in the GraphQL Schema to add fields to.
* @param bool $layout Whether or not these fields are part of a Flex Content layout.
*/
- protected function add_field_group_fields( $field_group, $type_name, $layout = false ) {
+ protected function add_field_group_fields( array $field_group, string $type_name, $layout = false ) {
/**
* If the field group has the show_in_graphql setting configured, respect it's setting
@@ -1175,613 +1285,211 @@ protected function add_field_group_fields( $field_group, $type_name, $layout = f
}
/**
- * Add field groups to Taxonomies
+ * Returns all available GraphQL Types
*
- * @return void
+ * @return array
*/
- protected function add_acf_fields_to_term_objects() {
-
- /**
- * Get a list of taxonomies that have been registered to show in graphql
- */
- $graphql_taxonomies = \WPGraphQL::get_allowed_taxonomies();
-
- /**
- * If there are no taxonomies exposed to GraphQL, bail
- */
- if ( empty( $graphql_taxonomies ) || ! is_array( $graphql_taxonomies ) ) {
- return;
+ public static function get_all_graphql_types() {
+ $graphql_types = array();
+
+ // Use GraphQL to get the Interface and the Types that implement them
+ $query = '
+ query GetPossibleTypes($name:String!){
+ __type(name:$name){
+ name
+ description
+ possibleTypes {
+ name
+ description
+ }
+ }
}
+ ';
+
+ $interfaces = [
+ 'ContentNode' => [
+ 'label' => __( 'Post Type', 'wp-graphql-acf' ),
+ 'plural_label' => __( 'All Post Types', 'wp-graphql-acf' ),
+ ],
+ 'TermNode' => [
+ 'label' => __( 'Taxonomy', 'wp-graphql-acf' ),
+ 'plural_label' => __( 'All Taxonomies', 'wp-graphql-acf' ),
+ ],
+ 'ContentTemplate' => [
+ 'label' => __( 'Page Template', 'wp-graphql-acf' ),
+ 'plural_label' => __( 'All Templates Assignable to Content', 'wp-graphql-acf' ),
+ ]
+ ];
- /**
- * Loop over the taxonomies exposed to GraphQL
- */
- foreach ( $graphql_taxonomies as $taxonomy ) {
+ foreach ( $interfaces as $interface_name => $config ) {
- /**
- * Get the field groups associated with the taxonomy
- */
- $field_groups = acf_get_field_groups(
- [
- 'taxonomy' => $taxonomy,
+ $interface_query = graphql([
+ 'query' => $query,
+ 'variables' => [
+ 'name' => $interface_name
]
- );
-
- /**
- * If there are no field groups for this taxonomy, move on to the next one.
- */
- if ( empty( $field_groups ) || ! is_array( $field_groups ) ) {
- continue;
- }
-
- /**
- * Get the Taxonomy object
- */
- $tax_object = get_taxonomy( $taxonomy );
+ ]);
- if ( empty( $tax_object ) || ! isset( $tax_object->graphql_single_name ) ) {
- return;
- }
+ $possible_types = $interface_query['data']['__type']['possibleTypes'];
+ asort( $possible_types );
- /**
- * Loop over the field groups for this post type
- */
- foreach ( $field_groups as $field_group ) {
-
- $field_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : Config::camel_case( $field_group['title'] );
-
- $field_group['type'] = 'group';
- $field_group['name'] = $field_name;
- $description = $field_group['description'] ? $field_group['description'] . ' | ' : '';
- $config = [
- 'name' => $field_name,
- 'description' => $description . sprintf( __( 'Added to the GraphQL Schema because the ACF Field Group "%1$s" was assigned to the "%2$s" taxonomy', 'wp-graphql-acf' ), $field_group['title'], $tax_object->name ),
- 'acf_field' => $field_group,
- 'acf_field_group' => null,
- 'resolve' => function( $root ) use ( $field_group ) {
- return isset( $root ) ? $root : null;
- }
- ];
+ if ( ! empty( $possible_types ) && is_array( $possible_types ) ) {
- $this->register_graphql_field( $tax_object->graphql_single_name, $field_name, $config );
- }
- }
- }
+ // Intentionally not translating "ContentNode Interface" as this is part of the GraphQL Schema and should not be translated.
+ $graphql_types[ $interface_name ] = '' . $interface_name . ' Interface (' . $config['plural_label'] . ')';
+ $label = ' (' . $config['label'] . ')';
+ foreach ( $possible_types as $type ) {
+ $type_label = $type['name'] . $label;
+ $type_key = $type['name'];
- /**
- * Add ACF Fields to comments
- *
- * @return void
- */
- protected function add_acf_fields_to_comments() {
-
- $comment_field_groups = [];
-
- /**
- * Get the field groups associated with the taxonomy
- */
- $field_groups = acf_get_field_groups();
-
- foreach ( $field_groups as $field_group ) {
- if ( ! empty( $field_group['location'] ) && is_array( $field_group['location'] ) ) {
- foreach ( $field_group['location'] as $locations ) {
- if ( ! empty( $locations ) && is_array( $locations ) ) {
- foreach ( $locations as $location ) {
- if ( 'comment' === $location['param'] && '!=' === $location['operator'] ) {
- continue;
- }
- if ( 'comment' === $location['param'] && '==' === $location['operator'] ) {
- $comment_field_groups[] = $field_group;
- }
- }
- }
+ $graphql_types[ $type_key ] = $type_label;
}
}
- }
- if ( empty( $comment_field_groups ) ) {
- return;
}
/**
- * Loop over the field groups for this post type
+ * Add comment to GraphQL types
*/
- foreach ( $comment_field_groups as $field_group ) {
-
- $field_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : Config::camel_case( $field_group['title'] );
-
- $field_group['type'] = 'group';
- $field_group['name'] = $field_name;
- $description = $field_group['description'] ? $field_group['description'] . ' | ' : '';
- $config = [
- 'name' => $field_name,
- 'description' => $description . sprintf( __( 'Added to the GraphQL Schema because the ACF Field Group "%s" was assigned to Comments', 'wp-graphql-acf' ), $field_group['title'] ),
- 'acf_field' => $field_group,
- 'acf_field_group' => null,
- 'resolve' => function( $root ) use ( $field_group ) {
- return isset( $root ) ? $root : null;
- }
- ];
-
- $this->register_graphql_field( 'Comment', $field_name, $config );
-
- }
-
- }
-
- /**
- * Add Fields to Menus in the GraphQL Schema
- *
- * @return void
- */
- protected function add_acf_fields_to_menus() {
-
- $menu_field_groups = [];
+ $graphql_types['Comment'] = __( 'Comment', 'wp-graphql-acf' );
/**
- * Get the field groups associated with the taxonomy
+ * Add menu to GraphQL types
*/
- $field_groups = acf_get_field_groups();
-
- foreach ( $field_groups as $field_group ) {
- if ( ! empty( $field_group['location'] ) && is_array( $field_group['location'] ) ) {
- foreach ( $field_group['location'] as $locations ) {
- if ( ! empty( $locations ) && is_array( $locations ) ) {
- foreach ( $locations as $location ) {
- if ( 'nav_menu' === $location['param'] && '!=' === $location['operator'] ) {
- continue;
- }
- if ( 'nav_menu' === $location['param'] && '==' === $location['operator'] ) {
- $menu_field_groups[] = $field_group;
- break;
- }
- }
- }
- }
- }
- }
+ $graphql_types['Menu'] = __( 'Menu', 'wp-graphql-acf' );
- if ( empty( $menu_field_groups ) ) {
- return;
- }
+ /**
+ * Add menu items to GraphQL types
+ */
+ $graphql_types['MenuItem'] = __( 'Menu Item', 'wp-graphql-acf' );
/**
- * Loop over the field groups for this post type
+ * Add users to GraphQL types
*/
- foreach ( $menu_field_groups as $field_group ) {
+ $graphql_types['User'] = __( 'User', 'wp-graphql-acf' );
- $field_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : Config::camel_case( $field_group['title'] );
- $field_group['type'] = 'group';
- $field_group['name'] = $field_name;
- $description = $field_group['description'] ? $field_group['description'] . ' | ' : '';
- $config = [
- 'name' => $field_name,
- 'description' => $description . sprintf( __( 'Added to the GraphQL Schema because the ACF Field Group "%s" was assigned to Menus', 'wp-graphql-acf' ), $field_group['title'] ),
- 'acf_field' => $field_group,
- 'acf_field_group' => null,
- 'resolve' => function( $root ) use ( $field_group ) {
- return isset( $root ) ? $root : null;
- }
- ];
+ /**
+ * Add options pages to GraphQL types
+ */
+ global $acf_options_page;
- $this->register_graphql_field( 'Menu', $field_name, $config );
+ if ( isset( $acf_options_page ) ) {
+ /**
+ * Get a list of post types that have been registered to show in graphql
+ */
+ $graphql_options_pages = acf_get_options_pages();
- }
+ /**
+ * If there are no post types exposed to GraphQL, bail
+ */
+ if ( ! empty( $graphql_options_pages ) && is_array( $graphql_options_pages ) ) {
- }
+ /**
+ * Prepare type key prefix and label surfix
+ */
+ $label = ' (' . __( 'ACF Options Page', 'wp-graphql-acf' ) . ')';
- /**
- * Add ACF Field Groups to Menu Items
- *
- * @return void
- */
- protected function add_acf_fields_to_menu_items() {
+ /**
+ * Loop over the post types exposed to GraphQL
+ */
+ foreach ( $graphql_options_pages as $options_page_key => $options_page ) {
+ if ( ! isset( $options_page['show_in_graphql'] ) || false === (bool) $options_page['show_in_graphql'] ) {
+ continue;
+ }
- $menu_item_field_groups = [];
+ /**
+ * Get options page properties.
+ */
+ $page_title = $options_page['page_title'];
+ $type_label = $page_title . $label;
+ $type_name = isset( $options_page['graphql_field_name'] ) ? Utils::format_type_name( $options_page['graphql_field_name'] ) : Utils::format_type_name( $options_page['menu_slug'] );
- /**
- * Get the field groups associated with the taxonomy
- */
- $field_groups = acf_get_field_groups();
- foreach ( $field_groups as $field_group ) {
- if ( ! empty( $field_group['location'] ) && is_array( $field_group['location'] ) ) {
- foreach ( $field_group['location'] as $locations ) {
- if ( ! empty( $locations ) && is_array( $locations ) ) {
- foreach ( $locations as $location ) {
- if ( 'nav_menu_item' === $location['param'] && '!=' === $location['operator'] ) {
- continue;
- }
- if ( 'nav_menu_item' === $location['param'] && '==' === $location['operator'] ) {
- $menu_item_field_groups[] = $field_group;
- }
- }
- }
+ $graphql_types[ $type_name ] = $type_label;
}
}
}
- if ( empty( $menu_item_field_groups ) ) {
- return;
- }
-
- /**
- * Loop over the field groups for this post type
- */
- foreach ( $menu_item_field_groups as $field_group ) {
-
- $field_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : Config::camel_case( $field_group['title'] );
-
- $field_group['type'] = 'group';
- $field_group['name'] = $field_name;
- $description = $field_group['description'] ? $field_group['description'] . ' | ' : '';
- $config = [
- 'name' => $field_name,
- 'description' => $description . sprintf( __( 'Added to the GraphQL Schema because the ACF Field Group "%s" was assigned to Menu Items', 'wp-graphql-acf' ), $field_group['title'] ),
- 'acf_field' => $field_group,
- 'acf_field_group' => null,
- 'resolve' => function( $root ) use ( $field_group ) {
- return isset( $root ) ? $root : null;
- }
- ];
-
- $this->register_graphql_field( 'MenuItem', $field_name, $config );
-
- }
+ return $graphql_types;
}
/**
- * Add ACF Field Groups to Media Items (attachments)
- *
- * @return void
+ * Adds acf field groups to GraphQL types.
*/
- protected function add_acf_fields_to_media_items() {
-
- $media_item_field_groups = [];
-
+ protected function add_acf_fields_to_graphql_types() {
/**
- * Get the field groups associated with the taxonomy
+ * Get all the field groups
*/
$field_groups = acf_get_field_groups();
- foreach ( $field_groups as $field_group ) {
- if ( ! empty( $field_group['location'] ) && is_array( $field_group['location'] ) ) {
- foreach ( $field_group['location'] as $locations ) {
- if ( ! empty( $locations ) && is_array( $locations ) ) {
- foreach ( $locations as $location ) {
- if ( 'attachment' === $location['param'] && '!=' === $location['operator'] ) {
- continue;
- }
- if ( 'attachment' === $location['param'] && '==' === $location['operator'] ) {
- $media_item_field_groups[] = $field_group;
- }
- }
- }
- }
- }
- }
-
- if ( empty( $media_item_field_groups ) ) {
- return;
- }
-
/**
- * Loop over the field groups for this post type
+ * If there are no acf field groups, bail
*/
- foreach ( $media_item_field_groups as $field_group ) {
-
- $field_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : Config::camel_case( $field_group['title'] );
-
- $field_group['type'] = 'group';
- $field_group['name'] = $field_name;
- $description = $field_group['description'] ? $field_group['description'] . ' | ' : '';
- $config = [
- 'name' => $field_name,
- 'description' => $description . sprintf( __( 'Added to the GraphQL Schema because the ACF Field Group "%s" was assigned to attachments', 'wp-graphql-acf' ), $field_group['title'] ),
- 'acf_field' => $field_group,
- 'acf_field_group' => null,
- 'resolve' => function( $root ) use ( $field_group ) {
- return isset( $root ) ? $root : null;
- }
- ];
-
- $this->register_graphql_field( 'MediaItem', $field_name, $config );
-
+ if ( empty( $field_groups ) || ! is_array( $field_groups ) ) {
+ return;
}
- }
-
- protected function add_acf_fields_to_individual_posts() {
-
- $post_field_groups = [];
-
- /**
- * Get the field groups associated with the taxonomy
- */
- $field_groups = acf_get_field_groups();
-
- $allowed_post_types = get_post_types( [
- 'show_ui' => true,
- 'show_in_graphql' => true
- ] );
/**
- * Remove the `attachment` post_type, as it's treated special and we don't
- * want to add field groups in the same way we do for other post types
+ * Loop over all the field groups
*/
- unset( $allowed_post_types['attachment'] );
-
-
foreach ( $field_groups as $field_group ) {
- if ( ! empty( $field_group['location'] ) && is_array( $field_group['location'] ) ) {
- foreach ( $field_group['location'] as $locations ) {
- if ( ! empty( $locations ) && is_array( $locations ) ) {
- foreach ( $locations as $location ) {
-
- /**
- * If the operator is not equal to, we don't need to do anything,
- * so we can just continue
- */
- if ( '!=' === $location['operator'] ) {
- continue;
- }
- /**
- * If the param (the post_type) is in the array of allowed_post_types
- */
- if ( in_array( $location['param'], $allowed_post_types, true ) && '==' === $location['operator'] ) {
+ $field_group_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : $field_group['title'];
+ $field_group_name = Utils::format_field_name( $field_group_name );
- $post_field_groups[] = [
- 'type' => $location['param'],
- 'field_group' => $field_group,
- 'post_id' => $location['value']
- ];
- }
- }
+ $manually_set_graphql_types = isset( $field_group['map_graphql_types_from_location_rules'] ) ? (bool) $field_group['map_graphql_types_from_location_rules'] : false;
+
+ if ( false === $manually_set_graphql_types ) {
+ if ( ! isset( $field_group['graphql_types'] ) || empty( $field_group['graphql_types'] ) ) {
+ $field_group['graphql_types'] = [];
+ $location_rules = $this->get_location_rules();
+ if ( isset( $location_rules[ $field_group_name ] ) ) {
+ $field_group['graphql_types'] = $location_rules[ $field_group_name ];
}
}
}
- }
-
-
- /**
- * If no field groups are assigned to a specific post, we don't need to modify the Schema
- */
- if ( empty( $post_field_groups ) ) {
- return;
- }
-
- /**
- * Loop over the field groups assigned to a specific post
- * and register them to the Schema
- */
- foreach ( $post_field_groups as $key => $group ) {
- if ( empty( $group['field_group'] ) || ! is_array( $group['field_group'] ) ) {
+ if ( ! is_array( $field_group['graphql_types'] ) || empty( $field_group['graphql_types'] ) ) {
continue;
}
- $post_object = get_post( (int) $group['post_id'] );
-
- $allowed_post_types = get_post_types( [ 'show_in_graphql' => true ] );
- if ( ! $post_object instanceof \WP_Post || ! in_array( $post_object->post_type, $allowed_post_types, true ) ) {
- continue;
+ /**
+ * Determine if the field group should be exposed
+ * to graphql
+ */
+ if ( ! $this->should_field_group_show_in_graphql( $field_group ) ) {
+ return;
}
- $field_group = $group['field_group'];
- $post_type_object = get_post_type_object( $post_object->post_type );
-
+ $graphql_types = array_unique( $field_group['graphql_types'] );
+ $graphql_types = array_filter( $graphql_types );
+ /**
+ * Prepare default info
+ */
$field_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : Config::camel_case( $field_group['title'] );
-
- $field_group['type'] = 'group';
- $field_group['name'] = $field_name;
- $description = $field_group['description'] ? $field_group['description'] . ' | ' : '';
- $config = [
- 'name' => $field_name,
- 'description' => $description . sprintf( __( 'Added to the GraphQL Schema because the ACF Field Group "%1$s" was assigned to an individual post of the post_type: "%2$s". The group will be present in the Schema for the "%3$s" Type, but will only resolve if the entity has content saved.', 'wp-graphql-acf' ), $field_group['title'], $post_type_object->name, $post_type_object->graphql_plural_name ),
- 'acf_field' => $field_group,
- 'acf_field_group' => null,
- 'resolve' => function( $root ) use ( $field_group ) {
- return isset( $root ) ? $root : null;
- }
- ];
-
- $this->register_graphql_field( $post_type_object->graphql_single_name, $field_name, $config );
-
- }
-
- }
-
- /**
- * Add field groups to users when assigned to user edit/register screens
- */
- protected function add_acf_fields_to_users() {
-
- /**
- * Get the field groups associated with the User edit form
- */
- $user_edit_field_groups = acf_get_field_groups( [
- 'user_form' => 'edit',
- ] );
-
- /**
- * Get the field groups associated with the User register form
- */
- $user_register_field_groups = acf_get_field_groups( [
- 'user_form' => 'register',
- ] );
-
- /**
- * Get a unique list of groups that match the register and edit user location rules
- */
- $field_groups = array_merge( $user_edit_field_groups, $user_register_field_groups );
- $field_groups = array_intersect_key( $field_groups, array_unique( array_map( 'serialize', $field_groups ) ) );
-
-
- foreach ( $field_groups as $field_group ) {
-
- $field_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : Config::camel_case( $field_group['title'] );
$field_group['type'] = 'group';
$field_group['name'] = $field_name;
- $description = $field_group['description'] ? $field_group['description'] . ' | ' : '';
$config = [
'name' => $field_name,
- 'description' => $description . sprintf( __( 'Added to the GraphQL Schema because the ACF Field Group "%1$s" was assigned to Users edit or register form', 'wp-graphql-acf' ), $field_group['title'] ),
'acf_field' => $field_group,
'acf_field_group' => null,
- 'resolve' => function( $root ) use ( $field_group ) {
+ 'resolve' => function ( $root ) use ( $field_group ) {
return isset( $root ) ? $root : null;
}
];
- $this->register_graphql_field( 'User', $field_name, $config );
-
-
- }
-
- }
-
- /**
- * Adds options pages and options page field groups to the schema.
- */
- protected function add_acf_fields_to_options_pages() {
- global $acf_options_page;
-
- if ( ! isset( $acf_options_page ) ) {
- return ;
- }
-
- /**
- * Get a list of post types that have been registered to show in graphql
- */
- $graphql_options_pages = acf_get_options_pages();
-
- /**
- * If there are no post types exposed to GraphQL, bail
- */
- if ( empty( $graphql_options_pages ) || ! is_array( $graphql_options_pages ) ) {
- return;
- }
-
- /**
- * Loop over the post types exposed to GraphQL
- */
- foreach ( $graphql_options_pages as $options_page_key => $options_page ) {
- if ( empty( $options_page['show_in_graphql'] ) ) {
- continue;
- }
-
- /**
- * Get options page properties.
- */
- $page_title = $options_page['page_title'];
- $page_slug = $options_page['menu_slug'];
-
- /**
- * Get the field groups associated with the options page
- */
- $field_groups = acf_get_field_groups(
- [
- 'options_page' => $options_page['menu_slug'],
- ]
- );
-
- /**
- * If there are no field groups for this options page, move on to the next one.
- */
- if ( empty( $field_groups ) || ! is_array( $field_groups ) ) {
- continue;
- }
-
- /**
- * Loop over the field groups for this options page.
- */
- $options_page_fields = array();
- foreach ( $field_groups as $field_group ) {
- $field_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : Config::camel_case( $field_group['title'] );
-
- $field_group['type'] = 'group';
- $field_group['name'] = $field_name;
- $config = [
- 'name' => $field_name,
- 'description' => $field_group['description'],
- 'acf_field' => $field_group,
- 'acf_field_group' => null,
- 'resolve' => function( $root ) use ( $field_group ) {
- return isset( $root ) ? $root : null;
- }
- ];
-
- $options_page_fields[ $field_name ] = $config;
-
- }
+ $qualifier = sprintf( __( 'Added to the GraphQL Schema because the ACF Field Group "%1$s" was set to Show in GraphQL.', 'wp-graphql-acf' ), $field_group['title'] );
+ $config['description'] = $field_group['description'] ? $field_group['description'] . ' | ' . $qualifier : $qualifier;
/**
- * Continue if no options to show in GraphQL
+ * Loop over the GraphQL types for this field group on
*/
- if ( empty( $options_page_fields ) ) {
- continue;
- }
-
- /**
- * Create field and type names. Use explicit graphql_field_name
- * if available and fallback to generating from title if not available.
- */
- if ( ! empty( $options_page['graphql_field_name'] ) ) {
- $field_name = $options_page['graphql_field_name'];
- $type_name = ucfirst( $options_page['graphql_field_name'] );
- } else {
- $field_name = Config::camel_case( $page_title );
- $type_name = ucfirst( Config::camel_case( $page_title ) );
- }
-
- /**
- * Register options page type to schema.
- */
- register_graphql_object_type(
- $type_name,
- [
- 'description' => sprintf( __( '%s options', 'wp-graphql-acf' ), $page_title ),
- 'fields' => [
- 'pageTitle' => [
- 'type' => 'String',
- 'resolve' => function( $source ) use ( $page_title ) {
- return ! empty( $page_title ) ? $page_title : null;
- },
- ],
- 'pageSlug' => [
- 'type' => 'String',
- 'resolve' => function( $source ) use ( $page_slug ) {
- return ! empty( $page_slug ) ? $page_slug : null;
- },
- ],
- ],
- ]
- );
-
- /**
- * Register options page type to the "RootQuery"
- */
- $options_page['type'] = 'options_page';
- register_graphql_field(
- 'RootQuery',
- $field_name,
- [
- 'type' => $type_name,
- 'description' => sprintf( __( '%s options', 'wp-graphql-acf' ), $options_page['page_title'] ),
- 'resolve' => function() use ( $options_page ) {
- return ! empty( $options_page ) ? $options_page : null;
- }
- ]
- );
-
- /**
- * Register option page fields to the option page type.
- */
- foreach ( $options_page_fields as $name => $config ) {
- $this->register_graphql_field( $type_name, $name, $config );
+ foreach ( $graphql_types as $graphql_type ) {
+ $this->register_graphql_field( $graphql_type, $field_name, $config );
}
}
+
}
}
diff --git a/src/js/main.js b/src/js/main.js
new file mode 100644
index 0000000..6a807b8
--- /dev/null
+++ b/src/js/main.js
@@ -0,0 +1,296 @@
+$j = jQuery.noConflict();
+
+$j(document).ready(function () {
+
+ var GraphqlLocationManager = new acf.Model({
+ id: 'graphqlLocationManager',
+ wait: 'ready',
+ events: {
+ 'click .add-location-rule': 'onClickAddRule',
+ 'click .add-location-group': 'onClickAddGroup',
+ 'click .remove-location-rule': 'onClickRemoveRule',
+ 'change .refresh-location-rule': 'onChangeRemoveRule',
+ 'change .rule-groups .operator select': 'onChangeRemoveRule',
+ 'change .rule-groups .value select': 'onChangeRemoveRule',
+ },
+ requestPending: false,
+ initialize: function () {
+ this.$el = $j('#acf-field-group-locations');
+ this.getGraphqlTypes();
+ },
+
+ onClickAddRule: function (e, $el) {
+ this.getGraphqlTypes();
+ },
+
+ onClickRemoveRule: function (e, $el) {
+ this.getGraphqlTypes();
+ },
+
+ onChangeRemoveRule: function (e, $el) {
+ setTimeout(function () {
+ GraphqlLocationManager.getGraphqlTypes();
+ }, 500);
+
+ },
+
+ onClickAddGroup: function (e, $el) {
+ this.getGraphqlTypes();
+ },
+
+ isRequestPending: function() {
+ return this.requestPending;
+ },
+
+ startRequest: function () {
+ this.requestPending = true;
+ },
+
+ finishRequest: function() {
+ this.requestPending = false;
+ },
+
+ getGraphqlTypes: function () {
+ getGraphqlTypesFromLocationRules();
+ },
+ });
+
+ /**
+ * Set the visibility of the GraphQL Fields based on the `show_in_graphql`
+ * field state.
+ */
+ function setGraphqlFieldVisibility() {
+
+ var showInGraphQLCheckbox = $j('#acf_field_group-show_in_graphql');
+ var graphqlFields = $j('#wpgraphql-acf-meta-box .acf-field');
+
+ graphqlFields.each(function (i, el) {
+ if ($j(this).attr('data-name') !== 'show_in_graphql') {
+ if (!showInGraphQLCheckbox.is(':checked')) {
+ $j(this).hide();
+ } else {
+ $j(this).show();
+ }
+ }
+ });
+
+ showInGraphQLCheckbox.on('change', function () {
+ setGraphqlFieldVisibility();
+ getGraphqlTypesFromLocationRules();
+ })
+
+ }
+
+ function toggleFieldRequirement() {
+
+ $j('#acf_field_group-show_in_graphql').on('change', function () {
+ var graphqlFieldNameWrap = $j('.acf-field[data-name="graphql_field_name"]'),
+ graphqlLabel = graphqlFieldNameWrap.find('label'),
+ graphqlInput = $j('#acf_field_group-graphql_field_name');
+
+ if ($j(this).is(':checked')) {
+
+ // Add span.acf-required if necessary.
+ if (graphqlFieldNameWrap.find('.acf-required').length === 0) {
+ graphqlLabel.append('*');
+ }
+
+ // Toggle required attributes and visual features.
+ graphqlFieldNameWrap.addClass('is-required');
+ graphqlLabel.find('.acf-required').show();
+ graphqlInput.attr('required', true);
+ } else {
+ graphqlFieldNameWrap.removeClass('is-required');
+ graphqlLabel.find('.acf-required').hide();
+ graphqlInput.attr('required', false);
+ }
+
+ });
+
+ }
+
+ /**
+ * Listen to state changes for checkboxes for Interfaces and the checkboxes for
+ * Possible Types of the interfaces
+ */
+ function initInterfaceCheckboxes() {
+
+ // Find all the checkboxes for Interface types
+ $j('span[data-interface]').each(function (i, el) {
+
+ // Get the interface name
+ let interfaceName = $j(el).data('interface');
+
+ // Get the checkbox for the interface
+ let interfaceCheckbox = $j('input[value="' + interfaceName + '"]');
+
+ // Find all checkboxes that implement the interface
+ let possibleTypesCheckboxes = $j('span[data-implements="' + interfaceName + '"]').siblings('input[type="checkbox"]');
+
+ // Prepend some space before to nest the Types beneath the Interface
+ possibleTypesCheckboxes.before(" ");
+
+ // Listen for changes on the Interface checkbox
+ interfaceCheckbox.change(function () {
+ possibleTypesCheckboxes.prop('checked', $j(this).is(":checked"));
+ })
+
+ // Listen for changes to the checkboxes that implement the Interface
+ possibleTypesCheckboxes.change(function () {
+
+ // Set the checked state of the Interface checkbox
+ if (!$j(this).is(":checked") && interfaceCheckbox.is(":checked")) {
+ interfaceCheckbox.prop("checked", false);
+ }
+
+ // Set the state of the Implementing checkboxes
+ if ($j(possibleTypesCheckboxes).not(":checked").length === 0) {
+ interfaceCheckbox.prop("checked", true);
+ }
+
+ })
+
+ });
+
+ }
+
+ /**
+ * JavaScript version of the PHP lcfirst
+ *
+ * @param str
+ * @returns {string}
+ */
+ function lcfirst(str) {
+ str += ''
+ const f = str.charAt(0)
+ .toLowerCase()
+ return f + str.substr(1)
+ }
+
+ /**
+ * JavaScript version of the PHP ucwords
+ *
+ * @param str
+ * @returns {string}
+ */
+ function ucwords(str) {
+ return str.toLowerCase().replace(/\b[a-z]/g, function (letter) {
+ return letter.toUpperCase();
+ })
+ }
+
+ /**
+ * Based on the WPGraphQL format_field_name function
+ *
+ * See: https://github.com/wp-graphql/wp-graphql/blob/cc0b383259383898c3a1bebe65adf1140290b37e/src/Utils/Utils.php#L85-L100
+ */
+ function formatFieldName(fieldName) {
+
+ fieldName.replace('[^a-zA-Z0-9 -]', '-');
+ fieldName = lcfirst(fieldName);
+ fieldName = lcfirst(fieldName.split('-').join(' '));
+ fieldName = ucwords(fieldName);
+ fieldName = lcfirst(fieldName.split(' ').join(''));
+ return fieldName;
+
+ }
+
+ /**
+ * Set the GraphQL Field Name value based on the Field Group Title
+ * if the graphql_field_name has not already been set.
+ */
+ function setGraphqlFieldName() {
+ var graphqlFieldNameField = $j('#acf_field_group-graphql_field_name');
+ var fieldGroupTitle = $j('#titlediv #title');
+ if ('' === graphqlFieldNameField.val()) {
+ graphqlFieldNameField.val(formatFieldName(fieldGroupTitle.val()));
+ }
+ fieldGroupTitle.on('change', function () {
+ setGraphqlFieldName();
+ });
+ }
+
+
+ /**
+ * Determine whether users should be able to interact with the checkboxes
+ * to manually set the GraphQL Types for the ACF Field Group
+ */
+ function graphqlMapTypesFromLocations() {
+ var checkboxes = $j('.acf-field[data-name="graphql_types"] .acf-checkbox-list input[type="checkbox"]');
+ var manualMapTypes = $j('#acf_field_group-map_graphql_types_from_location_rules');
+
+ if (manualMapTypes.not(':checked')) {
+ getGraphqlTypesFromLocationRules();
+ }
+
+ checkboxes.each(function (i, el) {
+ if (manualMapTypes.is(':checked')) {
+ $j(this).removeAttr("disabled");
+ } else {
+ $j(this).attr("disabled", true);
+ }
+ });
+ manualMapTypes.on('change', function () {
+ graphqlMapTypesFromLocations();
+ });
+ }
+
+ function getGraphqlTypesFromLocationRules() {
+
+ var showInGraphQLCheckbox = $j('#acf_field_group-show_in_graphql');
+ var form = $j('#post');
+ var formInputs = $j('#post :input');
+ var serialized = formInputs.serialize();
+ var checkboxes = $j('.acf-field[data-name="graphql_types"] .acf-checkbox-list input[type="checkbox"]');
+ var manualMapTypes = $j('#acf_field_group-map_graphql_types_from_location_rules');
+
+ // If Manual Type selection is checked,
+ // Don't attempt to get GraphQL Types from the location rules
+ if (manualMapTypes.is(':checked')) {
+ return;
+ }
+
+ if ( ! showInGraphQLCheckbox.is(':checked') ) {
+ return;
+ }
+
+ if ( 'pending' !== form.attr('data-request-pending') ) {
+
+ // Start the request
+ form.attr('data-request-pending', 'pending' );
+
+ // Make the request
+ $j.post(ajaxurl, {
+ action: 'get_acf_field_group_graphql_types',
+ data: serialized
+ }, function (res) {
+ var types = res && res['graphql_types'] ? res['graphql_types'] : [];
+
+ checkboxes.each(function (i, el) {
+ var checkbox = $j(this);
+ var value = $j(this).val();
+ checkbox.prop('checked', false);
+ if (types && types.length) {
+ if (-1 !== $j.inArray(value, types)) {
+ checkbox.prop('checked', true);
+ }
+ }
+ checkbox.trigger("change");
+ })
+
+ // Signal that the request is finished
+ form.removeAttr('data-request-pending');
+
+ });
+ }
+
+ };
+
+ // Initialize the functionality to track the state of the Interface checkboxes.
+ initInterfaceCheckboxes();
+ toggleFieldRequirement();
+ setGraphqlFieldVisibility();
+ setGraphqlFieldName();
+ graphqlMapTypesFromLocations();
+
+});
diff --git a/src/location-rules.php b/src/location-rules.php
new file mode 100644
index 0000000..af6f678
--- /dev/null
+++ b/src/location-rules.php
@@ -0,0 +1,985 @@
+acf_field_groups = isset( $acf_field_groups ) && ! empty( $acf_field_groups ) ? $acf_field_groups : acf_get_field_groups();
+ }
+
+ /**
+ * Given a field name, formats it for GraphQL
+ *
+ * @param string $field_name The field name to format
+ *
+ * @return string
+ */
+ public function format_field_name( string $field_name ) {
+
+ $replaced = preg_replace( '[^a-zA-Z0-9 -]', '_', $field_name );
+
+ // If any values were replaced, use the replaced string as the new field name
+ if ( ! empty( $replaced ) ) {
+ $field_name = $replaced;
+ }
+
+ $field_name = lcfirst( $field_name );
+ $field_name = lcfirst( str_replace( '-', ' ', ucwords( $field_name, '_' ) ) );
+ $field_name = lcfirst( str_replace( ' ', '', ucwords( $field_name, ' ' ) ) );
+
+ return $field_name;
+ }
+
+ /**
+ * Given a type name, formats it for GraphQL
+ *
+ * @param string $type_name The type name to format
+ *
+ * @return string
+ */
+ public function format_type_name( string $type_name ) {
+ return ucfirst( $this->format_field_name( $type_name ) );
+ }
+
+ /**
+ * Given the name of a GraphqL Field Group and the name of a GraphQL Type, this sets the
+ * field group to show in that Type
+ *
+ * @param string $field_group_name The name of the ACF Field Group
+ * @param string $graphql_type_name The name of the GraphQL Type
+ */
+ public function set_graphql_type( string $field_group_name, string $graphql_type_name ) {
+ $this->mapped_field_groups[ Utils::format_field_name( $field_group_name ) ][] = $this->format_type_name( $graphql_type_name );
+ }
+
+ /**
+ * Given the name of a GraphqL Field Group and the name of a GraphQL Type, this unsets the
+ * GraphQL Type for the field group
+ *
+ * @param string $field_group_name The name of the ACF Field Group
+ * @param string $graphql_type_name The name of the GraphQL Type
+ */
+ public function unset_graphql_type( string $field_group_name, string $graphql_type_name ) {
+ $this->unset_types[ $this->format_field_name( $field_group_name ) ][] = $this->format_type_name( $graphql_type_name );
+ }
+
+ /**
+ * Get the rules
+ *
+ * @return array
+ */
+ public function get_rules() {
+
+ if ( empty( $this->mapped_field_groups ) ) {
+ return [];
+ }
+
+
+ if ( empty( $this->unset_types ) ) {
+ return $this->mapped_field_groups;
+ }
+
+ /**
+ * Remove any Types that were flagged to unset
+ */
+ foreach ( $this->unset_types as $field_group => $types ) {
+
+ // If there are no mapped field groups for the rule being unset, return the mapped groups as is
+ if ( ! isset( $this->mapped_field_groups[ $field_group ] ) ) {
+ return $this->mapped_field_groups;
+ }
+
+ // If the types to unset are empty or not an array, return the mapped field groups as is
+ if ( empty( $types ) || ! is_array( $types ) ) {
+ return $this->mapped_field_groups;
+ }
+
+ // Loop over the types to unset, find the key of the type in the array, then unset it
+ foreach ( $types as $type ) {
+ if ( ( $key = array_search( $type, $this->mapped_field_groups[ $field_group ] ) ) !== false ) {
+ unset( $this->mapped_field_groups[ $field_group ][ $key ] );
+ }
+ }
+
+ }
+
+ // Return the mapped field groups, with the unset fields (if any) removed
+ return $this->mapped_field_groups;
+
+ }
+
+ /**
+ * Checks for conflicting rule types to avoid impossible states.
+ *
+ * If a field group is assigned to a rule such as "post_type == post" AND "taxonomy == Tag"
+ * this would be an impossible state, as an object can't be a Post and a Tag.
+ *
+ * If we detect conflicting rules, the rule set is not applied at all.
+ *
+ * @param array $and_params The parameters of the rule group
+ * @param mixed $param The current param being evaluated
+ * @param array $allowed_params The allowed params that shouldn't conflict
+ *
+ * @return bool
+ */
+ public function check_for_conflicts( array $and_params, $param, $allowed_params = [] ) {
+
+ if ( empty( $and_params ) ) {
+ return false;
+ }
+
+ $has_conflict = false;
+ $keys = array_keys( $and_params, $param );
+
+ if ( isset( $keys[0] ) ) {
+ unset( $and_params[ $keys[0] ] );
+ }
+
+ if ( ! empty( $and_params ) ) {
+ foreach ( $and_params as $key => $and_param ) {
+ if ( false === array_search( $and_param, $allowed_params, true ) ) {
+ $has_conflict = true;
+ }
+ }
+ }
+
+ return $has_conflict;
+
+ }
+
+ /**
+ * Checks for conflicting rule types to avoid impossible states.
+ *
+ * If a field group is assigned to a rule such as "post_type == post" AND "taxonomy == Tag"
+ * this would be an impossible state, as an object can't be a Post and a Tag.
+ *
+ * If we detect conflicting rules, the rule set is not applied at all.
+ *
+ * @param array $and_params The parameters of the rule group
+ * @param mixed $param The current param being evaluated
+ *
+ * @return bool
+ */
+ public function check_params_for_conflicts( array $and_params = [], $param ) {
+ switch ( $param ) {
+ case 'post_type':
+ $allowed_and_params = [
+ 'post_status',
+ 'post_format',
+ 'post_category',
+ 'post_taxonomy',
+ 'post',
+ ];
+ break;
+ case 'post_template':
+ case 'page_template':
+ $allowed_and_params = [
+ 'page_type',
+ 'page_parent',
+ 'page',
+ ];
+ break;
+ case 'post_status':
+ $allowed_and_params = [
+ 'post_type',
+ 'post_format',
+ 'post_category',
+ 'post_taxonomy',
+ ];
+ break;
+ case 'post_format':
+ case 'post_category':
+ case 'post':
+ case 'post_taxonomy':
+ $allowed_and_params = [
+ 'post_status',
+ 'post_type',
+ 'post_format',
+ 'post_category',
+ 'post_taxonomy',
+ 'post',
+ ];
+ break;
+ case 'page':
+ case 'page_parent':
+ case 'page_type':
+ $allowed_and_params = [
+ 'page_template',
+ 'page_type',
+ 'page_parent',
+ 'page',
+ ];
+ break;
+ case 'current_user':
+ case 'current_user_role':
+ // @todo:
+ // Right now, if you set current_user or current_user_role as the only rule,
+ // ACF adds the field group to every possible location in the Admin.
+ // This seems a bit heavy handed. 🤔
+ // We need to think through this a bit more, and how this rule
+ // Can be composed with other rules, etc.
+ $allowed_and_params = [];
+ break;
+ case 'user_form':
+ case 'user_role':
+ $allowed_and_params = [
+ 'user_form',
+ 'user_role',
+ ];
+ break;
+ case 'taxonomy':
+ case 'attachment':
+ case 'comment':
+ case 'widget':
+ case 'nav_menu':
+ case 'nav_menu_item':
+ case 'options_page':
+ default:
+ $allowed_and_params = [];
+ break;
+
+ }
+
+ return $this->check_for_conflicts( $and_params, $param, $allowed_and_params );
+
+ }
+
+ /**
+ * Determine how an ACF Location Rule should apply to the WPGraphQL Schema
+ *
+ * @param string $field_group_name The name of the ACF Field Group the rule applies to
+ * @param string $param The parameter of the rule
+ * @param string $operator The operator of the rule
+ * @param string $value The value of the rule
+ */
+ public function determine_rules( string $field_group_name, string $param, string $operator, string $value ) {
+
+ // Depending on the param of the rule, there's different logic to
+ // map to the Schema
+ switch ( $param ) {
+ case 'post_type':
+ $this->determine_post_type_rules( $field_group_name, $param, $operator, $value );
+ break;
+ case 'post_template':
+ case 'page_template':
+ $this->determine_post_template_rules( $field_group_name, $param, $operator, $value );
+ break;
+ case 'post_status':
+ $this->determine_post_status_rules( $field_group_name, $param, $operator, $value );
+ break;
+ case 'post_format':
+ case 'post_category':
+ case 'post_taxonomy':
+ $this->determine_post_taxonomy_rules( $field_group_name, $param, $operator, $value );
+ break;
+ case 'post':
+ $this->determine_post_rules( $field_group_name, $param, $operator, $value );
+ break;
+ case 'page_type':
+ $this->determine_page_type_rules( $field_group_name, $param, $operator, $value );
+ break;
+ case 'page_parent':
+ case 'page':
+ // If page or page_parent is set, regardless of operator and value,
+ // we can add the field group to the Page type
+ $this->set_graphql_type( $field_group_name, 'Page' );
+ break;
+ case 'current_user':
+ case 'current_user_role':
+ // @todo:
+ // Right now, if you set current_user or current_user_role as the only rule,
+ // ACF adds the field group to every possible location in the Admin.
+ // This seems a bit heavy handed. 🤔
+ // We need to think through this a bit more, and how this rule
+ // Can be composed with other rules, etc.
+ break;
+ case 'user_form':
+ case 'user_role':
+ // If user_role or user_form params are set, we need to expose the field group
+ // to the User type
+ $this->set_graphql_type( $field_group_name, 'User' );
+ break;
+ case 'taxonomy':
+ $this->determine_taxonomy_rules( $field_group_name, $param, $operator, $value );
+ break;
+ case 'attachment':
+ $this->determine_attachment_rules( $field_group_name, $param, $operator, $value );
+ break;
+ case 'comment':
+ $this->determine_comment_rules( $field_group_name, $param, $operator, $value );
+ break;
+ case 'widget':
+ // @todo: Widgets are not currently supported in WPGraphQL
+ break;
+ case 'nav_menu':
+ $this->determine_nav_menu_rules( $field_group_name, $param, $operator, $value );
+ break;
+ case 'nav_menu_item':
+ $this->determine_nav_menu_item_item_rules( $field_group_name, $param, $operator, $value );
+ break;
+ case 'options_page':
+ $this->determine_options_rules( $field_group_name, $param, $operator, $value );
+ break;
+ default:
+ // If a built-in location rule could not be matched,
+ // Custom rules (from extensions, etc) can hook in here and apply their
+ // rules to the WPGraphQL Schema
+ do_action( 'graphql_acf_match_location_rule', $field_group_name, $param, $operator, $value, $this );
+ break;
+
+ }
+
+ }
+
+ /**
+ * Determine GraphQL Schema location rules based on ACF Location rules for field groups
+ * that are configured with no `graphql_types` field.
+ *
+ * @return void
+ */
+ public function determine_location_rules() {
+
+ if ( ! empty( $this->acf_field_groups ) ) {
+ foreach ( $this->acf_field_groups as $field_group ) {
+
+ $field_group_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : $field_group['title'];
+
+ if ( ! empty( $field_group['location'] ) && is_array( $field_group['location'] ) ) {
+
+ foreach ( $field_group['location'] as $location_rule_group ) {
+ if ( ! empty( $location_rule_group ) ) {
+
+ foreach ( $location_rule_group as $group => $rule ) {
+
+ // Determine the and params for the rule group
+ $and_params = wp_list_pluck( $location_rule_group, 'param' );
+ $and_params = ! empty( $and_params ) ? array_values( $and_params ) : [];
+
+ $operator = isset( $rule['operator'] ) ? $rule['operator'] : '==';
+ $param = isset( $rule['param'] ) ? $rule['param'] : null;
+ $value = isset( $rule['value'] ) ? $rule['value'] : null;
+
+ if ( empty( $param ) || empty( $value ) ) {
+ continue;
+ }
+
+ if ( true === $this->check_params_for_conflicts( $and_params, $param ) ) {
+ continue;
+ }
+
+ $this->determine_rules( $field_group_name, $param, $operator, $value );
+
+ }
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Returns an array of Post Templates
+ *
+ * @return array
+ */
+ public function get_graphql_post_template_types() {
+
+ $registered_page_templates = wp_get_theme()->get_post_templates();
+
+ $page_templates['default'] = 'DefaultTemplate';
+
+ if ( ! empty( $registered_page_templates ) && is_array( $registered_page_templates ) ) {
+
+ foreach ( $registered_page_templates as $post_type_templates ) {
+ // Post templates are returned as an array of arrays. PHPStan believes they're returned as
+ // an array of strings and believes this will always evaluate to false.
+ // We should ignore the phpstan check here.
+ // @phpstan-ignore-next-line
+ if ( ! empty( $post_type_templates ) && is_array( $post_type_templates ) ) {
+ foreach ( $post_type_templates as $file => $name ) {
+
+ $name = ucwords( $name );
+ $replaced_name = preg_replace( '/[^\w]/', '', $name );
+
+ if ( ! empty( $replaced_name ) ) {
+ $name = $replaced_name;
+ }
+
+ if ( preg_match( '/^\d/', $name ) || false === strpos( strtolower( $name ), 'template' ) ) {
+ $name = 'Template_' . $name;
+ }
+
+ $page_templates[ $file ] = $name;
+ }
+ }
+ }
+ }
+
+ return $page_templates;
+ }
+
+ /**
+ * Determines how the ACF Rules should apply to the WPGraphQL Schema
+ *
+ * @param string $field_group_name The name of the ACF Field Group the rule applies to
+ * @param string $param The parameter of the rule
+ * @param string $operator The operator of the rule
+ * @param string $value The value of the rule
+ */
+ public function determine_post_type_rules( string $field_group_name, string $param, string $operator, string $value ) {
+ $allowed_post_types = get_post_types( [ 'show_in_graphql' => true ] );
+
+ if ( empty( $allowed_post_types ) ) {
+ return;
+ }
+
+ if ( '==' === $operator ) {
+
+ // If all post types
+ if ( 'all' === $value ) {
+
+ // loop over and set all post types
+ foreach ( $allowed_post_types as $allowed_post_type ) {
+
+ $post_type_object = get_post_type_object( $allowed_post_type );
+ $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null;
+ if ( ! empty( $graphql_name ) ) {
+ $this->set_graphql_type( $field_group_name, $graphql_name );
+ }
+ }
+ } else {
+ if ( in_array( $value, $allowed_post_types, true ) ) {
+ $post_type_object = get_post_type_object( $value );
+ $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null;
+ if ( ! empty( $graphql_name ) ) {
+ $this->set_graphql_type( $field_group_name, $graphql_name );
+ }
+ }
+ }
+
+
+ }
+
+ if ( '!=' === $operator ) {
+
+ if ( 'all' !== $value ) {
+ // loop over and set all post types
+ foreach ( $allowed_post_types as $allowed_post_type ) {
+ $post_type_object = get_post_type_object( $allowed_post_type );
+ $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null;
+ if ( ! empty( $graphql_name ) ) {
+ $this->set_graphql_type( $field_group_name, $graphql_name );
+ }
+ }
+ }
+
+ $post_type_object = get_post_type_object( $value );
+ $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null;
+ if ( ! empty( $graphql_name ) ) {
+ $this->unset_graphql_type( $field_group_name, $graphql_name );
+ }
+ }
+ }
+
+ /**
+ * Determines how the ACF Rules should apply to the WPGraphQL Schema
+ *
+ * @param string $field_group_name The name of the ACF Field Group the rule applies to
+ * @param string $param The parameter of the rule
+ * @param string $operator The operator of the rule
+ * @param string $value The value of the rule
+ */
+ public function determine_post_template_rules( string $field_group_name, string $param, string $operator, string $value ) {
+
+ $templates = $this->get_graphql_post_template_types();
+
+ if ( ! is_array( $templates ) || empty( $templates ) ) {
+ return;
+ }
+
+ if ( '==' === $operator ) {
+
+ // If the template is available in GraphQL, set it
+ if ( isset( $templates[ $value ] ) ) {
+ $this->set_graphql_type( $field_group_name, $templates[ $value ] );
+ }
+ }
+
+ if ( '!=' === $operator ) {
+
+ foreach ( $templates as $name => $template_type ) {
+ $this->set_graphql_type( $field_group_name, $template_type );
+ }
+
+ // If the Template is available in GraphQL, unset it
+ if ( isset( $templates[ $value ] ) ) {
+ $this->unset_graphql_type( $field_group_name, $templates[ $value ] );
+ }
+ }
+
+ }
+
+ /**
+ * Determines how the ACF Rules should apply to the WPGraphQL Schema
+ *
+ * @param string $field_group_name The name of the ACF Field Group the rule applies to
+ * @param string $param The parameter of the rule
+ * @param string $operator The operator of the rule
+ * @param string $value The value of the rule
+ */
+ public function determine_post_status_rules( string $field_group_name, string $param, string $operator, string $value ) {
+ // @todo: Should post status affect the GraphQL Schema at all?
+ // If a field group is set to show on "post_status == publish" as the only rule, what post type does that apply to? All? 🤔
+ // If a field group is set to show on "post_status != draft" does that mean the field group should be available on all post types in the Schema by default?
+ // This seems like a very difficult rule to translate to the Schema.
+ // Like, lets say I add a field group called: "Editor Notes" that I want to show for any status that is not "publish". In theory, if that's my only rule, that seems like it should apply to all post types across the board, and show in the Admin in any state of the post, other than publish. 🤔
+
+ // ACF Admin behavior seems to add it to the Admin on all post types, so WPGraphQL
+ // should respect this rule and also add it to all post types. The resolver should
+ // then determine whether to resolve the data or not, based on this rule.
+
+ // If Post Status is used to qualify a field group location,
+ // It will be added to the Schema for any Post Type that is set to show in GraphQL
+ $allowed_post_types = get_post_types( [ 'show_in_graphql' => true ] );
+ foreach ( $allowed_post_types as $post_type ) {
+
+ $post_type_object = get_post_type_object( $post_type );
+ $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null;
+ if ( ! empty( $graphql_name ) ) {
+ $this->set_graphql_type( $field_group_name, $graphql_name );
+ }
+ }
+
+ }
+
+ /**
+ * Determines how the ACF Rules should apply to the WPGraphQL Schema
+ *
+ * @param string $field_group_name The name of the ACF Field Group the rule applies to
+ * @param string $param The parameter of the rule
+ * @param string $operator The operator of the rule
+ * @param string $value The value of the rule
+ */
+ public function determine_post_format_rules( string $field_group_name, string $param, string $operator, string $value ) {
+
+ $post_format_taxonomy = get_taxonomy( 'post_format' );
+ $post_format_post_types = $post_format_taxonomy->object_type;
+
+ if ( ! is_array( $post_format_post_types ) || empty( $post_format_post_types ) ) {
+ return;
+ }
+
+ // If Post Format is used to qualify a field group location,
+ // It will be added to the Schema for any Post Type that supports post formats
+ // And shows in GraphQL
+ $allowed_post_types = get_post_types( [ 'show_in_graphql' => true ] );
+ foreach ( $allowed_post_types as $post_type ) {
+ if ( in_array( $post_type, $post_format_post_types, true ) ) {
+ $post_type_object = get_post_type_object( $value );
+ $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null;
+ if ( ! empty( $graphql_name ) ) {
+ $this->set_graphql_type( $field_group_name, $graphql_name );
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Determines how the ACF Rules should apply to the WPGraphQL Schema
+ *
+ * @param string $field_group_name The name of the ACF Field Group the rule applies to
+ * @param string $param The parameter of the rule
+ * @param string $operator The operator of the rule
+ * @param string $value The value of the rule
+ */
+ public function determine_post_taxonomy_rules( string $field_group_name, string $param, string $operator, string $value ) {
+
+ // If Post Taxonomy is used to qualify a field group location,
+ // It will be added to the Schema for the Post post type
+ $this->set_graphql_type( $field_group_name, 'Post' );
+
+ }
+
+ /**
+ * Determines how the ACF Rules should apply to the WPGraphQL Schema
+ *
+ * @param string $field_group_name The name of the ACF Field Group the rule applies to
+ * @param string $param The parameter of the rule
+ * @param string $operator The operator of the rule
+ * @param string $value The value of the rule
+ */
+ public function determine_post_rules( string $field_group_name, string $param, string $operator, string $value ) {
+
+ // If a Single post is used to qualify a field group location,
+ // It will be added to the Schema for the GraphQL Type for the post_type of the Post
+ // it is assigned to
+
+ if ( '==' === $operator ) {
+
+ if ( absint( $value ) ) {
+ $post = get_post( absint( $value ) );
+ if ( $post instanceof \WP_Post ) {
+ $post_type_object = get_post_type_object( $post->post_type );
+ if ( isset( $post_type_object->show_in_graphql ) && true === $post_type_object->show_in_graphql ) {
+ if ( isset( $post_type_object->graphql_single_name ) ) {
+ $this->set_graphql_type( $field_group_name, $post_type_object->graphql_single_name );
+ }
+ }
+ }
+ }
+
+ }
+
+ // If a single post is used as not equal,
+ // the field group should be added to ALL post types in the Schema
+ if ( '!=' === $operator ) {
+
+ $allowed_post_types = get_post_types( [ 'show_in_graphql' => true ] );
+
+ if ( empty( $allowed_post_types ) ) {
+ return;
+ }
+
+ // loop over and set all post types
+ foreach ( $allowed_post_types as $allowed_post_type ) {
+
+ $post_type_object = get_post_type_object( $allowed_post_type );
+ $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null;
+ if ( ! empty( $graphql_name ) ) {
+ $this->set_graphql_type( $field_group_name, $graphql_name );
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Determines how the ACF Rules should apply to the WPGraphQL Schema
+ *
+ * @param string $field_group_name The name of the ACF Field Group the rule applies to
+ * @param string $param The parameter of the rule
+ * @param string $operator The operator of the rule
+ * @param string $value The value of the rule
+ */
+ public function determine_page_type_rules( string $field_group_name, string $param, string $operator, string $value ) {
+
+ // If front_page or posts_page is set to equal_to or not_equal_to
+ // then the field group should be shown on the Post type
+ if ( in_array( $value, [ 'front_page', 'posts_page' ], true ) ) {
+ $this->set_graphql_type( $field_group_name, 'Page' );
+ }
+
+ // If top_level, parent, or child is set as equal_to or not_equal_to
+ // then the field group should be shown on all hierarchical post types
+ if ( in_array( $value, [ 'top_level', 'parent', 'child' ], true ) ) {
+
+ $hierarchical_post_types = get_post_types( [
+ 'show_in_graphql' => true,
+ 'hierarchical' => true
+ ] );
+
+ if ( empty( $hierarchical_post_types ) ) {
+ return;
+ }
+
+ // loop over and set all post types
+ foreach ( $hierarchical_post_types as $allowed_post_type ) {
+
+ $post_type_object = get_post_type_object( $allowed_post_type );
+ $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null;
+ if ( ! empty( $graphql_name ) ) {
+ $this->set_graphql_type( $field_group_name, $graphql_name );
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Determines how the ACF Rules should apply to the WPGraphQL Schema
+ *
+ * @param string $field_group_name The name of the ACF Field Group the rule applies to
+ * @param string $param The parameter of the rule
+ * @param string $operator The operator of the rule
+ * @param string $value The value of the rule
+ */
+ public function determine_taxonomy_rules( string $field_group_name, string $param, string $operator, string $value ) {
+
+ $allowed_taxonomies = get_taxonomies( [ 'show_in_graphql' => true ] );
+
+ if ( empty( $allowed_taxonomies ) ) {
+ return;
+ }
+
+ if ( '==' === $operator ) {
+
+ // If all post types
+ if ( 'all' === $value ) {
+
+ // loop over and set all post types
+ foreach ( $allowed_taxonomies as $allowed_taxonomy ) {
+
+ $tax_object = get_taxonomy( $allowed_taxonomy );
+ $graphql_name = isset( $tax_object->graphql_single_name ) ? $tax_object->graphql_single_name : null;
+ if ( ! empty( $graphql_name ) ) {
+ $this->set_graphql_type( $field_group_name, $graphql_name );
+ }
+ }
+
+ } else {
+ if ( in_array( $value, $allowed_taxonomies, true ) ) {
+ $tax_object = get_taxonomy( $value );
+ $graphql_name = isset( $tax_object->graphql_single_name ) ? $tax_object->graphql_single_name : null;
+ if ( ! empty( $graphql_name ) ) {
+ $this->set_graphql_type( $field_group_name, $graphql_name );
+ }
+ }
+ }
+
+
+ }
+
+ if ( '!=' === $operator ) {
+
+ if ( 'all' !== $value ) {
+
+ // loop over and set all post types
+ foreach ( $allowed_taxonomies as $allowed_taxonomy ) {
+
+ $tax_object = get_taxonomy( $allowed_taxonomy );
+ $graphql_name = isset( $tax_object->graphql_single_name ) ? $tax_object->graphql_single_name : null;
+ if ( ! empty( $graphql_name ) ) {
+ $this->set_graphql_type( $field_group_name, $graphql_name );
+ }
+ }
+
+ $tax_object = get_taxonomy( $value );
+ $graphql_name = isset( $tax_object->graphql_single_name ) ? $tax_object->graphql_single_name : null;
+ if ( ! empty( $graphql_name ) ) {
+ $this->unset_graphql_type( $field_group_name, $graphql_name );
+ }
+
+ }
+ }
+
+ }
+
+ /**
+ * Determines how the ACF Rules should apply to the WPGraphQL Schema
+ *
+ * @param string $field_group_name The name of the ACF Field Group the rule applies to
+ * @param string $param The parameter of the rule
+ * @param string $operator The operator of the rule
+ * @param string $value The value of the rule
+ */
+ public function determine_attachment_rules( string $field_group_name, string $param, string $operator, string $value ) {
+
+ if ( '==' === $operator ) {
+ $this->set_graphql_type( $field_group_name, 'MediaItem' );
+ }
+
+ if ( '!=' === $operator && 'all' === $value ) {
+ $this->unset_graphql_type( $field_group_name, 'MediaItem' );
+ }
+
+ }
+
+ /**
+ * Determines how the ACF Rules should apply to the WPGraphQL Schema
+ *
+ * @param string $field_group_name The name of the ACF Field Group the rule applies to
+ * @param string $param The parameter of the rule
+ * @param string $operator The operator of the rule
+ * @param string $value The value of the rule
+ */
+ public function determine_comment_rules( string $field_group_name, string $param, string $operator, string $value ) {
+
+ if ( '==' === $operator ) {
+ $this->set_graphql_type( $field_group_name, 'Comment' );
+ }
+
+ if ( '!=' === $operator ) {
+
+ // If not equal to all, unset from all comments
+ if ( 'all' === $value ) {
+ $this->unset_graphql_type( $field_group_name, 'Comment' );
+
+ // If not equal to just a specific post type/comment relationship,
+ // show the field group on the Comment Type
+ } else {
+ $this->set_graphql_type( $field_group_name, 'Comment' );
+ }
+
+ }
+
+ }
+
+ /**
+ * Determines how the ACF Rules should apply to the WPGraphQL Schema
+ *
+ * @param string $field_group_name The name of the ACF Field Group the rule applies to
+ * @param string $param The parameter of the rule
+ * @param string $operator The operator of the rule
+ * @param string $value The value of the rule
+ */
+ public function determine_nav_menu_rules( string $field_group_name, string $param, string $operator, string $value ) {
+
+ if ( '==' === $operator ) {
+ $this->set_graphql_type( $field_group_name, 'Menu' );
+ }
+
+ if ( '!=' === $operator ) {
+
+ // If not equal to all, unset from all Menu
+ if ( 'all' === $value ) {
+ $this->unset_graphql_type( $field_group_name, 'Menu' );
+
+ // If not equal to just a Menu,
+ // show the field group on all Menus
+ } else {
+ $this->set_graphql_type( $field_group_name, 'Menu' );
+ }
+
+ }
+
+ }
+
+ /**
+ * Determines how the ACF Rules should apply to the WPGraphQL Schema
+ *
+ * @param string $field_group_name The name of the ACF Field Group the rule applies to
+ * @param string $param The parameter of the rule
+ * @param string $operator The operator of the rule
+ * @param string $value The value of the rule
+ */
+ public function determine_nav_menu_item_item_rules( string $field_group_name, string $param, string $operator, string $value ) {
+
+ if ( '==' === $operator ) {
+ $this->set_graphql_type( $field_group_name, 'MenuItem' );
+ }
+
+ if ( '!=' === $operator ) {
+
+ // If not equal to all, unset from all MenuItem
+ if ( 'all' === $value ) {
+ $this->unset_graphql_type( $field_group_name, 'MenuItem' );
+
+ // If not equal to one Menu / location,
+ // show the field group on all MenuItems
+ } else {
+ $this->set_graphql_type( $field_group_name, 'MenuItem' );
+ }
+
+ }
+
+ }
+
+ /**
+ * Determines how the ACF Rules should apply to the WPGraphQL Schema
+ *
+ * @param string $field_group_name The name of the ACF Field Group the rule applies to
+ * @param string $param The parameter of the rule
+ * @param string $operator The operator of the rule
+ * @param string $value The value of the rule
+ */
+ public function determine_block_rules( string $field_group_name, string $param, string $operator, string $value ) {
+ // @todo: ACF Blocks are not formally supported by WPGraphQL / WPGraphQL for ACF. More to come in the future!
+ }
+
+ /**
+ * Determines how the ACF Rules should apply to the WPGraphQL Schema
+ *
+ * @param string $field_group_name The name of the ACF Field Group the rule applies to
+ * @param string $param The parameter of the rule
+ * @param string $operator The operator of the rule
+ * @param string $value The value of the rule
+ */
+ public function determine_options_rules( string $field_group_name, string $param, string $operator, string $value ) {
+
+ if ( '==' === $operator ) {
+ $options_page = acf_get_options_page( $value );
+
+ if ( ! isset( $options_page['show_in_graphql'] ) || false === (bool) $options_page['show_in_graphql'] ) {
+ return;
+ }
+ $type_name = isset( $options_page['graphql_field_name'] ) ? Utils::format_type_name( $options_page['graphql_field_name'] ) : Utils::format_type_name( $options_page['menu_slug'] );
+ $this->set_graphql_type( $field_group_name, $type_name );
+ }
+
+ if ( '!=' === $operator ) {
+
+ $options_pages = acf_get_options_pages();
+
+ if ( empty( $options_pages ) || ! is_array( $options_pages ) ) {
+ return;
+ }
+
+ // Show all options pages
+ foreach ( $options_pages as $options_page ) {
+ if ( ! isset( $options_page['show_in_graphql'] ) || false === (bool) $options_page['show_in_graphql'] ) {
+ continue;
+ }
+ $type_name = isset( $options_page['graphql_field_name'] ) ? Utils::format_type_name( $options_page['graphql_field_name'] ) : Utils::format_type_name( $options_page['menu_slug'] );
+ $this->set_graphql_type( $field_group_name, $type_name );
+ }
+
+ // Get the options page to unset
+ $options_page = acf_get_options_page( $value );
+ if ( ! isset( $options_page['show_in_graphql'] ) || false === $options_page['show_in_graphql'] ) {
+ return;
+ }
+ $type_name = isset( $options_page['graphql_field_name'] ) ? Utils::format_type_name( $options_page['graphql_field_name'] ) : Utils::format_type_name( $options_page['menu_slug'] );
+ $this->unset_graphql_type( $field_group_name, $type_name );
+ }
+
+ }
+
+}
diff --git a/tests/_bootstrap/bootstrap.php b/tests/_bootstrap/bootstrap.php
new file mode 100644
index 0000000..e69de29
diff --git a/tests/_data/images/test.png b/tests/_data/images/test.png
new file mode 100644
index 0000000..00c7c6a
Binary files /dev/null and b/tests/_data/images/test.png differ
diff --git a/tests/_output/.gitignore b/tests/_output/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/tests/_output/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/tests/_output/c3tmp/codecoverage.clover.xml b/tests/_output/c3tmp/codecoverage.clover.xml
deleted file mode 100644
index 3b1cffa..0000000
--- a/tests/_output/c3tmp/codecoverage.clover.xml
+++ /dev/null
@@ -1,361 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tests/_output/c3tmp/codecoverage.serialized b/tests/_output/c3tmp/codecoverage.serialized
deleted file mode 100644
index 0598ec1..0000000
Binary files a/tests/_output/c3tmp/codecoverage.serialized and /dev/null differ
diff --git a/tests/_output/c3tmp/codecoverage.tar b/tests/_output/c3tmp/codecoverage.tar
deleted file mode 100644
index c6a19dc..0000000
Binary files a/tests/_output/c3tmp/codecoverage.tar and /dev/null differ
diff --git a/tests/_output/coverage.wpunit.cov b/tests/_output/coverage.wpunit.cov
deleted file mode 100644
index 48a00b0..0000000
--- a/tests/_output/coverage.wpunit.cov
+++ /dev/null
@@ -1,1319 +0,0 @@
-setData(array (
- '/Users/jasonbahl/Sites/wordpress/wp-graphql-jwt-authentication/wp-graphql-jwt-authentication.php' =>
- array (
- 23 =>
- array (
- ),
- 24 =>
- array (
- ),
- 25 =>
- array (
- ),
- 27 =>
- array (
- ),
- 28 =>
- array (
- ),
- 29 =>
- array (
- ),
- 31 =>
- array (
- ),
- 53 =>
- array (
- ),
- 54 =>
- array (
- ),
- 55 =>
- array (
- ),
- 56 =>
- array (
- ),
- 57 =>
- array (
- ),
- 59 =>
- array (
- ),
- 66 =>
- array (
- ),
- 71 =>
- array (
- ),
- 72 =>
- array (
- ),
- 86 =>
- array (
- ),
- 88 =>
- array (
- ),
- 100 =>
- array (
- ),
- 102 =>
- array (
- ),
- 114 =>
- array (
- ),
- 115 =>
- array (
- ),
- 116 =>
- array (
- ),
- 119 =>
- array (
- ),
- 120 =>
- array (
- ),
- 121 =>
- array (
- ),
- 124 =>
- array (
- ),
- 125 =>
- array (
- ),
- 126 =>
- array (
- ),
- 129 =>
- array (
- ),
- 130 =>
- array (
- ),
- 131 =>
- array (
- ),
- 134 =>
- array (
- ),
- 135 =>
- array (
- ),
- 136 =>
- array (
- ),
- 138 =>
- array (
- ),
- 151 =>
- array (
- ),
- 152 =>
- array (
- ),
- 153 =>
- array (
- ),
- 154 =>
- array (
- ),
- 165 =>
- array (
- ),
- 170 =>
- array (
- ),
- 171 =>
- array (
- ),
- 172 =>
- array (
- ),
- 173 =>
- array (
- ),
- 175 =>
- array (
- ),
- 176 =>
- array (
- ),
- 177 =>
- array (
- ),
- 178 =>
- array (
- ),
- 183 =>
- array (
- ),
- 184 =>
- array (
- ),
- 185 =>
- array (
- ),
- 186 =>
- array (
- ),
- 188 =>
- array (
- ),
- 192 =>
- array (
- ),
- 195 =>
- array (
- ),
- 196 =>
- array (
- ),
- 198 =>
- array (
- ),
- ),
- '/Users/jasonbahl/Sites/wordpress/wp-graphql-jwt-authentication/src/Auth.php' =>
- array (
- 26 =>
- array (
- ),
- 27 =>
- array (
- ),
- 29 =>
- array (
- ),
- 46 =>
- array (
- ),
- 47 =>
- array (
- ),
- 48 =>
- array (
- ),
- 53 =>
- array (
- ),
- 58 =>
- array (
- ),
- 59 =>
- array (
- ),
- 60 =>
- array (
- ),
- 65 =>
- array (
- ),
- 70 =>
- array (
- ),
- 71 =>
- array (
- ),
- 72 =>
- array (
- ),
- 73 =>
- array (
- ),
- 74 =>
- array (
- ),
- 79 =>
- array (
- ),
- 80 =>
- array (
- ),
- 88 =>
- array (
- ),
- 89 =>
- array (
- ),
- 90 =>
- array (
- ),
- 92 =>
- array (
- ),
- 93 =>
- array (
- ),
- 102 =>
- array (
- ),
- 107 =>
- array (
- ),
- 114 =>
- array (
- ),
- 116 =>
- array (
- ),
- 118 =>
- array (
- ),
- 120 =>
- array (
- ),
- 132 =>
- array (
- ),
- 133 =>
- array (
- ),
- 134 =>
- array (
- ),
- 142 =>
- array (
- ),
- 148 =>
- array (
- ),
- 149 =>
- array (
- ),
- 150 =>
- array (
- ),
- 151 =>
- array (
- ),
- 152 =>
- array (
- ),
- 153 =>
- array (
- ),
- 154 =>
- array (
- ),
- 155 =>
- array (
- ),
- 156 =>
- array (
- ),
- 157 =>
- array (
- ),
- 158 =>
- array (
- ),
- 166 =>
- array (
- ),
- 171 =>
- array (
- ),
- 172 =>
- array (
- ),
- 182 =>
- array (
- ),
- 187 =>
- array (
- ),
- 189 =>
- array (
- ),
- 203 =>
- array (
- ),
- 204 =>
- array (
- ),
- 205 =>
- array (
- ),
- 213 =>
- array (
- ),
- 218 =>
- array (
- ),
- 219 =>
- array (
- ),
- 220 =>
- array (
- ),
- 221 =>
- array (
- ),
- 226 =>
- array (
- ),
- 231 =>
- array (
- ),
- 232 =>
- array (
- ),
- 233 =>
- array (
- ),
- 241 =>
- array (
- ),
- 242 =>
- array (
- ),
- 256 =>
- array (
- ),
- 261 =>
- array (
- ),
- 266 =>
- array (
- ),
- 267 =>
- array (
- ),
- 269 =>
- array (
- ),
- 271 =>
- array (
- ),
- 272 =>
- array (
- ),
- 282 =>
- array (
- ),
- 284 =>
- array (
- ),
- 285 =>
- array (
- ),
- 295 =>
- array (
- ),
- 296 =>
- array (
- ),
- 306 =>
- array (
- ),
- 307 =>
- array (
- ),
- 314 =>
- array (
- ),
- 315 =>
- array (
- ),
- 316 =>
- array (
- ),
- 318 =>
- array (
- ),
- 319 =>
- array (
- ),
- 321 =>
- array (
- ),
- 322 =>
- array (
- ),
- 325 =>
- array (
- ),
- 326 =>
- array (
- ),
- 341 =>
- array (
- ),
- 346 =>
- array (
- ),
- 347 =>
- array (
- ),
- 348 =>
- array (
- ),
- 349 =>
- array (
- ),
- 351 =>
- array (
- ),
- 353 =>
- array (
- ),
- 370 =>
- array (
- ),
- 375 =>
- array (
- ),
- 380 =>
- array (
- ),
- 385 =>
- array (
- ),
- 390 =>
- array (
- ),
- 392 =>
- array (
- ),
- 397 =>
- array (
- ),
- 398 =>
- array (
- ),
- 416 =>
- array (
- ),
- 421 =>
- array (
- ),
- 422 =>
- array (
- ),
- 423 =>
- array (
- ),
- 424 =>
- array (
- ),
- 425 =>
- array (
- ),
- 426 =>
- array (
- ),
- 427 =>
- array (
- ),
- 432 =>
- array (
- ),
- 434 =>
- array (
- ),
- 436 =>
- array (
- ),
- 438 =>
- array (
- ),
- 440 =>
- array (
- ),
- 442 =>
- array (
- ),
- 460 =>
- array (
- ),
- 465 =>
- array (
- ),
- 471 =>
- array (
- ),
- 472 =>
- array (
- ),
- 474 =>
- array (
- ),
- 476 =>
- array (
- ),
- 478 =>
- array (
- ),
- 480 =>
- array (
- ),
- 482 =>
- array (
- ),
- 486 =>
- array (
- ),
- 487 =>
- array (
- ),
- 488 =>
- array (
- ),
- 501 =>
- array (
- ),
- 509 =>
- array (
- ),
- 514 =>
- array (
- ),
- 521 =>
- array (
- ),
- 522 =>
- array (
- ),
- 523 =>
- array (
- ),
- 528 =>
- array (
- ),
- 529 =>
- array (
- ),
- 531 =>
- array (
- ),
- 536 =>
- array (
- ),
- 537 =>
- array (
- ),
- 538 =>
- array (
- ),
- 543 =>
- array (
- ),
- 548 =>
- array (
- ),
- 550 =>
- array (
- ),
- 551 =>
- array (
- ),
- 556 =>
- array (
- ),
- 557 =>
- array (
- ),
- 558 =>
- array (
- ),
- 563 =>
- array (
- ),
- 564 =>
- array (
- ),
- 565 =>
- array (
- ),
- 570 =>
- array (
- ),
- 572 =>
- array (
- ),
- 573 =>
- array (
- ),
- 574 =>
- array (
- ),
- 575 =>
- array (
- ),
- 580 =>
- array (
- ),
- 581 =>
- array (
- ),
- 582 =>
- array (
- ),
- 583 =>
- array (
- ),
- 585 =>
- array (
- ),
- 587 =>
- array (
- ),
- 589 =>
- array (
- ),
- 602 =>
- array (
- ),
- 607 =>
- array (
- ),
- 612 =>
- array (
- ),
- 619 =>
- array (
- ),
- 621 =>
- array (
- ),
- 628 =>
- array (
- ),
- 630 =>
- array (
- ),
- 632 =>
- array (
- ),
- ),
- '/Users/jasonbahl/Sites/wordpress/wp-graphql-jwt-authentication/src/Login.php' =>
- array (
- 20 =>
- array (
- ),
- 22 =>
- array (
- ),
- 23 =>
- array (
- ),
- 27 =>
- array (
- ),
- 29 =>
- array (
- ),
- 30 =>
- array (
- ),
- 31 =>
- array (
- ),
- 32 =>
- array (
- ),
- 33 =>
- array (
- ),
- 34 =>
- array (
- ),
- 35 =>
- array (
- ),
- 36 =>
- array (
- ),
- 37 =>
- array (
- ),
- 38 =>
- array (
- ),
- 39 =>
- array (
- ),
- 40 =>
- array (
- ),
- 41 =>
- array (
- ),
- 42 =>
- array (
- ),
- 43 =>
- array (
- ),
- 44 =>
- array (
- ),
- 45 =>
- array (
- ),
- 46 =>
- array (
- ),
- 47 =>
- array (
- ),
- 48 =>
- array (
- ),
- 49 =>
- array (
- ),
- 50 =>
- array (
- ),
- 51 =>
- array (
- ),
- 52 =>
- array (
- ),
- 53 =>
- array (
- ),
- 54 =>
- array (
- ),
- 55 =>
- array (
- ),
- 56 =>
- array (
- ),
- 62 =>
- array (
- ),
- 64 =>
- array (
- ),
- 65 =>
- array (
- ),
- 67 =>
- array (
- ),
- 69 =>
- array (
- ),
- 71 =>
- array (
- ),
- ),
- '/Users/jasonbahl/Sites/wordpress/wp-graphql-jwt-authentication/src/ManageTokens.php' =>
- array (
- 17 =>
- array (
- ),
- 18 =>
- array (
- ),
- 19 =>
- array (
- ),
- 20 =>
- array (
- ),
- 25 =>
- array (
- ),
- 26 =>
- array (
- ),
- 27 =>
- array (
- ),
- 28 =>
- array (
- ),
- 30 =>
- array (
- ),
- 31 =>
- array (
- ),
- 32 =>
- array (
- ),
- 33 =>
- array (
- ),
- 38 =>
- array (
- ),
- 39 =>
- array (
- ),
- 40 =>
- array (
- ),
- 41 =>
- array (
- ),
- 46 =>
- array (
- ),
- 47 =>
- array (
- ),
- 48 =>
- array (
- ),
- 49 =>
- array (
- ),
- 51 =>
- array (
- ),
- 52 =>
- array (
- ),
- 53 =>
- array (
- ),
- 54 =>
- array (
- ),
- 55 =>
- array (
- ),
- 66 =>
- array (
- ),
- 67 =>
- array (
- ),
- 68 =>
- array (
- ),
- 74 =>
- array (
- ),
- 79 =>
- array (
- ),
- 80 =>
- array (
- ),
- 81 =>
- array (
- ),
- 83 =>
- array (
- ),
- 84 =>
- array (
- ),
- 85 =>
- array (
- ),
- 87 =>
- array (
- ),
- 88 =>
- array (
- ),
- 89 =>
- array (
- ),
- 95 =>
- array (
- ),
- 100 =>
- array (
- ),
- 101 =>
- array (
- ),
- 102 =>
- array (
- ),
- 104 =>
- array (
- ),
- 105 =>
- array (
- ),
- 106 =>
- array (
- ),
- 108 =>
- array (
- ),
- 109 =>
- array (
- ),
- 110 =>
- array (
- ),
- 116 =>
- array (
- ),
- 121 =>
- array (
- ),
- 122 =>
- array (
- ),
- 123 =>
- array (
- ),
- 128 =>
- array (
- ),
- 129 =>
- array (
- ),
- 130 =>
- array (
- ),
- 132 =>
- array (
- ),
- 133 =>
- array (
- ),
- 134 =>
- array (
- ),
- 136 =>
- array (
- ),
- 137 =>
- array (
- ),
- 138 =>
- array (
- ),
- 139 =>
- array (
- ),
- 141 =>
- array (
- ),
- 142 =>
- array (
- ),
- 143 =>
- array (
- ),
- 145 =>
- array (
- ),
- 146 =>
- array (
- ),
- 147 =>
- array (
- ),
- 148 =>
- array (
- ),
- 152 =>
- array (
- ),
- 154 =>
- array (
- ),
- 164 =>
- array (
- ),
- 165 =>
- array (
- ),
- 166 =>
- array (
- ),
- 167 =>
- array (
- ),
- 169 =>
- array (
- ),
- 170 =>
- array (
- ),
- 171 =>
- array (
- ),
- 172 =>
- array (
- ),
- 174 =>
- array (
- ),
- 175 =>
- array (
- ),
- 188 =>
- array (
- ),
- 189 =>
- array (
- ),
- 190 =>
- array (
- ),
- 191 =>
- array (
- ),
- 192 =>
- array (
- ),
- 193 =>
- array (
- ),
- 194 =>
- array (
- ),
- 199 =>
- array (
- ),
- 200 =>
- array (
- ),
- 201 =>
- array (
- ),
- 202 =>
- array (
- ),
- 203 =>
- array (
- ),
- 205 =>
- array (
- ),
- 220 =>
- array (
- ),
- 225 =>
- array (
- ),
- 226 =>
- array (
- ),
- 227 =>
- array (
- ),
- 229 =>
- array (
- ),
- 231 =>
- array (
- ),
- 236 =>
- array (
- ),
- 241 =>
- array (
- ),
- 242 =>
- array (
- ),
- 243 =>
- array (
- ),
- 248 =>
- array (
- ),
- 249 =>
- array (
- ),
- 250 =>
- array (
- ),
- 251 =>
- array (
- ),
- 253 =>
- array (
- ),
- 255 =>
- array (
- ),
- 262 =>
- array (
- ),
- 267 =>
- array (
- ),
- 272 =>
- array (
- ),
- 277 =>
- array (
- ),
- 278 =>
- array (
- ),
- 279 =>
- array (
- ),
- 281 =>
- array (
- ),
- 283 =>
- array (
- ),
- 285 =>
- array (
- ),
- 287 =>
- array (
- ),
- 289 =>
- array (
- ),
- 290 =>
- array (
- ),
- 291 =>
- array (
- ),
- 293 =>
- array (
- ),
- 295 =>
- array (
- ),
- 297 =>
- array (
- ),
- ),
- '/Users/jasonbahl/Sites/wordpress/wp-graphql-jwt-authentication/src/RefreshToken.php' =>
- array (
- 21 =>
- array (
- ),
- 23 =>
- array (
- ),
- 24 =>
- array (
- ),
- 28 =>
- array (
- ),
- 30 =>
- array (
- ),
- 31 =>
- array (
- ),
- 32 =>
- array (
- ),
- 33 =>
- array (
- ),
- 34 =>
- array (
- ),
- 35 =>
- array (
- ),
- 36 =>
- array (
- ),
- 37 =>
- array (
- ),
- 38 =>
- array (
- ),
- 39 =>
- array (
- ),
- 40 =>
- array (
- ),
- 41 =>
- array (
- ),
- 42 =>
- array (
- ),
- 43 =>
- array (
- ),
- 44 =>
- array (
- ),
- 45 =>
- array (
- ),
- 48 =>
- array (
- ),
- 50 =>
- array (
- ),
- 51 =>
- array (
- ),
- 52 =>
- array (
- ),
- 53 =>
- array (
- ),
- 55 =>
- array (
- ),
- 56 =>
- array (
- ),
- 58 =>
- array (
- ),
- 59 =>
- array (
- ),
- 60 =>
- array (
- ),
- 62 =>
- array (
- ),
- 63 =>
- array (
- ),
- 65 =>
- array (
- ),
- 67 =>
- array (
- ),
- 69 =>
- array (
- ),
- ),
-));
-$coverage->setTests(array (
- 'UNCOVERED_FILES_FROM_WHITELIST' =>
- array (
- 'size' => 'unknown',
- 'status' => NULL,
- ),
-));
-
-$filter = $coverage->filter();
-$filter->setWhitelistedFiles(array (
- '/Users/jasonbahl/Sites/wordpress/wp-graphql-jwt-authentication/wp-graphql-jwt-authentication.php' => true,
- 0 => true,
- '/Users/jasonbahl/Sites/wordpress/wp-graphql-jwt-authentication/src/Auth.php' => true,
- '/Users/jasonbahl/Sites/wordpress/wp-graphql-jwt-authentication/src/Login.php' => true,
- '/Users/jasonbahl/Sites/wordpress/wp-graphql-jwt-authentication/src/ManageTokens.php' => true,
- '/Users/jasonbahl/Sites/wordpress/wp-graphql-jwt-authentication/src/RefreshToken.php' => true,
-));
-
-return $coverage;
\ No newline at end of file
diff --git a/tests/_output/coverage.xml b/tests/_output/coverage.xml
deleted file mode 100644
index 06d8f30..0000000
--- a/tests/_output/coverage.xml
+++ /dev/null
@@ -1,508 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tests/_output/functional.remote.coverage.xml b/tests/_output/functional.remote.coverage.xml
deleted file mode 100644
index 3b1cffa..0000000
--- a/tests/_output/functional.remote.coverage.xml
+++ /dev/null
@@ -1,361 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tests/_output/functional.remote.coverage/.css/bootstrap.min.css b/tests/_output/functional.remote.coverage/.css/bootstrap.min.css
deleted file mode 100644
index b27cb19..0000000
--- a/tests/_output/functional.remote.coverage/.css/bootstrap.min.css
+++ /dev/null
@@ -1,6 +0,0 @@
-/*!
- * Bootstrap v3.3.7 (http://getbootstrap.com)
- * Copyright 2011-2016 Twitter, Inc.
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
- *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../.fonts/glyphicons-halflings-regular.eot);src:url(../.fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../.fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../.fonts/glyphicons-halflings-regular.woff) format('woff'),url(../.fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../.fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}}
-/*# sourceMappingURL=bootstrap.min.css.map */
\ No newline at end of file
diff --git a/tests/_output/functional.remote.coverage/.css/nv.d3.min.css b/tests/_output/functional.remote.coverage/.css/nv.d3.min.css
deleted file mode 100644
index 7a6f7fe..0000000
--- a/tests/_output/functional.remote.coverage/.css/nv.d3.min.css
+++ /dev/null
@@ -1 +0,0 @@
-.nvd3 .nv-axis{pointer-events:none;opacity:1}.nvd3 .nv-axis path{fill:none;stroke:#000;stroke-opacity:.75;shape-rendering:crispEdges}.nvd3 .nv-axis path.domain{stroke-opacity:.75}.nvd3 .nv-axis.nv-x path.domain{stroke-opacity:0}.nvd3 .nv-axis line{fill:none;stroke:#e5e5e5;shape-rendering:crispEdges}.nvd3 .nv-axis .zero line,.nvd3 .nv-axis line.zero{stroke-opacity:.75}.nvd3 .nv-axis .nv-axisMaxMin text{font-weight:700}.nvd3 .x .nv-axis .nv-axisMaxMin text,.nvd3 .x2 .nv-axis .nv-axisMaxMin text,.nvd3 .x3 .nv-axis .nv-axisMaxMin text{text-anchor:middle}.nvd3 .nv-axis.nv-disabled{opacity:0}.nvd3 .nv-bars rect{fill-opacity:.75;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-bars rect.hover{fill-opacity:1}.nvd3 .nv-bars .hover rect{fill:#add8e6}.nvd3 .nv-bars text{fill:rgba(0,0,0,0)}.nvd3 .nv-bars .hover text{fill:rgba(0,0,0,1)}.nvd3 .nv-multibar .nv-groups rect,.nvd3 .nv-multibarHorizontal .nv-groups rect,.nvd3 .nv-discretebar .nv-groups rect{stroke-opacity:0;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-multibar .nv-groups rect:hover,.nvd3 .nv-multibarHorizontal .nv-groups rect:hover,.nvd3 .nv-candlestickBar .nv-ticks rect:hover,.nvd3 .nv-discretebar .nv-groups rect:hover{fill-opacity:1}.nvd3 .nv-discretebar .nv-groups text,.nvd3 .nv-multibarHorizontal .nv-groups text{font-weight:700;fill:rgba(0,0,0,1);stroke:rgba(0,0,0,0)}.nvd3 .nv-boxplot circle{fill-opacity:.5}.nvd3 .nv-boxplot circle:hover{fill-opacity:1}.nvd3 .nv-boxplot rect:hover{fill-opacity:1}.nvd3 line.nv-boxplot-median{stroke:#000}.nv-boxplot-tick:hover{stroke-width:2.5px}.nvd3.nv-bullet{font:10px sans-serif}.nvd3.nv-bullet .nv-measure{fill-opacity:.8}.nvd3.nv-bullet .nv-measure:hover{fill-opacity:1}.nvd3.nv-bullet .nv-marker{stroke:#000;stroke-width:2px}.nvd3.nv-bullet .nv-markerTriangle{stroke:#000;fill:#fff;stroke-width:1.5px}.nvd3.nv-bullet .nv-tick line{stroke:#666;stroke-width:.5px}.nvd3.nv-bullet .nv-range.nv-s0{fill:#eee}.nvd3.nv-bullet .nv-range.nv-s1{fill:#ddd}.nvd3.nv-bullet .nv-range.nv-s2{fill:#ccc}.nvd3.nv-bullet .nv-title{font-size:14px;font-weight:700}.nvd3.nv-bullet .nv-subtitle{fill:#999}.nvd3.nv-bullet .nv-range{fill:#bababa;fill-opacity:.4}.nvd3.nv-bullet .nv-range:hover{fill-opacity:.7}.nvd3.nv-candlestickBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.positive rect{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.negative rect{stroke:#d62728;fill:#d62728}.with-transitions .nv-candlestickBar .nv-ticks .nv-tick{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-candlestickBar .nv-ticks line{stroke:#333}.nvd3 .nv-legend .nv-disabled rect{}.nvd3 .nv-check-box .nv-box{fill-opacity:0;stroke-width:2}.nvd3 .nv-check-box .nv-check{fill-opacity:0;stroke-width:4}.nvd3 .nv-series.nv-disabled .nv-check-box .nv-check{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-controlsWrap .nv-legend .nv-check-box .nv-check{opacity:0}.nvd3.nv-linePlusBar .nv-bar rect{fill-opacity:.75}.nvd3.nv-linePlusBar .nv-bar rect:hover{fill-opacity:1}.nvd3 .nv-groups path.nv-line{fill:none}.nvd3 .nv-groups path.nv-area{stroke:none}.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point{fill-opacity:0;stroke-opacity:0}.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point{fill-opacity:.5!important;stroke-opacity:.5!important}.with-transitions .nvd3 .nv-groups .nv-point{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-scatter .nv-groups .nv-point.hover,.nvd3 .nv-groups .nv-point.hover{stroke-width:7px;fill-opacity:.95!important;stroke-opacity:.95!important}.nvd3 .nv-point-paths path{stroke:#aaa;stroke-opacity:0;fill:#eee;fill-opacity:0}.nvd3 .nv-indexLine{cursor:ew-resize}svg.nvd3-svg{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-ms-user-select:none;-moz-user-select:none;user-select:none;display:block;width:100%;height:100%}.nvtooltip.with-3d-shadow,.with-3d-shadow .nvtooltip{-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nvd3 text{font:400 12px Arial}.nvd3 .title{font:700 14px Arial}.nvd3 .nv-background{fill:#fff;fill-opacity:0}.nvd3.nv-noData{font-size:18px;font-weight:700}.nv-brush .extent{fill-opacity:.125;shape-rendering:crispEdges}.nv-brush .resize path{fill:#eee;stroke:#666}.nvd3 .nv-legend .nv-series{cursor:pointer}.nvd3 .nv-legend .nv-disabled circle{fill-opacity:0}.nvd3 .nv-brush .extent{fill-opacity:0!important}.nvd3 .nv-brushBackground rect{stroke:#000;stroke-width:.4;fill:#fff;fill-opacity:.7}.nvd3.nv-ohlcBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive{stroke:#2ca02c}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative{stroke:#d62728}.nvd3 .background path{fill:none;stroke:#EEE;stroke-opacity:.4;shape-rendering:crispEdges}.nvd3 .foreground path{fill:none;stroke-opacity:.7}.nvd3 .nv-parallelCoordinates-brush .extent{fill:#fff;fill-opacity:.6;stroke:gray;shape-rendering:crispEdges}.nvd3 .nv-parallelCoordinates .hover{fill-opacity:1;stroke-width:3px}.nvd3 .missingValuesline line{fill:none;stroke:#000;stroke-width:1;stroke-opacity:1;stroke-dasharray:5,5}.nvd3.nv-pie path{stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-pie .nv-pie-title{font-size:24px;fill:rgba(19,196,249,.59)}.nvd3.nv-pie .nv-slice text{stroke:#000;stroke-width:0}.nvd3.nv-pie path{stroke:#fff;stroke-width:1px;stroke-opacity:1}.nvd3.nv-pie .hover path{fill-opacity:.7}.nvd3.nv-pie .nv-label{pointer-events:none}.nvd3.nv-pie .nv-label rect{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-groups .nv-point.hover{stroke-width:20px;stroke-opacity:.5}.nvd3 .nv-scatter .nv-point.hover{fill-opacity:1}.nv-noninteractive{pointer-events:none}.nv-distx,.nv-disty{pointer-events:none}.nvd3.nv-sparkline path{fill:none}.nvd3.nv-sparklineplus g.nv-hoverValue{pointer-events:none}.nvd3.nv-sparklineplus .nv-hoverValue line{stroke:#333;stroke-width:1.5px}.nvd3.nv-sparklineplus,.nvd3.nv-sparklineplus g{pointer-events:all}.nvd3 .nv-hoverArea{fill-opacity:0;stroke-opacity:0}.nvd3.nv-sparklineplus .nv-xValue,.nvd3.nv-sparklineplus .nv-yValue{stroke-width:0;font-size:.9em;font-weight:400}.nvd3.nv-sparklineplus .nv-yValue{stroke:#f66}.nvd3.nv-sparklineplus .nv-maxValue{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-sparklineplus .nv-minValue{stroke:#d62728;fill:#d62728}.nvd3.nv-sparklineplus .nv-currentValue{font-weight:700;font-size:1.1em}.nvd3.nv-stackedarea path.nv-area{fill-opacity:.7;stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-stackedarea path.nv-area.hover{fill-opacity:.9}.nvd3.nv-stackedarea .nv-groups .nv-point{stroke-opacity:0;fill-opacity:0}.nvtooltip{position:absolute;background-color:rgba(255,255,255,1);color:rgba(0,0,0,1);padding:1px;border:1px solid rgba(0,0,0,.2);z-index:10000;display:block;font-family:Arial;font-size:13px;text-align:left;pointer-events:none;white-space:nowrap;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.nvtooltip{background:rgba(255,255,255,.8);border:1px solid rgba(0,0,0,.5);border-radius:4px}.nvtooltip.with-transitions,.with-transitions .nvtooltip{transition:opacity 50ms linear;-moz-transition:opacity 50ms linear;-webkit-transition:opacity 50ms linear;transition-delay:200ms;-moz-transition-delay:200ms;-webkit-transition-delay:200ms}.nvtooltip.x-nvtooltip,.nvtooltip.y-nvtooltip{padding:8px}.nvtooltip h3{margin:0;padding:4px 14px;line-height:18px;font-weight:400;background-color:rgba(247,247,247,.75);color:rgba(0,0,0,1);text-align:center;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.nvtooltip p{margin:0;padding:5px 14px;text-align:center}.nvtooltip span{display:inline-block;margin:2px 0}.nvtooltip table{margin:6px;border-spacing:0}.nvtooltip table td{padding:2px 9px 2px 0;vertical-align:middle}.nvtooltip table td.key{font-weight:400}.nvtooltip table td.value{text-align:right;font-weight:700}.nvtooltip table tr.highlight td{padding:1px 9px 1px 0;border-bottom-style:solid;border-bottom-width:1px;border-top-style:solid;border-top-width:1px}.nvtooltip table td.legend-color-guide div{width:8px;height:8px;vertical-align:middle}.nvtooltip table td.legend-color-guide div{width:12px;height:12px;border:1px solid #999}.nvtooltip .footer{padding:3px;text-align:center}.nvtooltip-pending-removal{pointer-events:none;display:none}.nvd3 .nv-interactiveGuideLine{pointer-events:none}.nvd3 line.nv-guideline{stroke:#ccc}
\ No newline at end of file
diff --git a/tests/_output/functional.remote.coverage/.css/style.css b/tests/_output/functional.remote.coverage/.css/style.css
deleted file mode 100644
index 824fb31..0000000
--- a/tests/_output/functional.remote.coverage/.css/style.css
+++ /dev/null
@@ -1,122 +0,0 @@
-body {
- padding-top: 10px;
-}
-
-.popover {
- max-width: none;
-}
-
-.glyphicon {
- margin-right:.25em;
-}
-
-.table-bordered>thead>tr>td {
- border-bottom-width: 1px;
-}
-
-.table tbody>tr>td, .table thead>tr>td {
- padding-top: 3px;
- padding-bottom: 3px;
-}
-
-.table-condensed tbody>tr>td {
- padding-top: 0;
- padding-bottom: 0;
-}
-
-.table .progress {
- margin-bottom: inherit;
-}
-
-.table-borderless th, .table-borderless td {
- border: 0 !important;
-}
-
-.table tbody tr.covered-by-large-tests, li.covered-by-large-tests, tr.success, td.success, li.success, span.success {
- background-color: #dff0d8;
-}
-
-.table tbody tr.covered-by-medium-tests, li.covered-by-medium-tests {
- background-color: #c3e3b5;
-}
-
-.table tbody tr.covered-by-small-tests, li.covered-by-small-tests {
- background-color: #99cb84;
-}
-
-.table tbody tr.danger, .table tbody td.danger, li.danger, span.danger {
- background-color: #f2dede;
-}
-
-.table tbody td.warning, li.warning, span.warning {
- background-color: #fcf8e3;
-}
-
-.table tbody td.info {
- background-color: #d9edf7;
-}
-
-td.big {
- width: 117px;
-}
-
-td.small {
-}
-
-td.codeLine {
- font-family: monospace;
- white-space: pre;
-}
-
-td span.comment {
- color: #888a85;
-}
-
-td span.default {
- color: #2e3436;
-}
-
-td span.html {
- color: #888a85;
-}
-
-td span.keyword {
- color: #2e3436;
- font-weight: bold;
-}
-
-pre span.string {
- color: #2e3436;
-}
-
-span.success, span.warning, span.danger {
- margin-right: 2px;
- padding-left: 10px;
- padding-right: 10px;
- text-align: center;
-}
-
-#classCoverageDistribution, #classComplexity {
- height: 200px;
- width: 475px;
-}
-
-#toplink {
- position: fixed;
- left: 5px;
- bottom: 5px;
- outline: 0;
-}
-
-svg text {
- font-family: "Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif;
- font-size: 11px;
- color: #666;
- fill: #666;
-}
-
-.scrollbox {
- height:245px;
- overflow-x:hidden;
- overflow-y:scroll;
-}
diff --git a/tests/_output/functional.remote.coverage/.fonts/glyphicons-halflings-regular.eot b/tests/_output/functional.remote.coverage/.fonts/glyphicons-halflings-regular.eot
deleted file mode 100644
index b93a495..0000000
Binary files a/tests/_output/functional.remote.coverage/.fonts/glyphicons-halflings-regular.eot and /dev/null differ
diff --git a/tests/_output/functional.remote.coverage/.fonts/glyphicons-halflings-regular.svg b/tests/_output/functional.remote.coverage/.fonts/glyphicons-halflings-regular.svg
deleted file mode 100644
index 94fb549..0000000
--- a/tests/_output/functional.remote.coverage/.fonts/glyphicons-halflings-regular.svg
+++ /dev/null
@@ -1,288 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/tests/_output/functional.remote.coverage/.fonts/glyphicons-halflings-regular.ttf b/tests/_output/functional.remote.coverage/.fonts/glyphicons-halflings-regular.ttf
deleted file mode 100644
index 1413fc6..0000000
Binary files a/tests/_output/functional.remote.coverage/.fonts/glyphicons-halflings-regular.ttf and /dev/null differ
diff --git a/tests/_output/functional.remote.coverage/.fonts/glyphicons-halflings-regular.woff b/tests/_output/functional.remote.coverage/.fonts/glyphicons-halflings-regular.woff
deleted file mode 100644
index 9e61285..0000000
Binary files a/tests/_output/functional.remote.coverage/.fonts/glyphicons-halflings-regular.woff and /dev/null differ
diff --git a/tests/_output/functional.remote.coverage/.fonts/glyphicons-halflings-regular.woff2 b/tests/_output/functional.remote.coverage/.fonts/glyphicons-halflings-regular.woff2
deleted file mode 100644
index 64539b5..0000000
Binary files a/tests/_output/functional.remote.coverage/.fonts/glyphicons-halflings-regular.woff2 and /dev/null differ
diff --git a/tests/_output/functional.remote.coverage/.js/bootstrap.min.js b/tests/_output/functional.remote.coverage/.js/bootstrap.min.js
deleted file mode 100644
index 9bcd2fc..0000000
--- a/tests/_output/functional.remote.coverage/.js/bootstrap.min.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/*!
- * Bootstrap v3.3.7 (http://getbootstrap.com)
- * Copyright 2011-2016 Twitter, Inc.
- * Licensed under the MIT license
- */
-if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'