├── .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 [![Build Status](https://travis-ci.org/oomphinc/WP-Forms-API.svg?branch=master)](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
elements: the form is expected to be 85 | * defined at this point. 86 | */ 87 | echo WP_Forms_API::render_form( $form, $values ); 88 | 89 | /** 90 | * Now I want to save the elements from this form. Each element gets an input 91 | * with the same name as its '#key' which defaults to its array key. 92 | */ 93 | add_action( 'save_post', function( $post ) use ( $form ) { 94 | $post = get_post( $post ); 95 | 96 | // Fill in posted values for this form in $values. Every key 97 | // in $values is guaranteed to be defined for every input in defined in $form. 98 | WP_Forms_API::process_form( $form, $values ); 99 | 100 | update_post_meta( $post->ID, 'city', $values['city'] ); 101 | } ) 102 | ``` 103 | 104 | ## Reference 105 | 106 | Forms and elements are represented with simple associative arrays. 107 | 108 | A form is a top-level object, but an element is also a form. 109 | 110 | Special keys start with '#', all other keys are interpreted as elements in themselves. 111 | 112 | Any form that contains a `#type` key is considered to be an input element and will 113 | create a corresponding named form input. 114 | 115 | ### Functions 116 | 117 | This plugin implements a class `WP_Forms_API` which provides the following static methods: 118 | 119 | 120 | * `WP_Forms_API::render_form( $form, &$values )` 121 | 122 | Recursively render a form using values in `$values` and return its markup. This is the primary rendering function you'll need. Applies `wp_form` filter to the form before rendering. 123 | 124 | `$form` - (array) - The form to render. 125 | 126 | `$values` - (array ref) - The values for the elements in this form. While the form structure is nested and heirarchical, the values structure is flat (mostly: see 'composite' values). 127 | 128 | `$top` - (optional array) - The top-level form. 129 | 130 | 131 | * `WP_Forms_API::render_element( $element, &$values )` - Render a single element. 132 | 133 | Renders an element, and any sub-forms, returning the rendered markup. You must specify a scalar `#key` in an element or else an exception will be thrown. 134 | 135 | `$form` - (array) - The form to render. 136 | 137 | `$values` - (array ref) - The values for the elements in this form. While the form structure is nested and heirarchical, the values structure is flat (mostly: see 'composite' values). 138 | 139 | Applies `wp_form_element` filter to element before rendering. 140 | 141 | 142 | * `WP_Forms_API::make_tag( $tagname, $attrs, $content = null )` 143 | 144 | Make and return a single HTML tag. 145 | 146 | `$tagname` - (string) - The name of the HTML tag to emit. If empty, do nothing. 147 | 148 | `$attrs` - (array) - An associative array of HTML attributes for this tag. 149 | 150 | `$content` - (string|false) - The content for this tag, if any. If null, then emit a self-closing tag. If `false`, then the tag will not be closed. 151 | 152 | 153 | * `WP_Forms_API::get_elements( $form )` - Initialize and return all of the elements of a form. 154 | 155 | `$form` - (array) The form to extract elements from. 156 | 157 | Use this function to get initialized sub-elements from a form, skipping all the special keys. 158 | 159 | 160 | * `WP_Forms_API::process_form( $form, &$values, $input = null )` - Process a submitted form and extract submitted values. 161 | 162 | `$form` - (array) - The form to process. 163 | 164 | `$values` - (array reference) - Save result values into this structure. 165 | 166 | `$input` - (array, optional) - The input array. Defaults to `$_POST`. 167 | 168 | 169 | ### Filters 170 | 171 | Forms and form elements are run through the `wp_form` and `wp_form_element` WP filters, respectively, before they are rendered. This gives developers the ability to define special input tags, (by setting in the `#tag` property), as well as updating or hiding elements as they are rendered. 172 | 173 | The `wp_form` filter receives `$form, $top` as arguments, where `$form` is the current form node, and `$top` is the top-level form. You can identify specific forms using the `#id` key, but any arbitrary key starting with `#` can be also be used. 174 | 175 | The `wp_form_element` filter recieves `$element, $form` as 176 | 177 | 178 | ### Forms 179 | 180 | All keys in forms are optional. 181 | 182 | * `#id` (string) 183 | 184 | The reference ID for this form, for filtering output. When defined, elements are run through the filter `wp_form_element_{$form_id}-{$element_key}` before they are rendered, so they can be modified, removed, or otherwise. 185 | 186 | * `#label` (string) 187 | 188 | The label for this form or element. 189 | 190 | * `#class` (array) 191 | 192 | CSS classes in `class` attributes. 193 | 194 | * `#attrs` (array) 195 | 196 | The attributes to use for the rendered container tag. If `class` key is specified, is it prepended by the classes in `#class`. 197 | 198 | * `#form` (array) 199 | 200 | The top-level form. This defaults to the form itself. 201 | 202 | * `#container` (string) 203 | 204 | The tag name for the container of this form or element. Defaults to `div`. 205 | 206 | * `#container_classes` (array) 207 | 208 | An array of CSS classes to add to the container element. 209 | 210 | 211 | ### Elements 212 | 213 | An element is an associative array with at least a `#type` key. An element 214 | can have any of the properties of a form, as well as the following: 215 | 216 | * `#type` (string) 217 | 218 | When present, indicates that the element is an input element. Values: 219 | 220 | * `'text'` – Plain text input. 221 | * `'select'` - A select box. Requires `#options` key. 222 | * `'checkbox'` - A boolean. 223 | * `'textarea'` - A textarea. 224 | * `'multiple'` - A collection of values. 225 | * `'composite'` - A composite value which is posted as an array in #key. 226 | * `'image'` - An image from the media library 227 | * `'post_select'` - A post 228 | 229 | * Any other value will be rendered as a text input. You can use custom types along with the `wp_form_element` filter to define input tag types 230 | 231 | * `#key` (string) 232 | 233 | The key for this element. Is used to create default form element `name` 234 | attributes and slugs for classes. 235 | 236 | * `#placeholder` (string) 237 | 238 | The placeholder to use in the form element. Applies to 'text' and 'textarea' types. 239 | 240 | * `#options` (array) 241 | 242 | The options, as given by 'value' => "Label", for this input element. Only applies to select. 243 | 244 | * `#required` (bool) 245 | 246 | Whether or not the key is required. (TODO: Currently only affects `'select'`-type elements.) 247 | 248 | * `#name` (string, optional) 249 | 250 | The input element name - defaults to `#key`. 251 | 252 | * `#slug` (string, optional) 253 | 254 | The CSS element name - defaults to `#key`. 255 | 256 | * `#size` (int) 257 | 258 | The size of the element. 259 | 260 | * `#multiple` (array) 261 | 262 | Required for `$form['#type'] == 'multiple'`. Define another form whose values are collected into an array for this element. 263 | 264 | * `#add_link` (string) 265 | 266 | When using #multiple, the text to show for the "Add Item" button. 267 | 268 | * `#remove_link` (string) 269 | 270 | When using #multiple, the text to show for the "Remove Item" button. 271 | 272 | * `#post_type` (string) 273 | 274 | For `#type = 'post_select'`, the space-separated list of valid post types to search against. 275 | 276 | * `#conditional` (array) 277 | 278 | Show or hide elements depending on the element's value. Please note that conditional logic only works on input types that trigger a Javascript change event (e.g. select menus, checkboxes, radios, etc) 279 | 280 | * `element` – A jQuery-esk selector for the element that should react to changes to this element's value (e.g. `#element-id`, `.element-class`, etc). 281 | * `action` - The action perform on the element. Either 'show' or 'hide'. 282 | * `value` - If the value of the input is this value, then show|hide the target element. 283 | 284 | ### Filterable element properties 285 | 286 | These properties should only be modified in the `wp_form_element` filter. 287 | 288 | * `#content` (string) 289 | 290 | Any content to put in the input tag. Additional content from rendering the tag will be appended to this value if it is provided. Only applies to `'select'` and `'checkbox'` types, but will render tags with this content if it is provided. 291 | 292 | * `#tag` (string) 293 | 294 | The actual tag name to use for the input. 295 | 296 | ### Rendered Input Names 297 | 298 | Form input elements rendered using this API are receive the name specified in `#name`, which defaults to as their `#key`. 299 | 300 | For `'#type' => 'composite'`, any elements in the form tree below the current element receive names that result in an associative array being submitted for that `#key`. For example, the form defined below: 301 | 302 | ```php 303 | $form = array( 304 | 'address' => array( 305 | '#type' => 'composite', 306 | 'city' => array( '#type' => 'text', '#label' => "City" ), 307 | 'state' => array( '#type' => 'text', '#label' => "State" ), 308 | 'zip' => array( '#type' => 'text', '#label' => "ZIP" ), 309 | ) 310 | ); 311 | ``` 312 | 313 | Will result in three input elements named `address[city]`, `address[state]`, and `address[zip]`. 314 | 315 | For `'#type' => 'multiple'`, you must specify a `#multiple` key, which is a form whose values are collected into an indexed array. For example, the following form: 316 | 317 | ```php 318 | $form = array( 319 | 'favorites' => array( 320 | '#type' => 'multiple', 321 | '#multiple' => array( 322 | 'name' => array( '#type' => 'text', '#label' => "Favorite:" ) 323 | ) 324 | ) 325 | ); 326 | ``` 327 | 328 | Will result in a multi-valued form with inputs named `favorites[0][name]`, `favorites[1][name]`, and so on, for each value submitted. The form will always render at least one empty input. When multiple-valued form elements are used, the script handle `wp-forms` is enqueued, which manages control of adding / removing multiple elements. 329 | 330 | In general, the naming should be unimportant to you when using `process_form()`, but it is important to know how `$values` will be structured `$values` after calling `process_form()`. 331 | 332 | 333 | ### Processing 334 | 335 | Use the method `WP_Forms_API::process_form( $form, $values )` to populate $values with named elements defined by $form. By default will use values from `$_POST`, but you can pass an optional third argument to pull element values from. 336 | 337 | The filter `wp_form_process` is called with arguments `$form, &$values, &$input` and allows modification of forms or sub-forms before they are rendered. You can access the top-level form in `$form['#form']` 338 | 339 | The filter `wp_form_process_element` is called with `$element, &$values, &$input`, and allows modification of individual elements before they are processed. You can access the sub-form that this element is a part of in `$element['#form']`, and the top-level form in `$element['#form']['#form']`. 340 | 341 | In each of these filters, `&$values` and `&$input` may refer to sub-arrays of the original `$values` and `$input` arrays. To access the top-level of these structures, access `$form['#form']['#values']` and `$form['#form']['#input']` for forms, and `$element['#form']['#form']['#values']` and `$element['#form']['#form']['#input']` for elements. 342 | 343 | 344 | ## CSS 345 | 346 | Element slugs are built from the chain of parent elements, separated by `'-'`. These slugs are used to form CSS class names for elements for targeted styling. 347 | 348 | Forms get the following classes: `.wp-form`, and if #id is defined, `.wp-form-{$form['#id']}`. They also receive the attribute `id="{$element['#id']}"` 349 | 350 | Each element is rendered in a container element of tag `#container` with classes defined in `#container_classes`. The following classes are also added to the container element: `.wp-form-element .wp-form-element-{$slug}`. 351 | 352 | Each input element is rendered using the tag `$element['#tag']` and attributes in `$element['#attrs']`. These values can be modified per-element using the `wp_form_element` filter. The default classes are `.wp-form-input` and `.wp-form-input-{$element['#slug']}`. 353 | 354 | Labels receive the classes `.wp-form-label` and `.wp-form-label-{$element['#slug']}`. 355 | 356 | 357 | ## Examples 358 | 359 | An example form might look like the following, which captures a name and a zip code: 360 | 361 | ```php 362 | 363 | array( 366 | '#type' => 'text', 367 | '#label' => "Enter your name:", 368 | '#placeholder' => "Charlie Brown" 369 | ), 370 | 'zipcode' => array( 371 | '#type' => 'text', 372 | '#label' => "Enter your ZIP code:", 373 | '#placeholder' => "90210", 374 | '#size' => 5 375 | ), 376 | 'save' => array( 377 | '#type' => 'submit', 378 | '#value' => "Save Information" 379 | ) 380 | ); 381 | 382 | echo WP_Forms_API::render_form( $form, $input ); 383 | ?> 384 |
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 | ![](https://github.com/gdtrombetti/WP-Forms-API/blob/master/images/Oomph_logo.png) 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 |
3 | array( 6 | '#type' => 'text', 7 | '#label' => "Enter your name:", 8 | '#placeholder' => "Charlie Brown" 9 | ), 10 | 'zipcode' => array( 11 | '#type' => 'text', 12 | '#label' => "Enter your ZIP code:", 13 | '#placeholder' => "90210", 14 | '#size' => 5 15 | ), 16 | 'save' => array( 17 | '#type' => 'submit', 18 | '#value' => "Save Information" 19 | ) 20 | ); 21 | 22 | echo WP_Forms_API::render_form( $form, $input ); 23 | ?> 24 |
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 . ''; 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 |