├── .github
└── pull_request_template.md
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── bin
├── install-wp-tests.sh
└── run-tests
├── composer.json
├── composer.lock
├── examples
└── simple.php
├── finally.jpg
├── images
└── Oomph_logo.png
├── inc
├── class-wp-forms-api.php
├── wp-forms-api.css
└── wp-forms-api.js
├── phpunit.xml.dist
├── tests
├── bootstrap.php
└── test-wp-forms-api.php
└── wp-forms-api.php
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Summary
2 |
3 | ## Relavant Ticket(s)
4 |
5 | ## Risk
6 |
7 | + [ ] Trivial
8 | + [ ] Low
9 | + [ ] Medium
10 | + [ ] High
11 |
12 | ## How to Test
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .htaccess
3 | vendor/
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | notifications:
4 | email:
5 | on_success: never
6 | on_failure: change
7 |
8 | php:
9 | - 5.3
10 | - 5.6
11 |
12 | env:
13 | - WP_VERSION=latest WP_MULTISITE=0
14 |
15 | matrix:
16 | include:
17 | - php: 5.3
18 | env: WP_VERSION=latest WP_MULTISITE=1
19 |
20 | before_script:
21 | - bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION
22 |
23 | script: phpunit
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Oomph, Inc
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WP Forms API [](https://travis-ci.org/oomphinc/WP-Forms-API)
2 |
3 |
4 |
5 |
6 |
7 | *A Drupal-esque API for creating and processing forms in WordPress.*
8 |
9 | Provides a `WP_Forms_API` class composed of static methods which can be used to render forms defined by arbitrary data structures. You can also process the results submitted in those forms into a coherent set of values, smoothing over data types, validation (in progress: https://github.com/oomphinc/WP-Forms-API/pull/35), conditional logic and allowing for functional integration into WordPress using filters.
10 |
11 | ## Why?
12 |
13 | Writing and managing admin forms in WordPress is a real pain in the butt, and a data-driven approach is much more flexible and Drupal-y. WordPress tends to implement forms and other complex markup structures with literal markup templates, but those can be very difficult to manage and update. I have not seen any other similar development projects that brings some of the best ideas from Drupal into WordPress where they can benefit developers and clients alike by providing rapid development tools.
14 |
15 | Having forms driven by data sets instead of templates and markup creates a very generic, predictable, and stylizable structure in the rendered form markup, and easy management and updates to the form structure.
16 |
17 | Also provides more WordPress-specific form controls, such as "Select Image" or "Select Post" fields, which can enable powerful admin forms with very little code.
18 |
19 | ## Overview
20 |
21 | There are two basic elements:
22 |
23 | 'form', which is any associative array.
24 |
25 | 'element', a form that has at least `#type` and `#key` keys.
26 |
27 | ## Build
28 |
29 | This project uses NPM to manage dependencies and gulp to build. Use `npm install` to install all the dependencies. Use `gulp` to build the CSS files.
30 |
31 | ## API Quick Start
32 |
33 | ```php
34 | /**
35 | * Define a form called 'my-form' which contains an address1 and address2
36 | * input, and another form called 'citystatezip' which contains three input
37 | * elements: city, state, zipcode
38 | */
39 | $form = array(
40 | '#id' => 'my-form',
41 |
42 | 'address1' => array(
43 | '#label' => "Street",
44 | '#type' => 'text',
45 | '#placeholder' => "Line 1",
46 | ),
47 | 'address2' => array(
48 | '#type' => 'text',
49 | '#placeholder' => "Line 2"
50 | ),
51 |
52 | 'citystatezip' => array(
53 | 'city' => array(
54 | '#type' => 'text',
55 | '#label' => "City",
56 | '#placeholder' => "Boston",
57 | '#size' => 20
58 | ),
59 | 'state' => array(
60 | '#type' => 'text',
61 | '#label' => "State",
62 | '#placeholder' => "MA",
63 | '#size' => 4,
64 | ),
65 | 'zipcode' => array(
66 | '#type' => 'text',
67 | '#label' => "ZIP",
68 | '#placeholder' => "01011",
69 | '#size' => 7
70 | )
71 | ),
72 | );
73 |
74 | /**
75 | * Define the values for this form
76 | */
77 | $values = array(
78 | 'city' => "Omaha",
79 | 'state' => "Nebraska"
80 | );
81 |
82 | /**
83 | * You can render the form in whatever context you'd like: Front-end, meta boxes,
84 | * wherever. Does not render containing
385 | ```
386 |
387 | This form will present two input fields: `'name'`, and `'zipcode'`, as well as a submit button that is labelled "Save Information".
388 |
389 | You can process the form using `WP_Forms_API::process_form()`:
390 |
391 | ```php
392 |
393 |
396 |
397 |
398 | Your name is:
399 |
400 |
401 |
402 | Your ZIP is:
403 |
404 | ```
405 |
406 | ## Testing
407 |
408 | This package contains PHP Unit tests. To start testing, install the composer dependencies and the test database:
409 |
410 | ```sh
411 | $ composer install
412 |
413 | $ bin/install-wp-tests.sh
414 | ```
415 |
416 | Then you can execute the tests simply by executing `bin/run-tests`.
417 |
418 | ## Please help!
419 |
420 | This project is merely a generalization of work I did for another project. I've spent many frustrating hours building forms in WordPress, and I knew there had to be an easier way. This doesn't claim to be nearly as powerful as the Drupal Forms API, but maybe one day, with your help, it could be!
421 |
422 | ## Plugin brought to you by Oomph, Inc
423 |
424 | 
425 |
426 | Oomph is a full service digital design and engineering firm assisting premium brands and media companies with large-scale content engagement solutions.
427 | http://oomphinc.com/
428 |
--------------------------------------------------------------------------------
/bin/install-wp-tests.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | if [ $# -lt 3 ]; then
4 | echo "usage: $0 [db-host] [wp-version]"
5 | exit 1
6 | fi
7 |
8 | DB_NAME=$1
9 | DB_USER=$2
10 | DB_PASS=$3
11 | DB_HOST=${4-localhost}
12 | WP_VERSION=${5-latest}
13 |
14 | WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib}
15 | WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress/}
16 |
17 | download() {
18 | if [ `which curl` ]; then
19 | curl -s "$1" > "$2";
20 | elif [ `which wget` ]; then
21 | wget -nv -O "$2" "$1"
22 | fi
23 | }
24 |
25 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+(\.[0-9]+)? ]]; then
26 | WP_TESTS_TAG="tags/$WP_VERSION"
27 | elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
28 | WP_TESTS_TAG="trunk"
29 | else
30 | # http serves a single offer, whereas https serves multiple. we only want one
31 | download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json
32 | grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json
33 | LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//')
34 | if [[ -z "$LATEST_VERSION" ]]; then
35 | echo "Latest WordPress version could not be found"
36 | exit 1
37 | fi
38 | WP_TESTS_TAG="tags/$LATEST_VERSION"
39 | fi
40 |
41 | set -ex
42 |
43 | install_wp() {
44 |
45 | if [ -d $WP_CORE_DIR ]; then
46 | return;
47 | fi
48 |
49 | mkdir -p $WP_CORE_DIR
50 |
51 | if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
52 | mkdir -p /tmp/wordpress-nightly
53 | download https://wordpress.org/nightly-builds/wordpress-latest.zip /tmp/wordpress-nightly/wordpress-nightly.zip
54 | unzip -q /tmp/wordpress-nightly/wordpress-nightly.zip -d /tmp/wordpress-nightly/
55 | mv /tmp/wordpress-nightly/wordpress/* $WP_CORE_DIR
56 | else
57 | if [ $WP_VERSION == 'latest' ]; then
58 | local ARCHIVE_NAME='latest'
59 | else
60 | local ARCHIVE_NAME="wordpress-$WP_VERSION"
61 | fi
62 | download https://wordpress.org/${ARCHIVE_NAME}.tar.gz /tmp/wordpress.tar.gz
63 | tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR
64 | fi
65 |
66 | download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php
67 | }
68 |
69 | install_test_suite() {
70 | # portable in-place argument for both GNU sed and Mac OSX sed
71 | if [[ $(uname -s) == 'Darwin' ]]; then
72 | local ioption='-i .bak'
73 | else
74 | local ioption='-i'
75 | fi
76 |
77 | # set up testing suite if it doesn't yet exist
78 | if [ ! -d $WP_TESTS_DIR ]; then
79 | # set up testing suite
80 | mkdir -p $WP_TESTS_DIR
81 | svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes
82 | fi
83 |
84 | cd $WP_TESTS_DIR
85 |
86 | if [ ! -f wp-tests-config.php ]; then
87 | download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php
88 | sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR':" "$WP_TESTS_DIR"/wp-tests-config.php
89 | sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php
90 | sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php
91 | sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
92 | sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php
93 | fi
94 |
95 | }
96 |
97 | install_db() {
98 | # parse DB_HOST for port or socket references
99 | local PARTS=(${DB_HOST//\:/ })
100 | local DB_HOSTNAME=${PARTS[0]};
101 | local DB_SOCK_OR_PORT=${PARTS[1]};
102 | local EXTRA=""
103 |
104 | if ! [ -z $DB_HOSTNAME ] ; then
105 | if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then
106 | EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp"
107 | elif ! [ -z $DB_SOCK_OR_PORT ] ; then
108 | EXTRA=" --socket=$DB_SOCK_OR_PORT"
109 | elif ! [ -z $DB_HOSTNAME ] ; then
110 | EXTRA=" --host=$DB_HOSTNAME --protocol=tcp"
111 | fi
112 | fi
113 |
114 | # create database
115 | mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA
116 | }
117 |
118 | install_wp
119 | install_test_suite
120 | install_db
121 |
--------------------------------------------------------------------------------
/bin/run-tests:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cd `git rev-parse --show-toplevel`
4 |
5 | [ ! -x vendor/bin/phpunit ] && composer install
6 |
7 | vendor/bin/phpunit
8 |
9 |
10 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "oomphinc/wp-forms-api",
3 | "description": "Drupal-esque API for creating and munging forms in WordPress",
4 | "homepage": "http://oomphinc.com/",
5 | "type": "wordpress-muplugin",
6 | "keywords": [ "wordpress", "forms", "fields", "elements", "api", "wp" ],
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Ben Doherty",
11 | "homepage": "https://github.com/bendoh",
12 | "role": "Developer"
13 | },
14 | {
15 | "name": "Stephen Beemsterboer",
16 | "homepage": "https://github.com/balbuf",
17 | "role": "Developer"
18 | },
19 | {
20 | "name": "Jonathan Cowher",
21 | "homepage": "https://github.com/jcowher",
22 | "role": "Developer"
23 | }
24 | ],
25 | "support": {
26 | "issues": "https://github.com/oomphinc/WP-Forms-API/issues",
27 | "source": "https://github.com/oomphinc/WP-Forms-API"
28 | },
29 | "require": {
30 | "php": ">=5.4",
31 | "oomphinc/composer-installers-extender": "^1.0"
32 | },
33 | "require-dev": {
34 | "phpunit/phpunit": "^5.2"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "6f4216d1edc37368f563cb1c4041454c",
8 | "packages": [
9 | {
10 | "name": "composer/installers",
11 | "version": "v1.2.0",
12 | "source": {
13 | "type": "git",
14 | "url": "https://github.com/composer/installers.git",
15 | "reference": "d78064c68299743e0161004f2de3a0204e33b804"
16 | },
17 | "dist": {
18 | "type": "zip",
19 | "url": "https://api.github.com/repos/composer/installers/zipball/d78064c68299743e0161004f2de3a0204e33b804",
20 | "reference": "d78064c68299743e0161004f2de3a0204e33b804",
21 | "shasum": ""
22 | },
23 | "require": {
24 | "composer-plugin-api": "^1.0"
25 | },
26 | "replace": {
27 | "roundcube/plugin-installer": "*",
28 | "shama/baton": "*"
29 | },
30 | "require-dev": {
31 | "composer/composer": "1.0.*@dev",
32 | "phpunit/phpunit": "4.1.*"
33 | },
34 | "type": "composer-plugin",
35 | "extra": {
36 | "class": "Composer\\Installers\\Plugin",
37 | "branch-alias": {
38 | "dev-master": "1.0-dev"
39 | }
40 | },
41 | "autoload": {
42 | "psr-4": {
43 | "Composer\\Installers\\": "src/Composer/Installers"
44 | }
45 | },
46 | "notification-url": "https://packagist.org/downloads/",
47 | "license": [
48 | "MIT"
49 | ],
50 | "authors": [
51 | {
52 | "name": "Kyle Robinson Young",
53 | "email": "kyle@dontkry.com",
54 | "homepage": "https://github.com/shama"
55 | }
56 | ],
57 | "description": "A multi-framework Composer library installer",
58 | "homepage": "https://composer.github.io/installers/",
59 | "keywords": [
60 | "Craft",
61 | "Dolibarr",
62 | "Hurad",
63 | "ImageCMS",
64 | "MODX Evo",
65 | "Mautic",
66 | "OXID",
67 | "Plentymarkets",
68 | "RadPHP",
69 | "SMF",
70 | "Thelia",
71 | "WolfCMS",
72 | "agl",
73 | "aimeos",
74 | "annotatecms",
75 | "attogram",
76 | "bitrix",
77 | "cakephp",
78 | "chef",
79 | "cockpit",
80 | "codeigniter",
81 | "concrete5",
82 | "croogo",
83 | "dokuwiki",
84 | "drupal",
85 | "elgg",
86 | "expressionengine",
87 | "fuelphp",
88 | "grav",
89 | "installer",
90 | "joomla",
91 | "kohana",
92 | "laravel",
93 | "lithium",
94 | "magento",
95 | "mako",
96 | "mediawiki",
97 | "modulework",
98 | "moodle",
99 | "phpbb",
100 | "piwik",
101 | "ppi",
102 | "puppet",
103 | "reindex",
104 | "roundcube",
105 | "shopware",
106 | "silverstripe",
107 | "symfony",
108 | "typo3",
109 | "wordpress",
110 | "yawik",
111 | "zend",
112 | "zikula"
113 | ],
114 | "time": "2016-08-13T20:53:52+00:00"
115 | },
116 | {
117 | "name": "oomphinc/composer-installers-extender",
118 | "version": "v1.1.1",
119 | "source": {
120 | "type": "git",
121 | "url": "https://github.com/oomphinc/composer-installers-extender.git",
122 | "reference": "e29c900d3d6a851ee8261859fb39edbb1f34722e"
123 | },
124 | "dist": {
125 | "type": "zip",
126 | "url": "https://api.github.com/repos/oomphinc/composer-installers-extender/zipball/e29c900d3d6a851ee8261859fb39edbb1f34722e",
127 | "reference": "e29c900d3d6a851ee8261859fb39edbb1f34722e",
128 | "shasum": ""
129 | },
130 | "require": {
131 | "composer-plugin-api": "^1.0",
132 | "composer/installers": "^1.0"
133 | },
134 | "type": "composer-plugin",
135 | "extra": {
136 | "class": "OomphInc\\ComposerInstallersExtender\\Plugin"
137 | },
138 | "autoload": {
139 | "psr-4": {
140 | "OomphInc\\ComposerInstallersExtender\\": "src/"
141 | }
142 | },
143 | "notification-url": "https://packagist.org/downloads/",
144 | "license": [
145 | "MIT"
146 | ],
147 | "authors": [
148 | {
149 | "name": "Stephen Beemsterboer",
150 | "email": "stephen@oomphinc.com",
151 | "homepage": "https://github.com/balbuf"
152 | }
153 | ],
154 | "description": "Extend the composer/installers plugin to accept any arbitrary package type.",
155 | "homepage": "http://www.oomphinc.com/",
156 | "time": "2016-07-05T15:00:34+00:00"
157 | }
158 | ],
159 | "packages-dev": [
160 | {
161 | "name": "doctrine/instantiator",
162 | "version": "1.0.5",
163 | "source": {
164 | "type": "git",
165 | "url": "https://github.com/doctrine/instantiator.git",
166 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
167 | },
168 | "dist": {
169 | "type": "zip",
170 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
171 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
172 | "shasum": ""
173 | },
174 | "require": {
175 | "php": ">=5.3,<8.0-DEV"
176 | },
177 | "require-dev": {
178 | "athletic/athletic": "~0.1.8",
179 | "ext-pdo": "*",
180 | "ext-phar": "*",
181 | "phpunit/phpunit": "~4.0",
182 | "squizlabs/php_codesniffer": "~2.0"
183 | },
184 | "type": "library",
185 | "extra": {
186 | "branch-alias": {
187 | "dev-master": "1.0.x-dev"
188 | }
189 | },
190 | "autoload": {
191 | "psr-4": {
192 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
193 | }
194 | },
195 | "notification-url": "https://packagist.org/downloads/",
196 | "license": [
197 | "MIT"
198 | ],
199 | "authors": [
200 | {
201 | "name": "Marco Pivetta",
202 | "email": "ocramius@gmail.com",
203 | "homepage": "http://ocramius.github.com/"
204 | }
205 | ],
206 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
207 | "homepage": "https://github.com/doctrine/instantiator",
208 | "keywords": [
209 | "constructor",
210 | "instantiate"
211 | ],
212 | "time": "2015-06-14T21:17:01+00:00"
213 | },
214 | {
215 | "name": "myclabs/deep-copy",
216 | "version": "1.6.0",
217 | "source": {
218 | "type": "git",
219 | "url": "https://github.com/myclabs/DeepCopy.git",
220 | "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe"
221 | },
222 | "dist": {
223 | "type": "zip",
224 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/5a5a9fc8025a08d8919be87d6884d5a92520cefe",
225 | "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe",
226 | "shasum": ""
227 | },
228 | "require": {
229 | "php": ">=5.4.0"
230 | },
231 | "require-dev": {
232 | "doctrine/collections": "1.*",
233 | "phpunit/phpunit": "~4.1"
234 | },
235 | "type": "library",
236 | "autoload": {
237 | "psr-4": {
238 | "DeepCopy\\": "src/DeepCopy/"
239 | }
240 | },
241 | "notification-url": "https://packagist.org/downloads/",
242 | "license": [
243 | "MIT"
244 | ],
245 | "description": "Create deep copies (clones) of your objects",
246 | "homepage": "https://github.com/myclabs/DeepCopy",
247 | "keywords": [
248 | "clone",
249 | "copy",
250 | "duplicate",
251 | "object",
252 | "object graph"
253 | ],
254 | "time": "2017-01-26T22:05:40+00:00"
255 | },
256 | {
257 | "name": "phpdocumentor/reflection-common",
258 | "version": "1.0",
259 | "source": {
260 | "type": "git",
261 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
262 | "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c"
263 | },
264 | "dist": {
265 | "type": "zip",
266 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
267 | "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
268 | "shasum": ""
269 | },
270 | "require": {
271 | "php": ">=5.5"
272 | },
273 | "require-dev": {
274 | "phpunit/phpunit": "^4.6"
275 | },
276 | "type": "library",
277 | "extra": {
278 | "branch-alias": {
279 | "dev-master": "1.0.x-dev"
280 | }
281 | },
282 | "autoload": {
283 | "psr-4": {
284 | "phpDocumentor\\Reflection\\": [
285 | "src"
286 | ]
287 | }
288 | },
289 | "notification-url": "https://packagist.org/downloads/",
290 | "license": [
291 | "MIT"
292 | ],
293 | "authors": [
294 | {
295 | "name": "Jaap van Otterdijk",
296 | "email": "opensource@ijaap.nl"
297 | }
298 | ],
299 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
300 | "homepage": "http://www.phpdoc.org",
301 | "keywords": [
302 | "FQSEN",
303 | "phpDocumentor",
304 | "phpdoc",
305 | "reflection",
306 | "static analysis"
307 | ],
308 | "time": "2015-12-27T11:43:31+00:00"
309 | },
310 | {
311 | "name": "phpdocumentor/reflection-docblock",
312 | "version": "3.1.1",
313 | "source": {
314 | "type": "git",
315 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
316 | "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e"
317 | },
318 | "dist": {
319 | "type": "zip",
320 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e",
321 | "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e",
322 | "shasum": ""
323 | },
324 | "require": {
325 | "php": ">=5.5",
326 | "phpdocumentor/reflection-common": "^1.0@dev",
327 | "phpdocumentor/type-resolver": "^0.2.0",
328 | "webmozart/assert": "^1.0"
329 | },
330 | "require-dev": {
331 | "mockery/mockery": "^0.9.4",
332 | "phpunit/phpunit": "^4.4"
333 | },
334 | "type": "library",
335 | "autoload": {
336 | "psr-4": {
337 | "phpDocumentor\\Reflection\\": [
338 | "src/"
339 | ]
340 | }
341 | },
342 | "notification-url": "https://packagist.org/downloads/",
343 | "license": [
344 | "MIT"
345 | ],
346 | "authors": [
347 | {
348 | "name": "Mike van Riel",
349 | "email": "me@mikevanriel.com"
350 | }
351 | ],
352 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
353 | "time": "2016-09-30T07:12:33+00:00"
354 | },
355 | {
356 | "name": "phpdocumentor/type-resolver",
357 | "version": "0.2.1",
358 | "source": {
359 | "type": "git",
360 | "url": "https://github.com/phpDocumentor/TypeResolver.git",
361 | "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb"
362 | },
363 | "dist": {
364 | "type": "zip",
365 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb",
366 | "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb",
367 | "shasum": ""
368 | },
369 | "require": {
370 | "php": ">=5.5",
371 | "phpdocumentor/reflection-common": "^1.0"
372 | },
373 | "require-dev": {
374 | "mockery/mockery": "^0.9.4",
375 | "phpunit/phpunit": "^5.2||^4.8.24"
376 | },
377 | "type": "library",
378 | "extra": {
379 | "branch-alias": {
380 | "dev-master": "1.0.x-dev"
381 | }
382 | },
383 | "autoload": {
384 | "psr-4": {
385 | "phpDocumentor\\Reflection\\": [
386 | "src/"
387 | ]
388 | }
389 | },
390 | "notification-url": "https://packagist.org/downloads/",
391 | "license": [
392 | "MIT"
393 | ],
394 | "authors": [
395 | {
396 | "name": "Mike van Riel",
397 | "email": "me@mikevanriel.com"
398 | }
399 | ],
400 | "time": "2016-11-25T06:54:22+00:00"
401 | },
402 | {
403 | "name": "phpspec/prophecy",
404 | "version": "v1.7.0",
405 | "source": {
406 | "type": "git",
407 | "url": "https://github.com/phpspec/prophecy.git",
408 | "reference": "93d39f1f7f9326d746203c7c056f300f7f126073"
409 | },
410 | "dist": {
411 | "type": "zip",
412 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073",
413 | "reference": "93d39f1f7f9326d746203c7c056f300f7f126073",
414 | "shasum": ""
415 | },
416 | "require": {
417 | "doctrine/instantiator": "^1.0.2",
418 | "php": "^5.3|^7.0",
419 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2",
420 | "sebastian/comparator": "^1.1|^2.0",
421 | "sebastian/recursion-context": "^1.0|^2.0|^3.0"
422 | },
423 | "require-dev": {
424 | "phpspec/phpspec": "^2.5|^3.2",
425 | "phpunit/phpunit": "^4.8 || ^5.6.5"
426 | },
427 | "type": "library",
428 | "extra": {
429 | "branch-alias": {
430 | "dev-master": "1.6.x-dev"
431 | }
432 | },
433 | "autoload": {
434 | "psr-0": {
435 | "Prophecy\\": "src/"
436 | }
437 | },
438 | "notification-url": "https://packagist.org/downloads/",
439 | "license": [
440 | "MIT"
441 | ],
442 | "authors": [
443 | {
444 | "name": "Konstantin Kudryashov",
445 | "email": "ever.zet@gmail.com",
446 | "homepage": "http://everzet.com"
447 | },
448 | {
449 | "name": "Marcello Duarte",
450 | "email": "marcello.duarte@gmail.com"
451 | }
452 | ],
453 | "description": "Highly opinionated mocking framework for PHP 5.3+",
454 | "homepage": "https://github.com/phpspec/prophecy",
455 | "keywords": [
456 | "Double",
457 | "Dummy",
458 | "fake",
459 | "mock",
460 | "spy",
461 | "stub"
462 | ],
463 | "time": "2017-03-02T20:05:34+00:00"
464 | },
465 | {
466 | "name": "phpunit/php-code-coverage",
467 | "version": "4.0.7",
468 | "source": {
469 | "type": "git",
470 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
471 | "reference": "09e2277d14ea467e5a984010f501343ef29ffc69"
472 | },
473 | "dist": {
474 | "type": "zip",
475 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/09e2277d14ea467e5a984010f501343ef29ffc69",
476 | "reference": "09e2277d14ea467e5a984010f501343ef29ffc69",
477 | "shasum": ""
478 | },
479 | "require": {
480 | "ext-dom": "*",
481 | "ext-xmlwriter": "*",
482 | "php": "^5.6 || ^7.0",
483 | "phpunit/php-file-iterator": "^1.3",
484 | "phpunit/php-text-template": "^1.2",
485 | "phpunit/php-token-stream": "^1.4.2 || ^2.0",
486 | "sebastian/code-unit-reverse-lookup": "^1.0",
487 | "sebastian/environment": "^1.3.2 || ^2.0",
488 | "sebastian/version": "^1.0 || ^2.0"
489 | },
490 | "require-dev": {
491 | "ext-xdebug": "^2.1.4",
492 | "phpunit/phpunit": "^5.7"
493 | },
494 | "suggest": {
495 | "ext-xdebug": "^2.5.1"
496 | },
497 | "type": "library",
498 | "extra": {
499 | "branch-alias": {
500 | "dev-master": "4.0.x-dev"
501 | }
502 | },
503 | "autoload": {
504 | "classmap": [
505 | "src/"
506 | ]
507 | },
508 | "notification-url": "https://packagist.org/downloads/",
509 | "license": [
510 | "BSD-3-Clause"
511 | ],
512 | "authors": [
513 | {
514 | "name": "Sebastian Bergmann",
515 | "email": "sb@sebastian-bergmann.de",
516 | "role": "lead"
517 | }
518 | ],
519 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
520 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
521 | "keywords": [
522 | "coverage",
523 | "testing",
524 | "xunit"
525 | ],
526 | "time": "2017-03-01T09:12:17+00:00"
527 | },
528 | {
529 | "name": "phpunit/php-file-iterator",
530 | "version": "1.4.2",
531 | "source": {
532 | "type": "git",
533 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
534 | "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5"
535 | },
536 | "dist": {
537 | "type": "zip",
538 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
539 | "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
540 | "shasum": ""
541 | },
542 | "require": {
543 | "php": ">=5.3.3"
544 | },
545 | "type": "library",
546 | "extra": {
547 | "branch-alias": {
548 | "dev-master": "1.4.x-dev"
549 | }
550 | },
551 | "autoload": {
552 | "classmap": [
553 | "src/"
554 | ]
555 | },
556 | "notification-url": "https://packagist.org/downloads/",
557 | "license": [
558 | "BSD-3-Clause"
559 | ],
560 | "authors": [
561 | {
562 | "name": "Sebastian Bergmann",
563 | "email": "sb@sebastian-bergmann.de",
564 | "role": "lead"
565 | }
566 | ],
567 | "description": "FilterIterator implementation that filters files based on a list of suffixes.",
568 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
569 | "keywords": [
570 | "filesystem",
571 | "iterator"
572 | ],
573 | "time": "2016-10-03T07:40:28+00:00"
574 | },
575 | {
576 | "name": "phpunit/php-text-template",
577 | "version": "1.2.1",
578 | "source": {
579 | "type": "git",
580 | "url": "https://github.com/sebastianbergmann/php-text-template.git",
581 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
582 | },
583 | "dist": {
584 | "type": "zip",
585 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
586 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
587 | "shasum": ""
588 | },
589 | "require": {
590 | "php": ">=5.3.3"
591 | },
592 | "type": "library",
593 | "autoload": {
594 | "classmap": [
595 | "src/"
596 | ]
597 | },
598 | "notification-url": "https://packagist.org/downloads/",
599 | "license": [
600 | "BSD-3-Clause"
601 | ],
602 | "authors": [
603 | {
604 | "name": "Sebastian Bergmann",
605 | "email": "sebastian@phpunit.de",
606 | "role": "lead"
607 | }
608 | ],
609 | "description": "Simple template engine.",
610 | "homepage": "https://github.com/sebastianbergmann/php-text-template/",
611 | "keywords": [
612 | "template"
613 | ],
614 | "time": "2015-06-21T13:50:34+00:00"
615 | },
616 | {
617 | "name": "phpunit/php-timer",
618 | "version": "1.0.9",
619 | "source": {
620 | "type": "git",
621 | "url": "https://github.com/sebastianbergmann/php-timer.git",
622 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
623 | },
624 | "dist": {
625 | "type": "zip",
626 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
627 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
628 | "shasum": ""
629 | },
630 | "require": {
631 | "php": "^5.3.3 || ^7.0"
632 | },
633 | "require-dev": {
634 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
635 | },
636 | "type": "library",
637 | "extra": {
638 | "branch-alias": {
639 | "dev-master": "1.0-dev"
640 | }
641 | },
642 | "autoload": {
643 | "classmap": [
644 | "src/"
645 | ]
646 | },
647 | "notification-url": "https://packagist.org/downloads/",
648 | "license": [
649 | "BSD-3-Clause"
650 | ],
651 | "authors": [
652 | {
653 | "name": "Sebastian Bergmann",
654 | "email": "sb@sebastian-bergmann.de",
655 | "role": "lead"
656 | }
657 | ],
658 | "description": "Utility class for timing",
659 | "homepage": "https://github.com/sebastianbergmann/php-timer/",
660 | "keywords": [
661 | "timer"
662 | ],
663 | "time": "2017-02-26T11:10:40+00:00"
664 | },
665 | {
666 | "name": "phpunit/php-token-stream",
667 | "version": "1.4.11",
668 | "source": {
669 | "type": "git",
670 | "url": "https://github.com/sebastianbergmann/php-token-stream.git",
671 | "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7"
672 | },
673 | "dist": {
674 | "type": "zip",
675 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7",
676 | "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7",
677 | "shasum": ""
678 | },
679 | "require": {
680 | "ext-tokenizer": "*",
681 | "php": ">=5.3.3"
682 | },
683 | "require-dev": {
684 | "phpunit/phpunit": "~4.2"
685 | },
686 | "type": "library",
687 | "extra": {
688 | "branch-alias": {
689 | "dev-master": "1.4-dev"
690 | }
691 | },
692 | "autoload": {
693 | "classmap": [
694 | "src/"
695 | ]
696 | },
697 | "notification-url": "https://packagist.org/downloads/",
698 | "license": [
699 | "BSD-3-Clause"
700 | ],
701 | "authors": [
702 | {
703 | "name": "Sebastian Bergmann",
704 | "email": "sebastian@phpunit.de"
705 | }
706 | ],
707 | "description": "Wrapper around PHP's tokenizer extension.",
708 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
709 | "keywords": [
710 | "tokenizer"
711 | ],
712 | "time": "2017-02-27T10:12:30+00:00"
713 | },
714 | {
715 | "name": "phpunit/phpunit",
716 | "version": "5.7.15",
717 | "source": {
718 | "type": "git",
719 | "url": "https://github.com/sebastianbergmann/phpunit.git",
720 | "reference": "b99112aecc01f62acf3d81a3f59646700a1849e5"
721 | },
722 | "dist": {
723 | "type": "zip",
724 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b99112aecc01f62acf3d81a3f59646700a1849e5",
725 | "reference": "b99112aecc01f62acf3d81a3f59646700a1849e5",
726 | "shasum": ""
727 | },
728 | "require": {
729 | "ext-dom": "*",
730 | "ext-json": "*",
731 | "ext-libxml": "*",
732 | "ext-mbstring": "*",
733 | "ext-xml": "*",
734 | "myclabs/deep-copy": "~1.3",
735 | "php": "^5.6 || ^7.0",
736 | "phpspec/prophecy": "^1.6.2",
737 | "phpunit/php-code-coverage": "^4.0.4",
738 | "phpunit/php-file-iterator": "~1.4",
739 | "phpunit/php-text-template": "~1.2",
740 | "phpunit/php-timer": "^1.0.6",
741 | "phpunit/phpunit-mock-objects": "^3.2",
742 | "sebastian/comparator": "^1.2.4",
743 | "sebastian/diff": "~1.2",
744 | "sebastian/environment": "^1.3.4 || ^2.0",
745 | "sebastian/exporter": "~2.0",
746 | "sebastian/global-state": "^1.1",
747 | "sebastian/object-enumerator": "~2.0",
748 | "sebastian/resource-operations": "~1.0",
749 | "sebastian/version": "~1.0.3|~2.0",
750 | "symfony/yaml": "~2.1|~3.0"
751 | },
752 | "conflict": {
753 | "phpdocumentor/reflection-docblock": "3.0.2"
754 | },
755 | "require-dev": {
756 | "ext-pdo": "*"
757 | },
758 | "suggest": {
759 | "ext-xdebug": "*",
760 | "phpunit/php-invoker": "~1.1"
761 | },
762 | "bin": [
763 | "phpunit"
764 | ],
765 | "type": "library",
766 | "extra": {
767 | "branch-alias": {
768 | "dev-master": "5.7.x-dev"
769 | }
770 | },
771 | "autoload": {
772 | "classmap": [
773 | "src/"
774 | ]
775 | },
776 | "notification-url": "https://packagist.org/downloads/",
777 | "license": [
778 | "BSD-3-Clause"
779 | ],
780 | "authors": [
781 | {
782 | "name": "Sebastian Bergmann",
783 | "email": "sebastian@phpunit.de",
784 | "role": "lead"
785 | }
786 | ],
787 | "description": "The PHP Unit Testing framework.",
788 | "homepage": "https://phpunit.de/",
789 | "keywords": [
790 | "phpunit",
791 | "testing",
792 | "xunit"
793 | ],
794 | "time": "2017-03-02T15:22:43+00:00"
795 | },
796 | {
797 | "name": "phpunit/phpunit-mock-objects",
798 | "version": "3.4.3",
799 | "source": {
800 | "type": "git",
801 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
802 | "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24"
803 | },
804 | "dist": {
805 | "type": "zip",
806 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/3ab72b65b39b491e0c011e2e09bb2206c2aa8e24",
807 | "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24",
808 | "shasum": ""
809 | },
810 | "require": {
811 | "doctrine/instantiator": "^1.0.2",
812 | "php": "^5.6 || ^7.0",
813 | "phpunit/php-text-template": "^1.2",
814 | "sebastian/exporter": "^1.2 || ^2.0"
815 | },
816 | "conflict": {
817 | "phpunit/phpunit": "<5.4.0"
818 | },
819 | "require-dev": {
820 | "phpunit/phpunit": "^5.4"
821 | },
822 | "suggest": {
823 | "ext-soap": "*"
824 | },
825 | "type": "library",
826 | "extra": {
827 | "branch-alias": {
828 | "dev-master": "3.2.x-dev"
829 | }
830 | },
831 | "autoload": {
832 | "classmap": [
833 | "src/"
834 | ]
835 | },
836 | "notification-url": "https://packagist.org/downloads/",
837 | "license": [
838 | "BSD-3-Clause"
839 | ],
840 | "authors": [
841 | {
842 | "name": "Sebastian Bergmann",
843 | "email": "sb@sebastian-bergmann.de",
844 | "role": "lead"
845 | }
846 | ],
847 | "description": "Mock Object library for PHPUnit",
848 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
849 | "keywords": [
850 | "mock",
851 | "xunit"
852 | ],
853 | "time": "2016-12-08T20:27:08+00:00"
854 | },
855 | {
856 | "name": "sebastian/code-unit-reverse-lookup",
857 | "version": "1.0.1",
858 | "source": {
859 | "type": "git",
860 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
861 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18"
862 | },
863 | "dist": {
864 | "type": "zip",
865 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
866 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
867 | "shasum": ""
868 | },
869 | "require": {
870 | "php": "^5.6 || ^7.0"
871 | },
872 | "require-dev": {
873 | "phpunit/phpunit": "^5.7 || ^6.0"
874 | },
875 | "type": "library",
876 | "extra": {
877 | "branch-alias": {
878 | "dev-master": "1.0.x-dev"
879 | }
880 | },
881 | "autoload": {
882 | "classmap": [
883 | "src/"
884 | ]
885 | },
886 | "notification-url": "https://packagist.org/downloads/",
887 | "license": [
888 | "BSD-3-Clause"
889 | ],
890 | "authors": [
891 | {
892 | "name": "Sebastian Bergmann",
893 | "email": "sebastian@phpunit.de"
894 | }
895 | ],
896 | "description": "Looks up which function or method a line of code belongs to",
897 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
898 | "time": "2017-03-04T06:30:41+00:00"
899 | },
900 | {
901 | "name": "sebastian/comparator",
902 | "version": "1.2.4",
903 | "source": {
904 | "type": "git",
905 | "url": "https://github.com/sebastianbergmann/comparator.git",
906 | "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be"
907 | },
908 | "dist": {
909 | "type": "zip",
910 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
911 | "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
912 | "shasum": ""
913 | },
914 | "require": {
915 | "php": ">=5.3.3",
916 | "sebastian/diff": "~1.2",
917 | "sebastian/exporter": "~1.2 || ~2.0"
918 | },
919 | "require-dev": {
920 | "phpunit/phpunit": "~4.4"
921 | },
922 | "type": "library",
923 | "extra": {
924 | "branch-alias": {
925 | "dev-master": "1.2.x-dev"
926 | }
927 | },
928 | "autoload": {
929 | "classmap": [
930 | "src/"
931 | ]
932 | },
933 | "notification-url": "https://packagist.org/downloads/",
934 | "license": [
935 | "BSD-3-Clause"
936 | ],
937 | "authors": [
938 | {
939 | "name": "Jeff Welch",
940 | "email": "whatthejeff@gmail.com"
941 | },
942 | {
943 | "name": "Volker Dusch",
944 | "email": "github@wallbash.com"
945 | },
946 | {
947 | "name": "Bernhard Schussek",
948 | "email": "bschussek@2bepublished.at"
949 | },
950 | {
951 | "name": "Sebastian Bergmann",
952 | "email": "sebastian@phpunit.de"
953 | }
954 | ],
955 | "description": "Provides the functionality to compare PHP values for equality",
956 | "homepage": "http://www.github.com/sebastianbergmann/comparator",
957 | "keywords": [
958 | "comparator",
959 | "compare",
960 | "equality"
961 | ],
962 | "time": "2017-01-29T09:50:25+00:00"
963 | },
964 | {
965 | "name": "sebastian/diff",
966 | "version": "1.4.1",
967 | "source": {
968 | "type": "git",
969 | "url": "https://github.com/sebastianbergmann/diff.git",
970 | "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e"
971 | },
972 | "dist": {
973 | "type": "zip",
974 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e",
975 | "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e",
976 | "shasum": ""
977 | },
978 | "require": {
979 | "php": ">=5.3.3"
980 | },
981 | "require-dev": {
982 | "phpunit/phpunit": "~4.8"
983 | },
984 | "type": "library",
985 | "extra": {
986 | "branch-alias": {
987 | "dev-master": "1.4-dev"
988 | }
989 | },
990 | "autoload": {
991 | "classmap": [
992 | "src/"
993 | ]
994 | },
995 | "notification-url": "https://packagist.org/downloads/",
996 | "license": [
997 | "BSD-3-Clause"
998 | ],
999 | "authors": [
1000 | {
1001 | "name": "Kore Nordmann",
1002 | "email": "mail@kore-nordmann.de"
1003 | },
1004 | {
1005 | "name": "Sebastian Bergmann",
1006 | "email": "sebastian@phpunit.de"
1007 | }
1008 | ],
1009 | "description": "Diff implementation",
1010 | "homepage": "https://github.com/sebastianbergmann/diff",
1011 | "keywords": [
1012 | "diff"
1013 | ],
1014 | "time": "2015-12-08T07:14:41+00:00"
1015 | },
1016 | {
1017 | "name": "sebastian/environment",
1018 | "version": "2.0.0",
1019 | "source": {
1020 | "type": "git",
1021 | "url": "https://github.com/sebastianbergmann/environment.git",
1022 | "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac"
1023 | },
1024 | "dist": {
1025 | "type": "zip",
1026 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac",
1027 | "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac",
1028 | "shasum": ""
1029 | },
1030 | "require": {
1031 | "php": "^5.6 || ^7.0"
1032 | },
1033 | "require-dev": {
1034 | "phpunit/phpunit": "^5.0"
1035 | },
1036 | "type": "library",
1037 | "extra": {
1038 | "branch-alias": {
1039 | "dev-master": "2.0.x-dev"
1040 | }
1041 | },
1042 | "autoload": {
1043 | "classmap": [
1044 | "src/"
1045 | ]
1046 | },
1047 | "notification-url": "https://packagist.org/downloads/",
1048 | "license": [
1049 | "BSD-3-Clause"
1050 | ],
1051 | "authors": [
1052 | {
1053 | "name": "Sebastian Bergmann",
1054 | "email": "sebastian@phpunit.de"
1055 | }
1056 | ],
1057 | "description": "Provides functionality to handle HHVM/PHP environments",
1058 | "homepage": "http://www.github.com/sebastianbergmann/environment",
1059 | "keywords": [
1060 | "Xdebug",
1061 | "environment",
1062 | "hhvm"
1063 | ],
1064 | "time": "2016-11-26T07:53:53+00:00"
1065 | },
1066 | {
1067 | "name": "sebastian/exporter",
1068 | "version": "2.0.0",
1069 | "source": {
1070 | "type": "git",
1071 | "url": "https://github.com/sebastianbergmann/exporter.git",
1072 | "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4"
1073 | },
1074 | "dist": {
1075 | "type": "zip",
1076 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4",
1077 | "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4",
1078 | "shasum": ""
1079 | },
1080 | "require": {
1081 | "php": ">=5.3.3",
1082 | "sebastian/recursion-context": "~2.0"
1083 | },
1084 | "require-dev": {
1085 | "ext-mbstring": "*",
1086 | "phpunit/phpunit": "~4.4"
1087 | },
1088 | "type": "library",
1089 | "extra": {
1090 | "branch-alias": {
1091 | "dev-master": "2.0.x-dev"
1092 | }
1093 | },
1094 | "autoload": {
1095 | "classmap": [
1096 | "src/"
1097 | ]
1098 | },
1099 | "notification-url": "https://packagist.org/downloads/",
1100 | "license": [
1101 | "BSD-3-Clause"
1102 | ],
1103 | "authors": [
1104 | {
1105 | "name": "Jeff Welch",
1106 | "email": "whatthejeff@gmail.com"
1107 | },
1108 | {
1109 | "name": "Volker Dusch",
1110 | "email": "github@wallbash.com"
1111 | },
1112 | {
1113 | "name": "Bernhard Schussek",
1114 | "email": "bschussek@2bepublished.at"
1115 | },
1116 | {
1117 | "name": "Sebastian Bergmann",
1118 | "email": "sebastian@phpunit.de"
1119 | },
1120 | {
1121 | "name": "Adam Harvey",
1122 | "email": "aharvey@php.net"
1123 | }
1124 | ],
1125 | "description": "Provides the functionality to export PHP variables for visualization",
1126 | "homepage": "http://www.github.com/sebastianbergmann/exporter",
1127 | "keywords": [
1128 | "export",
1129 | "exporter"
1130 | ],
1131 | "time": "2016-11-19T08:54:04+00:00"
1132 | },
1133 | {
1134 | "name": "sebastian/global-state",
1135 | "version": "1.1.1",
1136 | "source": {
1137 | "type": "git",
1138 | "url": "https://github.com/sebastianbergmann/global-state.git",
1139 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
1140 | },
1141 | "dist": {
1142 | "type": "zip",
1143 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
1144 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
1145 | "shasum": ""
1146 | },
1147 | "require": {
1148 | "php": ">=5.3.3"
1149 | },
1150 | "require-dev": {
1151 | "phpunit/phpunit": "~4.2"
1152 | },
1153 | "suggest": {
1154 | "ext-uopz": "*"
1155 | },
1156 | "type": "library",
1157 | "extra": {
1158 | "branch-alias": {
1159 | "dev-master": "1.0-dev"
1160 | }
1161 | },
1162 | "autoload": {
1163 | "classmap": [
1164 | "src/"
1165 | ]
1166 | },
1167 | "notification-url": "https://packagist.org/downloads/",
1168 | "license": [
1169 | "BSD-3-Clause"
1170 | ],
1171 | "authors": [
1172 | {
1173 | "name": "Sebastian Bergmann",
1174 | "email": "sebastian@phpunit.de"
1175 | }
1176 | ],
1177 | "description": "Snapshotting of global state",
1178 | "homepage": "http://www.github.com/sebastianbergmann/global-state",
1179 | "keywords": [
1180 | "global state"
1181 | ],
1182 | "time": "2015-10-12T03:26:01+00:00"
1183 | },
1184 | {
1185 | "name": "sebastian/object-enumerator",
1186 | "version": "2.0.1",
1187 | "source": {
1188 | "type": "git",
1189 | "url": "https://github.com/sebastianbergmann/object-enumerator.git",
1190 | "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7"
1191 | },
1192 | "dist": {
1193 | "type": "zip",
1194 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7",
1195 | "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7",
1196 | "shasum": ""
1197 | },
1198 | "require": {
1199 | "php": ">=5.6",
1200 | "sebastian/recursion-context": "~2.0"
1201 | },
1202 | "require-dev": {
1203 | "phpunit/phpunit": "~5"
1204 | },
1205 | "type": "library",
1206 | "extra": {
1207 | "branch-alias": {
1208 | "dev-master": "2.0.x-dev"
1209 | }
1210 | },
1211 | "autoload": {
1212 | "classmap": [
1213 | "src/"
1214 | ]
1215 | },
1216 | "notification-url": "https://packagist.org/downloads/",
1217 | "license": [
1218 | "BSD-3-Clause"
1219 | ],
1220 | "authors": [
1221 | {
1222 | "name": "Sebastian Bergmann",
1223 | "email": "sebastian@phpunit.de"
1224 | }
1225 | ],
1226 | "description": "Traverses array structures and object graphs to enumerate all referenced objects",
1227 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
1228 | "time": "2017-02-18T15:18:39+00:00"
1229 | },
1230 | {
1231 | "name": "sebastian/recursion-context",
1232 | "version": "2.0.0",
1233 | "source": {
1234 | "type": "git",
1235 | "url": "https://github.com/sebastianbergmann/recursion-context.git",
1236 | "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a"
1237 | },
1238 | "dist": {
1239 | "type": "zip",
1240 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a",
1241 | "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a",
1242 | "shasum": ""
1243 | },
1244 | "require": {
1245 | "php": ">=5.3.3"
1246 | },
1247 | "require-dev": {
1248 | "phpunit/phpunit": "~4.4"
1249 | },
1250 | "type": "library",
1251 | "extra": {
1252 | "branch-alias": {
1253 | "dev-master": "2.0.x-dev"
1254 | }
1255 | },
1256 | "autoload": {
1257 | "classmap": [
1258 | "src/"
1259 | ]
1260 | },
1261 | "notification-url": "https://packagist.org/downloads/",
1262 | "license": [
1263 | "BSD-3-Clause"
1264 | ],
1265 | "authors": [
1266 | {
1267 | "name": "Jeff Welch",
1268 | "email": "whatthejeff@gmail.com"
1269 | },
1270 | {
1271 | "name": "Sebastian Bergmann",
1272 | "email": "sebastian@phpunit.de"
1273 | },
1274 | {
1275 | "name": "Adam Harvey",
1276 | "email": "aharvey@php.net"
1277 | }
1278 | ],
1279 | "description": "Provides functionality to recursively process PHP variables",
1280 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
1281 | "time": "2016-11-19T07:33:16+00:00"
1282 | },
1283 | {
1284 | "name": "sebastian/resource-operations",
1285 | "version": "1.0.0",
1286 | "source": {
1287 | "type": "git",
1288 | "url": "https://github.com/sebastianbergmann/resource-operations.git",
1289 | "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52"
1290 | },
1291 | "dist": {
1292 | "type": "zip",
1293 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
1294 | "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
1295 | "shasum": ""
1296 | },
1297 | "require": {
1298 | "php": ">=5.6.0"
1299 | },
1300 | "type": "library",
1301 | "extra": {
1302 | "branch-alias": {
1303 | "dev-master": "1.0.x-dev"
1304 | }
1305 | },
1306 | "autoload": {
1307 | "classmap": [
1308 | "src/"
1309 | ]
1310 | },
1311 | "notification-url": "https://packagist.org/downloads/",
1312 | "license": [
1313 | "BSD-3-Clause"
1314 | ],
1315 | "authors": [
1316 | {
1317 | "name": "Sebastian Bergmann",
1318 | "email": "sebastian@phpunit.de"
1319 | }
1320 | ],
1321 | "description": "Provides a list of PHP built-in functions that operate on resources",
1322 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
1323 | "time": "2015-07-28T20:34:47+00:00"
1324 | },
1325 | {
1326 | "name": "sebastian/version",
1327 | "version": "2.0.1",
1328 | "source": {
1329 | "type": "git",
1330 | "url": "https://github.com/sebastianbergmann/version.git",
1331 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
1332 | },
1333 | "dist": {
1334 | "type": "zip",
1335 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
1336 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
1337 | "shasum": ""
1338 | },
1339 | "require": {
1340 | "php": ">=5.6"
1341 | },
1342 | "type": "library",
1343 | "extra": {
1344 | "branch-alias": {
1345 | "dev-master": "2.0.x-dev"
1346 | }
1347 | },
1348 | "autoload": {
1349 | "classmap": [
1350 | "src/"
1351 | ]
1352 | },
1353 | "notification-url": "https://packagist.org/downloads/",
1354 | "license": [
1355 | "BSD-3-Clause"
1356 | ],
1357 | "authors": [
1358 | {
1359 | "name": "Sebastian Bergmann",
1360 | "email": "sebastian@phpunit.de",
1361 | "role": "lead"
1362 | }
1363 | ],
1364 | "description": "Library that helps with managing the version number of Git-hosted PHP projects",
1365 | "homepage": "https://github.com/sebastianbergmann/version",
1366 | "time": "2016-10-03T07:35:21+00:00"
1367 | },
1368 | {
1369 | "name": "symfony/yaml",
1370 | "version": "v3.2.4",
1371 | "source": {
1372 | "type": "git",
1373 | "url": "https://github.com/symfony/yaml.git",
1374 | "reference": "9724c684646fcb5387d579b4bfaa63ee0b0c64c8"
1375 | },
1376 | "dist": {
1377 | "type": "zip",
1378 | "url": "https://api.github.com/repos/symfony/yaml/zipball/9724c684646fcb5387d579b4bfaa63ee0b0c64c8",
1379 | "reference": "9724c684646fcb5387d579b4bfaa63ee0b0c64c8",
1380 | "shasum": ""
1381 | },
1382 | "require": {
1383 | "php": ">=5.5.9"
1384 | },
1385 | "require-dev": {
1386 | "symfony/console": "~2.8|~3.0"
1387 | },
1388 | "suggest": {
1389 | "symfony/console": "For validating YAML files using the lint command"
1390 | },
1391 | "type": "library",
1392 | "extra": {
1393 | "branch-alias": {
1394 | "dev-master": "3.2-dev"
1395 | }
1396 | },
1397 | "autoload": {
1398 | "psr-4": {
1399 | "Symfony\\Component\\Yaml\\": ""
1400 | },
1401 | "exclude-from-classmap": [
1402 | "/Tests/"
1403 | ]
1404 | },
1405 | "notification-url": "https://packagist.org/downloads/",
1406 | "license": [
1407 | "MIT"
1408 | ],
1409 | "authors": [
1410 | {
1411 | "name": "Fabien Potencier",
1412 | "email": "fabien@symfony.com"
1413 | },
1414 | {
1415 | "name": "Symfony Community",
1416 | "homepage": "https://symfony.com/contributors"
1417 | }
1418 | ],
1419 | "description": "Symfony Yaml Component",
1420 | "homepage": "https://symfony.com",
1421 | "time": "2017-02-16T22:46:52+00:00"
1422 | },
1423 | {
1424 | "name": "webmozart/assert",
1425 | "version": "1.2.0",
1426 | "source": {
1427 | "type": "git",
1428 | "url": "https://github.com/webmozart/assert.git",
1429 | "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f"
1430 | },
1431 | "dist": {
1432 | "type": "zip",
1433 | "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f",
1434 | "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f",
1435 | "shasum": ""
1436 | },
1437 | "require": {
1438 | "php": "^5.3.3 || ^7.0"
1439 | },
1440 | "require-dev": {
1441 | "phpunit/phpunit": "^4.6",
1442 | "sebastian/version": "^1.0.1"
1443 | },
1444 | "type": "library",
1445 | "extra": {
1446 | "branch-alias": {
1447 | "dev-master": "1.3-dev"
1448 | }
1449 | },
1450 | "autoload": {
1451 | "psr-4": {
1452 | "Webmozart\\Assert\\": "src/"
1453 | }
1454 | },
1455 | "notification-url": "https://packagist.org/downloads/",
1456 | "license": [
1457 | "MIT"
1458 | ],
1459 | "authors": [
1460 | {
1461 | "name": "Bernhard Schussek",
1462 | "email": "bschussek@gmail.com"
1463 | }
1464 | ],
1465 | "description": "Assertions to validate method input/output with nice error messages.",
1466 | "keywords": [
1467 | "assert",
1468 | "check",
1469 | "validate"
1470 | ],
1471 | "time": "2016-11-23T20:04:58+00:00"
1472 | }
1473 | ],
1474 | "aliases": [],
1475 | "minimum-stability": "stable",
1476 | "stability-flags": [],
1477 | "prefer-stable": false,
1478 | "prefer-lowest": false,
1479 | "platform": {
1480 | "php": ">=5.4"
1481 | },
1482 | "platform-dev": []
1483 | }
1484 |
--------------------------------------------------------------------------------
/examples/simple.php:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 |
27 |
30 |
31 |
32 | Your name is:
33 |
34 |
35 |
36 | Your ZIP is:
37 |
38 |
--------------------------------------------------------------------------------
/finally.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oomphinc/WP-Forms-API/e44a6fe30f9c4247c3e1da55178c26d760298097/finally.jpg
--------------------------------------------------------------------------------
/images/Oomph_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oomphinc/WP-Forms-API/e44a6fe30f9c4247c3e1da55178c26d760298097/images/Oomph_logo.png
--------------------------------------------------------------------------------
/inc/class-wp-forms-api.php:
--------------------------------------------------------------------------------
1 | '',
21 |
22 | // The type of input element
23 | '#type' => null,
24 |
25 | // The key of this input element, matching the form key.
26 | '#key' => '',
27 |
28 | // The slug of this input element, built heirarchically.
29 | '#slug' => '',
30 |
31 | // The name of this input element. Typically derived from the key in the form.
32 | '#name' => '',
33 |
34 | // Reference to the top-level form
35 | '#form' => null,
36 |
37 | // Input placeholder
38 | '#placeholder' => null,
39 |
40 | // Default value
41 | '#default' => null,
42 |
43 | // Text field size
44 | '#size' => null,
45 |
46 | // Select / Multi-select options, value => label
47 | '#options' => array(),
48 |
49 | // Value used for checkboxes
50 | '#checked' => '1',
51 |
52 | // Container used for this element
53 | '#container' => 'div',
54 |
55 | // Classes applied to this container
56 | '#container_classes' => array(),
57 | '#markup' => '',
58 |
59 | // Attributes on the input element
60 | '#attrs' => array(),
61 |
62 | // Classes applied to the input element
63 | '#class' => array(),
64 |
65 | // The label to attach to this element
66 | '#label' => null,
67 |
68 | // The label's position -- either "before" or "after"
69 | '#label_position' => 'before',
70 |
71 | // The textual description of this element
72 | '#description' => null,
73 |
74 | // Whether or not a value is required
75 | '#required' => false,
76 |
77 | // When #type=multiple, the index of this particular element in the set
78 | '#index' => null,
79 |
80 | // For #type=select fields, allow multiple values to be selected
81 | // For #type=multiple, the form that can capture multiple values
82 | '#multiple' => null,
83 |
84 | // The content of the input tag, when applicable
85 | '#content' => null,
86 |
87 | // Add / Remove link text for multi-value elements
88 | '#add_link' => 'Add item',
89 | '#remove_link' => 'Remove item',
90 |
91 | // Tag name of element. Typically derived from `#type`
92 | '#tag' => '',
93 |
94 | // Value of element. Typically filled in with process_form()
95 | '#value' => null,
96 |
97 | // Whether or not to allow HTML in an input value. Sanitizes using
98 | // wp_kses_post
99 | '#allow_html' => false,
100 |
101 | // Conditional logic. Used to conditional show/hide elements depending
102 | // on the field's value. NOTE: Can only be used on elements that trigger a
103 | // change event.
104 | // Format:
105 | // [ 'element value' => [ 'target selector' => 'class to add' ] ]
106 | // Multiple element values and target selectors are allowed.
107 | '#conditional' => null,
108 |
109 | // Whether to use only the array values passed to an options argument
110 | '#labels_as_values' => false,
111 | );
112 |
113 | /**
114 | * Initialize this module
115 | *
116 | * @action init
117 | */
118 | static function init() {
119 | wp_register_script( 'wp-forms', plugins_url( 'wp-forms-api.js', __FILE__ ), array( 'jquery-ui-autocomplete', 'jquery-ui-sortable', 'backbone', 'wp-util' ), 1, true );
120 | add_action( 'admin_enqueue_scripts', array( __CLASS__, 'admin_enqueue' ) );
121 | add_action( 'wp_ajax_wp_form_search_posts', array( __CLASS__, 'search_posts' ) );
122 | add_action( 'wp_ajax_wp_form_search_terms', array( __CLASS__, 'search_terms' ) );
123 | add_action( 'print_media_templates', array( __CLASS__, 'media_templates' ) );
124 | }
125 |
126 | /**
127 | * Search for a post by name.
128 | *
129 | * @action wp_ajax_wp_form_search_posts
130 | */
131 | static function search_posts() {
132 | global $wpdb;
133 |
134 | if( !isset( $_POST['term'] ) ) {
135 | wp_send_json_error();
136 | }
137 |
138 | $posts = apply_filters( 'wp_form_pre_search_posts', null );
139 |
140 | if( !isset( $posts ) ) {
141 | $query_args = array(
142 | 's' => $_POST['term'],
143 | 'post_status' => 'any'
144 | );
145 |
146 | if( isset( $_POST['post_type'] ) ) {
147 | $query_args['post_type'] = (array) $_POST['post_type'];
148 | }
149 |
150 | $query_args = apply_filters( 'wp_form_search_posts', $query_args );
151 |
152 | $query = new WP_Query( $query_args );
153 | $posts = $query->posts;
154 | }
155 |
156 | $posts = apply_filters( 'wp_form_search_results', $posts );
157 |
158 | wp_send_json_success( $posts );
159 | }
160 |
161 | /**
162 | * Search for a term by name.
163 | *
164 | * @action wp_ajax_wp_form_search_posts
165 | */
166 | static function search_terms() {
167 | global $wpdb;
168 |
169 | $input = filter_input_array( INPUT_POST, array(
170 | 'term' => FILTER_SANITIZE_STRING,
171 | 'taxonomy' => FILTER_SANITIZE_STRING
172 | ) );
173 |
174 | if( empty( $input['taxonomy'] ) || !taxonomy_exists( $input['taxonomy'] ) ) {
175 | wp_send_json_error();
176 | }
177 |
178 | $terms = apply_filters( 'wp_form_pre_search_terms', null );
179 |
180 | if( !isset( $terms ) ) {
181 | $query_args = array(
182 | 'search' => $input['term'],
183 | 'hide_empty' => false
184 | );
185 |
186 | $terms = get_terms( $input['taxonomy'], $query_args );
187 | }
188 |
189 | $terms = apply_filters( 'wp_form_search_terms', $terms );
190 |
191 | wp_send_json_success( $terms );
192 | }
193 |
194 | /**
195 | * Enqueue admin assets
196 | *
197 | * @action admin_enqueue_scripts
198 | */
199 | static function admin_enqueue() {
200 | wp_enqueue_media();
201 | wp_enqueue_style( 'wp-forms', plugins_url( 'wp-forms-api.css', __FILE__ ) );
202 | }
203 |
204 | /**
205 | * Return HTML with tag $tagname and keyed attrs $attrs.
206 | *
207 | * If $content is not null, contain with $tagname and
208 | * render close tag.
209 | *
210 | * If $content === false, just emit an open tag.
211 | */
212 | static function make_tag( $tagname, $attrs, $content = null ) {
213 | if( empty( $tagname ) ) {
214 | return;
215 | }
216 |
217 | $html = '<' . $tagname;
218 |
219 | foreach( $attrs as $attr => $val ) {
220 | $html .= ' ' . $attr . '="' . esc_attr( $val ) . '"';
221 | }
222 |
223 | // Self-closing tag:
224 | if( !isset( $content ) ) {
225 | $html .= ' />';
226 | }
227 | else {
228 | $html .= '>';
229 |
230 | if( $content !== false ) {
231 | $html .= $content . '' . $tagname . '>';
232 | }
233 | }
234 |
235 | return $html;
236 | }
237 |
238 | /**
239 | * Get elements from a form.
240 | *
241 | * @param array $form
242 | *
243 | * Filters out elements with keys starting with '#', and sets default
244 | * properties for each element so they can be safely assumed to be present.
245 | */
246 | static function get_elements( $form ) {
247 | $elements = array();
248 |
249 | foreach( $form as $key => &$element ) {
250 | if( $key[0] == '#' ) {
251 | continue;
252 | }
253 |
254 | $element += self::$element_defaults;
255 |
256 | if( !is_array( $element['#class'] ) ) {
257 | $element['#class'] = array( $element['#class'] );
258 | }
259 |
260 | // Default some properties to $key
261 | foreach( array( '#key', '#slug', '#name' ) as $field ) {
262 | if( empty( $element[$field] ) ) {
263 | $element[$field] = $key;
264 | }
265 | }
266 |
267 | $elements[$key] = &$element;
268 | }
269 |
270 | return $elements;
271 | }
272 |
273 | /**
274 | * Is the element a button?
275 | *
276 | * @return bool
277 | */
278 | static function is_button( $element ) {
279 | return preg_match( '/^button|submit$/', $element['#type'] );
280 | }
281 |
282 | /**
283 | * Render forms.
284 | *
285 | * @param array $form. any value with a key not
286 | * starting with '#' is considered an element.
287 | *
288 | * Special keys, all optional:
289 | *
290 | * #key
291 | * The key for this form. Optional.
292 | *
293 | * #id
294 | * The ID for this form. Optional.
295 | *
296 | * #attrs
297 | * Form container tag attributes.
298 | *
299 | * Elements are also forms, but a form is not necessarily an element.
300 | * If a member value has a '#type' key, then it is considered an element.
301 | *
302 | * There is no strict typing of these object, merely duck-typing. If it doesn't have
303 | * a '#key', it can be considered a form, if it does, it is a renderable element that
304 | * is associated with a value in $values.
305 | *
306 | * Elements are rendered separately, in render_element(). The form structure is walked
307 | * through using the render_form() method.
308 | *
309 | * @param array $values. The values of the form, where each key is the '#key' of the element.
310 | * @return string of HTML representing the form
311 | *
312 | * Special rules may apply, see below.
313 | */
314 | static function render_form( $form, &$values ) {
315 | if( !isset( $form['#form'] ) ) {
316 | $form['#form'] = $form;
317 | }
318 |
319 | $form += self::$element_defaults;
320 |
321 | $form['#class'][] = 'wp-form';
322 |
323 | if( $form['#id'] ) {
324 | $form['#attrs']['id'] = $form['#id'];
325 | $form['#class'][] = 'wp-form-' . $form['#id'];
326 | }
327 |
328 | $form = apply_filters( 'wp_form', $form );
329 |
330 | $elements = self::get_elements( $form );
331 |
332 | // No elements = no form
333 | if( empty( $elements ) ) {
334 | return;
335 | }
336 |
337 | $form['#attrs']['class'] = join( ' ', $form['#class'] );
338 |
339 | $markup = '';
340 |
341 | $value_root = &$values;
342 |
343 | if( $form['#type'] == 'composite' && $form['#key'] ) {
344 | $value_root = &$values[$form['#key']];
345 | }
346 |
347 | foreach( $elements as $key => $element ) {
348 | $element['#form'] = $form;
349 |
350 | // Add index when applicable
351 | if( isset( $form['#index'] ) && $form['#name'] ) {
352 | $element['#name'] = $form['#name'] . '[' . $form['#index'] . '][' . $key . ']';
353 | $element['#slug'] = $form['#slug'] . '-' . $form['#index'] . '-' . $key;
354 | }
355 | else {
356 | if( $form['#slug'] ) {
357 | $element['#slug'] = $form['#slug'] . '-' . $element['#slug'];
358 | }
359 | }
360 |
361 | if( $form['#type'] == 'composite' && $form['#name'] ) {
362 | $element['#name'] = $form['#name'] . '[' . $element['#key'] . ']';
363 | }
364 |
365 | $markup .= self::render_element( $element, $value_root );
366 | }
367 |
368 | wp_enqueue_script( 'wp-forms' );
369 | wp_enqueue_style( 'wp-forms' );
370 |
371 | return self::make_tag( $form['#container'], $form['#attrs'], $markup );
372 | }
373 |
374 | /**
375 | * Render an element
376 | *
377 | * @param array $element
378 | *
379 | * The element to render. Any keys starting with '#' are considered special,
380 | * any other keys are considered sub-elements
381 | *
382 | * Meaningful keys:
383 | *
384 | * #type - When present, this element contains an input.
385 | * 'text' – Plan text
386 | * 'select' - A select box. Requires #options
387 | * 'checkbox' - A boolean
388 | * 'textarea' - A textarea
389 | * 'composite' - A composite value which is posted as an array in #key
390 | * 'image' - An image selection field
391 | * 'attachment' - An attachment selection field
392 | * 'radio' - A radio button
393 | * 'multiple' - A zero-to-infinity multiple value defined in #multiple key
394 | * 'markup' - Literal markup. Specify markup value in #markup key.
395 | * 'post_select' - A post selection field. Can specify types in #post_type key.
396 | * 'term_select' - A taxonomy term selection field. Can specify types in #taxonomy key.
397 | *
398 | * #key
399 | * The key (form name) of this element. This is the only absolutely required
400 | * key in the element, but is set as part of get_elements().
401 | *
402 | * #placeholder
403 | * Placeholder for elements that support it
404 | *
405 | * #options
406 | * Array of options for select boxes, given by value => label
407 | *
408 | * #slug
409 | * The machine-readable slug for this element. This is used to compose
410 | * machine-readable ids and class names.
411 | *
412 | * #label
413 | * Displayed label for this element
414 | *
415 | * #required
416 | * TODO: Does nothing right now. Will hide non-default options in select
417 | * boxes
418 | *
419 | * #multiple
420 | * If defined, a form structure that becomes part of a collection with CRUD.
421 | * instances of the child can be created and updated, and is stored as an
422 | * array rather than a dictionary in $values.
423 | *
424 | * #add_link
425 | * Link text to show to add an item to this multiple list
426 | *
427 | * #remove_link
428 | * Link text to show to remove an item to this multiple list
429 | *
430 | * #markup
431 | * Literal markup to use. Only applies to '#type' = 'markup'
432 | *
433 | * @param array $values
434 | *
435 | * The array of values to use to populate input elements.
436 | *
437 | * @param array $form
438 | *
439 | * The top-level form for this element.
440 | */
441 | static function render_element( $element, &$values, $form = null ) {
442 | if( !isset( $form ) ) {
443 | $form = $element;
444 | }
445 |
446 | if( !isset( $element['#form'] ) ) {
447 | $element['#form'] = &$form;
448 | }
449 |
450 | // All elements require a key, always.
451 | if( !is_scalar( $element['#key'] ) ) {
452 | throw new Exception( "Form UI error: Every element must have a #key" );
453 | }
454 |
455 | // Allow for pre-processing of this element
456 | $element = apply_filters( 'wp_form_prepare_element', $element );
457 | $element = apply_filters( 'wp_form_prepare_element_key_' . $element['#key'], $element );
458 |
459 | // Ignore inputted values for buttons. Just use their #value for display.
460 | if( !self::is_button( $element ) && !isset( $element['#value'] ) ) {
461 | if( isset( $values[$element['#key']] ) ) {
462 | $element['#value'] = $values[$element['#key']];
463 | }
464 | else if( isset( $element['#default'] ) ) {
465 | $element['#value'] = $element['#default'];
466 | }
467 | }
468 |
469 | $input_id = $element['#id'] ? $element['#id'] : 'wp-form-' . $element['#slug'];
470 |
471 | $element['#container_classes'][] = 'wp-form-key-' . $element['#key'];
472 | $element['#container_classes'][] = 'wp-form-slug-' . $element['#slug'];
473 |
474 | if( $element['#type'] ) {
475 | $attrs = &$element['#attrs'];
476 | $attrs['id'] = $input_id;
477 | $attrs['name'] = $element['#name'];
478 | $attrs['type'] = $element['#type'];
479 |
480 | $element['#tag'] = 'input';
481 | $element['#container_classes'][] = 'wp-form-element';
482 | $element['#container_classes'][] = 'wp-form-type-' . $element['#type'];
483 |
484 | $element['#class'][] = 'wp-form-input';
485 |
486 | if( $element['#type'] !== 'checkbox' && is_scalar( $element['#value'] ) && strlen( $element['#value'] ) > 0 ) {
487 | $attrs['value'] = $element['#value'];
488 | }
489 |
490 | if( $element['#placeholder'] ) {
491 | $attrs['placeholder'] = $element['#placeholder'];
492 | }
493 |
494 | if( $element['#size'] ) {
495 | $attrs['size'] = $element['#size'];
496 | }
497 |
498 | // Backwards-compatible logic for conditional elements
499 | if( isset( $element['#conditional']['element'], $element['#conditional']['action'], $element['#conditional']['value'] ) ) {
500 | $element['#conditional'] = array(
501 | // [ element value => [] ]
502 | $element['#conditional']['value'] => array(
503 | // [ target selector => class to apply ]
504 | $element['#conditional']['element'] => 'wp-form-conditional-' . $element['#conditional']['action'],
505 | ),
506 | );
507 | }
508 | // Conditional actions
509 | if ( !empty( $element['#conditional'] ) ) {
510 | $attrs['data-conditional'] = json_encode( $element['#conditional'] );
511 | }
512 |
513 | // Adjust form element attributes based on input type
514 | switch( $element['#type'] ) {
515 | case 'button':
516 | $element['#tag'] = 'button';
517 | break;
518 |
519 | case 'checkbox':
520 | // value attribute is arbitrary, we will only be looking for presence of the key
521 | // the #checked value will be used for the actual field value to save
522 | $attrs += array( 'value' => '1' );
523 | $element['#content'] = null;
524 | $element['#label_position'] = 'after';
525 |
526 | if ( $element['#value'] === $element['#checked'] ) {
527 | $attrs['checked'] = 'checked';
528 | }
529 |
530 | break;
531 |
532 | case 'radio':
533 | if( !$element['#options'] ) {
534 | $element['#options'] = array( 'No', 'Yes' );
535 | }
536 |
537 | $element['#tag'] = 'div';
538 | $element['#class'][] = 'wp-form-radio-group';
539 | $element['#content'] = '';
540 | $element['#label_position'] = 'after';
541 |
542 | foreach( $element['#options'] as $value => $label ) {
543 | $radio_attrs = array(
544 | 'type' => 'radio',
545 | 'name' => $element['#name'],
546 | 'value' => $value
547 | );
548 |
549 | if( $value === $element['#value'] || $value === $element['#default'] ) {
550 | $radio_attrs['checked'] = 'checked';
551 | }
552 |
553 | $element['#content'] .= self::make_tag( 'label', array(
554 | 'for' => $element['#slug'] . '-' . $value,
555 | ), self::make_tag( 'input', $radio_attrs, $label ) );
556 | }
557 |
558 | break;
559 |
560 | case 'textarea':
561 | $element['#tag'] = 'textarea';
562 | $element['#content'] = esc_textarea( $element['#value'] );
563 | unset( $attrs['value'] );
564 | unset( $attrs['type'] );
565 |
566 | break;
567 |
568 | case 'multiple':
569 | $element['#tag'] = 'div';
570 | $element['#content'] = self::render_multiple_element( $element, $values[$element['#key']] );
571 | unset( $attrs['value'] );
572 | unset( $attrs['type'] );
573 | unset( $attrs['name'] );
574 | break;
575 |
576 | case 'composite':
577 | unset( $attrs['value'] );
578 | unset( $attrs['name'] );
579 | unset( $attrs['type'] );
580 | $element['#content'] = null;
581 | $element['#tag'] = '';
582 | break;
583 |
584 | case 'select':
585 | $element['#tag'] = 'select';
586 | unset( $attrs['value'] );
587 | unset( $attrs['type'] );
588 |
589 | $options = array();
590 |
591 | if( $element['#multiple'] ) {
592 | $attrs['multiple'] = 'multiple';
593 | $attrs['name'] .= '[]';
594 | $element['#value'] = array_map( 'strval', (array) $element['#value'] );
595 | }
596 |
597 | if( !$element['#required'] ) {
598 | $options[''] = isset( $element['#placeholder'] ) ? $element['#placeholder'] : "- select -";
599 | }
600 |
601 | $options = $options + $element['#options'];
602 |
603 | $element['#content'] = self::render_options( $options, $element );
604 |
605 | break;
606 |
607 | case 'attachment':
608 | case 'image':
609 | // Fancy JavaScript UI will take care of this field. Degrades to a simple
610 | // ID field
611 | wp_enqueue_media();
612 |
613 | $element['#class'][] = 'select-attachment-field';
614 |
615 | if( $element['#type'] == 'image' ) {
616 | $element['#class'][] = 'select-image-field';
617 | }
618 |
619 | $attrs['type'] = 'text';
620 | $attrs['data-attachment-type'] = $element['#type'];
621 |
622 | break;
623 |
624 | case 'mce':
625 | if( !user_can_richedit() ) {
626 | // User doesn't have capabilities to richedit - just display
627 | // a regular textarea with html tags allowed
628 | $element['#type'] = 'textarea';
629 | $element['#allow_html'] = true;
630 |
631 | if( !isset( $attrs['rows'] ) ) {
632 | $attrs['rows'] = 10;
633 | }
634 |
635 | return self::render_element( $element, $values, $form );
636 | }
637 |
638 | $element['#tag'] = 'div';
639 | $element['#class'][] = 'wp-forms-mce-area';
640 | $element['#id'] = 'wp-form-mce-' . $element['#slug'];
641 | unset( $attrs['value'] );
642 |
643 | ob_start();
644 | wp_editor( $element['#value'], $element['#id'], array( 'textarea_name' => $element['#name'] ) );
645 | $element['#content'] = ob_get_clean();
646 |
647 | break;
648 |
649 | case 'post_select':
650 | $element['#class'][] = 'wp-form-post-select';
651 | $attrs['type'] = 'hidden';
652 |
653 | if( isset( $element['#post_type'] ) ) {
654 | $element['#post_type'] = (array) $element['#post_type'];
655 | }
656 |
657 | $attrs['data-post-type'] = implode( ' ', $element['#post_type'] );
658 |
659 | if( $element['#value'] ) {
660 | $post = get_post( $element['#value'] );
661 |
662 | if( $post ) {
663 | $attrs['data-title'] = $post->post_title;
664 | }
665 | }
666 |
667 | break;
668 |
669 | case 'term_select':
670 | $element['#class'][] = 'wp-form-term-select';
671 | $attrs['type'] = 'hidden';
672 |
673 | $attrs['data-taxonomy'] = $element['#taxonomy'];
674 |
675 | if( $element['#value'] ) {
676 | $term = get_term( (int) $element['#value'], $element['#taxonomy'] );
677 |
678 | if( $term && !is_wp_error( $term ) ) {
679 | $attrs['data-name'] = $term->name;
680 | }
681 | }
682 |
683 | break;
684 |
685 | default:
686 | $element['#content'] = null;
687 | break;
688 | }
689 | }
690 |
691 | $element = apply_filters( 'wp_form_element', $element );
692 | $element = apply_filters( 'wp_form_element_key_' . $element['#key'], $element );
693 |
694 | $markup = '';
695 |
696 | $label = '';
697 | if( isset( $element['#label'] ) ) {
698 | $label = self::make_tag( 'label', array(
699 | 'class' => 'wp-form-label',
700 | 'for' => $input_id,
701 | ), esc_html( $element['#label'] ) );
702 | }
703 |
704 | $attrs['class'] = join( ' ', $element['#class'] );
705 |
706 | // Markup types just get a literal markup block
707 | if( $element['#type'] == 'markup' ) {
708 | $markup .= $element['#markup'];
709 | }
710 | // Tagname may have been unset (such as in a composite value)
711 | else if( $element['#tag'] ) {
712 | $markup .= $element['#label_position'] == 'before' ? $label : '';
713 | $markup .= self::make_tag( $element['#tag'], $element['#attrs'], $element['#content'] );
714 | $markup .= $element['#label_position'] == 'after' ? $label : '';
715 | }
716 |
717 | if( $element['#description'] ) {
718 | $markup .= self::make_tag( 'p', array( 'class' => 'description' ), $element['#description'] );
719 | }
720 |
721 | $markup .= self::render_form( $element, $values );
722 |
723 | return self::make_tag( $element['#container'], array( 'class' => join( ' ', $element['#container_classes'] ) ), $markup );
724 | }
725 |
726 | /**
727 | * Recursively render the options and any contained for a select menu
728 | */
729 | static function render_options( $options, &$element ) {
730 | $markup = '';
731 |
732 | foreach( $options as $value => $label ) {
733 | // ignore the value and use the label instead?
734 | // but not for a value of empty string, used for the placeholder option
735 | if ( $element['#labels_as_values'] && !is_array( $label ) && $value !== '' ) {
736 | $value = $label;
737 | }
738 | $option_atts = array( 'value' => $value );
739 |
740 | if( isset( $element['#value'] ) &&
741 | ( ( $element['#multiple'] && in_array( (string) $value, $element['#value'] ) ) ||
742 | (string) $value === (string) $element['#value'] ) ) {
743 | $option_atts['selected'] = "selected";
744 | }
745 |
746 | // Allow for nesting one-level deeper by using a key which becomes
747 | // the label for an and an array value representing
748 | // the options within that optgroup. A downside to this data format
749 | // is that values and optgroup labels share the same namespace and thus
750 | // a value share the same label of an optgroup.
751 | //
752 | // This isn't exactly correct, but it works for most cases. Can we make it better?
753 | if( is_array( $label ) ) {
754 | $markup .= self::make_tag( 'optgroup', array( 'label' => $value ), self::render_options( $label, $element ) );
755 | }
756 | else {
757 | $markup .= self::make_tag( 'option', $option_atts, esc_html( $label ) );
758 | }
759 | }
760 |
761 | return $markup;
762 | }
763 |
764 | /**
765 | * Render a multi-element, one that can receive CRUD operations
766 | */
767 | static function render_multiple_element( $element, &$values ) {
768 | if( !isset( $element['#multiple'] ) ) {
769 | return;
770 | }
771 |
772 | $multiple = $element['#multiple'];
773 |
774 | $multiple += array(
775 | '#key' => $element['#key'],
776 | '#slug' => $element['#slug'],
777 | '#name' => $element['#name'],
778 | '#type' => ''
779 | );
780 |
781 | $container_atts = array(
782 | 'class' => 'wp-form-multiple wp-form-multiple-' . $element['#key'],
783 | );
784 |
785 | // Placeholders filled in by JavaScript
786 | $multiple['#index'] = '%INDEX%';
787 | $multiple['#slug'] = $element['#slug'] . '-%INDEX%';
788 |
789 | $item_classes = array( 'wp-form-multiple-item' );
790 | $blank_values = array_fill_keys( array_keys( $element ), '' );
791 |
792 | if( !is_array( $values ) || empty( $values ) ) {
793 | $values = array();
794 | }
795 |
796 | $multiple_ui =
797 | self::make_tag( 'span', array( 'class' => 'dashicons dashicons-dismiss remove-multiple-item' ), '' ) .
798 | self::make_tag( 'span', array( 'class' => 'dashicons dashicons-sort sort-multiple-item' ), '' );
799 |
800 | // First, render a JavaScript template which can be filled out.
801 | // JavaScript replaces %INDEX% with the actual index. Indexes are used
802 | // to ensure the correct order and grouping when the values come back out in PHP
803 | $template = self::make_tag( 'li', array( 'class' => implode( ' ', $item_classes ) ),
804 | $multiple_ui .
805 | self::render_form( $multiple, $blank_values ) );
806 |
807 | $markup = self::make_tag( 'script', array( 'type' => 'text/html', 'class' => 'wp-form-multiple-template' ), $template );
808 |
809 | $list_items = '';
810 |
811 | // Now render each item with a remove link and a particular index
812 | foreach( $values as $index => $value ) {
813 | // Throw out non-integer indices
814 | if( !is_int( $index ) ) {
815 | continue;
816 | }
817 |
818 | $multiple['#index'] = $index;
819 | $multiple['#slug'] = $element['#slug'];
820 |
821 | $list_items .= self::make_tag( 'li', array( 'class' => implode( ' ', $item_classes ) ),
822 | $multiple_ui .
823 | self::render_form( $multiple, $value ) );
824 | }
825 |
826 | $markup .= self::make_tag( 'ol', array( 'class' => 'wp-form-multiple-list' ), $list_items );
827 |
828 | // Render the "add" link
829 | $markup .= self::make_tag( 'a', array( 'class' => 'add-multiple-item' ), $element['#add_link'] );
830 |
831 | return self::make_tag( 'div', $container_atts, $markup );
832 | }
833 |
834 | /**
835 | * Process a form, filling in $values with what's been posted
836 | */
837 | static function process_form( $form, &$values, &$input = null ) {
838 | $form += self::$element_defaults;
839 |
840 | if( !isset( $form['#form'] ) ) {
841 | $form['#form'] = $form;
842 | $form['#values'] = &$values;
843 | $form['#input'] = &$input;
844 | }
845 |
846 | if( !isset( $input ) ) {
847 | $input = &$_POST;
848 | }
849 |
850 | // avoid double slashing
851 | $input = stripslashes_deep( $input );
852 |
853 | $form = apply_filters_ref_array( 'wp_form_process', array( &$form, &$values, &$input ) );
854 |
855 | foreach( self::get_elements( $form ) as $key => $element ) {
856 | $element['#form'] = $form;
857 |
858 | self::process_element( $element, $values, $input );
859 | }
860 | }
861 |
862 | /**
863 | * Recursively process a meta form element, filling in $values accordingly
864 | *
865 | * @param array $element - The element to process.
866 | *
867 | * @param array &$values - Processed values are written to this array with
868 | * for any element in the form with a '#key' and a '#type'.
869 | */
870 | static function process_element( $element, &$values, &$input ) {
871 | $values_root = &$values;
872 | $input_root = &$input;
873 |
874 | // Process button value by simple presence of #key
875 | if( self::is_button( $element ) ) {
876 | $element['#value'] = isset( $input[$element['#key']] ) && $input[$element['#key']];
877 | }
878 | // Process checkbox by presence of #key, using the #checked value
879 | // If not key is not set or is empty, set value to false
880 | else if ( $element['#type'] === 'checkbox' ) {
881 | if ( !empty( $input[$element['#key']] ) ) {
882 | $element['#value'] = $element['#checked'];
883 | } else {
884 | $element['#value'] = false;
885 | }
886 | }
887 | // Munge composite elements
888 | else if( $element['#type'] == 'composite' ) {
889 | $values_root = &$values[$element['#key']];
890 | $input_root = &$input[$element['#key']];
891 | }
892 | // Munge multi-select elements
893 | else if( $element['#type'] == 'select' && $element['#multiple'] ) {
894 | $element['#value'] = isset( $input[$element['#key']] ) ? (array) $input[$element['#key']] : array();
895 | }
896 | // Munge multiple elements
897 | else if( $element['#type'] == 'multiple' ) {
898 | $values[$element['#key']] = array();
899 |
900 | if( isset( $input[$element['#key']] ) && is_array( $input[$element['#key']] ) ) {
901 | foreach( $input[$element['#key']] as $item ) {
902 | self::process_form( $element['#multiple'], $value, $item );
903 | $values[$element['#key']][] = $value;
904 |
905 | // Unset $value so it does not keep data from a previous iteration
906 | // this caused all unchecked checkboxes following a checked input
907 | // in a multiple field to store as checked on save.
908 | unset( $value );
909 | }
910 | }
911 | }
912 | // Or just pull the value from the input
913 | else if( isset( $input[$element['#key']] ) ) {
914 | $element['#value'] = $input[$element['#key']];
915 |
916 | // Sanitization of fields that allow html
917 | if( ( isset( $element['#allow_html'] ) && $element['#allow_html'] ) || $element['#type'] == 'mce' ) {
918 | $element['#value'] = wp_kses_post( $element['#value'] );
919 | }
920 | // Simple sanitization of most values
921 | else if( isset( $element['#type'] ) && $element['#type'] != 'composite' ) {
922 | $element['#value'] = sanitize_text_field( $element['#value'] );
923 | }
924 | }
925 |
926 | // If there's a value, use it. May have been fed in as part of the form
927 | // structure
928 | if( isset( $element['#value'] ) ) {
929 | $values[$element['#key']] = $element['#value'];
930 | }
931 |
932 | $element = apply_filters_ref_array( 'wp_form_process_element', array( &$element, &$values, &$input ) );
933 |
934 | self::process_form( $element, $values_root, $input_root );
935 | }
936 |
937 | /**
938 | * Templates used in this module
939 | */
940 | static function media_templates() { ?>
941 |
962 | .wp-form {
10 | margin: 0;
11 | }
12 |
13 | .wp-form-label {
14 | margin-right: 1.5em;
15 | }
16 |
17 | .wp-form-element {
18 | clear: both;
19 | }
20 |
21 | .wp-form-input {
22 | width: auto;
23 | }
24 |
25 | .wp-form-element {
26 | margin: 0 1em 1em 0;
27 | display: inline-block;
28 | }
29 |
30 | .wp-form-type-mce {
31 | display: block;
32 | }
33 |
34 | .wp-form-type-post_select,
35 | .wp-form-type-term_select,
36 | .wp-form-type-image {
37 | display: block;
38 | }
39 | .wp-form-type-post_select .wp-form-label,
40 | .wp-form-type-term_select .wp-form-label,
41 | .wp-form-type-image .wp-form-label {
42 | display: block;
43 | }
44 |
45 | .wp-form-type-text .wp-form-input,
46 | .wp-form-type-url .wp-form-input,
47 | .wp-form-type-textarea .wp-form-input {
48 | width: 95%;
49 | }
50 |
51 | .wp-form-key-citystatezip .wp-form-element,
52 | .wp-form-key-coordinates .wp-form-element {
53 | display: inline-block;
54 | }
55 |
56 | .wp-form-type-url,
57 | .wp-form-type-text,
58 | .wp-form-type-textarea,
59 | .wp-form-type-multiple,
60 | .wp-form-type-composite {
61 | display: block;
62 | }
63 |
64 | .wp-form-type-composite > .wp-form-label {
65 | display: block;
66 | }
67 |
68 | .wp-form-type-date {
69 | display: inline-block;
70 | }
71 |
72 | .wp-form-form {
73 | margin: .25em 0 1em;
74 | }
75 |
76 | .wp-form-type-multiple ol {
77 | margin: 0;
78 | padding: 0;
79 | list-style-position: inside;
80 | }
81 | .wp-form-type-multiple ol li {
82 | background-color: #fff;
83 | }
84 | .wp-form-type-multiple .wp-form-multiple-item {
85 | border-bottom: 1px solid #d0d0d0;
86 | padding: 5px;
87 | }
88 | .wp-form-type-multiple .add-multiple-item,
89 | .wp-form-type-multiple .remove-multiple-item {
90 | cursor: pointer;
91 | font-weight: bold;
92 | }
93 | .wp-form-type-multiple .remove-multiple-item {
94 | float: right;
95 | }
96 | .wp-form-type-multiple .add-multiple-item:before {
97 | display: inline;
98 | content: "+ ";
99 | }
100 |
101 | .wp-form-radio-group label {
102 | margin: .5em 1em;
103 | }
104 |
105 | /**
106 | * Styles for conditional show/hide
107 | */
108 | .wp-form-conditional-show {
109 | display: block !important;
110 | }
111 | input.wp-form-conditional-show,
112 | select.wp-form-conditional-show,
113 | textarea.wp-form-conditional-show,
114 | img.wp-form-conditional-show {
115 | display: inline-block !important;
116 | }
117 |
118 | .wp-form-conditional-hide {
119 | display: none !important;
120 | }
121 |
122 | /**
123 | * Attachment selection field, maybe image
124 | */
125 | .wp-form-type-attachment,
126 | .wp-form-type-image {
127 | display: block;
128 | }
129 | .wp-form-type-attachment .select-attachment-field,
130 | .wp-form-type-image .select-attachment-field {
131 | position: relative;
132 | float: left;
133 | width: 100%;
134 | }
135 | .wp-form-type-attachment .select-attachment-field .attachment-container,
136 | .wp-form-type-image .select-attachment-field .attachment-container {
137 | float: left;
138 | background-color: #fff;
139 | box-sizing: border-box;
140 | padding: 5px;
141 | margin-right: 1em;
142 | width: 150px;
143 | height: 150px;
144 | cursor: pointer;
145 | border: 1px solid #d8d8d8;
146 | border-radius: 5px;
147 | }
148 | .wp-form-type-attachment .select-attachment-field .attachment-container img,
149 | .wp-form-type-image .select-attachment-field .attachment-container img {
150 | max-width: 100%;
151 | max-height: 100%;
152 | width: auto;
153 | height: auto;
154 | position: relative;
155 | left: 50%;
156 | top: 50%;
157 | transform: translate(-50%, -50%);
158 | }
159 | .wp-form-type-attachment .select-attachment-field label,
160 | .wp-form-type-image .select-attachment-field label {
161 | display: block;
162 | }
163 | .wp-form-type-attachment .select-attachment-field label span,
164 | .wp-form-type-image .select-attachment-field label span {
165 | display: block;
166 | }
167 | .wp-form-type-attachment .select-attachment-field .ui-dirty,
168 | .wp-form-type-image .select-attachment-field .ui-dirty {
169 | background-color: #f8e0e0;
170 | }
171 | .wp-form-type-attachment .select-attachment-field .attachment-delete,
172 | .wp-form-type-image .select-attachment-field .attachment-delete {
173 | display: block;
174 | width: 1em;
175 | height: 1em;
176 | position: absolute;
177 | top: .25em;
178 | left: .25em;
179 | font-family: "dashicons";
180 | color: #000;
181 | font-size: 1.5em;
182 | background: rgba(250, 250, 250, 0.6);
183 | border: 1px solid #ddd;
184 | border-radius: 5px;
185 | cursor: pointer;
186 | }
187 | .wp-form-type-attachment .select-attachment-field .attachment-delete::before,
188 | .wp-form-type-image .select-attachment-field .attachment-delete::before {
189 | display: inline;
190 | content: "\f158";
191 | }
192 |
--------------------------------------------------------------------------------
/inc/wp-forms-api.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Deal with various features of the fancy "Forms UI" type implementaion
3 | */
4 | (function($) {
5 | var fapi = window.wpFormsApi = window.wpFormsApi || {}
6 | , media = wp.media;
7 |
8 | // Adjust styles to account for variations when using box-sizing
9 | $.widget('custom.postListMenu', $.ui.autocomplete, {
10 | _resizeMenu: function() {
11 | this.menu.element.width($(this.element).outerWidth());
12 | $(this.menu.element).css('z-index', 10000000);
13 | },
14 | });
15 |
16 | // Multiple-list field
17 | var initializeMultiple = function(context) {
18 | $(context).find('.wp-form-multiple').each(function() {
19 | var $container = $(this)
20 | , $list = $container.find('.wp-form-multiple-list')
21 | , $tmpl = $container.find('.wp-form-multiple-template')
22 | ;
23 | // do not re-initialize
24 | if ($container.data('initialized')) {
25 | return;
26 | }
27 |
28 | function reindex() {
29 | // Note which elements are checked to prevent radio buttons from losing
30 | // their checked state when elements are renamed (and there is a brief
31 | // name collision!)
32 | var $checked = $list.find(':checked');
33 |
34 | $list.find('> li').each(function(i) {
35 | var $item = $(this);
36 |
37 | $item.find('*').each(function() {
38 | // Replace patterns like -2- or [4] with -$index- and [$index] in attributes
39 | // Not exactly super safe, but easy
40 | for(var j = 0; j < this.attributes.length; j++) {
41 | this.attributes[j].value = this.attributes[j].value.replace(
42 | /([ \w_\[\]-]+[\[-])\d+([\]-][ \w_\[\]-]+)/g, '$1' + i + '$2');
43 | }
44 | });
45 | });
46 |
47 | $checked.prop('checked', true);
48 | }
49 |
50 | $list.sortable({
51 | handle: '.sort-multiple-item',
52 | update: reindex
53 | });
54 |
55 | $container
56 | // Add a new multiple item on click
57 | .on('click', '.add-multiple-item', function() {
58 | var $t = $(this),
59 | count = $list.children('li').length,
60 | $html = $($tmpl.text().replace(/%INDEX%/g, count));
61 |
62 | initialize($html);
63 |
64 | $list.append($html);
65 | })
66 |
67 | // Remove an item item on click
68 | .on('click', '.remove-multiple-item', function() {
69 | var $t = $(this),
70 | $item = $t.parents('li');
71 |
72 | $item.remove();
73 |
74 | reindex();
75 | });
76 |
77 | // prevent double init which would register multiple click handlers
78 | $container.data('initialized',true);
79 | });
80 | }
81 |
82 | // Image field
83 | if (media && typeof media == 'function') {
84 | var WPFormImageField = media.View.extend({
85 | template: media.template('wp-form-attachment-field'),
86 | events: {
87 | 'change .wp-form-attachment-id': 'update',
88 | 'click .attachment-delete': 'removeAttachment',
89 | 'click .attachment-container': 'selectAttachment'
90 | },
91 |
92 | selectAttachment: function() {
93 | var view = this,
94 | frameOpts = {
95 | frame: 'select',
96 | title: this.input_type == 'image' ? "Select Image" : "Select Attachment"
97 | };
98 |
99 | if(this.input_type == 'image') {
100 | frameOpts.library = { type: 'image' };
101 | }
102 |
103 | media.frame = media(frameOpts).open();
104 |
105 | media.frame.on('select', function(el) {
106 | var image = this.get('library').get('selection').single();
107 |
108 | view.model.set(image.attributes);
109 | });
110 | },
111 |
112 | removeAttachment: function() {
113 | // Don't clear the model entirely, just the set values
114 | this.model.set({
115 | id: null,
116 | title: null,
117 | link: null,
118 | url: null,
119 | editLink: null
120 | });
121 | },
122 |
123 | initialize: function() {
124 | if(!this.model) {
125 | this.model = new Backbone.Model();
126 | }
127 |
128 | this.model.on('change', this.render, this);
129 | },
130 |
131 | prepare: function() {
132 | return this.model.toJSON();
133 | },
134 |
135 | update: function() {
136 | var view = this,
137 | $field = this.$el.find('.wp-form-attachment-id'),
138 | attachmentId = $field.val(),
139 | attachment = media.model.Attachment.get(attachmentId).clone(),
140 | inputName = view.model.get('input_name'),
141 | inputType = view.model.get('input_type');
142 |
143 | view.model.clear({ silent: true });
144 | view.model.set({
145 | id: attachmentId,
146 | input_name: inputName,
147 | input_type: inputType
148 | });
149 |
150 | $field.addClass('ui-dirty');
151 |
152 | attachment.fetch()
153 | .done(function() {
154 | $field.removeClass('ui-dirty');
155 | view.model.set(this.attributes);
156 | })
157 | .fail(function() {
158 | $field.addClass('ui-dirty');
159 | });
160 | }
161 | });
162 |
163 | var initializeAttachments = function(context) {
164 | $(context).find('.select-attachment-field').each(function() {
165 | var view = new WPFormImageField({
166 | model: media.model.Attachment.get(this.value).clone()
167 | });
168 |
169 | view.model.fetch();
170 |
171 | view.model.set('input_name', this.name || $(this).find('input').attr('name'));
172 | view.model.set('input_type', $(this).data('attachment-type'));
173 |
174 | view.render();
175 |
176 | view.$el.attr('class', $(this).attr('class'));
177 | view.$el.data('view', view);
178 |
179 | $(this).replaceWith(view.$el);
180 | });
181 | }
182 | }
183 |
184 | var initializePostSelect = function(context, args) {
185 | args = args || {};
186 |
187 | $(context).find('.wp-form-post-select').each(function() {
188 | var items = new Backbone.Collection(),
189 | $input = $(this),
190 | $field = $input.prev('input'),
191 | model = new Backbone.Model({ id: $input.val() });
192 |
193 | if($field.length == 0) {
194 | $field = $(' ');
195 | }
196 |
197 | $(this).before($field);
198 |
199 | if($input.data('title')) {
200 | $field.val($input.data('title'));
201 | model.set('title', $input.data('title'));
202 | }
203 |
204 | $field.attr('placeholder', $input.attr('placeholder'));
205 |
206 | var update = function(ev, ui) {
207 | var id = ui.item && ui.item.model.get('id');
208 |
209 | $input.val(id);
210 | $input.trigger('selected', ui.item);
211 | };
212 |
213 | // Extend jQuery UI autocomplete with a custom resizer
214 | $field.postListMenu(_.extend(args, {
215 | source: function(request, response) {
216 | var attrs = { term: request.term };
217 |
218 | if($input.data('post-type')) {
219 | attrs['post_type'] = $input.data('post-type').split(' ');
220 | }
221 |
222 | wp.ajax.post('wp_form_search_posts', attrs)
223 | .done(function(data) {
224 | response(_.map(data, function(v) {
225 | v.id = v.ID;
226 |
227 | var itemModel = new Backbone.Model(v);
228 |
229 | items.remove(v.id);
230 | items.add(itemModel);
231 |
232 | return {
233 | label: v.post_title,
234 | value: v.post_title,
235 | model: itemModel
236 | }
237 | }));
238 | })
239 | .fail(function(data) {
240 | response([]);
241 | });
242 | },
243 | change: update,
244 | select: update,
245 | minLength: 0
246 | }));
247 |
248 | $input.trigger('selected', { model: model });
249 | });
250 | }
251 |
252 | var initializeTermSelect = function(context) {
253 | $(context).find('.wp-form-term-select').each(function() {
254 | var items = new Backbone.Collection(),
255 | $input = $(this),
256 | $field = $input.prev('input');
257 |
258 | if($field.length == 0) {
259 | $field = $(' ');
260 | }
261 |
262 | $(this).before($field);
263 |
264 | if($input.data('name')) {
265 | $field.val($input.data('name'));
266 | }
267 |
268 | $field.attr('placeholder', $input.attr('placeholder'));
269 | $field.attr('class', $input.attr('class').replace(/\bwp-form-[^\s]*\s*/g, ''));
270 |
271 | var update = function(ev, ui) {
272 | var id = ui.item ? ui.item.model.get('term_id') : '',
273 | label = ui.item ? ui.item.model.get('name') : '';
274 |
275 | $input.val(id);
276 | $input.trigger('selected', ui.item);
277 | };
278 |
279 | $field.autocomplete({
280 | source: function(request, response) {
281 | var attrs = { term: request.term };
282 |
283 | if($input.data('taxonomy')) {
284 | attrs['taxonomy'] = $input.data('taxonomy');
285 | }
286 |
287 | wp.ajax.post('wp_form_search_terms', attrs)
288 | .done(function(data) {
289 | response(_.map(data, function(v) {
290 | v.id = v.ID;
291 |
292 | var itemModel = new Backbone.Model(v);
293 |
294 | items.remove(v.id);
295 | items.add(itemModel);
296 |
297 | return {
298 | label: v.name,
299 | value: v.name,
300 | model: itemModel
301 | }
302 | }));
303 | })
304 | .fail(function(data) {
305 | response([]);
306 | });
307 | },
308 | change: update,
309 | select: update,
310 | minLength: 0
311 | });
312 | });
313 | }
314 |
315 | function initializeConditionalLogic(context) {
316 | $(context).find('[data-conditional]:not(div), div[data-conditional] input[type=radio]').on('change', conditionalLogicInputChange).trigger('change');
317 | }
318 |
319 | function conditionalLogicInputChange() {
320 | var $this = $(this)
321 | , conditions = $this.closest('[data-conditional]').data('conditional')
322 | // For checkboxes, we cannot use .val() because it will always
323 | // return the value attribute regardless if the checkbox is checked
324 | , inputValue = $this.is(':checkbox:not(:checked)') ? false : $this.val()
325 | ;
326 |
327 | // no conditions? bail!
328 | if (!(conditions instanceof Object)) return;
329 |
330 | // for radios, we need to find the currently selected radio of the bunch
331 | if ($this.attr('type') === 'radio') {
332 | inputValue = $(document.getElementsByName($this.attr('name'))).find(':checked').val();
333 | }
334 |
335 | // loop through conditions and apply classes
336 | // { 'element value': { 'target selector': 'class to add', ... }, ... }
337 | for (var value in conditions) {
338 | if (conditions[value] instanceof Object) {
339 | for (var selector in conditions[value]) {
340 | $(selector).toggleClass(conditions[value][selector], value == inputValue);
341 | }
342 | }
343 | }
344 | }
345 |
346 | function initialize(context) {
347 | if (media && typeof media == 'function') {
348 | initializeAttachments(context);
349 | }
350 |
351 | initializePostSelect(context);
352 | initializeTermSelect(context);
353 | initializeConditionalLogic(context);
354 | initializeMultiple(context);
355 | }
356 |
357 | $(function() {
358 | initialize('body');
359 | });
360 |
361 | fapi.setup = initialize;
362 |
363 | if (media && typeof media == 'function') {
364 | fapi.initializeAttachments = initializeAttachments;
365 | }
366 |
367 | fapi.initializePostSelect = initializePostSelect;
368 | fapi.initializeTermSelect = initializeTermSelect;
369 | fapi.initializeConditionalLogic = initializeConditionalLogic;
370 | fapi.initializeMultiple = initializeMultiple;
371 | })(jQuery);
372 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 | ./tests/
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | array( '#type' => 'text' )
8 | ), $values );
9 |
10 | $this->assertContains( 'type="text"', $html );
11 | $this->assertContains( 'name="text-input"', $html );
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/wp-forms-api.php:
--------------------------------------------------------------------------------
1 |