├── changelog.txt ├── includes ├── Pods_GF.php ├── Pods_GF_Addon.php ├── Pods_GF_CLI.php ├── Pods_GF_UI.php └── functions.php ├── license.txt ├── pods-gravity-forms.php ├── readme.txt ├── ui ├── pods-gf-admin.js ├── pods-gf.css └── pods-gf.js └── vendor-prefixed ├── autoload-classmap.php ├── autoload.php └── erusev └── parsedown ├── LICENSE.txt └── Parsedown.php /changelog.txt: -------------------------------------------------------------------------------- 1 | Found a bug? Have a great feature idea? Get on GitHub and tell us about it and we'll get right on it: https://github.com/pods-framework/pods-gravity-forms/issues/new 2 | 3 | Our GitHub has the full list of all prior releases of Pods Gravity Forms: https://github.com/pods-framework/pods-gravity-forms/releases 4 | 5 | = 1.5.0 - March 29th, 2024 = 6 | 7 | * New requirements that match Pods: WP 6.0+, PHP 7.2+, and Pods 3.0+ to prep for Pods Gravity Forms 2.0 (@sc0ttkclark) 8 | * Added: Support value overrides for checkbox GF fields when prepopulating. (@sc0ttkclark) 9 | * Added: Allow for passing `?pods_gf_debug=1` to a form submit URL to debug the form submission mapping to Pods which outputs debug information and stops before the save runs). (@sc0ttkclark) 10 | * Added: New hook `pods_gf_dynamic_select_show_empty_option` lets you disable showing the "empty option" in dynamic fields being prepopulated. (@sc0ttkclark) 11 | * Added: New hook `pods_gf_addon_options_{$form_id}` that lets you filter the options built for a feed for the `Pods_GF` object. (@sc0ttkclark) 12 | * Tweak: New `pods-gf-ui-view-only` class added to the view-only mode. (@sc0ttkclark) 13 | * Tweak: Expanded secondary submit handling with the ability to have cancel button. (@sc0ttkclark) 14 | * Fixed: Resolved various PHP notices. (@sc0ttkclark) 15 | * Fixed: Removed "comments" from the field mapping options in the feed. (@sc0ttkclark) 16 | * Fixed: Remove extra HTML in the feed labels. (@sc0ttkclark) 17 | * Fixed: For non-select GF field types, trim the dashes on the custom `select_text` option used. (@sc0ttkclark) 18 | * Fixed: Prepopulating field values works more consistently now when passing the prepopulated filter pre-chunked arrays of values. (@sc0ttkclark) 19 | * Fixed: View-only mode forced to use page zero. (@sc0ttkclark) 20 | * Fixed: Prevent duplicate submissions during feed processing. (@sc0ttkclark) 21 | * Fixed: Only allow working with active leads in `Pods_GF_UI`. (@sc0ttkclark) 22 | * Fixed: Prevent Markdown conflicts with other plugins and update PHP 8 compatibility by switching to the Parsedown library. #166 (@sc0ttkclark) 23 | * Fixed: Stop prepopulating fields that aren't opted-in to it. #168 (@sc0ttkclark) 24 | 25 | ALSO: Pods Gravity Forms 2.0 is still in development and it brings complete compatibility with the latest Gravity Forms releases. We could use your support to help it get over the finish line this year. Please consider [donating to the Pods project](https://friends.pods.io/) to help us get there more quickly. 26 | 27 | = 1.4.5 - July 22nd, 2022 = 28 | 29 | * Tested against WP 6.0 30 | * Added: Not seeing something map correctly? As a site admin, you now have the power to debug the form submission and see what might be going on. Add `?pods_gf_debug_gf_to_pods=1` to the URL of the form action before submitting to take advantage of the admin-only debug mode. This will output the values as they would be sent to Pods, the entry information used to reference it, and the feed options used at the time. It will stop the form from completely saving to Pods so you can tweak and debug your form feeds however much you'd like to perfect them. (@sc0ttkclark) 31 | * Fixed: Conditional checks for feeds has been resolved and now won't get confused when there are multiple feeds for the same form in certain cases. (@sc0ttkclark) 32 | * Fixed: Additional compatibility with Gravity Flow. #157 (@JoryHogeveen) 33 | * Pods 2.9 is in beta and after it is released, new mapping of GF List Fields to Pods repeatable fields will be added. This add-on will also be updated with minimum version requirements updated for WP 5.5+, Pods 2.8+, and Gravity Forms 2.5+. Complete testing will be done at that time to ensure complete compatibility. 34 | 35 | = 1.4.4 - October 6th, 2021 = 36 | 37 | * Tested against WP 5.8 38 | * Get ready for Pods 2.8 in just a week! This add-on will receive updates to ensure it is compatible with the latest Gravity Forms and the changes in Pods 2.8 39 | 40 | = 1.4.3 - March 26th, 2020 = 41 | 42 | * Added: Now requiring PHP 5.4+ 43 | * Added: Freemius support when running Pods 2.7.17 44 | * Fixed: Prepopulate handling for relationship fields. 45 | * Fixed: Prevent errors when form doesn't exist by the time it gets to our hook. 46 | 47 | = 1.4.2 - March 2nd, 2020 = 48 | 49 | * Fixed: Ajax handling for various callbacks that hook into `gform_pre_render`. 50 | * Fixed: Cleaned up logic and prevent PHP notices with multi-select arrays when setting up choices arrays. 51 | * Fixed: Make sure `Pods_GF_UI` does not return false on UI callbacks to prevent access errors. 52 | * Fixed: Add mapping feeds to the import/export! (props @travislopes) 53 | 54 | = 1.4.1 - October 16th, 2018 = 55 | 56 | * Fixed: When syncing multiple entries, the field values were caching and not unique per entry resulting in what appeared to be duplicated content inserts/updates. 57 | 58 | = 1.4 - October 16th, 2018 = 59 | 60 | * Support: Added support for Gravity Forms 2.3 database tables changes (You may see a warning on the Edit Pod screen but this is a false positive because we cache a list of all tables to transients and it triggers the warning solved by removing those old "rg" tables) 61 | * Changed: Backwards compatibility issue -- You can now more easily set custom override values, however the old style was not able to be brought over -- you'll want to update your feeds when possible, the old values will not show up and you'll have to select the custom override value option once more, then fill it in 62 | * Changed: Backwards compatibility issue -- Now requiring WordPress 4.6+ 63 | * Feature: When editing entries in the admin area, changes now sync to the associated Pod item (except trash/deletes) 64 | * Feature: New Bulk Entry Syncing to Pods WP-CLI command `wp pods-gf sync --form=123` or you can specify which feed (even if it is not active) with `wp pods-gf sync --form=123 --feed=2` 65 | * Feature: Support for List field mapping to a Pod field which ends up serializing the value, but can be prepopulated back into the Gravity Form 66 | * Feature: List field mapping to relationship fields related to another Pod (list columns map to individual fields in the related Pod) with new filters `pods_gf_field_columns_mapping` and `pods_gf_field_column_row` 67 | * Feature: Support for Chained Select field mapping to a Pod field 68 | * Feature: New Custom fields section added for Pods that support meta (Posts, Terms, Users, Media, and Comments), you can set additional custom fields including ability to set custom values there too 69 | * Feature: Ability to set conditional processing per feed, based on specific values submitted 70 | * Added: Whenever you create a new feed, mapping will automatically be associated between a Gravity Form field and a Pod field if the labels match 71 | * Added: Custom override values now support GF merge tags by default (no insert UI yet) like `{form_id}` and any other merge tag 72 | * Added: Required WP Object Fields in mapping are no longer required if you choose to 'Enable editing with this form using ____' option for Post/Media or User pod types 73 | * Added: Support for E-mail field mappings with 'Confirm E-mail' enabled 74 | * Added: Support for Date fields with multiple inputs (date dropdown / text fields) 75 | * Added: Smarter requirement handling for WP object fields based on object type (only require what the WP insert API requires) 76 | * Added: New mapping fields are now available for more Entry and Payment fields 77 | * Added: New merge tags `{pods.id}` and `{pods.permalink}` are available for usage and in the merge tag selection dropdowns 78 | * Improved: Added headings to each group of feed options so they are easier to work with 79 | * Improved: Address field mapping for Country, State, and CA Provinces now convert properly to their Pods counterparts 80 | * Updated: PHP Markdown library updated to 1.0.2 81 | * Fixed: Issues with using 'bypass' as a save action 82 | * Fixed: Dynamic select options should set the current value (as posted in form) properly 83 | * Fixed: Date/time fields shouldn't auto populate with empty dates such as 0000-00-00 anymore 84 | * Fixed: Additional attachment processing fixes 85 | * Fixed: Lots of Pods GF UI issues resolved 86 | * Fixed: Removed Autocomplete limit (was 30) that was being enforced, now all data from related field will show 87 | * Fixed: Dynamic mapping value checking to support arrays of values 88 | * Fixed: Lots of Prepopulating fixes 89 | * Fixed: Now supports multi page form validation and prepopulating 90 | 91 | = 1.3 - June 2nd, 2017 = 92 | 93 | * Added: When creating new feeds mapping will automatically be detected based on matching field labels 94 | * Added: New option to prepopulate the form fields with data based on the field mapping in the feed (same type of logic as edit). Limitations with certain field types, please submit issues with problems you find here. 95 | * Added: Rewrote the whole File Upload field mapping logic and tested against Single/Multi file fields (props @mika31, @copperleaf, @zanematthew, @zorog, @chriswagoner for testing help, props @spivurno for official GF support code help) 96 | * Added: Support for feeds with submissions from the forms embedded on the dashboard and in the admin area (props @richardW8k) 97 | * Added: Field names to field mapping screen 98 | * Added: Ability to define a custom override value for each field mapping 99 | * Added: Ability to enable editing of user data using current logged in user ID (only for User pod feeds) 100 | * Added: Ability to enable editing of post data using current post ID on singular templates (only for Post type pod feeds) 101 | * Added: Ability to define custom 'content' in Pods GF UI custom actions instead of including a form 102 | * Added: Ability to relate to GF forms using a relationship field (new option: Gravity Forms > Forms) 103 | * Added: Ability to map Address and List fields 104 | * Added: Ability to map Category and Post Tag fields 105 | * Added: Ability to map sub fields to a pod field (Name [First Name], Address [Street Line 1], etc) 106 | * Fixed: Ensure time fields get mapped correctly (props @mmarvin1) 107 | * Fixed: Ensure default pods-gf-ui shortcode is only added/run on content within the loop (props @jamesgol) 108 | * Fixed: Empty id used for Pods GF UI 109 | * Fixed: Callback handling for Pods GF UI 110 | * Fixed: Default Post Author mapping 111 | 112 | = 1.2 - October 4th, 2016 = 113 | 114 | * Added: When using a custom action and setting the form ID option in Pods GF UI, a new custom action will be used which embeds the GF form (if no callback provided in action_data option) 115 | * Added: New Pods GF UI option, specific to each action, for `action_link` which corresponds to the `action_links` Pods UI option 116 | * Fixed: Support for recent GF versions where pre_save_id hook uses a different Form-specific naming convention 117 | * Fixed: Custom confirmation handling may have not been functioning properly in some cases 118 | * Fixed: Removed some issues that were causing PHP notices 119 | 120 | 121 | = 1.1 - June 13th, 2016 = 122 | 123 | * Added: Support for edit mode when using the Pods GF add-on mapping in the GF UI -- Use the new filter `pods_gf_addon_edit_id`, just return the ID to edit and the options will automatically be set for you 124 | * Added: When filtering the Pods data in `Pods_GF::gf_to_pods()` (via the `pods_gf_to_pods_data` and related filters), if you set the proper ID field in that array it will now be used to *save* over the existing item; Helpful for dynamic editing configurations based upon different processes and workflows in the code 125 | * Added: `Pods_GF::confirmation()` now supports `{@gf_to_pods_id}` replacement in confirmation URLs, replacing the variable properly to the resulting saved ID 126 | * Fixed: `Pods_GF::_gf_to_pods_handler()` would sometimes get the action improperly set to `edit`, but only `add`, `save`, or `bypass` are valid 127 | * Fixed: When an invalid pod is called in `Pods_GF::_gf_to_pods_handler()`, there's now a proper fallback to avoid PHP errors/warnings/notices 128 | * Fixed: When an invalid pod is called in `Pods_GF::_gf_field_validation()`, there's now a proper fallback to avoid PHP errors/warnings/notices 129 | * Fixed: `Pods_GF::confirmation()` would add the `gform_confirmation_{$form_id}` filter incorrectly and would cause PHP warnings about the callback, causing the confirmation functionality to not work properly 130 | * Fixed: `Pods_GF::confirmation()` confirmation URL replacement now handles a few more cases where previously PHP notices would result 131 | * Changed: `Pods_GF` is now storing multiple instances statically, cannot be called with `new Pods_GF()`, must be called with `Pods_GF::get_instance()` but more importantly should be called through the standard `pods_gf()` helper function to remain backwards compatible with previous versions 132 | * Changed: `Pods_GF::$gf_to_pods_id` is no longer an integer, but an array of integers keyed by the GF Form ID 133 | * Changed: `Pods_GF::$keep_files` is no longer an boolean, but an array of booleans keyed by the GF Form ID 134 | 135 | = 1.0 - March 4th, 2016 = 136 | 137 | * Initial release 138 | -------------------------------------------------------------------------------- /includes/Pods_GF_Addon.php: -------------------------------------------------------------------------------- 1 | 'pods_gf_admin', 110 | 'enqueue' => array( array( 'admin_page' => array( 'form_settings' ) ) ), 111 | 'src' => PODS_GF_URL . '/ui/pods-gf-admin.js', 112 | 'version' => $this->_version, 113 | 'deps' => array( 'jquery' ), 114 | ), 115 | ); 116 | 117 | return array_merge( parent::scripts(), $scripts ); 118 | 119 | } 120 | 121 | /*public function plugin_page() { 122 | 123 | ?> 124 | This page appears in the Forms menu 125 | __( 'Name', 'pods-gravity-forms' ), 136 | 'type' => 'text', 137 | 'name' => 'feedName', 138 | 'tooltip' => __( 'Name for this feed', 'pods-gravity-forms' ), 139 | 'class' => 'medium', 140 | ); 141 | 142 | $gf_fields = array(); 143 | 144 | $gf_form = GFAPI::get_form( pods_v( 'id' ) ); 145 | 146 | if ( ! empty( $gf_form ) && ! empty( $gf_form['fields'] ) ) { 147 | $gf_fields = $gf_form['fields']; 148 | } 149 | 150 | $pods_api = pods_api(); 151 | $all_pods = $pods_api->load_pods( array( 'names' => true ) ); 152 | 153 | $pod_choice_list = array(); 154 | $pod_choice_list[] = array( 155 | 'label' => __( 'Select a Pod', 'pods-gravity-forms' ), 156 | 'value' => '', 157 | ); 158 | 159 | foreach ( $all_pods as $name => $label ) { 160 | $pod_choice_list[] = array( 161 | 'label' => $label . ' (' . $name . ')', 162 | 'value' => $name, 163 | ); 164 | } 165 | 166 | $feed_field_pod = array( 167 | 'label' => __( 'Pod', 'pods-gravity-forms' ), 168 | 'type' => 'select', 169 | 'name' => 'pod', 170 | 'tooltip' => __( 'Select the pod', 'pods-gravity-forms' ), 171 | 'choices' => $pod_choice_list, 172 | 'onchange' => "jQuery(this).parents('form').submit();", 173 | 'required' => true, 174 | ); 175 | 176 | $selected_pod = $this->get_setting( 'pod' ); 177 | $enable_current_post = (int) $this->get_setting( 'enable_current_post' ); 178 | $enable_current_user = (int) $this->get_setting( 'enable_current_user' ); 179 | 180 | $posted_settings = $this->get_posted_settings(); 181 | 182 | if ( isset( $posted_settings['enable_current_post'] ) ) { 183 | $enable_current_post = (int) $posted_settings['enable_current_post']; 184 | } 185 | 186 | if ( isset( $posted_settings['enable_current_user'] ) ) { 187 | $enable_current_user = (int) $posted_settings['enable_current_user']; 188 | } 189 | 190 | $pod_fields = array(); 191 | $pod_type = ''; 192 | 193 | if ( ! empty( $selected_pod ) ) { 194 | $pod_object = $pods_api->load_pod( array( 'name' => $selected_pod ) ); 195 | 196 | if ( ! empty( $pod_object ) ) { 197 | $pod_type = $pod_object['type']; 198 | 199 | foreach ( $pod_object['fields'] as $name => $field ) { 200 | $pod_fields[] = array( 201 | 'needs_process' => true, 202 | 'name' => $name, 203 | 'field' => $field, 204 | ); 205 | } 206 | } 207 | } 208 | 209 | $feed_field_pod_fields = array( 210 | 'name' => 'pod_fields', 211 | 'label' => __( 'Pod Fields', 'pods-gravity-forms' ), 212 | 'type' => 'field_map', 213 | 'dependency' => 'pod', 214 | 'field_map' => $pod_fields, 215 | ); 216 | 217 | $ignore_object_fields = array( 218 | 'ID', 219 | 'post_type', 220 | 'comment_type', 221 | 'taxonomy', 222 | 'guid', 223 | 'menu_order', 224 | 'post_mime_type', 225 | 'comment_count', 226 | 'comment_status', 227 | 'ping_status', 228 | 'post_date_gmt', 229 | 'post_modified_gmt', 230 | 'post_password', 231 | 'post_status', 232 | 'post_content_filtered', 233 | 'pinged', 234 | 'to_ping', 235 | 'comments', 236 | ); 237 | 238 | $wp_object_fields = array(); 239 | 240 | if ( ! empty( $pod_object ) ) { 241 | foreach ( $pod_object['object_fields'] as $name => $field ) { 242 | if ( in_array( $name, $ignore_object_fields, true ) ) { 243 | continue; 244 | } 245 | 246 | if ( in_array( $pod_type, array( 'post_type', 'media' ), true ) ) { 247 | if ( 1 === $enable_current_post ) { 248 | $field['options']['required'] = 0; 249 | } elseif ( in_array( $name, array( 'post_title', 'post_content' ), true ) ) { 250 | $field['options']['required'] = 1; 251 | } 252 | } elseif ( 'taxonomy' === $pod_type ) { 253 | if ( 'name' === $name ) { 254 | $field['options']['required'] = 1; 255 | } 256 | } elseif ( 'user' === $pod_type ) { 257 | if ( 1 === $enable_current_user ) { 258 | $field['options']['required'] = 0; 259 | } elseif ( 'user_login' === $name ) { 260 | $field['options']['required'] = 1; 261 | } 262 | } 263 | 264 | $wp_object_fields[ $name ] = array( 265 | 'needs_process' => true, 266 | 'name' => $name, 267 | 'field' => $field, 268 | ); 269 | } 270 | } 271 | 272 | if ( 'post_type' === $pod_type ) { 273 | $wp_object_fields['_thumbnail_id'] = array( 274 | 'name' => '_thumbnail_id', 275 | 'label' => __( 'Featured Image', 'pods-gravity-forms' ), 276 | ); 277 | } 278 | 279 | $feed_field_wp_object_fields = array( 280 | 'name' => 'wp_object_fields', 281 | 'label' => __( 'WP Object Fields', 'pods-gravity-forms' ), 282 | 'type' => 'field_map', 283 | 'dependency' => 'pod', 284 | 'field_map' => array_values( $wp_object_fields ), 285 | ); 286 | 287 | $settings = array(); 288 | 289 | /////////////////// 290 | // Pod feed mapping 291 | /////////////////// 292 | $settings['pod_mapping'] = array( 293 | 'title' => __( 'Pod Feed Mapping', 'pods-gravity-forms' ), 294 | 'fields' => array( 295 | $feed_field_name, 296 | $feed_field_pod, 297 | $feed_field_pod_fields, 298 | ), 299 | ); 300 | 301 | if ( ! empty( $feed_field_wp_object_fields['field_map'] ) ) { 302 | $settings['pod_mapping']['fields'][] = $feed_field_wp_object_fields; 303 | } 304 | 305 | $blacklisted_keys = array(); 306 | 307 | // Build field mapping data arrays 308 | foreach ( $settings['pod_mapping']['fields'] as $k => $field_set ) { 309 | if ( empty( $field_set['field_map'] ) ) { 310 | continue; 311 | } 312 | 313 | foreach ( $field_set['field_map'] as $kf => $field_map ) { 314 | $blacklisted_keys[] = $field_map['name']; 315 | 316 | if ( ! empty( $field_map['needs_process'] ) ) { 317 | $name = $field_map['name']; 318 | $field = $field_map['field']; 319 | 320 | $field_required = false; 321 | 322 | if ( isset( $field['options']['required'] ) && 1 === (int) $field['options']['required'] ) { 323 | $field_required = true; 324 | 325 | if ( isset( $wp_object_fields[ $name ] ) ) { 326 | if ( in_array( $pod_type, array( 'post_type', 'media' ), true ) && 1 === $enable_current_post ) { 327 | $field_required = false; 328 | } elseif ( 'user' === $pod_type && 1 === $enable_current_user ) { 329 | $field_required = false; 330 | } 331 | } 332 | } 333 | 334 | $field_map = array( 335 | 'name' => $name, 336 | 'label' => $field['label'], 337 | 'required' => $field_required, 338 | ); 339 | 340 | if ( 0 === (int) pods_v( 'fid' ) ) { 341 | foreach ( $gf_fields as $gf_field ) { 342 | if ( strtolower( $field['label'] ) === strtolower( $gf_field['label'] ) ) { 343 | $field_map['default_value'] = $gf_field['id']; 344 | } 345 | } 346 | } 347 | } 348 | 349 | // Add field names to labels 350 | $field_map['label'] = sprintf( 351 | '%s (%s)', 352 | esc_html( $field_map['label'] ), 353 | esc_html( $field_map['name'] ) 354 | ); 355 | 356 | $settings['pod_mapping']['fields'][ $k ]['field_map'][ $kf ] = $field_map; 357 | } 358 | } 359 | 360 | /////////////////// 361 | // Custom fields 362 | /////////////////// 363 | if ( in_array( $pod_type, array( 'post_type', 'taxonomy', 'user', 'media', 'comment' ), true ) ) { 364 | $settings['custom_fields'] = array( 365 | 'title' => esc_html__( 'Custom Fields', 'pods-gravity-forms' ), 366 | 'fields' => array( 367 | array( 368 | 'name' => 'custom_fields', 369 | 'label' => esc_html__( 'Custom Fields', 'pods-gravity-forms' ), 370 | 'type' => 'generic_map', 371 | 'key_field' => array( 372 | 'choices' => $this->get_meta_field_map( $selected_pod, $pod_type, $blacklisted_keys ), 373 | 'placeholder' => esc_html__( 'Custom Field Name', 'pods-gravity-forms' ), 374 | 'title' => esc_html__( 'Name', 'pods-gravity-forms' ), 375 | ), 376 | 'value_field' => array( 377 | 'choices' => 'form_fields', 378 | 'custom_value' => false, 379 | 'merge_tags' => true, 380 | 'placeholder' => esc_html__( 'Custom Field Value', 'pods-gravity-forms' ), 381 | ), 382 | ), 383 | ), 384 | ); 385 | } 386 | 387 | /////////////////// 388 | // Advanced 389 | /////////////////// 390 | $settings['advanced'] = array( 391 | 'title' => __( 'Advanced', 'pods-gravity-forms' ), 392 | 'fields' => array(), 393 | ); 394 | 395 | $settings['advanced']['fields'][] = array( 396 | 'name' => 'update_pod_item', 397 | 'label' => __( 'Support entry updates', 'pods-gravity-forms' ), 398 | 'type' => 'checkbox', 399 | 'choices' => array( 400 | array( 401 | 'value' => 1, 402 | 'label' => __( 'Update pod item if the entry is updated', 'pods-gravity-forms' ), 403 | 'name' => 'update_pod_item', 404 | ), 405 | ), 406 | ); 407 | 408 | $settings['advanced']['fields'][] = array( 409 | 'name' => 'enable_markdown', 410 | 'label' => __( 'Enable Markdown', 'pods-gravity-forms' ), 411 | 'type' => 'checkbox', 412 | 'choices' => array( 413 | array( 414 | 'value' => 1, 415 | 'label' => __( 'Enable Markdown in HTML Fields', 'pods-gravity-forms' ), 416 | 'name' => 'enable_markdown', 417 | ), 418 | ), 419 | ); 420 | 421 | if ( 'user' === $pod_type ) { 422 | $settings['advanced']['fields'][] = array( 423 | 'name' => 'enable_current_user', 424 | 'label' => __( 'Enable editing with this form using logged in user', 'pods-gravity-forms' ), 425 | 'type' => 'checkbox', 426 | 'choices' => array( 427 | array( 428 | 'value' => 1, 429 | 'label' => __( 'Enable editing with this form using the logged in user data', 'pods-gravity-forms' ), 430 | 'name' => 'enable_current_user', 431 | ), 432 | ), 433 | ); 434 | 435 | $settings['advanced']['fields'][] = array( 436 | 'name' => 'enable_prepopulate', 437 | 'label' => __( 'Enable populating field values for this form using logged in user', 'pods-gravity-forms' ), 438 | 'type' => 'checkbox', 439 | 'choices' => array( 440 | array( 441 | 'value' => 1, 442 | 'label' => __( 'Enable populating field values for this form using the logged in user data', 'pods-gravity-forms' ), 443 | 'name' => 'enable_prepopulate', 444 | ), 445 | ), 446 | ); 447 | } elseif ( in_array( $pod_type, array( 'post_type', 'media' ), true ) ) { 448 | $settings['advanced']['fields'][] = array( 449 | 'name' => 'enable_current_post', 450 | 'label' => __( 'Enable editing with this form using current post', 'pods-gravity-forms' ), 451 | 'type' => 'checkbox', 452 | 'choices' => array( 453 | array( 454 | 'value' => 1, 455 | 'label' => __( 'Enable editing with this form using the current post ID (only works on singular template)', 'pods-gravity-forms' ), 456 | 'name' => 'enable_current_post', 457 | ), 458 | ), 459 | ); 460 | 461 | $settings['advanced']['fields'][] = array( 462 | 'name' => 'enable_prepopulate', 463 | 'label' => __( 'Enable populating field values for this form using current post', 'pods-gravity-forms' ), 464 | 'type' => 'checkbox', 465 | 'choices' => array( 466 | array( 467 | 'value' => 1, 468 | 'label' => __( 'Enable populating field values for this form using the current post ID (only works on singular template)', 'pods-gravity-forms' ), 469 | 'name' => 'enable_prepopulate', 470 | ), 471 | ), 472 | ); 473 | } 474 | 475 | $settings['advanced']['fields'][] = array( 476 | 'name' => 'delete_entry', 477 | 'label' => __( 'Delete Gravity Form Entry on submission', 'pods-gravity-forms' ), 478 | 'type' => 'checkbox', 479 | 'choices' => array( 480 | array( 481 | 'value' => 1, 482 | 'label' => __( 'Delete entry after processing', 'pods-gravity-forms' ), 483 | 'name' => 'delete_entry', 484 | ), 485 | ), 486 | ); 487 | 488 | $addon_slug = $this->get_slug(); 489 | 490 | add_filter( "gform_{$addon_slug}_field_map_choices", array( $this, 'add_field_map_choices' ) ); 491 | 492 | $settings['advanced']['fields'][] = array( 493 | 'name' => 'feed_condition', 494 | 'label' => __( 'Conditional Logic', 'pods-gravity-forms' ), 495 | 'checkbox_label' => __( 'Enable', 'pods-gravity-forms' ), 496 | 'type' => 'feed_condition', 497 | ); 498 | 499 | return $settings; 500 | 501 | } 502 | 503 | /** 504 | * Prepare fields for meta field mapping. 505 | * 506 | * Props go to GF Post Creation add-on for the initial source of this method. 507 | * 508 | * @param string $pod_name Current pod name 509 | * @param string $pod_type Current pod type 510 | * @param string[] $blacklisted_keys Meta keys to exclude 511 | * 512 | * @uses GFFormsModel::get_custom_field_names() 513 | * 514 | * @return array 515 | */ 516 | public function get_meta_field_map( $pod_name = '', $pod_type = '', $blacklisted_keys = array() ) { 517 | 518 | // Setup meta fields array. 519 | $meta_fields = array( 520 | array( 521 | 'label' => esc_html__( 'Select a Custom Field Name', 'pods-gravity-forms' ), 522 | 'value' => '', 523 | ), 524 | ); 525 | 526 | /////////////////// 527 | // Custom fields 528 | /////////////////// 529 | 530 | // Get most used post meta keys 531 | $meta_keys = $this->get_custom_field_names( $pod_name, $pod_type ); 532 | 533 | // If no meta keys exist, return an empty array. 534 | if ( empty( $meta_keys ) ) { 535 | return array(); 536 | } 537 | 538 | // Add post meta keys to the meta fields array. 539 | foreach ( $meta_keys as $meta_key ) { 540 | $meta_fields[] = array( 541 | 'label' => $meta_key, 542 | 'value' => $meta_key, 543 | ); 544 | } 545 | 546 | /////////////////// 547 | // Custom key 548 | /////////////////// 549 | $meta_fields[] = array( 550 | 'label' => esc_html__( 'Add New Custom Field Name', 'pods-gravity-forms' ), 551 | 'value' => 'gf_custom', 552 | ); 553 | 554 | return $meta_fields; 555 | 556 | } 557 | 558 | /** 559 | * Get most common custom field names from DB. 560 | * 561 | * @param string $pod_name Current pod name 562 | * @param string $pod_type Current pod type 563 | * @param string[] $blacklisted_keys Meta keys to exclude 564 | * 565 | * @return string[] 566 | */ 567 | public function get_custom_field_names( $pod_name = '', $pod_type = '', $blacklisted_keys = array() ) { 568 | 569 | global $wpdb; 570 | 571 | $object_table = $wpdb->posts; 572 | $meta_table = $wpdb->postmeta; 573 | $id_col = 'ID'; 574 | $meta_id_col = 'post_id'; 575 | $blacklist_where = " 576 | AND `object`.`post_type` LIKE '_pods_%' 577 | "; 578 | 579 | if ( 'taxonomy' === $pod_type ) { 580 | $object_table = $wpdb->terms; 581 | $meta_table = $wpdb->termmeta; 582 | $id_col = 'term_id'; 583 | $meta_id_col = 'term_id'; 584 | $blacklist_where = ''; 585 | } elseif ( 'user' === $pod_type ) { 586 | $object_table = $wpdb->users; 587 | $meta_table = $wpdb->usermeta; 588 | $id_col = 'ID'; 589 | $meta_id_col = 'user_id'; 590 | $blacklist_where = ''; 591 | } elseif ( 'comment' === $pod_type ) { 592 | $object_table = $wpdb->users; 593 | $meta_table = $wpdb->usermeta; 594 | $id_col = 'comment_ID'; 595 | $meta_id_col = 'comment_id'; 596 | $blacklist_where = ''; 597 | } 598 | 599 | $where = ''; 600 | 601 | $pods_blacklist_keys = array(); 602 | 603 | if ( $blacklist_where ) { 604 | $sql = " 605 | SELECT `meta`.`meta_key` 606 | FROM `{$meta_table}` AS `meta` 607 | LEFT JOIN `{$object_table}` AS `object` ON `object`.`{$id_col}` = `meta`.`{$meta_id_col}` 608 | WHERE 609 | `meta`.`meta_key` NOT LIKE '\_%' {$blacklist_where} 610 | GROUP BY `meta`.`meta_key` 611 | "; 612 | 613 | $pods_blacklist_keys = $wpdb->get_col( $sql ); 614 | } 615 | 616 | $pods_blacklist_keys = array_merge( $pods_blacklist_keys, $blacklisted_keys ); 617 | $pods_blacklist_keys = array_unique( $pods_blacklist_keys ); 618 | $pods_blacklist_keys = array_filter( $pods_blacklist_keys ); 619 | 620 | if ( $pods_blacklist_keys ) { 621 | $placeholders = array_fill( 0, count( $pods_blacklist_keys ), '%s' ); 622 | 623 | $where = " 624 | AND `meta`.`meta_key` NOT IN ( " . implode( ", ", $placeholders ) . " ) 625 | "; 626 | 627 | $where = $wpdb->prepare( $where, $pods_blacklist_keys ); 628 | } 629 | 630 | $sql = " 631 | SELECT `meta`.`meta_key`, COUNT(*) AS `total_count` 632 | FROM `{$meta_table}` AS `meta` 633 | WHERE 634 | `meta`.`meta_key` NOT LIKE '\_%' {$where} 635 | GROUP BY `meta`.`meta_key` 636 | ORDER BY `total_count` DESC 637 | LIMIT 50 638 | "; 639 | 640 | $meta_keys = $wpdb->get_col( $sql ); 641 | 642 | if ( $meta_keys ) { 643 | natcasesort( $meta_keys ); 644 | } 645 | 646 | return $meta_keys; 647 | 648 | } 649 | 650 | /** 651 | * @param $choices 652 | * 653 | * @return array 654 | */ 655 | public function add_field_map_choices( $choices ) { 656 | 657 | // Remove first choice 658 | array_shift( $choices ); 659 | 660 | $choices = array_merge( 661 | array( 662 | // Add first choice back 663 | array( 664 | 'value' => '', 665 | 'label' => __( 'Select a Field', 'gravityforms' ), // Use gravtiyforms text domain here 666 | ), 667 | // Make custom override first option 668 | array( 669 | 'value' => 'gf_custom', 670 | 'label' => __( 'Custom override value', 'pods-gravity-forms' ), 671 | ), 672 | ), 673 | $choices, 674 | array( 675 | array( 676 | 'value' => 'transaction_id', 677 | 'label' => 'Transaction ID', 678 | ), 679 | array( 680 | 'value' => 'payment_amount', 681 | 'label' => 'Payment Amount', 682 | ), 683 | array( 684 | 'value' => 'payment_date', 685 | 'label' => 'Payment Date', 686 | ), 687 | array( 688 | 'value' => 'payment_status', 689 | 'label' => 'Payment Status', 690 | ), 691 | ) 692 | ); 693 | 694 | foreach ( $choices as $k => $choice ) { 695 | if ( '_pods_item_id' === $choice['value'] ) { 696 | unset( $choices[ $k ] ); 697 | 698 | $choices = array_values( $choices ); 699 | 700 | break; 701 | } 702 | } 703 | 704 | return $choices; 705 | 706 | } 707 | 708 | /*** 709 | * Renders and initializes a drop down field based on the $field array 710 | * 711 | * @param array $field - Field array containing the configuration options of this field 712 | * @param bool $echo = true - true to echo the output to the screen, false to simply return the contents as a string 713 | * 714 | * @return string The HTML for the field 715 | */ 716 | public function settings_select( $field, $echo = true ) { 717 | 718 | $has_gf_custom = false; 719 | 720 | if ( ! empty( $field['choices'] ) ) { 721 | foreach ( $field['choices'] as $choice ) { 722 | if ( isset( $choice['value'] ) && 'gf_custom' === $choice['value'] ) { 723 | $has_gf_custom = true; 724 | 725 | break; 726 | } 727 | } 728 | } 729 | 730 | // Select has no custom choice or we already took over the first select. 731 | if ( empty( $field['choices'] ) || ! $has_gf_custom || ! empty( $field['_pods_custom_select'] ) ) { 732 | return parent::settings_select( $field, $echo ); 733 | } 734 | 735 | // Already doing custom select. 736 | if ( ! empty( $field['type'] ) && 'generic_map' === $field['type'] ) { 737 | return parent::settings_select( $field, $echo ); 738 | } 739 | 740 | $field['_pods_custom_select'] = true; 741 | 742 | return parent::settings_select_custom( $field, $echo ); 743 | 744 | } 745 | 746 | /** 747 | * @return string|void 748 | */ 749 | public function field_map_title() { 750 | 751 | return __( 'Pod Field', 'pods-gravity-forms' ); 752 | 753 | } 754 | 755 | /** 756 | * @return array 757 | */ 758 | public function feed_list_columns() { 759 | 760 | return array( 761 | 'feedName' => __( 'Name', 'pods-gravity-forms' ), 762 | 'pod' => __( 'Pod', 'pods-gravity-forms' ) 763 | ); 764 | 765 | } 766 | 767 | /** 768 | * @param $feed 769 | * 770 | * @return string 771 | */ 772 | public function get_column_value_pod( $feed ) { 773 | 774 | return '' . $feed['meta']['pod'] . ''; 775 | 776 | } 777 | 778 | /** 779 | * 780 | */ 781 | public function init_admin() { 782 | 783 | parent::init_admin(); 784 | 785 | add_action( 'gform_field_standard_settings', array( $this, 'populate_related_items_settings' ), 10, 2 ); 786 | add_filter( 'gform_tooltips', array( $this, 'populate_related_items_tooltip' ) ); 787 | add_action( 'gform_editor_js', array( $this, 'populate_related_items_editor_script' ) ); 788 | 789 | } 790 | 791 | /** 792 | * @param $position 793 | * @param $form_id 794 | */ 795 | public function populate_related_items_settings( $position, $form_id ) { 796 | 797 | if ( -1 === $position ) { 798 | ?> 799 | 808 | 819 | 829 | %s %s', __( 'Populate Related Items from Pods', 'pods-gravity-forms' ), __( 'Check this box to populate the related items from Pods instead of keeping the list up-to-date manually.' ) ); 841 | 842 | return $tooltips; 843 | 844 | } 845 | 846 | /** 847 | * Init integration. 848 | */ 849 | public function init() { 850 | parent::init(); 851 | 852 | if ( ! $this->is_gravityforms_supported() ) { 853 | return; 854 | } 855 | 856 | // Handle normal forms. 857 | add_filter( 'gform_pre_render', array( $this, '_gf_pre_render' ), 9, 3 ); 858 | add_filter( 'gform_admin_pre_render', array( $this, '_gf_pre_render' ), 9, 1 ); 859 | add_filter( 'gform_pre_process', array( $this, '_gf_pre_process' ) ); 860 | 861 | // Handle merge tags 862 | add_filter( 'gform_custom_merge_tags', array( $this, '_gf_custom_merge_tags' ), 10, 2 ); 863 | add_filter( 'gform_merge_tag_data', array( $this, '_gf_add_merge_tags' ), 10, 3 ); 864 | add_filter( 'gform_replace_merge_tags', array( $this, '_gf_replace_merge_tags' ), 10, 2 ); 865 | 866 | // Handle entry detail edits. 867 | add_action( 'gform_pre_entry_detail', array( $this, '_gf_pre_entry_detail' ), 10, 2 ); 868 | add_action( 'check_admin_referer', array( $this, '_check_admin_referer' ), 10, 2 ); 869 | add_action( 'gform_entry_detail_content_before', array( $this, '_gf_entry_detail_content_before' ), 10, 2 ); 870 | 871 | // Handle entry updates. 872 | add_action( 'gform_post_update_entry', array( $this, '_gf_post_update_entry' ), 9, 2 ); 873 | add_action( 'gform_after_update_entry', array( $this, '_gf_after_update_entry' ), 9, 3 ); 874 | 875 | // Handle Payment Add-on callbacks. 876 | add_action( 'gform_action_pre_payment_callback', array( $this, '_gf_action_pre_payment_callback' ), 10, 2 ); 877 | } 878 | 879 | /** 880 | * Processes feed action. 881 | * 882 | * @since 1.4.2 883 | * @access public 884 | * 885 | * @param array $feed The Feed Object currently being processed. 886 | * @param array $entry The Entry Object currently being processed. 887 | * @param array $form The Form Object currently being processed. 888 | * 889 | * @return array|null Returns a modified entry object or null. 890 | */ 891 | public function process_feed( $feed, $entry, $form ) { 892 | 893 | if ( empty( $this->pods_gf[ $feed['id'] ] ) ) { 894 | return null; 895 | } 896 | 897 | $form = $this->_gf_pre_render( $form, false, $entry ); 898 | 899 | /** @var Pods_GF $pods_gf */ 900 | $pods_gf = $this->pods_gf[ $feed['id'] ]; 901 | 902 | try { 903 | $pods_gf->options['entry'] = $entry; 904 | 905 | // Ensure other custom Pods GF submission handling does not duplicate. 906 | remove_action( 'gform_after_submission_' . $form['id'], [ $pods_gf, '_gf_after_submission' ] ); 907 | 908 | $pods_gf->_gf_after_submission( $entry, $form ); 909 | 910 | $id = $pods_gf->options['pod_item_id']; 911 | 912 | // Set post_id if we have it. 913 | if ( 'post_type' === $pods_gf->pod->pod_data['type'] ) { 914 | $entry['post_id'] = $id; 915 | 916 | return $entry; 917 | } 918 | } 919 | catch ( Exception $e ) { 920 | // @todo Log something to the form entry 921 | if ( defined( 'WP_CLI' ) ) { 922 | \WP_CLI::warning( 'Feed processing error: ' . $e->getMessage() ); 923 | } 924 | } 925 | 926 | return null; 927 | 928 | } 929 | 930 | /** 931 | * Action handler for Gravity Forms: gform_action_pre_payment_callback. 932 | * 933 | * @param array $action Action data being saved. 934 | * @param array $entry GF Entry array. 935 | */ 936 | public function _gf_action_pre_payment_callback( $action, $entry ) { 937 | 938 | $form = GFAPI::get_form( $entry['form_id'] ); 939 | 940 | $this->_gf_pre_process( $form ); 941 | 942 | } 943 | 944 | /** 945 | * Action handler for Gravity Forms: gform_post_update_entry. 946 | * 947 | * @param array $entry GF Entry array 948 | * @param array $original_entry Original GF Entry array 949 | */ 950 | public function _gf_post_update_entry( $entry, $original_entry ) { 951 | 952 | $form = GFAPI::get_form( $entry['form_id'] ); 953 | 954 | $this->_gf_pre_process( $form ); 955 | 956 | } 957 | 958 | /** 959 | * Action handler for Gravity Forms: gform_after_update_entry. 960 | * 961 | * @param array $form GF Form array 962 | * @param array $entry GF Entry array 963 | * @param array $original_entry Original GF Entry array 964 | */ 965 | public function _gf_after_update_entry( $form, $entry, $original_entry ) { 966 | 967 | $this->_gf_pre_process( $form ); 968 | 969 | } 970 | 971 | /** 972 | * Hook into action to setup Pods GF add-on hooks before form entry edit form is shown. 973 | * 974 | * @param array|object $form GF form data. 975 | * @param array|object $entry GF entry data. 976 | */ 977 | public function _gf_pre_entry_detail( $form, $entry ) { 978 | 979 | // Remove other hooks for workarounds we don't need if this hook now exists. Not in GF when this was written. 980 | remove_action( 'check_admin_referer', array( $this, '_check_admin_referer' ) ); 981 | remove_action( 'gform_entry_detail_content_before', array( $this, '_gf_entry_detail_content_before' ) ); 982 | 983 | $this->_gf_pre_render( $form, false, $entry, true ); 984 | 985 | } 986 | 987 | /** 988 | * Hook into check_admin_referer to setup Pods GF add-on hooks before updating form entry. 989 | * 990 | * @param string $action The nonce action. 991 | * @param false|int $result False if the nonce is invalid, 1 if the nonce is valid and generated between 992 | * 0-12 hours ago, 2 if the nonce is valid and generated between 12-24 hours ago. 993 | */ 994 | public function _check_admin_referer( $action, $result ) { 995 | 996 | // So hacky, we need GF to add a better hook than this. 997 | if ( $result && 'gforms_save_entry' === $action && class_exists( 'GFEntryDetail' ) ) { 998 | $form = GFEntryDetail::get_current_form(); 999 | 1000 | if ( $form ) { 1001 | $this->_gf_pre_process( $form ); 1002 | } 1003 | } 1004 | 1005 | } 1006 | 1007 | /** 1008 | * Hook into action to setup Pods GF add-on hooks before form entry edit form is shown. 1009 | * 1010 | * @param array|object $form GF form data. 1011 | * @param array|object $entry GF entry data. 1012 | */ 1013 | public function _gf_entry_detail_content_before( $form, $entry ) { 1014 | 1015 | $this->_gf_pre_render( $form, false, $entry, true ); 1016 | 1017 | } 1018 | 1019 | /** 1020 | * @param $form 1021 | * 1022 | * @return mixed 1023 | */ 1024 | public function _gf_pre_render( $form, $ajax = false, $entry = null, $admin_edit = false ) { 1025 | // Bad form / form ID. 1026 | if ( empty( $form ) ) { 1027 | return $form; 1028 | } 1029 | 1030 | static $setup = array(); 1031 | 1032 | if ( ! empty( $setup[ $form['id'] ] ) ) { 1033 | return $setup[ $form['id'] ]; 1034 | } 1035 | 1036 | $feeds = $this->get_feeds( $form['id'] ); 1037 | 1038 | if ( empty( $feeds ) ) { 1039 | $setup[ $form['id'] ] = $form; 1040 | 1041 | return $setup[ $form['id'] ]; 1042 | } 1043 | 1044 | $pod_fields = array(); 1045 | $pod_name = ''; 1046 | $feed = null; 1047 | 1048 | if ( empty( $entry ) ) { 1049 | $entry = GFFormsModel::get_current_lead(); 1050 | } 1051 | 1052 | foreach ( $feeds as $feed ) { 1053 | if ( 1 !== (int) $feed['is_active'] ) { 1054 | continue; 1055 | } 1056 | 1057 | if ( $admin_edit && empty( $feed['meta']['update_pod_item'] ) ) { 1058 | continue; 1059 | } 1060 | 1061 | $pod_fields = self::get_field_map_fields_with_custom_values( $feed, 'pod_fields' ); 1062 | $object_fields = self::get_field_map_fields_with_custom_values( $feed, 'wp_object_fields' ); 1063 | 1064 | $pod_fields = array_merge( $pod_fields, $object_fields ); 1065 | 1066 | $pod_name = $feed['meta']['pod']; 1067 | 1068 | break; 1069 | } 1070 | 1071 | if ( $pod_fields && $pod_name ) { 1072 | $pod_obj = pods( $pod_name, null, false ); 1073 | 1074 | if ( empty( $pod_obj ) || ! $pod_obj->valid() ) { 1075 | $setup[ $form['id'] ] = $form; 1076 | 1077 | return $setup[ $form['id'] ]; 1078 | } 1079 | 1080 | $dynamic_selects = array(); 1081 | 1082 | /** 1083 | * @var GF_Field $gf_field 1084 | */ 1085 | foreach ( $form['fields'] as $gf_field ) { 1086 | if ( empty( $gf_field->pods_populate_related_items ) ) { 1087 | continue; 1088 | } 1089 | 1090 | $pod_field = null; 1091 | 1092 | foreach ( $pod_fields as $k => $field_options ) { 1093 | if ( (string) $gf_field->id === (string) $field_options['gf_field'] ) { 1094 | $pod_field = $field_options['field']; 1095 | } 1096 | } 1097 | 1098 | if ( empty( $pod_field ) ) { 1099 | continue; 1100 | } 1101 | 1102 | $pod_field_options = $pod_obj->fields( $pod_field ); 1103 | 1104 | if ( empty( $pod_field_options ) ) { 1105 | continue; 1106 | } 1107 | 1108 | // Override limit for autocomplete 1109 | $object_params = array( 1110 | 'limit' => -1, 1111 | ); 1112 | 1113 | $data = PodsForm::field_method( $pod_field_options['type'], 'get_field_data', $pod_field_options, array(), $object_params ); 1114 | 1115 | if ( empty( $data ) ) { 1116 | continue; 1117 | } 1118 | 1119 | if ( isset( $data[''] ) ) { 1120 | unset( $data[''] ); 1121 | } 1122 | 1123 | $select_text = pods_v( $pod_field_options['type'] . '_select_text', $pod_field_options['options'], __( '-- Select One --', 'pods' ), true ); 1124 | 1125 | $options = array( 1126 | 'options' => $data, 1127 | ); 1128 | 1129 | if ( $select_text ) { 1130 | $options['select_text'] = $select_text; 1131 | } 1132 | 1133 | $dynamic_selects[ $gf_field->id ] = $options; 1134 | } 1135 | 1136 | if ( ! empty( $dynamic_selects ) ) { 1137 | $form = Pods_GF::gf_dynamic_select( $form, false, $dynamic_selects ); 1138 | } 1139 | } 1140 | 1141 | $form = $this->_gf_pre_process( $form ); 1142 | 1143 | $setup[ $form['id'] ] = $form; 1144 | 1145 | return $setup[ $form['id'] ]; 1146 | 1147 | } 1148 | 1149 | /** 1150 | * @param $form 1151 | * 1152 | * @return mixed 1153 | */ 1154 | public function _gf_pre_process( $form ) { 1155 | 1156 | static $setup = array(); 1157 | 1158 | if ( ! empty( $setup[ $form['id'] ] ) ) { 1159 | return $setup[ $form['id'] ]; 1160 | } 1161 | 1162 | $feeds = $this->get_feeds( $form['id'] ); 1163 | 1164 | $entry = GFFormsModel::get_current_lead(); 1165 | 1166 | if ( ! empty( $feeds ) ) { 1167 | foreach ( $feeds as $feed ) { 1168 | if ( 1 !== (int) $feed['is_active'] || ! $this->is_feed_condition_met( $feed, $form, $entry ) ) { 1169 | continue; 1170 | } 1171 | 1172 | $this->setup_pods_gf( $form, $feed ); 1173 | } 1174 | } 1175 | 1176 | $setup[ $form['id'] ] = $form; 1177 | 1178 | return $setup[ $form['id'] ]; 1179 | 1180 | } 1181 | 1182 | /** 1183 | * @param array $form Form object. 1184 | * @param array $feed Feed object. 1185 | * 1186 | * @return boolean 1187 | */ 1188 | public function setup_pods_gf( $form, $feed ) { 1189 | 1190 | // Block new post being created in GF 1191 | add_filter( 'gform_disable_post_creation_' . $form['id'], '__return_true' ); 1192 | 1193 | $pod_fields = self::get_field_map_fields_with_custom_values( $feed, 'pod_fields' ); 1194 | $object_fields = self::get_field_map_fields_with_custom_values( $feed, 'wp_object_fields' ); 1195 | $custom_fields = self::get_field_map_custom_fields_with_custom_values( $feed ); 1196 | 1197 | $fields = array_merge( $pod_fields, $object_fields, $custom_fields ); 1198 | 1199 | $options = array( 1200 | // array ( 'gf_field_id' => 'pod_field_name' ) 1201 | 'fields' => $fields, 1202 | 'update_pod_item' => 1 === (int) pods_v( 'update_pod_item', $feed['meta'], 0 ), 1203 | 'markdown' => 1 === (int) pods_v( 'enable_markdown', $feed['meta'], 0 ), 1204 | 'auto_delete' => 1 === (int) pods_v( 'delete_entry', $feed['meta'], 0 ), 1205 | 'gf_to_pods_priority' => 'submission', 1206 | ); 1207 | 1208 | // Setup pod object 1209 | $pod = pods( $feed['meta']['pod'] ); 1210 | 1211 | $edit_id = 0; 1212 | $prepopulate = false; 1213 | $prepopulate_id = 0; 1214 | 1215 | if ( 'user' === $pod->pod_data['type'] && is_user_logged_in() ) { 1216 | // Support user data editing 1217 | if ( 1 === (int) pods_v( 'enable_current_user', $feed['meta'], 0 ) ) { 1218 | $edit_id = get_current_user_id(); 1219 | } 1220 | 1221 | // Support prepopulating 1222 | if ( 1 === (int) pods_v( 'enable_prepopulate', $feed['meta'], 0 ) ) { 1223 | $prepopulate = true; 1224 | 1225 | $prepopulate_id = get_current_user_id(); 1226 | } 1227 | } elseif ( in_array( $pod->pod_data['type'], array( 'post_type', 'media' ), true ) && is_singular( $pod->pod ) ) { 1228 | // Support post data editing 1229 | if ( 1 === (int) pods_v( 'enable_current_post', $feed['meta'], 0 ) ) { 1230 | $edit_id = get_the_ID(); 1231 | } 1232 | 1233 | // Support prepopulating 1234 | if ( 1 === (int) pods_v( 'enable_prepopulate', $feed['meta'], 0 ) ) { 1235 | $prepopulate = true; 1236 | 1237 | $prepopulate_id = get_the_ID(); 1238 | } 1239 | } 1240 | 1241 | /** 1242 | * Allow filtering of which item ID to use when editing (default none, always add new items) 1243 | * 1244 | * @param int $edit_id Edit ID 1245 | * @param string $pod_name Pod name 1246 | * @param int $form_id GF Form ID 1247 | * @param array $feed GF Form feed array 1248 | * @param array $form GF Form array 1249 | * @param array $options Pods GF options 1250 | * @param Pods $pod Pods object 1251 | */ 1252 | $edit_id = (int) apply_filters( 'pods_gf_addon_edit_id', $edit_id, $feed['meta']['pod'], $form['id'], $feed, $form, $options, $pod ); 1253 | 1254 | /** 1255 | * Allow filtering of whether to prepopulate form fields (default none) 1256 | * 1257 | * @param bool $prepopulate Whether to prepopulate or not 1258 | * @param string $pod_name Pod name 1259 | * @param int $form_id GF Form ID 1260 | * @param array $feed GF Form feed array 1261 | * @param array $form GF Form array 1262 | * @param array $options Pods GF options 1263 | * @param Pods $pod Pods object 1264 | */ 1265 | $prepopulate = (boolean) apply_filters( 'pods_gf_addon_prepopulate', $prepopulate, $feed['meta']['pod'], $form['id'], $feed, $form, $options, $pod ); 1266 | 1267 | if ( empty( $edit_id ) && $prepopulate ) { 1268 | /** 1269 | * Allow filtering of which item ID to use when prepopulating form fields (default is same as Edit ID) 1270 | * 1271 | * @param int $prepopulate_id ID to use when prepopulating 1272 | * @param string $pod_name Pod name 1273 | * @param int $form_id GF Form ID 1274 | * @param array $feed GF Form feed array 1275 | * @param array $form GF Form array 1276 | * @param array $options Pods GF options 1277 | * @param Pods $pod Pods object 1278 | */ 1279 | $prepopulate_id = (int) apply_filters( 'pods_gf_addon_prepopulate_id', $prepopulate_id, $feed['meta']['pod'], $form['id'], $feed, $form, $options, $pod ); 1280 | } 1281 | 1282 | if ( 0 < $edit_id ) { 1283 | $options['edit'] = true; 1284 | 1285 | if ( $prepopulate ) { 1286 | $options['prepopulate'] = true; 1287 | } 1288 | 1289 | $pod->fetch( $edit_id ); 1290 | } elseif ( $prepopulate && 0 < $prepopulate_id ) { 1291 | $options['prepopulate'] = true; 1292 | 1293 | $pod->fetch( $prepopulate_id ); 1294 | } 1295 | 1296 | /** 1297 | * Allow filtering of Pods GF options to set custom settings apart from Pods GF add-on options 1298 | * 1299 | * @param array $options Pods GF options 1300 | * @param string $pod_name Pod name 1301 | * @param int $form_id GF Form ID 1302 | * @param array $feed GF Form feed array 1303 | * @param array $form GF Form array 1304 | * @param Pods $pod Pods object 1305 | */ 1306 | $options = apply_filters( 'pods_gf_addon_options', $options, $feed['meta']['pod'], $form['id'], $feed, $form, $pod ); 1307 | 1308 | /** 1309 | * Allow filtering of Pods GF options to set custom settings apart from Pods GF add-on options based on form ID. 1310 | * 1311 | * @param array $options Pods GF options. 1312 | * @param string $pod_name Pod name. 1313 | * @param int $form_id GF Form ID. 1314 | * @param array $feed GF Form feed array. 1315 | * @param array $form GF Form array. 1316 | * @param Pods $pod Pods object. 1317 | */ 1318 | $options = apply_filters( 'pods_gf_addon_options_' . $form['id'], $options, $feed['meta']['pod'], $form['id'], $feed, $form, $pod ); 1319 | 1320 | $this->pods_gf[ $feed['id'] ] = pods_gf( $pod, $form['id'], $options ); 1321 | 1322 | $setup[ $form['id'] ] = true; 1323 | 1324 | return true; 1325 | 1326 | } 1327 | 1328 | /** 1329 | * Add custom merge tags for Pods GF. 1330 | * 1331 | * @param array $merge_tags Merge tags. 1332 | * @param int $form_id Form ID. 1333 | * 1334 | * @return array Merge tags, 1335 | */ 1336 | public function _gf_custom_merge_tags( $merge_tags, $form_id ) { 1337 | 1338 | $merge_tags[] = array( 1339 | 'tag' => '{pods:id}', 1340 | 'label' => esc_html__( 'Pods GF Item ID', 'pods-gravity-forms' ), 1341 | ); 1342 | 1343 | $merge_tags[] = array( 1344 | 'tag' => '{pods:permalink}', 1345 | 'label' => esc_html__( 'Pods GF Item Permalink', 'pods-gravity-forms' ), 1346 | ); 1347 | 1348 | return $merge_tags; 1349 | 1350 | } 1351 | 1352 | /** 1353 | * Add custom merge tags for Pods GF. 1354 | * 1355 | * @param array $data Merge tag data. 1356 | * @param string $text Content to replace custom merge tags in. 1357 | * @param false|array $form GF form object. 1358 | * 1359 | * @return array Merge tag data. 1360 | */ 1361 | public function _gf_add_merge_tags( $data, $text, $form ) { 1362 | 1363 | if ( empty( $form ) || empty( $form['id'] ) ) { 1364 | return $data; 1365 | } 1366 | 1367 | $form_id = $form['id']; 1368 | 1369 | $id = 0; 1370 | $permalink = ''; 1371 | 1372 | if ( ! empty( Pods_GF::$gf_to_pods_id[ $form_id ] ) ) { 1373 | $id = Pods_GF::$gf_to_pods_id[ $form_id ]; 1374 | } 1375 | 1376 | if ( ! empty( Pods_GF::$gf_to_pods_id[ $form_id . '_permalink' ] ) ) { 1377 | $permalink = Pods_GF::$gf_to_pods_id[ $form_id . '_permalink' ]; 1378 | } 1379 | 1380 | $data['pods'] = array( 1381 | 'id' => $id, 1382 | 'permalink' => $permalink, 1383 | ); 1384 | 1385 | return $data; 1386 | 1387 | } 1388 | 1389 | /** 1390 | * Replace custom merge tags in content for Pods GF. 1391 | * 1392 | * @param string $content Content to replace custom merge tags in. 1393 | * @param false|array $form GF form object. 1394 | * 1395 | * @return string Content with custom merge tags replaced. 1396 | */ 1397 | public function _gf_replace_merge_tags( $content, $form ) { 1398 | 1399 | if ( empty( $form ) || empty( $form['id'] ) ) { 1400 | return $content; 1401 | } 1402 | 1403 | $form_id = $form['id']; 1404 | 1405 | $id = 0; 1406 | $permalink = ''; 1407 | 1408 | if ( ! empty( Pods_GF::$gf_to_pods_id[ $form_id ] ) ) { 1409 | $id = Pods_GF::$gf_to_pods_id[ $form_id ]; 1410 | } 1411 | 1412 | if ( ! empty( Pods_GF::$gf_to_pods_id[ $form_id . '_permalink' ] ) ) { 1413 | $permalink = Pods_GF::$gf_to_pods_id[ $form_id . '_permalink' ]; 1414 | } 1415 | 1416 | // For backcompat purposes. 1417 | $id_merge_tags = array( 1418 | '{pods:id}', 1419 | '{gf_to_pods_id}', 1420 | '{@gf_to_pods_id}', 1421 | ); 1422 | 1423 | // For backcompat purposes. 1424 | $permalink_merge_tags = array( 1425 | '{pods:permalink}', 1426 | '{gf_to_pods_permalink}', 1427 | '{@gf_to_pods_permalink}', 1428 | ); 1429 | 1430 | $content = str_replace( $id_merge_tags, $id, $content ); 1431 | $content = str_replace( $permalink_merge_tags, $permalink, $content ); 1432 | 1433 | return $content; 1434 | 1435 | } 1436 | 1437 | /** 1438 | * Get field map field values with support for custom override values. 1439 | * 1440 | * This differs from get_field_map_fields() in that it returns an array 1441 | * that is already formatted for Pods GF field mapping usage. 1442 | * 1443 | * @param array $feed 1444 | * @param string $field_name 1445 | * 1446 | * @return array 1447 | */ 1448 | public static function get_field_map_fields_with_custom_values( $feed, $field_name ) { 1449 | 1450 | $prefix = $field_name . '_'; 1451 | 1452 | $old_custom_prefix = $prefix . 'override_custom_'; 1453 | 1454 | $fields = array(); 1455 | 1456 | $skip = array(); 1457 | 1458 | foreach ( $feed['meta'] as $config_field_name => $config ) { 1459 | $config_field_name = (string) $config_field_name; 1460 | $gf_field_custom = sprintf( '%s_custom', $config_field_name ); 1461 | 1462 | if ( in_array( $config_field_name, $skip, true ) ) { 1463 | continue; 1464 | } 1465 | 1466 | if ( 0 === strpos( $config_field_name, $old_custom_prefix ) ) { 1467 | // Skip override values (old way) 1468 | continue; 1469 | } 1470 | 1471 | if ( 0 !== strpos( $config_field_name, $prefix ) && $field_name !== $config_field_name ) { 1472 | continue; 1473 | } 1474 | 1475 | // Get field name 1476 | $field_name = substr( $config_field_name, strlen( $prefix ) ); 1477 | 1478 | // Mapping value is the GF field ID 1479 | $gf_field = trim( $config ); 1480 | 1481 | // Mapping value 1482 | $mapping_value = array( 1483 | 'gf_field' => $gf_field, 1484 | 'field' => $field_name, 1485 | ); 1486 | 1487 | if ( 'gf_custom' === $gf_field ) { 1488 | // Support override value settings (new way) 1489 | $gf_field = sprintf( '_pods_gf_custom_%s', $field_name ); 1490 | 1491 | $mapping_value['gf_field'] = $gf_field; 1492 | $mapping_value['value'] = ''; 1493 | 1494 | if ( ! empty( $feed['meta'][ $gf_field_custom ] ) ) { 1495 | $value = trim( $feed['meta'][ $gf_field_custom ] ); 1496 | 1497 | if ( ! empty( $value ) ) { 1498 | $mapping_value['value'] = $value; 1499 | 1500 | $mapping_value['gf_merge_tags'] = true; 1501 | } 1502 | } 1503 | } elseif ( '_pods_custom' === $gf_field ) { 1504 | // Support override value settings (old way) 1505 | $gf_field = sprintf( '_pods_gf_custom_%s', $field_name ); 1506 | 1507 | $mapping_value['gf_field'] = $gf_field; 1508 | $mapping_value['value'] = ''; 1509 | 1510 | if ( ! empty( $feed['meta'][ $old_custom_prefix ] ) ) { 1511 | $value = trim( $feed['meta'][ $old_custom_prefix ] ); 1512 | 1513 | if ( ! empty( $value ) ) { 1514 | $mapping_value['value'] = $value; 1515 | 1516 | $mapping_value['gf_merge_tags'] = true; 1517 | } 1518 | } 1519 | } 1520 | 1521 | $skip[] = $gf_field_custom; 1522 | 1523 | if ( ! empty( $gf_field ) ) { 1524 | $fields[] = $mapping_value; 1525 | } 1526 | } 1527 | 1528 | return $fields; 1529 | 1530 | } 1531 | 1532 | /** 1533 | * Get field map field values with support for custom override values. 1534 | * 1535 | * This differs from get_field_map_fields() in that it returns an array 1536 | * that is already formatted for Pods GF field mapping usage. 1537 | * 1538 | * @param array $feed 1539 | * 1540 | * @return array 1541 | */ 1542 | public static function get_field_map_custom_fields_with_custom_values( $feed ) { 1543 | 1544 | if ( empty( $feed['meta']['custom_fields'] ) ) { 1545 | return array(); 1546 | } 1547 | 1548 | $configs = $feed['meta']['custom_fields']; 1549 | 1550 | $fields = array(); 1551 | 1552 | foreach ( $configs as $config ) { 1553 | $config = array_map( 'trim', $config ); 1554 | 1555 | $gf_field = $config['value']; 1556 | $field_name = $config['key']; 1557 | 1558 | if ( in_array( $field_name, array( 'gf_custom', '' ), true ) ) { 1559 | $field_name = $config['custom_key']; 1560 | } 1561 | 1562 | // Mapping value 1563 | $mapping_value = array( 1564 | 'gf_field' => $gf_field, 1565 | 'field' => $field_name, 1566 | ); 1567 | 1568 | if ( in_array( $gf_field, array( 'gf_custom', '' ), true ) && ! empty( $config['custom_value'] ) ) { 1569 | $mapping_value['gf_field'] = sprintf( '_pods_gf_custom_%s', $field_name ); 1570 | $mapping_value['value'] = $config['custom_value']; 1571 | $mapping_value['gf_merge_tags'] = true; 1572 | } 1573 | 1574 | if ( '' === $mapping_value['gf_field'] || '' === $mapping_value['field'] ) { 1575 | continue; 1576 | } 1577 | 1578 | $fields[] = $mapping_value; 1579 | } 1580 | 1581 | return $fields; 1582 | 1583 | } 1584 | 1585 | /** 1586 | * @param array $entry_meta 1587 | * @param int $form_id 1588 | * 1589 | * @return array 1590 | */ 1591 | public function get_entry_meta( $entry_meta, $form_id ) { 1592 | 1593 | if ( $this->has_feed( $form_id ) ) { 1594 | $entry_meta['_pods_item_id'] = array( 1595 | 'label' => 'Pod Item ID', 1596 | 'is_numeric' => true, 1597 | 'is_default_column' => true, 1598 | 'update_entry_meta_callback' => array( $this, 'update_entry_meta_pod_id' ), 1599 | 'filter' => array( 1600 | 'operators' => array( 1601 | 'is', 1602 | 'isnot', 1603 | '>', 1604 | '<', 1605 | ), 1606 | ), 1607 | ); 1608 | } 1609 | 1610 | return $entry_meta; 1611 | 1612 | } 1613 | 1614 | /** 1615 | * @param $key 1616 | * @param $entry 1617 | * @param $form 1618 | * 1619 | * @return int 1620 | */ 1621 | public function update_entry_meta_pod_id( $key, $entry, $form ) { 1622 | 1623 | if ( ! empty( Pods_GF::$gf_to_pods_id[ $form['id'] ] ) ) { 1624 | $value = Pods_GF::$gf_to_pods_id[ $form['id'] ]; 1625 | } else { 1626 | $value = 0; 1627 | } 1628 | 1629 | return $value; 1630 | 1631 | } 1632 | 1633 | /** 1634 | * Registers hooks which need to be included before the init hook is triggered. 1635 | * 1636 | * @since 1.4.2 1637 | * @access public 1638 | */ 1639 | public function pre_init() { 1640 | 1641 | add_filter( 'gform_export_form', array( $this, '_gf_export_form' ) ); 1642 | add_action( 'gform_forms_post_import', array( $this, '_gf_forms_post_import' ) ); 1643 | 1644 | parent::pre_init(); 1645 | 1646 | } 1647 | 1648 | /** 1649 | * Adds form feeds to form object during export. 1650 | * 1651 | * @since 1.4.2 1652 | * @access public 1653 | * 1654 | * @param array $form The form to be exported. 1655 | * 1656 | * @uses GFFeedAddOn::get_feeds() 1657 | * 1658 | * @return array 1659 | */ 1660 | public function _gf_export_form( $form ) { 1661 | 1662 | // Get feeds for form. 1663 | $feeds = $this->get_feeds( $form['id'] ); 1664 | 1665 | // If feeds array does not exist for form, create it. 1666 | if ( ! isset( $form['feeds'] ) ) { 1667 | $form['feeds'] = array(); 1668 | } 1669 | 1670 | // Add feeds to form. 1671 | $form['feeds'][ $this->_slug ] = $feeds; 1672 | 1673 | return $form; 1674 | 1675 | } 1676 | 1677 | /** 1678 | * Imports the feeds for the newly imported forms. 1679 | * 1680 | * @since 1.4.2 1681 | * @access public 1682 | * 1683 | * @param array $forms The imported forms. 1684 | * 1685 | * @uses GFAPI::add_feed() 1686 | * @uses GFAPI::get_form() 1687 | * @uses GFAPI::update_form() 1688 | * @uses GFFeedAddOn::update_feed_active() 1689 | */ 1690 | public function _gf_forms_post_import( $forms ) { 1691 | 1692 | // Loop through imported forms. 1693 | foreach ( $forms as $import_form ) { 1694 | 1695 | // Get latest version of form object. 1696 | $form = GFAPI::get_form( $import_form['id'] ); 1697 | 1698 | // If no feeds are found for form, skip. 1699 | if ( ! rgars( $form, 'feeds/' . $this->_slug ) ) { 1700 | continue; 1701 | } 1702 | 1703 | // Import feeds. 1704 | foreach ( $form['feeds'][ $this->_slug ] as $feed ) { 1705 | 1706 | // Add feed. 1707 | $new_feed_id = GFAPI::add_feed( $form['id'], $feed['meta'], $this->_slug ); 1708 | 1709 | // Set active status. 1710 | if ( ! $feed['is_active'] ) { 1711 | $this->update_feed_active( $new_feed_id, false ); 1712 | } 1713 | 1714 | } 1715 | 1716 | // Remove Pods feeds from form object. 1717 | unset( $form['feeds'][ $this->_slug ] ); 1718 | 1719 | // If no other feeds are found, remove feeds array. 1720 | if ( empty( $form['feeds'] ) ) { 1721 | unset( $form['feeds'] ); 1722 | } 1723 | 1724 | // Save form. 1725 | GFAPI::update_form( $form ); 1726 | 1727 | } 1728 | 1729 | } 1730 | 1731 | } 1732 | 1733 | new Pods_GF_Addon(); 1734 | -------------------------------------------------------------------------------- /includes/Pods_GF_CLI.php: -------------------------------------------------------------------------------- 1 | 14 | * : The Gravity Form ID. 15 | * 16 | * [--feed=] 17 | * : The Gravity Form Pods Feed ID. 18 | * 19 | * ## EXAMPLES 20 | * 21 | * wp pods-gf sync --form=123 --feed=2 22 | * 23 | * @param $args 24 | * @param $assoc_args 25 | * 26 | * @throws \WP_CLI\ExitException 27 | */ 28 | public function sync( $args, $assoc_args ) { 29 | 30 | add_filter( 'pods_gf_to_pods_update_pod_items', '__return_true' ); 31 | 32 | $form_id = 0; 33 | 34 | if ( ! empty( $assoc_args['form'] ) ) { 35 | $form_id = absint( $assoc_args['form'] ); 36 | } 37 | 38 | if ( empty( $form_id ) ) { 39 | \WP_CLI::error( esc_html__( 'Form ID is required.', 'pods-gravity-forms' ) ); 40 | } 41 | 42 | $feed_id = 0; 43 | 44 | if ( ! empty( $assoc_args['feed'] ) ) { 45 | $feed_id = absint( $assoc_args['feed'] ); 46 | } 47 | 48 | // Get form. 49 | $form = \GFAPI::get_form( $form_id ); 50 | 51 | if ( empty( $form ) || is_wp_error( $form ) ) { 52 | \WP_CLI::error( esc_html__( 'Form not found.', 'pods-gravity-forms' ) ); 53 | } 54 | 55 | $active_only = true; 56 | 57 | if ( 0 < $feed_id ) { 58 | $active_only = false; 59 | } 60 | 61 | // Get feed. 62 | $feeds = \GFAPI::get_feeds( $feed_id, $form_id, 'pods-gravity-forms', $active_only ); 63 | 64 | if ( empty( $feeds ) || is_wp_error( $feeds ) ) { 65 | \WP_CLI::error( esc_html__( 'Feed not found.', 'pods-gravity-forms' ) ); 66 | } 67 | 68 | // Use first feed. 69 | $feed = reset( $feeds ); 70 | 71 | $feed_id = $feed['id']; 72 | 73 | if ( empty( $feed_id ) ) { 74 | \WP_CLI::error( esc_html__( 'Invalid feed.', 'pods-gravity-forms' ) ); 75 | } 76 | 77 | /** @var Pods_GF_Addon $pods_gf_addon */ 78 | $pods_gf_addon = Pods_GF_Addon::get_instance(); 79 | 80 | $pods_gf_addon->setup_pods_gf( $form, $feed ); 81 | 82 | $pods_gf_addon->pods_gf[ $feed_id ]->options['update_pod_item'] = 1; 83 | 84 | $total_entries = 0; 85 | 86 | $search_criteria = array( 87 | 'status' => 'active', 88 | ); 89 | 90 | $paging = array( 91 | 'offset' => 0, 92 | 'page_size' => 50, 93 | ); 94 | 95 | $entries = \GFAPI::get_entries( $form_id, $search_criteria, null, $paging, $total_entries ); 96 | 97 | /** @var \cli\progress\Bar $progress_bar */ 98 | /* translators: Total entries number is used in this message. */ 99 | $progress_bar = \WP_CLI\Utils\make_progress_bar( sprintf( esc_html_x( 'Syncing %s entries', 'Sync status message for WP-CLI feed sync using total entries count', 'pods-gravity-forms' ), number_format_i18n( $total_entries ) ), $total_entries ); 100 | 101 | $entries_counter = 0; 102 | 103 | // Loop through all pages of entries and process feeds. 104 | do { 105 | // Loop through entries and process feed. 106 | foreach ( $entries as $entry ) { 107 | $pods_gf_addon->process_feed( $feed, $entry, $form ); 108 | 109 | Pods_GF::$actioned = []; 110 | 111 | $progress_bar->tick(); 112 | 113 | $entries_counter++; 114 | } 115 | 116 | $paging['offset'] = $entries_counter; 117 | 118 | $entries = \GFAPI::get_entries( $form_id, $search_criteria, null, $paging ); 119 | } while ( $entries ); 120 | 121 | $progress_bar->finish(); 122 | 123 | /* translators: Feed ID is used in this message. */ 124 | \WP_CLI::success( sprintf( esc_html_x( 'Form entries synced to Pods using feed %d.', 'Success message for WP-CLI feed sync using Feed ID', 'pods-gravity-forms' ), $feed_id ) ); 125 | 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /includes/Pods_GF_UI.php: -------------------------------------------------------------------------------- 1 | array(), 32 | 'header' => array(), 33 | 'actions_custom' => array(), 34 | 'actions_disabled' => array( 35 | 'reorder', 36 | 'duplicate', 37 | 'export' 38 | ), 39 | 'fields' => array(), 40 | 'restrict' => array() 41 | ); 42 | 43 | /** 44 | * @var string Current action 45 | */ 46 | public $action = 'manage'; 47 | 48 | /** 49 | * @var array Available actions 50 | */ 51 | public $actions = array( 52 | /* 53 | 'action_name' => array( 54 | 'label' => 'Label used in action link', 55 | 'heading' => 'Title used as action on action page', 56 | 'header' => 'Title used on action page', 57 | 58 | 'fields' => array( 59 | '1' => 'pod_field_name', // GF field ID or $_POST name, mapped to a pod field 60 | '2' => array( 61 | 'field' => 'pod_field_name' 62 | ), 63 | // All fields processed, even if not found/set in GF/$_POST 64 | '_faux' => array( 65 | 'field' => 'pod_field_name', 66 | 'value' => 'Manual value to use' 67 | ) 68 | ), 69 | 'pod' => 'mypod', // Can be used on other actions to override the pod used too 70 | 71 | 'save_action' => 'add', // What action to use when saving (add|save) 72 | 'save_id' => 123, // ID to override what to save to (if save action is save) 73 | 74 | 'callback' => null, // Function to callback on action page 75 | 'access_callback' => null, // Access callback to override access rights 76 | 77 | 'disabled' => false, // Whether action is disabled 78 | 79 | // GF specific 80 | 'form' => 123, // Form ID 81 | 'dynamic_select' => array( 82 | '1' => array( // GF Field ID 83 | 'pod' => 'other_pod', // Pod to pull data from 84 | 'field_text' => 'post_title', // Field to pull option text from, defaults to 'name' 85 | 'field_value' => 'ID', // Field to pull option value from, defaults to id() 86 | 87 | 'options' => array( // Custom array of options to use 88 | array( 89 | 'text' => 'Option 1', 90 | 'value' => 1 91 | ), 92 | array( 93 | 'text' => 'Option 2', 94 | 'value' => 2 95 | ) 96 | ), 97 | 'default' => 14, // Default value to select 98 | 'params' => array(), // Params to use for find() 99 | ) 100 | ), 101 | 102 | 'field' => 'status', // Pod field name for action to interact with (used with Status action) 103 | 'data' => array(), // Array of options available for field (used with Status action) 104 | 105 | 'prepopulate' => false, // Disable value prepopulate, defaults to true 106 | 107 | // Redirect after submission (confirmation redirect, or action=edit&id={entry_id}) 108 | 'redirect_after' => false, // Disable redirection after submission (defaults to false, true for 'edit') 109 | 110 | // Add a button to Save for Later the form being submitted, to come back to 111 | 'save_for_later' => true, // simple 112 | 'save_for_later' => array( 113 | 'redirect' => '/custom-url-to-redirect-to-on-save/', // Redirect to URL on Save for Later 114 | 'exclude_pages' => array( 1 ) // Exclude Pages from showing button 115 | ), 116 | 117 | // Remember values when they are submitted and prepopulate on next form load 118 | 'remember' => true, // simple 119 | 'remember' => array( 120 | 'fields' => array( 121 | '1', 122 | '5' 123 | ) 124 | ), 125 | 126 | // Make inputs read only 127 | 'read_only' => true, // simple 128 | 'read_only' => array( 129 | 'fields' => array( 130 | '1', 131 | '5' 132 | ) 133 | ), 134 | 135 | // Automatically delete the GF entry created, defaults to false 136 | 'auto_delete' => true, 137 | 138 | // Enable Markdown syntax for GF HTML fields, defaults to false 139 | 'markdown' => true, 140 | 141 | // Enable editing leads instead of inserting them 142 | 'edit' => true, 143 | 144 | // Customize the submit text of a form submit button on the fly 145 | 'submit_button' => 'http://mysite.com/my/site/my-image.png', // simple button image url 146 | 'submit_button' => 'Time to Submit', // simple button text 147 | 'submit_button' => array( 148 | 'imageUrl' => '/my/site/my-image.png', // use image url for button 149 | 150 | 'text' => 'Submit!' // use text for button 151 | ), 152 | 153 | // Add a secondary submit action to the form 154 | 'secondary_submits' => array( 155 | 'imageUrl' => '/my/site/my-image.png', // use image url for button 156 | 157 | 'text' => 'Submit!', // use text for button 158 | 159 | 'action' => 'secondary-action', // name of button: pods_gf_ui_action_{$action} 160 | 161 | 'value' => 'custom-value', // custom value of button (default is 1) 162 | 163 | 'value_from_ui' => 'next_id' // get value from PodsUI object (next_id|prev_id) 164 | ), 165 | // Multiple secondary submit buttons 166 | 'secondary_submits' => array( 167 | array( 168 | 'imageUrl' => '/my/site/my-image.png', // use image url for button 169 | 170 | 'text' => 'Submit!', // use text for button 171 | 172 | 'action' => 'secondary-action', // name of button: pods_gf_ui_action_{$action} 173 | 174 | 'value' => 'custom-value', // custom value of button (default is 1) 175 | 176 | 'value_from_ui' => 'prev_id' // get value from PodsUI object (next_id|prev_id) 177 | ), 178 | array( 179 | 'imageUrl' => '/my/site/my-image2.png', // use image url for button 180 | 181 | 'text' => 'Submit2!', // use text for button 182 | 183 | 'action' => 'secondary-action2', // name of button: pods_gf_ui_action_{$action} 184 | 185 | 'value' => 'custom-value2', // custom value of button (default is 1) 186 | 187 | 'value_from_ui' => 'next_id' // get value from PodsUI object (next_id|prev_id) 188 | ) 189 | ) 190 | 191 | // Customize the submit redirect URL of a form on the fly 192 | 'confirmation' => 'http://mysite.com/my/site/?id={entry_id}', // simple redirect 193 | 'confirmation' => '/my/site/?id={entry_id}', // simple redirect (relative site) 194 | 'confirmation' => '?id={entry_id}', // simple redirect (relative to path) 195 | 'confirmation' => 'Totally Awesome, Great Job!', // simple text 196 | 'confirmation' => array( 197 | 'url' => '/my/site/?id={entry_id}', // redirect to a url 198 | 199 | 'message' => 'Thanks!' // show a message 200 | ) 201 | ), 202 | */ 203 | 204 | 'manage' => array( // table ui manage 205 | 'form' => 0, 206 | 'fields' => array(), 207 | 'callback' => null, 208 | 'access_callback' => null, 209 | 'disabled' => false, 210 | 'prepopulate' => true 211 | ), 212 | 'add' => array( // form 213 | 'form' => 0, 214 | 'fields' => array(), 215 | 'dynamic_select' => array(), 216 | 'callback' => null, 217 | 'access_callback' => null, 218 | 'disabled' => false, 219 | 'prepopulate' => true 220 | ), 221 | 'edit' => array( // alternate form or original form (pre-populate data) 222 | 'form' => 0, 223 | 'fields' => array(), 224 | 'dynamic_select' => array(), 225 | 'callback' => null, 226 | 'access_callback' => null, 227 | 'edit' => true, 228 | 'disabled' => false, 229 | 'redirect_after' => true, 230 | 'prepopulate' => true 231 | ), 232 | 'status' => array( // switching status, can define field name (default 'status') 233 | 'label' => 'Change Status', 234 | 'form' => 0, 235 | 'field' => 'status', 236 | 'data' => array(), // what stati to use (dynamically build, based on pod/field) 237 | 'fields' => array(), 238 | 'dynamic_select' => array(), 239 | 'callback' => null, 240 | 'access_callback' => null, 241 | 'edit' => true, 242 | 'disabled' => true 243 | ), 244 | 'view' => array( // view details 245 | 'label' => 'View Details', 246 | 'fields' => array(), 247 | 'callback' => null, 248 | 'access_callback' => null, 249 | 'disabled' => true, 250 | 'read_only' => true, 251 | 'prepopulate' => true 252 | ), 253 | 'delete' => array( 254 | 'callback' => null, 255 | 'access_callback' => null, 256 | 'disabled' => true, 257 | 'keep_files' => false 258 | ) 259 | ); 260 | 261 | /** 262 | * @var string|null Access denied reason text 263 | */ 264 | public $access_reason; 265 | 266 | /** 267 | * @var array Workflow constraints for actions 268 | */ 269 | public $workflow_constraints = array( 270 | 'add' => array( 271 | 'limit' => -1, // simple limit 272 | 'limit_status' => array( // enhanced limit with params 273 | 'type' => 'limit', 274 | 'compare' => '<', 275 | 'value' => -1, 276 | 'params' => array( 277 | 278 | ), 279 | ) 280 | ) 281 | ); 282 | 283 | /** 284 | * @var array Notification configs 285 | */ 286 | public $notifications = array( 287 | /* 288 | 'action_name' => array( 289 | // User IDs, E-mails, or Roles 290 | 'template-name-or-id' => array( 291 | 1, 292 | 2, 293 | 'email@site.com', 294 | 'administrator' 295 | ), 296 | 'template-name-or-id2' => 1, // Simple user 297 | 'template-name-or-id3' => 'email@site.com', // Simple e-mail 298 | 'template-name-or-id4' => 'administrator', // Simple role mapping 299 | ), 300 | */ 301 | ); 302 | 303 | /** 304 | * Setup Pods_GF_UI object 305 | * 306 | * @param array $options Pods_GF_UI option overrides 307 | */ 308 | public function __construct( $options ) { 309 | 310 | $this->init_ui(); 311 | 312 | if ( is_array( $options ) && !empty( $options ) ) { 313 | foreach ( $options as $option => $value ) { 314 | if ( isset( $this->{$option} ) && is_array( $this->{$option} ) && is_array( $value ) ) { 315 | foreach ( $value as $k => $v ) { 316 | if ( isset( $this->{$option}[ $k ] ) && 'ui' != $option ) { 317 | $this->{$option}[ $k ] = array_merge( $this->{$option}[ $k ], $v ); 318 | } 319 | else { 320 | $this->{$option}[ $k ] = $v; 321 | } 322 | } 323 | } 324 | else { 325 | $this->{$option} = $value; 326 | } 327 | } 328 | } 329 | 330 | $this->setup_ui(); 331 | 332 | foreach ( $this->actions as $action => $action_data ) { 333 | $form_id = (int) pods_v( 'form', $action_data ); 334 | 335 | if ( !pods_v( 'disabled', $action_data ) && 0 < $form_id && $this->action == $action ) { 336 | $pod = pods_v( 'pod', $action_data, $this->pod ); 337 | 338 | $pods_gf = pods_gf( $pod, $form_id, $action_data ); 339 | 340 | self::$pods_gf = $pods_gf; 341 | 342 | break; 343 | } 344 | } 345 | 346 | } 347 | 348 | /** 349 | * Initialize the default options 350 | */ 351 | private function init_ui() { 352 | 353 | foreach ( $this->actions as $action => $options ) { 354 | if ( !$this->access( $action ) ) { 355 | $this->actions[ $action ][ 'disabled' ] = true; 356 | 357 | $this->ui[ 'actions_disabled' ][ $action ] = $action; 358 | } 359 | 360 | if ( isset( $this->actions[ $action ][ 'form_id' ] ) ) { 361 | $this->actions[ $action ][ 'form' ] = $this->actions[ $action ][ 'form_id' ]; 362 | } 363 | } 364 | 365 | $this->actions[ 'add' ][ 'callback' ] = array( $this, '_action_add' ); 366 | $this->actions[ 'edit' ][ 'callback' ] = array( $this, '_action_edit' ); 367 | $this->actions[ 'view' ][ 'callback' ] = array( $this, '_action_view' ); 368 | $this->actions[ 'delete' ][ 'callback' ] = array( $this, '_action_delete' ); 369 | 370 | $this->action = pods_v( 'action', 'get', $this->action, true ); 371 | 372 | } 373 | 374 | /** 375 | * Setup UI from options 376 | */ 377 | private function setup_ui() { 378 | 379 | $defaults = array( 380 | 'heading' => '', 381 | 'header' => '', 382 | 'label' => '', 383 | 'label_alt' => '', 384 | 'form' => 0, 385 | 'edit' => false, 386 | 'fields' => array(), 387 | 'dynamic_select' => array(), 388 | 'callback' => null, 389 | 'callback_copy' => null, 390 | 'access_callback' => null, 391 | 'content' => null, 392 | 'action_data' => array(), 393 | 'disabled' => false, 394 | 'prepopulate' => true, 395 | 'save_for_later' => array(), 396 | ); 397 | 398 | $id = (int) pods_v( 'id' ); 399 | 400 | if ( 0 < $this->id ) { 401 | $id = $this->id; 402 | } 403 | 404 | foreach ( $this->actions as $action => $options ) { 405 | $this->actions[ $action ] = $options = array_merge( $defaults, $options ); 406 | 407 | if ( !empty( $options[ 'heading' ] ) && empty( $this->ui[ 'heading' ][ $action ] ) ) { 408 | $this->ui[ 'heading' ][ $action ] = $options[ 'heading' ]; 409 | } 410 | 411 | if ( !empty( $options[ 'header' ] ) && empty( $this->ui[ 'header' ][ $action ] ) ) { 412 | $this->ui[ 'header' ][ $action ] = $options[ 'header' ]; 413 | } 414 | 415 | if ( !empty( $options[ 'label' ] ) ) { 416 | $this->ui[ 'label' ][ $action ] = $options[ 'label' ]; 417 | 418 | if ( empty( $this->ui[ 'heading' ][ $action ] ) ) { 419 | $this->ui[ 'heading' ][ $action ] = $options[ 'label' ]; 420 | } 421 | 422 | if ( empty( $this->ui[ 'header' ][ $action ] ) ) { 423 | $this->ui[ 'header' ][ $action ] = $options[ 'label' ]; 424 | } 425 | 426 | if ( 'add' == $action ) { 427 | $this->ui[ 'label' ][ 'add_new' ] = $options[ 'label' ]; 428 | } 429 | } 430 | elseif ( 'add' == $action && !empty( $options[ 'label_alt' ] ) ) { 431 | $this->ui[ 'label' ][ 'add_new' ] = $options[ 'label_alt' ]; 432 | } 433 | 434 | if ( !empty( $options[ 'callback' ] ) ) { 435 | if ( in_array( $action, array( 'add', 'edit' ) ) ) { 436 | $this->ui[ 'actions_custom' ][ $action ] = array( 437 | 'callback' => $options[ 'callback' ], 438 | ); 439 | } 440 | else { 441 | $this->ui[ 'actions_custom' ][ $action ] = array( 442 | 'callback' => $options[ 'callback' ], 443 | ); 444 | } 445 | } 446 | elseif ( !empty( $options[ 'callback_copy' ] ) ) { 447 | if ( is_array( $this->ui[ 'actions_custom' ][ $options[ 'callback_copy' ] ] ) && isset( $this->ui[ 'actions_custom' ][ $options[ 'callback_copy' ] ][ 'callback' ] ) ) { 448 | $this->ui[ 'actions_custom' ][ $action ] = array( 449 | 'callback' => $this->ui[ 'actions_custom' ][ $options[ 'callback_copy' ] ][ 'callback' ], 450 | ); 451 | } 452 | else { 453 | $this->ui[ 'actions_custom' ][ $action ] = array( 454 | 'callback' => $this->ui[ 'actions_custom' ][ $options[ 'callback_copy' ] ], 455 | ); 456 | } 457 | } elseif ( ( ! empty( $options['form'] ) || ! empty( $options['content'] ) ) && 'manage' !== $action ) { 458 | $this->ui[ 'actions_custom' ][ $action ] = array( 459 | 'callback' => array( $this, '_action_custom' ), 460 | ); 461 | } 462 | else { 463 | $this->ui['actions_custom'][ $action ] = array( 464 | 'label' => $action, 465 | ); 466 | } 467 | 468 | if ( ! empty( $options['content'] ) ) { 469 | $this->ui[ 'actions_custom' ][ $action ]['content'] = $options['content']; 470 | } 471 | 472 | if ( !empty( $options[ 'action_data' ] ) ) { 473 | $this->ui[ 'actions_custom' ][ $action ] = array_merge( $this->ui[ 'actions_custom' ][ $action ], (array) pods_v( 'action_data', $options, null, true ) ); 474 | } 475 | 476 | if ( !empty( $options[ 'action_link' ] ) ) { 477 | $this->ui[ 'action_links' ][ $action ] = pods_v( 'action_link', $options, null, true ); 478 | } 479 | 480 | if ( !empty( $options[ 'fields' ] ) ) { 481 | $this->ui[ 'fields' ][ $action ] = $options[ 'fields' ]; 482 | } 483 | } 484 | 485 | if ( 0 < $this->actions[ 'manage' ][ 'form' ] ) { 486 | $this->pod = array(); 487 | 488 | $total_found = 0; 489 | 490 | if ( 0 < $id ) { 491 | $lead = GFAPI::get_entry( $id ); 492 | 493 | if ( !empty( $lead ) && ! is_wp_error( $lead ) && 'active' === $lead['status'] ) { 494 | $this->pod = array( 495 | $lead[ 'id' ] => $lead 496 | ); 497 | 498 | // @todo Replace the below code when GF adds functionality to get all lead meta 499 | global $wpdb, $_gform_lead_meta; 500 | 501 | $gf_meta_table = pods_gf_get_gf_table_name( 'entry_meta' ); 502 | 503 | $old_schema = version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ); 504 | 505 | $lead_id_column_name = $old_schema ? 'lead_id' : 'entry_id'; 506 | 507 | $gf_meta = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value FROM {$gf_meta_table} WHERE {$lead_id_column_name} = %d", $lead[ 'id' ] ) ); 508 | 509 | foreach ( $gf_meta as $gf_meta_value ) { 510 | $meta_value = maybe_unserialize( $gf_meta_value->meta_value ); 511 | 512 | $cache_key = $lead[ 'id' ] . "_" . $gf_meta_value->meta_key; 513 | 514 | $_gform_lead_meta[ $cache_key ] = $meta_value; 515 | 516 | $this->pod[ $lead[ 'id' ] ][ $gf_meta_value->meta_key ] = $meta_value; 517 | } 518 | 519 | $this->id = $id; 520 | } 521 | 522 | $total_found = count( $this->pod ); 523 | } 524 | else { 525 | $search_criteria = apply_filters( 'pods_gf_ui_search_criteria', array( 526 | 'status' => 'active', 527 | ) ); 528 | $sorting = apply_filters( 'pods_gf_ui_sorting', null ); 529 | $paging = array( 530 | 'offset' => 0, 531 | 'page_size' => 20 532 | ); 533 | 534 | $page = (int) pods_v( 'pg', 'get', 1, true ); 535 | $offset = ( ( $page - 1 ) * $paging[ 'page_size' ] ); 536 | 537 | $paging[ 'offset' ] += $offset; 538 | 539 | $leads = GFAPI::get_entries( $this->actions[ 'manage' ][ 'form' ], $search_criteria, $sorting, $paging, $total_found ); 540 | 541 | // @todo Hook into save for later data and display saved entries in the list like normal entries 542 | 543 | foreach ( $leads as $lead ) { 544 | $this->pod[ $lead[ 'id' ] ] = $lead; 545 | 546 | // @todo Replace the below code when GF adds functionality to get all lead meta 547 | global $wpdb, $_gform_lead_meta; 548 | 549 | $gf_meta_table = pods_gf_get_gf_table_name( 'entry_meta' ); 550 | 551 | $old_schema = version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ); 552 | 553 | $lead_id_column_name = $old_schema ? 'lead_id' : 'entry_id'; 554 | 555 | $gf_meta = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value FROM {$gf_meta_table} WHERE {$lead_id_column_name} = %d", $lead[ 'id' ] ) ); 556 | 557 | foreach ( $gf_meta as $gf_meta_value ) { 558 | $meta_value = maybe_unserialize( $gf_meta_value->meta_value ); 559 | 560 | $cache_key = $lead[ 'id' ] . "_" . $gf_meta_value->meta_key; 561 | 562 | $_gform_lead_meta[ $cache_key ] = $meta_value; 563 | 564 | $this->pod[ $lead[ 'id' ] ][ $gf_meta_value->meta_key ] = $meta_value; 565 | } 566 | } 567 | } 568 | 569 | $this->pod = apply_filters( 'pods_gf_ui_leads', $this->pod, $this->action, $this ); 570 | 571 | $default_ui = array( 572 | 'data' => $this->pod, 573 | 'total' => count( $this->pod ), 574 | 'total_found' => $total_found, 575 | 'searchable' => false, 576 | 'sortable' => false, 577 | 'pagination' => true 578 | ); 579 | 580 | if ( 0 < $this->id && !empty( $this->pod ) ) { 581 | $this->pod = current( $this->pod ); 582 | } 583 | 584 | if ( empty( $this->ui[ 'fields' ][ 'manage' ] ) ) { 585 | $this->ui[ 'fields' ][ 'manage' ] = array( 586 | 'id' => array( 587 | 'label' => 'ID', 588 | 'type' => 'number' 589 | ), 590 | 'created_by' => array( 591 | 'label' => 'Submitter', 592 | 'type' => 'pick', 593 | 'pick_val' => 'user' 594 | ), 595 | 'date_created' => array( 596 | 'label' => 'Date Created', 597 | 'type' => 'datetime' 598 | ) 599 | ); 600 | } 601 | 602 | $this->ui = array_merge( $default_ui, $this->ui ); 603 | } 604 | 605 | if ( is_array( $this->pod ) ) { 606 | $default_ui = array( 607 | 'data' => $this->pod, 608 | 'total' => count( $this->pod ), 609 | 'total_found' => count( $this->pod ), 610 | 'searchable' => false, 611 | 'sortable' => false, 612 | 'pagination' => false 613 | ); 614 | 615 | $this->ui = array_merge( $default_ui, $this->ui ); 616 | } 617 | elseif ( !is_object( $this->pod ) && !empty( $this->pod ) ) { 618 | $this->pod = pods( $this->pod, ( 0 < $id ? $id : null ) ); 619 | $this->id = $this->pod->id(); 620 | } 621 | 622 | if ( 0 < $this->id ) { 623 | $_GET[ 'id' ] = $this->id; 624 | } 625 | 626 | foreach ( $this->actions as $action => $options ) { 627 | if ( false === pods_v( 'disabled', $this->actions[ $action ], false, true ) ) { 628 | if ( in_array( $action, $this->ui[ 'actions_disabled' ] ) ) { 629 | unset( $this->ui[ 'actions_disabled' ][ array_search( $action, $this->ui[ 'actions_disabled' ] ) ] ); 630 | } 631 | } 632 | elseif ( !in_array( $action, $this->ui[ 'actions_disabled' ] ) ) { 633 | $this->ui[ 'actions_disabled' ][] = $action; 634 | } 635 | } 636 | 637 | if ( !isset( $this->actions[ $this->action ] ) || !$this->access( $this->action ) ) { 638 | $this->action = 'manage'; 639 | } 640 | 641 | $_GET[ 'action' ] = $this->action; 642 | $this->ui[ 'action' ] = $this->action; 643 | 644 | } 645 | 646 | /** 647 | * Handle current action 648 | * 649 | * @param array $args 650 | * 651 | * @return bool|mixed|PodsUI 652 | */ 653 | public function action( $args = array() ) { 654 | 655 | $ui = false; 656 | 657 | if ( isset( $GLOBALS[ 'pods-gf-ui-off' ] ) && $GLOBALS[ 'pods-gf-ui-off' ] ) { 658 | return $ui; 659 | } 660 | 661 | $GLOBALS[ 'pods-gf-ui-off' ] = true; 662 | 663 | $args = array_merge( 664 | array( 665 | 'action' => $this->action 666 | ), 667 | $args 668 | ); 669 | 670 | $action = $args[ 'action' ]; 671 | 672 | if ( isset( $this->actions[ $action ] ) ) { 673 | // Pods object 674 | // $this->pod = pods( 'pod_name' ); 675 | if ( is_object( $this->pod ) ) { 676 | $ui = $this->pod->ui( $this->ui, true ); 677 | } 678 | // An array of GF entries $lead_id => $entry 679 | // $this->pod = array( .... ); 680 | elseif ( is_array( $this->pod ) ) { 681 | $ui = pods_ui( $this->ui ); 682 | } 683 | else { 684 | do_action( 'pods_gf_ui_action_' . $action, $this, $args ); 685 | } 686 | } 687 | 688 | $GLOBALS[ 'pods-gf-ui-off' ] = false; 689 | 690 | return $ui; 691 | 692 | } 693 | 694 | /** 695 | * Run UI, just shorthand for action() 696 | * 697 | * @param array $args 698 | * 699 | * @return bool|mixed|PodsUI 700 | * @see action 701 | */ 702 | public function ui( $args = array() ) { 703 | 704 | return $this->action( $args ); 705 | 706 | } 707 | 708 | /** 709 | * Check if user has access to a specific action 710 | * 711 | * @param string $action Action name 712 | * 713 | * @return bool Whether user has access to a specific action 714 | */ 715 | private function access( $action ) { 716 | 717 | $access = true; 718 | 719 | // Action disabled 720 | if ( true === pods_v( 'disabled', $this->actions[ $action ], false, true ) || in_array( $action, $this->ui[ 'actions_disabled' ] ) ) { 721 | $access = false; 722 | $this->access_reason = 'Action disabled'; 723 | } 724 | 725 | // Workflow constraints 726 | if ( $access && isset( $this->workflow_constraints[ $action ] ) ) { 727 | $constraints = array(); 728 | 729 | foreach ( $this->workflow_constraints[ $action ] as $constraint => $constraint_options ) { 730 | $constraints[ $constraint ] = $constraint_options; 731 | 732 | // Non-arrays are always limit 733 | if ( !is_array( $constraint_options ) ) { 734 | $constraints[ $constraint ] = array( 735 | 'type' => 'limit', 736 | 'compare' => '<', 737 | 'value' => (int) $constraint_options 738 | ); 739 | } 740 | } 741 | 742 | if ( !empty( $constraints ) ) { 743 | $access = $this->access_constraints( $constraints ); 744 | } 745 | } 746 | 747 | // @todo replace callbacks with apply_filters or do_action 748 | // Access Callback 749 | $access_callback = pods_v( 'access_callback', $this->actions[ $action ], null, true ); 750 | 751 | if ( null !== $access_callback && is_callable( $access_callback ) ) { 752 | $access = call_user_func( $access_callback, $access, $this ); 753 | } 754 | 755 | return (boolean) $access; 756 | 757 | } 758 | 759 | /** 760 | * Handle access constraints 761 | * 762 | * @param array $constraints Constraints arrays 763 | * 764 | * @return bool Whether the constraint rules all passed 765 | */ 766 | public function access_constraints( $constraints ) { 767 | 768 | $access = true; 769 | 770 | if ( is_array( $constraints ) && !empty( $constraints ) && is_object( $this->pod ) ) { 771 | $check = pods( $this->pod->pod ); 772 | 773 | foreach ( $constraints as $constraint ) { 774 | $constraint = array_merge( 775 | array( 776 | 'type' => 'limit', 777 | 'compare' => '<', 778 | 'value' => -1, 779 | 'params' => array() 780 | ), 781 | $constraint 782 | ); 783 | 784 | // Limit constraints 785 | if ( 'limit' == $constraint[ 'type' ] ) { 786 | $constraint[ 'value' ] = (int) $constraint[ 'value' ]; 787 | 788 | // Invalid value 789 | if ( $constraint[ 'value' ] < 0 ) { 790 | continue; 791 | } 792 | 793 | $params = array_merge( 794 | array( 795 | 'limit' => 1 // doesn't matter what's returned, we look at total_found() 796 | ), 797 | $constraint[ 'params' ] 798 | ); 799 | 800 | $check->find( $params ); 801 | 802 | // version_compare is the poor developer's math comparison function 803 | if ( !version_compare( (string) $check->total_found(), (string) $constraint[ 'value' ], $constraint[ 'compare' ] ) ) { 804 | $access = false; 805 | $this->access_reason = 'Limit constraint reached'; 806 | } 807 | } 808 | } 809 | } 810 | 811 | return $access; 812 | 813 | } 814 | 815 | /** 816 | * Embed Add form 817 | * 818 | * @param PodsUI $obj 819 | */ 820 | public function _action_add( $obj ) { 821 | 822 | self::$pods_ui =& $obj; 823 | 824 | if ( $obj->restricted( $obj->action ) ) { 825 | return; 826 | } 827 | ?> 828 |
829 |
icon ) { ?> style="background-position:0 0;background-size:100%;background-image:url(icon; ?>);">
830 |

831 | header[ 'add' ]; 833 | 834 | if ( !in_array( 'manage', $obj->actions_disabled ) && !in_array( 'manage', $obj->actions_hidden ) && ! $obj->restricted( 'manage' ) ) { 835 | $link = pods_var_update( array( 'action' . $obj->num => 'manage', 'id' . $obj->num => '' ), PodsUI::$allowed, $obj->exclusion() ); 836 | 837 | if ( !empty( $obj->action_links[ 'manage' ] ) ) { 838 | $link = $obj->action_links[ 'manage' ]; 839 | } 840 | ?> 841 | « heading[ 'manage' ] ); ?> 842 | 845 |

846 | 847 | pod, $obj, $this ); 849 | 850 | if ( isset( $this->actions[ $this->action ][ 'form' ] ) && 0 < $this->actions[ $this->action ][ 'form' ] ) { 851 | gravity_form_enqueue_scripts( $this->actions[ $this->action ][ 'form' ] ); 852 | 853 | wp_print_scripts(); 854 | wp_print_styles(); 855 | 856 | gravity_form( $this->actions[ $this->action ][ 'form' ], false, false ); 857 | } 858 | elseif ( is_object( $this->pod ) ) { 859 | $this->pod->form(); 860 | } 861 | else { 862 | do_action( 'pods_gf_ui' . __FUNCTION__ . '_form', $this->pod, $obj, $this ); 863 | } 864 | 865 | do_action( 'pods_gf_ui' . __FUNCTION__ . '_post', $this->pod, $obj, $this ); 866 | ?> 867 |
868 | row ) ) { 891 | $obj->get_row(); 892 | } 893 | 894 | if ( empty( $obj->row ) ) { 895 | $obj->message( sprintf( __( '%s not found.', 'pods-gravity-forms' ), $obj->item ) ); 896 | 897 | return; 898 | } 899 | 900 | if ( $obj->restricted( $obj->action, $obj->row ) ) { 901 | return $obj->error( sprintf( __( 'Error: You do not have access to this %s.', 'pods' ), $obj->item ) ); 902 | } 903 | ?> 904 |
905 |
icon ) { ?> style="background-position:0 0;background-size:100%;background-image:url(icon; ?>);">
906 |

907 | do_template( $duplicate ? $obj->header[ 'duplicate' ] : $obj->header[ $obj->action ] ); 909 | 910 | if ( !in_array( 'manage', $obj->actions_disabled ) && !in_array( 'manage', $obj->actions_hidden ) && ! $obj->restricted( 'manage' ) ) { 911 | $link = pods_var_update( array( 'action' . $obj->num => 'manage', 'id' . $obj->num => '' ), PodsUI::$allowed, $obj->exclusion() ); 912 | 913 | if ( !empty( $obj->action_links[ 'manage' ] ) ) { 914 | $link = $obj->action_links[ 'manage' ]; 915 | } 916 | ?> 917 | « heading[ 'manage' ] ); ?> 918 | 921 |

922 | 923 | pod, $obj, $this ); 925 | 926 | if ( isset( $this->actions[ $this->action ][ 'form' ] ) && 0 < $this->actions[ $this->action ][ 'form' ] ) { 927 | gravity_form_enqueue_scripts( $this->actions[ $this->action ][ 'form' ] ); 928 | 929 | gravity_form( $this->actions[ $this->action ][ 'form' ], false, false ); 930 | } 931 | elseif ( is_object( $this->pod ) ) { 932 | $this->pod->form(); 933 | } 934 | else { 935 | do_action( 'pods_gf_ui' . __FUNCTION__ . '_form', $this->pod, $duplicate, $obj, $this ); 936 | } 937 | 938 | do_action( 'pods_gf_ui' . __FUNCTION__ . '_post', $this->pod, $obj, $this ); 939 | ?> 940 |
941 | actions[ $this->action ] ); 955 | 956 | if ( empty( $obj->row ) ) { 957 | $obj->get_row(); 958 | } 959 | 960 | if ( empty( $obj->row ) ) { 961 | $obj->message( sprintf( __( '%s not found.', 'pods-gravity-forms' ), $obj->item ) ); 962 | 963 | return; 964 | } 965 | 966 | if ( $obj->restricted( $obj->action, $obj->row ) ) { 967 | return; 968 | } 969 | ?> 970 |
971 |
icon ) { ?> style="background-position:0 0;background-size:100%;background-image:url(icon; ?>);">
972 |

973 | do_template( $obj->header[ $obj->action ] ); 975 | 976 | if ( !in_array( 'manage', $obj->actions_disabled ) && !in_array( 'manage', $obj->actions_hidden ) && ! $obj->restricted( 'manage' ) ) { 977 | $link = pods_var_update( array( 'action' . $obj->num => 'manage', 'id' . $obj->num => '' ), PodsUI::$allowed, $obj->exclusion() ); 978 | 979 | if ( !empty( $obj->action_links[ 'manage' ] ) ) { 980 | $link = $obj->action_links[ 'manage' ]; 981 | } 982 | ?> 983 | « heading[ 'manage' ] ); ?> 984 | 987 |

988 | 989 | pod, $obj, $this ); 991 | 992 | if ( isset( $this->actions[ $this->action ][ 'form' ] ) && 0 < $this->actions[ $this->action ][ 'form' ] ) { 993 | gravity_form_enqueue_scripts( $this->actions[ $this->action ][ 'form' ] ); 994 | 995 | gravity_form( $this->actions[ $this->action ][ 'form' ], false, false ); 996 | } 997 | elseif ( is_object( $this->pod ) ) { 998 | echo $this->pod->view( $fields ); 999 | } 1000 | else { 1001 | do_action( 'pods_gf_ui' . __FUNCTION__, $this->pod, $obj, $this ); 1002 | } 1003 | 1004 | do_action( 'pods_gf_ui' . __FUNCTION__ . '_post', $this->pod, $obj, $this ); 1005 | ?> 1006 |
1007 | restricted( $obj->action, $obj->row ) ) { 1021 | return; 1022 | } 1023 | 1024 | if ( is_object( $this->pod ) ) { 1025 | return; // continue as normal 1026 | } 1027 | 1028 | if ( is_array( $this->pod ) ) { 1029 | pods_gf(); 1030 | 1031 | Pods_GF::gf_delete_entry( $id, $this->actions[ 'delete' ][ 'keep_files' ] ); 1032 | 1033 | unset( $obj->data[ $id ] ); 1034 | 1035 | $obj->total = count( $obj->data ); 1036 | $obj->total_found = count( $obj->data ); 1037 | } 1038 | else { 1039 | do_action( 'pods_gf_ui' . __FUNCTION__, $this->id, $this->pod, $obj, $this ); 1040 | } 1041 | 1042 | $obj->message( sprintf( __( '%s deleted successfully.', 'pods-gravity-forms' ), $obj->item ) ); 1043 | 1044 | return null; 1045 | 1046 | } 1047 | 1048 | /** 1049 | * Embed Custom form 1050 | * 1051 | * @param PodsUI $obj 1052 | */ 1053 | public function _action_custom( $obj ) { 1054 | 1055 | self::$pods_ui =& $obj; 1056 | 1057 | if ( $obj->restricted( $obj->action, $obj->row ) ) { 1058 | return; 1059 | } 1060 | ?> 1061 |
1062 |
icon ) { ?> style="background-position:0 0;background-size:100%;background-image:url(icon; ?>);">
1063 |

1064 | header[ $this->action ]; 1066 | 1067 | if ( !in_array( 'manage', $obj->actions_disabled ) && !in_array( 'manage', $obj->actions_hidden ) && ! $obj->restricted( 'manage' ) ) { 1068 | $link = pods_var_update( array( 'action' . $obj->num => 'manage', 'id' . $obj->num => '' ), PodsUI::$allowed, $obj->exclusion() ); 1069 | 1070 | if ( !empty( $obj->action_links[ 'manage' ] ) ) { 1071 | $link = $obj->action_links[ 'manage' ]; 1072 | } 1073 | ?> 1074 | « heading[ 'manage' ] ); ?> 1075 | 1078 |

1079 | 1080 | pod, $obj, $this ); 1082 | 1083 | if ( ! empty( $this->actions[ $this->action ]['content'] ) ) { 1084 | /** 1085 | * Filter content for page 1086 | * 1087 | * @param string $content 1088 | * @param Pods_GF_UI $pods_gf_ui 1089 | */ 1090 | $content = apply_filters( 'pods_gf_ui_content', $this->actions[ $this->action ]['content'], $this ); 1091 | $content = apply_filters( 'the_content', $content, 0 ); 1092 | 1093 | echo $content; 1094 | } elseif ( isset( $this->actions[ $this->action ]['form'] ) && 0 < $this->actions[ $this->action ]['form'] ) { 1095 | gravity_form_enqueue_scripts( $this->actions[ $this->action ]['form'] ); 1096 | 1097 | wp_print_scripts(); 1098 | wp_print_styles(); 1099 | 1100 | gravity_form( $this->actions[ $this->action ]['form'], false, false ); 1101 | } elseif ( is_object( $this->pod ) ) { 1102 | $this->pod->form(); 1103 | } else { 1104 | do_action( 'pods_gf_ui' . __FUNCTION__ . '_form', $this->pod, $obj, $this ); 1105 | } 1106 | 1107 | do_action( 'pods_gf_ui' . __FUNCTION__ . '_post', $this->pod, $obj, $this ); 1108 | ?> 1109 |
1110 | ui( $args ); 49 | 50 | return ob_get_clean(); 51 | } 52 | 53 | return ''; 54 | 55 | } 56 | 57 | /** 58 | * Init Pods GF UI if there's a config to run 59 | */ 60 | function pods_gf_ui_init() { 61 | 62 | /** 63 | * @var $pods_gf_ui Pods_GF_UI 64 | */ 65 | global $pods_gf_ui, $pods_gf_ui_loaded, $post; 66 | 67 | do_action( 'pods_gf_init' ); 68 | 69 | $options = array(); 70 | 71 | $path = explode( '?', $_SERVER[ 'REQUEST_URI' ] ); 72 | $path = explode( '#', $path[ 0 ] ); 73 | $path = trim( $path[ 0 ], '/' ); 74 | 75 | $page = null; 76 | 77 | if ( is_singular() ) { 78 | if ( is_object( $post ) && ( is_single( $post ) || is_page( $post ) ) ) { 79 | $page = $post->post_name; 80 | } 81 | else { 82 | wp_reset_postdata(); 83 | 84 | if ( is_object( $post ) ) { 85 | $page = $post->post_name; 86 | } 87 | } 88 | } 89 | 90 | // Root 91 | if ( strlen( $path ) < 1 ) { 92 | $uri = '/'; 93 | 94 | $options = apply_filters( 'pods_gf_ui_init=' . $uri, $options, $uri, $page ); 95 | } 96 | // Pages and wildcards 97 | else { 98 | $uri = '/' . $path . '/'; 99 | 100 | $exploded_path = array_reverse( explode( '/', $path ) ); 101 | $exploded_w = $exploded_path; 102 | $total = count( $exploded_path ); 103 | 104 | foreach ( $exploded_path as $k => $exploded ) { 105 | if ( $k == ( $total - 1 ) ) { 106 | break; 107 | } 108 | 109 | $exploded_w[ $k ] = '*'; 110 | 111 | $wildcard_uri = '/' . implode( '/', array_reverse( $exploded_w ) ) . '/'; 112 | 113 | $options = apply_filters( 'pods_gf_ui_init=' . $wildcard_uri, $options, $uri, $page ); 114 | 115 | if ( ! is_array( $options ) ) { 116 | break; 117 | } 118 | } 119 | 120 | if ( is_array( $options ) ) { 121 | $options = apply_filters( 'pods_gf_ui_init=' . $uri, $options, $uri, $page ); 122 | } 123 | } 124 | 125 | if ( is_array( $options ) ) { 126 | $options = apply_filters( 'pods_gf_ui_init', $options, $uri, $page ); 127 | } 128 | 129 | // Bail on processing 130 | if ( empty( $options ) ) { 131 | return; 132 | } 133 | 134 | $pods_gf_ui = pods_gf_ui( $options ); 135 | 136 | do_action( 'pods_gf_ui_loaded', $pods_gf_ui, $options, $uri, $page ); 137 | 138 | // Add content handler 139 | add_filter( 'the_content', 'pods_gf_ui_content' ); 140 | 141 | } 142 | 143 | /** 144 | * Ouput Pods GF UI if there's a config set for the page 145 | * 146 | * @param string $content 147 | * @param int $post_id 148 | * 149 | * @return string Content 150 | */ 151 | function pods_gf_ui_content( $content, $post_id = 0 ) { 152 | 153 | if ( !apply_filters( 'pods_gf_ui_content_filter', true, $post_id ) ) { 154 | return $content; 155 | } 156 | 157 | global $post; 158 | 159 | if ( empty( $post_id ) && is_object( $post ) ) { 160 | $post_id = $post->ID; 161 | } 162 | 163 | if ( in_the_loop() && false === strpos( $content, '[pods-gf-ui' ) && !empty( $post_id ) && ( is_single( $post_id ) || is_page( $post_id ) ) ) { 164 | $content .= "\n" . pods_gf_ui_shortcode( array(), '' ); 165 | } 166 | 167 | return $content; 168 | 169 | } 170 | 171 | /** 172 | * Detect if there's a shortcode currently set in the content, if so, run it 173 | */ 174 | function pods_gf_ui_detect_shortcode() { 175 | 176 | /** 177 | * @var $pods_gf_ui Pods_GF_UI 178 | */ 179 | global $pods_gf_ui; 180 | 181 | if ( !is_object( $pods_gf_ui ) && is_singular() ) { 182 | global $post; 183 | 184 | $form_id = (int) pods_v( 'gform_submit', 'post' ); 185 | 186 | if ( 0 < $form_id && ! empty( $_POST[ 'is_submit_' . $form_id ] ) && preg_match( '/\[pods\-gf\-ui/i', $post->post_content ) ) { 187 | $form_info = GFFormsModel::get_form( $form_id ); 188 | 189 | if ( !empty( $form_info ) && $form_info->is_active ) { 190 | $GLOBALS[ 'pods-gf-ui-off' ] = true; 191 | 192 | do_shortcode( $post->post_content ); 193 | 194 | unset( $GLOBALS[ 'pods-gf-ui-off' ] ); 195 | } 196 | } 197 | } 198 | 199 | } 200 | 201 | /** 202 | * Run Admin AJAX for Save for Later 203 | */ 204 | function pods_gf_save_for_later_ajax() { 205 | 206 | require_once( PODS_GF_DIR . 'includes/Pods_GF.php' ); 207 | 208 | Pods_GF::gf_save_for_later_ajax(); 209 | 210 | } 211 | 212 | /** 213 | * Backwards-compatible method for retrieving GF table name 214 | * 215 | * @since 1.4 216 | * 217 | * @param $table_type 218 | * 219 | * @return string 220 | */ 221 | function pods_gf_get_gf_table_name( $table_type ){ 222 | 223 | $table_name = ''; 224 | 225 | $old_schema = version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ); 226 | 227 | 228 | switch( $table_type ) { 229 | 230 | case 'entry': 231 | 232 | $table_name = $old_schema ? GFFormsModel::get_lead_table_name() : GFFormsModel::get_entry_table_name(); 233 | 234 | break; 235 | 236 | case 'entry_details': 237 | 238 | $table_name = $old_schema ? GFFormsModel::get_lead_details_table_name() : GFFormsModel::get_entry_meta_table_name(); 239 | 240 | break; 241 | 242 | case 'entry_meta': 243 | 244 | $table_name = $old_schema ? GFFormsModel::get_lead_meta_table_name() : GFFormsModel::get_entry_meta_table_name(); 245 | 246 | break; 247 | 248 | } 249 | 250 | 251 | return $table_name; 252 | } -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 6 | 7 | Everyone is permitted to copy and distribute verbatim copies 8 | of this license document, but changing it is not allowed. 9 | 10 | Preamble 11 | 12 | The licenses for most software are designed to take away your 13 | freedom to share and change it. By contrast, the GNU General Public 14 | License is intended to guarantee your freedom to share and change free 15 | software--to make sure the software is free for all its users. This 16 | General Public License applies to most of the Free Software 17 | Foundation's software and to any other program whose authors commit to 18 | using it. (Some other Free Software Foundation software is covered by 19 | the GNU Library General Public License instead.) You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | this service if you wish), that you receive source code or can get it 26 | if you want it, that you can change the software or use pieces of it 27 | in new free programs; and that you know you can do these things. 28 | 29 | To protect your rights, we need to make restrictions that forbid 30 | anyone to deny you these rights or to ask you to surrender the rights. 31 | These restrictions translate to certain responsibilities for you if you 32 | distribute copies of the software, or if you modify it. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must give the recipients all the rights that 36 | you have. You must make sure that they, too, receive or can get the 37 | source code. And you must show them these terms so they know their 38 | rights. 39 | 40 | We protect your rights with two steps: (1) copyright the software, and 41 | (2) offer you this license which gives you legal permission to copy, 42 | distribute and/or modify the software. 43 | 44 | Also, for each author's protection and ours, we want to make certain 45 | that everyone understands that there is no warranty for this free 46 | software. If the software is modified by someone else and passed on, we 47 | want its recipients to know that what they have is not the original, so 48 | that any problems introduced by others will not reflect on the original 49 | authors' reputations. 50 | 51 | Finally, any free program is threatened constantly by software 52 | patents. We wish to avoid the danger that redistributors of a free 53 | program will individually obtain patent licenses, in effect making the 54 | program proprietary. To prevent this, we have made it clear that any 55 | patent must be licensed for everyone's free use or not licensed at all. 56 | 57 | The precise terms and conditions for copying, distribution and 58 | modification follow. 59 | 60 | GNU GENERAL PUBLIC LICENSE 61 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 62 | 63 | 0. This License applies to any program or other work which contains 64 | a notice placed by the copyright holder saying it may be distributed 65 | under the terms of this General Public License. The "Program", below, 66 | refers to any such program or work, and a "work based on the Program" 67 | means either the Program or any derivative work under copyright law: 68 | that is to say, a work containing the Program or a portion of it, 69 | either verbatim or with modifications and/or translated into another 70 | language. (Hereinafter, translation is included without limitation in 71 | the term "modification".) Each licensee is addressed as "you". 72 | 73 | Activities other than copying, distribution and modification are not 74 | covered by this License; they are outside its scope. The act of 75 | running the Program is not restricted, and the output from the Program 76 | is covered only if its contents constitute a work based on the 77 | Program (independent of having been made by running the Program). 78 | Whether that is true depends on what the Program does. 79 | 80 | 1. You may copy and distribute verbatim copies of the Program's 81 | source code as you receive it, in any medium, provided that you 82 | conspicuously and appropriately publish on each copy an appropriate 83 | copyright notice and disclaimer of warranty; keep intact all the 84 | notices that refer to this License and to the absence of any warranty; 85 | and give any other recipients of the Program a copy of this License 86 | along with the Program. 87 | 88 | You may charge a fee for the physical act of transferring a copy, and 89 | you may at your option offer warranty protection in exchange for a fee. 90 | 91 | 2. You may modify your copy or copies of the Program or any portion 92 | of it, thus forming a work based on the Program, and copy and 93 | distribute such modifications or work under the terms of Section 1 94 | above, provided that you also meet all of these conditions: 95 | 96 | a) You must cause the modified files to carry prominent notices 97 | stating that you changed the files and the date of any change. 98 | 99 | b) You must cause any work that you distribute or publish, that in 100 | whole or in part contains or is derived from the Program or any 101 | part thereof, to be licensed as a whole at no charge to all third 102 | parties under the terms of this License. 103 | 104 | c) If the modified program normally reads commands interactively 105 | when run, you must cause it, when started running for such 106 | interactive use in the most ordinary way, to print or display an 107 | announcement including an appropriate copyright notice and a 108 | notice that there is no warranty (or else, saying that you provide 109 | a warranty) and that users may redistribute the program under 110 | these conditions, and telling the user how to view a copy of this 111 | License. (Exception: if the Program itself is interactive but 112 | does not normally print such an announcement, your work based on 113 | the Program is not required to print an announcement.) 114 | 115 | These requirements apply to the modified work as a whole. If 116 | identifiable sections of that work are not derived from the Program, 117 | and can be reasonably considered independent and separate works in 118 | themselves, then this License, and its terms, do not apply to those 119 | sections when you distribute them as separate works. But when you 120 | distribute the same sections as part of a whole which is a work based 121 | on the Program, the distribution of the whole must be on the terms of 122 | this License, whose permissions for other licensees extend to the 123 | entire whole, and thus to each and every part regardless of who wrote it. 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | -------------------------------------------------------------------------------- /pods-gravity-forms.php: -------------------------------------------------------------------------------- 1 |

%s

', esc_html__( 'Pods Gravity Forms requires that the Pods and Gravity Forms core plugins be installed and activated.', 'pods-gravity-forms' ) ); 120 | } 121 | 122 | } 123 | 124 | /** 125 | * Add Advanced Related Objects 126 | * 127 | * @since 1.3 128 | */ 129 | function pods_gf_add_related_objects() { 130 | 131 | PodsField_Pick::$related_objects['gf-forms'] = [ 132 | 'label' => __( 'Forms', 'pods' ), 133 | 'group' => __( 'Gravity Forms', 'pods' ), 134 | 'simple' => true, 135 | 'data_callback' => 'pods_gf_add_related_objects_forms', 136 | ]; 137 | 138 | } 139 | 140 | add_action( 'pods_form_ui_field_pick_related_objects_other', 'pods_gf_add_related_objects' ); 141 | 142 | /** 143 | * Pods related data callback for GF Forms 144 | * 145 | * @param string $name The name of the field 146 | * @param string|array $value The value of the field 147 | * @param array $options Field options 148 | * @param array $pod Pod data 149 | * @param int $id Item ID 150 | * 151 | * @return array 152 | * 153 | * @since 1.3 154 | */ 155 | function pods_gf_add_related_objects_forms( $name = null, $value = null, $options = null, $pod = null, $id = null ) { 156 | 157 | $data = []; 158 | 159 | // Get all forms. 160 | $forms = RGFormsModel::get_forms( null, 'title' ); 161 | 162 | foreach ( $forms as $form ) { 163 | $form_title = $form->title; 164 | 165 | if ( 1 !== (int) $form->is_active ) { 166 | $form_title = sprintf( '%s (%s)', $form_title, __( 'inactive', 'pods-gravity-forms' ) ); 167 | } 168 | 169 | $data[ $form->id ] = $form_title; 170 | } 171 | 172 | return apply_filters( 'pods_form_ui_field_pick_' . __FUNCTION__, $data, $name, $value, $options, $pod, $id ); 173 | 174 | } 175 | 176 | /** 177 | * Register add-on with Pods Freemius connection. 178 | */ 179 | function pods_gravity_forms_freemius() { 180 | try { 181 | fs_dynamic_init( [ 182 | 'id' => '5754', 183 | 'slug' => 'pods-gravity-forms', 184 | 'type' => 'plugin', 185 | 'public_key' => 'pk_1aaaee6bf8963f2077405e84f2ac5', 186 | 'is_premium' => false, 187 | 'has_paid_plans' => false, 188 | 'is_org_compliant' => true, 189 | 'parent' => [ 190 | 'id' => '5347', 191 | 'slug' => 'pods', 192 | 'public_key' => 'pk_737105490825babae220297e18920', 193 | 'name' => 'Pods', 194 | ], 195 | 'menu' => [ 196 | 'slug' => 'pods-settings', 197 | 'contact' => false, 198 | 'support' => false, 199 | 'affiliation' => false, 200 | 'account' => true, 201 | 'pricing' => false, 202 | 'addons' => true, 203 | 'parent' => [ 204 | 'slug' => 'pods', 205 | ], 206 | ], 207 | ] ); 208 | } catch ( \Exception $exception ) { 209 | return; 210 | } 211 | } 212 | 213 | add_action( 'pods_freemius_init', 'pods_gravity_forms_freemius' ); 214 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === Pods Gravity Forms Add-On === 2 | Contributors: sc0ttkclark, jimtrue, naomicbush, gravityplus 3 | Donate link: https://friends.pods.io/ 4 | Tags: pods, gravity forms, form mapping 5 | Requires at least: 6.0 6 | Tested up to: 6.8 7 | Requires PHP: 7.2 8 | Stable tag: 1.5.1 9 | License: GPLv2 or later 10 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 11 | 12 | Integrate with Gravity Forms to create a Pod item from a form submission. 13 | 14 | == Description == 15 | 16 | * **Requires:** [Pods](https://wordpress.org/plugins/pods/) 3.0+, [Gravity Forms](https://pods.io/gravityforms/) 1.9+ 17 | * **Demo:** Want to try Pods GF out? Check out the [Gravity Forms Live Demo](https://www.gravityforms.com/gravity-forms-demo/) and install the Pods and Pods Gravity Forms plugins once you're there 18 | * **Bugs/Ideas:** Please report bugs or request features on [GitHub](https://github.com/pods-framework/pods-gravity-forms/) 19 | 20 | Special thanks to Rocketgenius for their sponsorship support and to Naomi C. Bush for her help in the initial add-on UI work. 21 | 22 | = WP-CLI Command for Syncing Entries = 23 | 24 | This add-on provides the ability to sync entries from a Form Submission and Entry Edit screen. To bulk sync all entries even prior to setting up a Pods Gravity Form Feed, you can run a WP-CLI command. 25 | 26 | **Example 1: Sync all entries for Form 123 first active Pod feed** 27 | 28 | `wp pods-gf sync --form=123` 29 | 30 | **Example 2: Sync all entries for Form 123 using a specific feed (even if it is inactive)** 31 | 32 | `wp pods-gf sync --form=123 --feed=2` 33 | 34 | = Mapping GF List Fields to a Pods Relationship field = 35 | 36 | You can map a GF List field to a Relationship field related to another Pod. Using the below examples you can customize how the automatic mapping works. By default, the list columns will map to the pod fields with the same labels. 37 | 38 | **Example 1: Customize what columns map to which Related Pod fields for Form ID 1, Field ID 2** 39 | 40 | Customizing a list field row can be done by using the `pods_gf_field_columns_mapping` filter, which has Form ID and Field ID variations (`pods_gf_field_columns_mapping_{form_id}` and `pods_gf_field_columns_mapping_{form_id}_{field_id}`). 41 | 42 | ` 43 | add_filter( 'pods_gf_field_columns_mapping_1_2', 'my_columns_mapping', 10, 4 ); 44 | 45 | /** 46 | * Filter list columns mapping for related pod fields. 47 | * 48 | * @param array $columns List field columns. 49 | * @param array $form GF form. 50 | * @param GF_Field $gf_field GF field data. 51 | * @param Pods $pod Pods object. 52 | * 53 | * @return array 54 | */ 55 | function my_columns_mapping( $columns, $form, $gf_field, $related_obj ) { 56 | 57 | $columns[0] = 'first_field'; 58 | $columns[1] = 'second_field'; 59 | $columns[2] = 'third_field'; 60 | 61 | return $columns; 62 | 63 | } 64 | ` 65 | 66 | **Example 2: Customize a List row for Form ID 1, Field ID 2** 67 | 68 | Customizing a list field row can be done by using the `pods_gf_field_column_row` filter, which has Form ID and Field ID variations (`pods_gf_field_column_row_{form_id}` and `pods_gf_field_column_row_{form_id}_{field_id}`). 69 | 70 | ` 71 | add_filter( 'pods_gf_field_column_row_1_2', 'my_column_row_override', 10, 6 ); 72 | 73 | /** 74 | * Filter list field row for relationship field saving purposes. 75 | * 76 | * @param array $row List field row. 77 | * @param array $columns List field columns. 78 | * @param array $form GF form. 79 | * @param GF_Field $gf_field GF field data. 80 | * @param array $options Pods GF options. 81 | * @param Pods|false $related_obj Related Pod object. 82 | * 83 | * @return array 84 | */ 85 | function my_column_row_override( $row, $columns, $form, $gf_field, $options, $related_obj ) { 86 | 87 | // Update certain row fields based on the value of specific column. 88 | if ( ! empty( $row['user_relationship_field'] ) ) { 89 | $user = get_userdata( (int) $row['user'] ); 90 | 91 | // Set the post_title to match the User display name. 92 | if ( $user && ! is_wp_error( $user ) ) { 93 | $row['post_title'] = $user->display_name; 94 | } 95 | } 96 | 97 | return $row; 98 | 99 | } 100 | ` 101 | 102 | == Screenshots == 103 | 104 | 1. In the Pods Admin, create your Pods and Pod Fields: Pods Admin -> Add New 105 | 2. In the Pods Admin, create your Pods and Pod Fields: Pod Edit Screen 106 | 3. Create your Gravity Form that will be used to create a Pod item 107 | 4. Form Settings->Pods menu 108 | 5. Pods feed page 109 | 6. Map form fields to Pod fields 110 | 7. Example form 111 | 8. New Pod item created from form submission 112 | 9. Form entries page showing Pod ID 113 | 114 | == Changelog == 115 | 116 | = 1.5.1 - March 30th, 2025 = 117 | 118 | * Fixed: Resolved issue with auto-delete option when using Pods feeds. (@sc0ttkclark) 119 | * Tested against WP 6.8. (@sc0ttkclark) 120 | 121 | = 1.5.0 - March 29th, 2024 = 122 | 123 | * New requirements that match Pods: WP 6.0+, PHP 7.2+, and Pods 3.0+ to prep for Pods Gravity Forms 2.0 (@sc0ttkclark) 124 | * Added: Support value overrides for checkbox GF fields when prepopulating. (@sc0ttkclark) 125 | * Added: Allow for passing `?pods_gf_debug=1` to a form submit URL to debug the form submission mapping to Pods which outputs debug information and stops before the save runs). (@sc0ttkclark) 126 | * Added: New hook `pods_gf_dynamic_select_show_empty_option` lets you disable showing the "empty option" in dynamic fields being prepopulated. (@sc0ttkclark) 127 | * Added: New hook `pods_gf_addon_options_{$form_id}` that lets you filter the options built for a feed for the `Pods_GF` object. (@sc0ttkclark) 128 | * Tweak: New `pods-gf-ui-view-only` class added to the view-only mode. (@sc0ttkclark) 129 | * Tweak: Expanded secondary submit handling with the ability to have cancel button. (@sc0ttkclark) 130 | * Fixed: Resolved various PHP notices. (@sc0ttkclark) 131 | * Fixed: Removed "comments" from the field mapping options in the feed. (@sc0ttkclark) 132 | * Fixed: Remove extra HTML in the feed labels. (@sc0ttkclark) 133 | * Fixed: For non-select GF field types, trim the dashes on the custom `select_text` option used. (@sc0ttkclark) 134 | * Fixed: Prepopulating field values works more consistently now when passing the prepopulated filter pre-chunked arrays of values. (@sc0ttkclark) 135 | * Fixed: View-only mode forced to use page zero. (@sc0ttkclark) 136 | * Fixed: Prevent duplicate submissions during feed processing. (@sc0ttkclark) 137 | * Fixed: Only allow working with active leads in `Pods_GF_UI`. (@sc0ttkclark) 138 | * Fixed: Prevent Markdown conflicts with other plugins and update PHP 8 compatibility by switching to the Parsedown library. #166 (@sc0ttkclark) 139 | * Fixed: Stop prepopulating fields that aren't opted-in to it. #168 (@sc0ttkclark) 140 | 141 | ALSO: Pods Gravity Forms 2.0 is still in development and it brings complete compatibility with the latest Gravity Forms releases. We could use your support to help it get over the finish line this year. Please consider [donating to the Pods project](https://friends.pods.io/) to help us get there more quickly. 142 | 143 | = 1.4.5 - July 22nd, 2022 = 144 | 145 | * Tested against WP 6.0 146 | * Added: Not seeing something map correctly? As a site admin, you now have the power to debug the form submission and see what might be going on. Add `?pods_gf_debug_gf_to_pods=1` to the URL of the form action before submitting to take advantage of the admin-only debug mode. This will output the values as they would be sent to Pods, the entry information used to reference it, and the feed options used at the time. It will stop the form from completely saving to Pods so you can tweak and debug your form feeds however much you'd like to perfect them. (@sc0ttkclark) 147 | * Fixed: Conditional checks for feeds has been resolved and now won't get confused when there are multiple feeds for the same form in certain cases. (@sc0ttkclark) 148 | * Fixed: Additional compatibility with Gravity Flow. #157 (@JoryHogeveen) 149 | * Pods 2.9 is in beta and after it is released, new mapping of GF List Fields to Pods repeatable fields will be added. This add-on will also be updated with minimum version requirements updated for WP 5.5+, Pods 2.8+, and Gravity Forms 2.5+. Complete testing will be done at that time to ensure complete compatibility. 150 | 151 | = 1.4.4 - October 6th, 2021 = 152 | 153 | * Tested against WP 5.8 154 | * Get ready for Pods 2.8 in just a week! This add-on will receive updates to ensure it is compatible with the latest Gravity Forms and the changes in Pods 2.8 155 | 156 | = 1.4.3 - March 26th, 2020 = 157 | 158 | * Added: Now requiring PHP 5.4+ 159 | * Added: Freemius support when running Pods 2.7.17 160 | * Fixed: Prepopulate handling for relationship fields. 161 | * Fixed: Prevent errors when form doesn't exist by the time it gets to our hook. 162 | 163 | = 1.4.2 - March 2nd, 2020 = 164 | 165 | * Fixed: Ajax handling for various callbacks that hook into `gform_pre_render`. 166 | * Fixed: Cleaned up logic and prevent PHP notices with multi-select arrays when setting up choices arrays. 167 | * Fixed: Make sure `Pods_GF_UI` does not return false on UI callbacks to prevent access errors. 168 | * Fixed: Add mapping feeds to the import/export! (props @travislopes) 169 | 170 | = 1.4.1 - October 16th, 2018 = 171 | 172 | * Fixed: When syncing multiple entries, the field values were caching and not unique per entry resulting in what appeared to be duplicated content inserts/updates. 173 | 174 | = 1.4 - October 16th, 2018 = 175 | 176 | * Support: Added support for Gravity Forms 2.3 database tables changes (You may see a warning on the Edit Pod screen but this is a false positive because we cache a list of all tables to transients and it triggers the warning solved by removing those old "rg" tables) 177 | * Changed: Backwards compatibility issue -- You can now more easily set custom override values, however the old style was not able to be brought over -- you'll want to update your feeds when possible, the old values will not show up and you'll have to select the custom override value option once more, then fill it in 178 | * Changed: Backwards compatibility issue -- Now requiring WordPress 4.6+ 179 | * Feature: When editing entries in the admin area, changes now sync to the associated Pod item (except trash/deletes) 180 | * Feature: New Bulk Entry Syncing to Pods WP-CLI command `wp pods-gf sync --form=123` or you can specify which feed (even if it is not active) with `wp pods-gf sync --form=123 --feed=2` 181 | * Feature: Support for List field mapping to a Pod field which ends up serializing the value, but can be prepopulated back into the Gravity Form 182 | * Feature: List field mapping to relationship fields related to another Pod (list columns map to individual fields in the related Pod) with new filters `pods_gf_field_columns_mapping` and `pods_gf_field_column_row` 183 | * Feature: Support for Chained Select field mapping to a Pod field 184 | * Feature: New Custom fields section added for Pods that support meta (Posts, Terms, Users, Media, and Comments), you can set additional custom fields including ability to set custom values there too 185 | * Feature: Ability to set conditional processing per feed, based on specific values submitted 186 | * Added: Whenever you create a new feed, mapping will automatically be associated between a Gravity Form field and a Pod field if the labels match 187 | * Added: Custom override values now support GF merge tags by default (no insert UI yet) like `{form_id}` and any other merge tag 188 | * Added: Required WP Object Fields in mapping are no longer required if you choose to 'Enable editing with this form using ____' option for Post/Media or User pod types 189 | * Added: Support for E-mail field mappings with 'Confirm E-mail' enabled 190 | * Added: Support for Date fields with multiple inputs (date dropdown / text fields) 191 | * Added: Smarter requirement handling for WP object fields based on object type (only require what the WP insert API requires) 192 | * Added: New mapping fields are now available for more Entry and Payment fields 193 | * Added: New merge tags `{pods.id}` and `{pods.permalink}` are available for usage and in the merge tag selection dropdowns 194 | * Improved: Added headings to each group of feed options so they are easier to work with 195 | * Improved: Address field mapping for Country, State, and CA Provinces now convert properly to their Pods counterparts 196 | * Updated: PHP Markdown library updated to 1.0.2 197 | * Fixed: Issues with using 'bypass' as a save action 198 | * Fixed: Dynamic select options should set the current value (as posted in form) properly 199 | * Fixed: Date/time fields shouldn't auto populate with empty dates such as 0000-00-00 anymore 200 | * Fixed: Additional attachment processing fixes 201 | * Fixed: Lots of Pods GF UI issues resolved 202 | * Fixed: Removed Autocomplete limit (was 30) that was being enforced, now all data from related field will show 203 | * Fixed: Dynamic mapping value checking to support arrays of values 204 | * Fixed: Lots of Prepopulating fixes 205 | * Fixed: Now supports multi page form validation and prepopulating 206 | -------------------------------------------------------------------------------- /ui/pods-gf-admin.js: -------------------------------------------------------------------------------- 1 | jQuery( function() { 2 | 3 | // Handle showing/hiding override value 4 | var $form_settings = jQuery( '.gforms_form_settings' ); 5 | 6 | if ( $form_settings[0] ) { 7 | $form_settings.on( 'change', '.gaddon-setting.gaddon-select', function() { 8 | 9 | var $this = jQuery( this ), 10 | value = $this.val(), 11 | $parent_td = $this.closest( 'td' ), 12 | $custom_override = jQuery( '.pods-custom-override', $parent_td ); 13 | 14 | if ( $custom_override[0] ) { 15 | $custom_override.toggleClass( 'hidden', ( '_pods_custom' !== value ) ); 16 | } 17 | 18 | } ); 19 | } 20 | 21 | } ); -------------------------------------------------------------------------------- /ui/pods-gf.css: -------------------------------------------------------------------------------- 1 | .pods-gf-secondary-submit { 2 | float: right; 3 | } -------------------------------------------------------------------------------- /ui/pods-gf.js: -------------------------------------------------------------------------------- 1 | jQuery( function() { 2 | if ( jQuery( '.pods-gf-save-for-later' )[ 0 ] && 'undefined' != typeof ajaxurl ) { 3 | jQuery( '.gform_page_footer' ).on( 'click', '.pods-gf-save-for-later', function( e ) { 4 | e.preventDefault(); 5 | 6 | var $this = jQuery( this ), 7 | $form = $this.closest( 'form' ); 8 | 9 | // Save all current $_POST 10 | $form.prop( 'action', ajaxurl + '?action=pods_gf_save_for_later&form_id=' + $form.prop( 'id' ) ); 11 | $form.submit(); 12 | } ).on( 'click', '.pods-gf-save-for-later-reset', function( e ) { 13 | e.preventDefault(); 14 | 15 | if ( !confirm( 'Are you sure you want to reset your saved form?' ) ) { 16 | return; 17 | } 18 | 19 | var $this = jQuery( this ), 20 | $form = $this.closest( 'form' ); 21 | 22 | // Clear saved form 23 | $form.prop( 'action', ajaxurl + '?action=pods_gf_save_for_later&form_id=' + $form.prop( 'id' ) + '&pods_gf_clear_saved_form=1&pods_gf_save_for_later_redirect=' + encodeURI( document.location.href ) ); 24 | $form.submit(); 25 | } ); 26 | 27 | jQuery( 'div.gform_wrapper' ).each( function() { 28 | var $this = jQuery( this ), 29 | $pods_save_for_later = jQuery( '.pods-gf-save-for-later', $this ), 30 | $pods_save_for_later_reset = jQuery( '.pods-gf-save-for-later-reset', $this ); 31 | 32 | if ( $pods_save_for_later[ 0 ] ) { 33 | jQuery( '.gform_page_footer', $this ).each( function() { 34 | var $t = jQuery( this ); 35 | 36 | if ( !jQuery( '.pods-gf-save-for-later', $t )[ 0 ] ) { 37 | var $new_save_button = $pods_save_for_later.clone(); 38 | 39 | $new_save_button.appendTo( $t ); 40 | 41 | if ( $pods_save_for_later_reset[ 0 ] ) { 42 | var $new_reset_button = $pods_save_for_later_reset.clone(); 43 | 44 | $new_reset_button.appendTo( $t ); 45 | } 46 | } 47 | } ); 48 | } 49 | } ); 50 | } 51 | 52 | if ( jQuery( '.pods-gf-secondary-submit' )[ 0 ] ) { 53 | jQuery( 'div.gform_wrapper' ).each( function() { 54 | var $this = jQuery( this ), 55 | $pods_secondary_submit = jQuery( '.pods-gf-secondary-submit', $this ); 56 | 57 | if ( $pods_secondary_submit[ 0 ] ) { 58 | jQuery( '.gform_page_footer', $this ).each( function() { 59 | var $t = jQuery( this ), 60 | $secondary_submit = jQuery( '.pods-gf-secondary-submit', $t ); 61 | 62 | $secondary_submit.each( function() { 63 | var $secondary_t = jQuery( this ), 64 | $submit = $this.find( 'input[id^="gform_submit_button_"]' ), 65 | events = $submit.data( 'events' ); 66 | 67 | if ( 'undefined' != typeof events ) { 68 | // Iterate through all event types 69 | jQuery.each( events, function( eventType, eventArray ) { 70 | if ( 'undefined' != typeof eventArray ) { 71 | // Iterate through every bound handler 72 | jQuery.each( eventArray, function( index, event ) { 73 | // Take event namespaces into account 74 | var eventToBind = ( '' != event.namespace ) 75 | ? ( event.type + '.' + event.namespace ) 76 | : ( event.type ); 77 | 78 | // Bind event 79 | $secondary_t.on( eventToBind, event.data, event.handler ); 80 | } ); 81 | } 82 | } ); 83 | } 84 | } ); 85 | } ); 86 | 87 | // Secondary submits for each page of a form 88 | $pods_secondary_submits = jQuery( 'div.gform_page_footer.top_label .pods-gf-secondary-submit', $this ); 89 | 90 | $pods_secondary_submits.each( function() { 91 | var $secondary_submit = jQuery( this ); 92 | 93 | jQuery( '.gform_page_footer' ).not( '.top_label' ).each( function() { 94 | var $page_footer = jQuery( this ); 95 | 96 | $new_secondary = $secondary_submit.clone(); 97 | 98 | $new_secondary.insertBefore( jQuery( 'img.spinner', $page_footer ) ); 99 | } ); 100 | } ); 101 | } 102 | } ); 103 | } 104 | 105 | // Set target page number to zero if we are in a view-only form. 106 | if ( jQuery( '.pods-ui.pods-gf-ui-view-only' )[ 0 ] && 1 < jQuery( '.gform_page' ).length ) { 107 | jQuery( '.pods-ui.pods-gf-ui-view-only' ).find( 'input[id^="gform_target_page_number_"]' ).val( 0 ); 108 | } 109 | } ); -------------------------------------------------------------------------------- /vendor-prefixed/autoload-classmap.php: -------------------------------------------------------------------------------- 1 | $strauss_src . '/erusev/parsedown/Parsedown.php', 9 | ); -------------------------------------------------------------------------------- /vendor-prefixed/autoload.php: -------------------------------------------------------------------------------- 1 | DefinitionData = array(); 28 | 29 | # standardize line breaks 30 | $text = str_replace(array("\r\n", "\r"), "\n", $text); 31 | 32 | # remove surrounding line breaks 33 | $text = trim($text, "\n"); 34 | 35 | # split text into lines 36 | $lines = explode("\n", $text); 37 | 38 | # iterate through lines to identify blocks 39 | $markup = $this->lines($lines); 40 | 41 | # trim line breaks 42 | $markup = trim($markup, "\n"); 43 | 44 | return $markup; 45 | } 46 | 47 | # 48 | # Setters 49 | # 50 | 51 | function setBreaksEnabled($breaksEnabled) 52 | { 53 | $this->breaksEnabled = $breaksEnabled; 54 | 55 | return $this; 56 | } 57 | 58 | protected $breaksEnabled; 59 | 60 | function setMarkupEscaped($markupEscaped) 61 | { 62 | $this->markupEscaped = $markupEscaped; 63 | 64 | return $this; 65 | } 66 | 67 | protected $markupEscaped; 68 | 69 | function setUrlsLinked($urlsLinked) 70 | { 71 | $this->urlsLinked = $urlsLinked; 72 | 73 | return $this; 74 | } 75 | 76 | protected $urlsLinked = true; 77 | 78 | function setSafeMode($safeMode) 79 | { 80 | $this->safeMode = (bool) $safeMode; 81 | 82 | return $this; 83 | } 84 | 85 | protected $safeMode; 86 | 87 | protected $safeLinksWhitelist = array( 88 | 'http://', 89 | 'https://', 90 | 'ftp://', 91 | 'ftps://', 92 | 'mailto:', 93 | 'data:image/png;base64,', 94 | 'data:image/gif;base64,', 95 | 'data:image/jpeg;base64,', 96 | 'irc:', 97 | 'ircs:', 98 | 'git:', 99 | 'ssh:', 100 | 'news:', 101 | 'steam:', 102 | ); 103 | 104 | # 105 | # Lines 106 | # 107 | 108 | protected $BlockTypes = array( 109 | '#' => array('Header'), 110 | '*' => array('Rule', 'List'), 111 | '+' => array('List'), 112 | '-' => array('SetextHeader', 'Table', 'Rule', 'List'), 113 | '0' => array('List'), 114 | '1' => array('List'), 115 | '2' => array('List'), 116 | '3' => array('List'), 117 | '4' => array('List'), 118 | '5' => array('List'), 119 | '6' => array('List'), 120 | '7' => array('List'), 121 | '8' => array('List'), 122 | '9' => array('List'), 123 | ':' => array('Table'), 124 | '<' => array('Comment', 'Markup'), 125 | '=' => array('SetextHeader'), 126 | '>' => array('Quote'), 127 | '[' => array('Reference'), 128 | '_' => array('Rule'), 129 | '`' => array('FencedCode'), 130 | '|' => array('Table'), 131 | '~' => array('FencedCode'), 132 | ); 133 | 134 | # ~ 135 | 136 | protected $unmarkedBlockTypes = array( 137 | 'Code', 138 | ); 139 | 140 | # 141 | # Blocks 142 | # 143 | 144 | protected function lines(array $lines) 145 | { 146 | $CurrentBlock = null; 147 | 148 | foreach ($lines as $line) 149 | { 150 | if (chop($line) === '') 151 | { 152 | if (isset($CurrentBlock)) 153 | { 154 | $CurrentBlock['interrupted'] = true; 155 | } 156 | 157 | continue; 158 | } 159 | 160 | if (strpos($line, "\t") !== false) 161 | { 162 | $parts = explode("\t", $line); 163 | 164 | $line = $parts[0]; 165 | 166 | unset($parts[0]); 167 | 168 | foreach ($parts as $part) 169 | { 170 | $shortage = 4 - mb_strlen($line, 'utf-8') % 4; 171 | 172 | $line .= str_repeat(' ', $shortage); 173 | $line .= $part; 174 | } 175 | } 176 | 177 | $indent = 0; 178 | 179 | while (isset($line[$indent]) and $line[$indent] === ' ') 180 | { 181 | $indent ++; 182 | } 183 | 184 | $text = $indent > 0 ? substr($line, $indent) : $line; 185 | 186 | # ~ 187 | 188 | $Line = array('body' => $line, 'indent' => $indent, 'text' => $text); 189 | 190 | # ~ 191 | 192 | if (isset($CurrentBlock['continuable'])) 193 | { 194 | $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock); 195 | 196 | if (isset($Block)) 197 | { 198 | $CurrentBlock = $Block; 199 | 200 | continue; 201 | } 202 | else 203 | { 204 | if ($this->isBlockCompletable($CurrentBlock['type'])) 205 | { 206 | $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); 207 | } 208 | } 209 | } 210 | 211 | # ~ 212 | 213 | $marker = $text[0]; 214 | 215 | # ~ 216 | 217 | $blockTypes = $this->unmarkedBlockTypes; 218 | 219 | if (isset($this->BlockTypes[$marker])) 220 | { 221 | foreach ($this->BlockTypes[$marker] as $blockType) 222 | { 223 | $blockTypes []= $blockType; 224 | } 225 | } 226 | 227 | # 228 | # ~ 229 | 230 | foreach ($blockTypes as $blockType) 231 | { 232 | $Block = $this->{'block'.$blockType}($Line, $CurrentBlock); 233 | 234 | if (isset($Block)) 235 | { 236 | $Block['type'] = $blockType; 237 | 238 | if ( ! isset($Block['identified'])) 239 | { 240 | $Blocks []= $CurrentBlock; 241 | 242 | $Block['identified'] = true; 243 | } 244 | 245 | if ($this->isBlockContinuable($blockType)) 246 | { 247 | $Block['continuable'] = true; 248 | } 249 | 250 | $CurrentBlock = $Block; 251 | 252 | continue 2; 253 | } 254 | } 255 | 256 | # ~ 257 | 258 | if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted'])) 259 | { 260 | $CurrentBlock['element']['text'] .= "\n".$text; 261 | } 262 | else 263 | { 264 | $Blocks []= $CurrentBlock; 265 | 266 | $CurrentBlock = $this->paragraph($Line); 267 | 268 | $CurrentBlock['identified'] = true; 269 | } 270 | } 271 | 272 | # ~ 273 | 274 | if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) 275 | { 276 | $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); 277 | } 278 | 279 | # ~ 280 | 281 | $Blocks []= $CurrentBlock; 282 | 283 | unset($Blocks[0]); 284 | 285 | # ~ 286 | 287 | $markup = ''; 288 | 289 | foreach ($Blocks as $Block) 290 | { 291 | if (isset($Block['hidden'])) 292 | { 293 | continue; 294 | } 295 | 296 | $markup .= "\n"; 297 | $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']); 298 | } 299 | 300 | $markup .= "\n"; 301 | 302 | # ~ 303 | 304 | return $markup; 305 | } 306 | 307 | protected function isBlockContinuable($Type) 308 | { 309 | return method_exists($this, 'block'.$Type.'Continue'); 310 | } 311 | 312 | protected function isBlockCompletable($Type) 313 | { 314 | return method_exists($this, 'block'.$Type.'Complete'); 315 | } 316 | 317 | # 318 | # Code 319 | 320 | protected function blockCode($Line, $Block = null) 321 | { 322 | if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted'])) 323 | { 324 | return; 325 | } 326 | 327 | if ($Line['indent'] >= 4) 328 | { 329 | $text = substr($Line['body'], 4); 330 | 331 | $Block = array( 332 | 'element' => array( 333 | 'name' => 'pre', 334 | 'handler' => 'element', 335 | 'text' => array( 336 | 'name' => 'code', 337 | 'text' => $text, 338 | ), 339 | ), 340 | ); 341 | 342 | return $Block; 343 | } 344 | } 345 | 346 | protected function blockCodeContinue($Line, $Block) 347 | { 348 | if ($Line['indent'] >= 4) 349 | { 350 | if (isset($Block['interrupted'])) 351 | { 352 | $Block['element']['text']['text'] .= "\n"; 353 | 354 | unset($Block['interrupted']); 355 | } 356 | 357 | $Block['element']['text']['text'] .= "\n"; 358 | 359 | $text = substr($Line['body'], 4); 360 | 361 | $Block['element']['text']['text'] .= $text; 362 | 363 | return $Block; 364 | } 365 | } 366 | 367 | protected function blockCodeComplete($Block) 368 | { 369 | $text = $Block['element']['text']['text']; 370 | 371 | $Block['element']['text']['text'] = $text; 372 | 373 | return $Block; 374 | } 375 | 376 | # 377 | # Comment 378 | 379 | protected function blockComment($Line) 380 | { 381 | if ($this->markupEscaped or $this->safeMode) 382 | { 383 | return; 384 | } 385 | 386 | if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!') 387 | { 388 | $Block = array( 389 | 'markup' => $Line['body'], 390 | ); 391 | 392 | if (preg_match('/-->$/', $Line['text'])) 393 | { 394 | $Block['closed'] = true; 395 | } 396 | 397 | return $Block; 398 | } 399 | } 400 | 401 | protected function blockCommentContinue($Line, array $Block) 402 | { 403 | if (isset($Block['closed'])) 404 | { 405 | return; 406 | } 407 | 408 | $Block['markup'] .= "\n" . $Line['body']; 409 | 410 | if (preg_match('/-->$/', $Line['text'])) 411 | { 412 | $Block['closed'] = true; 413 | } 414 | 415 | return $Block; 416 | } 417 | 418 | # 419 | # Fenced Code 420 | 421 | protected function blockFencedCode($Line) 422 | { 423 | if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches)) 424 | { 425 | $Element = array( 426 | 'name' => 'code', 427 | 'text' => '', 428 | ); 429 | 430 | if (isset($matches[1])) 431 | { 432 | /** 433 | * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes 434 | * Every HTML element may have a class attribute specified. 435 | * The attribute, if specified, must have a value that is a set 436 | * of space-separated tokens representing the various classes 437 | * that the element belongs to. 438 | * [...] 439 | * The space characters, for the purposes of this specification, 440 | * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab), 441 | * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and 442 | * U+000D CARRIAGE RETURN (CR). 443 | * 444 | @license MIT 445 | Modified by Pods Framework Team on 29-March-2024 using {@see https://github.com/BrianHenryIE/strauss}. 446 | */ 447 | $language = substr($matches[1], 0, strcspn($matches[1], " \t\n\f\r")); 448 | 449 | $class = 'language-'.$language; 450 | 451 | $Element['attributes'] = array( 452 | 'class' => $class, 453 | ); 454 | } 455 | 456 | $Block = array( 457 | 'char' => $Line['text'][0], 458 | 'element' => array( 459 | 'name' => 'pre', 460 | 'handler' => 'element', 461 | 'text' => $Element, 462 | ), 463 | ); 464 | 465 | return $Block; 466 | } 467 | } 468 | 469 | protected function blockFencedCodeContinue($Line, $Block) 470 | { 471 | if (isset($Block['complete'])) 472 | { 473 | return; 474 | } 475 | 476 | if (isset($Block['interrupted'])) 477 | { 478 | $Block['element']['text']['text'] .= "\n"; 479 | 480 | unset($Block['interrupted']); 481 | } 482 | 483 | if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text'])) 484 | { 485 | $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1); 486 | 487 | $Block['complete'] = true; 488 | 489 | return $Block; 490 | } 491 | 492 | $Block['element']['text']['text'] .= "\n".$Line['body']; 493 | 494 | return $Block; 495 | } 496 | 497 | protected function blockFencedCodeComplete($Block) 498 | { 499 | $text = $Block['element']['text']['text']; 500 | 501 | $Block['element']['text']['text'] = $text; 502 | 503 | return $Block; 504 | } 505 | 506 | # 507 | # Header 508 | 509 | protected function blockHeader($Line) 510 | { 511 | if (isset($Line['text'][1])) 512 | { 513 | $level = 1; 514 | 515 | while (isset($Line['text'][$level]) and $Line['text'][$level] === '#') 516 | { 517 | $level ++; 518 | } 519 | 520 | if ($level > 6) 521 | { 522 | return; 523 | } 524 | 525 | $text = trim($Line['text'], '# '); 526 | 527 | $Block = array( 528 | 'element' => array( 529 | 'name' => 'h' . min(6, $level), 530 | 'text' => $text, 531 | 'handler' => 'line', 532 | ), 533 | ); 534 | 535 | return $Block; 536 | } 537 | } 538 | 539 | # 540 | # List 541 | 542 | protected function blockList($Line) 543 | { 544 | list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]'); 545 | 546 | if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches)) 547 | { 548 | $Block = array( 549 | 'indent' => $Line['indent'], 550 | 'pattern' => $pattern, 551 | 'element' => array( 552 | 'name' => $name, 553 | 'handler' => 'elements', 554 | ), 555 | ); 556 | 557 | if($name === 'ol') 558 | { 559 | $listStart = stristr($matches[0], '.', true); 560 | 561 | if($listStart !== '1') 562 | { 563 | $Block['element']['attributes'] = array('start' => $listStart); 564 | } 565 | } 566 | 567 | $Block['li'] = array( 568 | 'name' => 'li', 569 | 'handler' => 'li', 570 | 'text' => array( 571 | $matches[2], 572 | ), 573 | ); 574 | 575 | $Block['element']['text'] []= & $Block['li']; 576 | 577 | return $Block; 578 | } 579 | } 580 | 581 | protected function blockListContinue($Line, array $Block) 582 | { 583 | if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches)) 584 | { 585 | if (isset($Block['interrupted'])) 586 | { 587 | $Block['li']['text'] []= ''; 588 | 589 | $Block['loose'] = true; 590 | 591 | unset($Block['interrupted']); 592 | } 593 | 594 | unset($Block['li']); 595 | 596 | $text = isset($matches[1]) ? $matches[1] : ''; 597 | 598 | $Block['li'] = array( 599 | 'name' => 'li', 600 | 'handler' => 'li', 601 | 'text' => array( 602 | $text, 603 | ), 604 | ); 605 | 606 | $Block['element']['text'] []= & $Block['li']; 607 | 608 | return $Block; 609 | } 610 | 611 | if ($Line['text'][0] === '[' and $this->blockReference($Line)) 612 | { 613 | return $Block; 614 | } 615 | 616 | if ( ! isset($Block['interrupted'])) 617 | { 618 | $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); 619 | 620 | $Block['li']['text'] []= $text; 621 | 622 | return $Block; 623 | } 624 | 625 | if ($Line['indent'] > 0) 626 | { 627 | $Block['li']['text'] []= ''; 628 | 629 | $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); 630 | 631 | $Block['li']['text'] []= $text; 632 | 633 | unset($Block['interrupted']); 634 | 635 | return $Block; 636 | } 637 | } 638 | 639 | protected function blockListComplete(array $Block) 640 | { 641 | if (isset($Block['loose'])) 642 | { 643 | foreach ($Block['element']['text'] as &$li) 644 | { 645 | if (end($li['text']) !== '') 646 | { 647 | $li['text'] []= ''; 648 | } 649 | } 650 | } 651 | 652 | return $Block; 653 | } 654 | 655 | # 656 | # Quote 657 | 658 | protected function blockQuote($Line) 659 | { 660 | if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) 661 | { 662 | $Block = array( 663 | 'element' => array( 664 | 'name' => 'blockquote', 665 | 'handler' => 'lines', 666 | 'text' => (array) $matches[1], 667 | ), 668 | ); 669 | 670 | return $Block; 671 | } 672 | } 673 | 674 | protected function blockQuoteContinue($Line, array $Block) 675 | { 676 | if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) 677 | { 678 | if (isset($Block['interrupted'])) 679 | { 680 | $Block['element']['text'] []= ''; 681 | 682 | unset($Block['interrupted']); 683 | } 684 | 685 | $Block['element']['text'] []= $matches[1]; 686 | 687 | return $Block; 688 | } 689 | 690 | if ( ! isset($Block['interrupted'])) 691 | { 692 | $Block['element']['text'] []= $Line['text']; 693 | 694 | return $Block; 695 | } 696 | } 697 | 698 | # 699 | # Rule 700 | 701 | protected function blockRule($Line) 702 | { 703 | if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text'])) 704 | { 705 | $Block = array( 706 | 'element' => array( 707 | 'name' => 'hr' 708 | ), 709 | ); 710 | 711 | return $Block; 712 | } 713 | } 714 | 715 | # 716 | # Setext 717 | 718 | protected function blockSetextHeader($Line, array $Block = null) 719 | { 720 | if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) 721 | { 722 | return; 723 | } 724 | 725 | if (chop($Line['text'], $Line['text'][0]) === '') 726 | { 727 | $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2'; 728 | 729 | return $Block; 730 | } 731 | } 732 | 733 | # 734 | # Markup 735 | 736 | protected function blockMarkup($Line) 737 | { 738 | if ($this->markupEscaped or $this->safeMode) 739 | { 740 | return; 741 | } 742 | 743 | if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches)) 744 | { 745 | $element = strtolower($matches[1]); 746 | 747 | if (in_array($element, $this->textLevelElements)) 748 | { 749 | return; 750 | } 751 | 752 | $Block = array( 753 | 'name' => $matches[1], 754 | 'depth' => 0, 755 | 'markup' => $Line['text'], 756 | ); 757 | 758 | $length = strlen($matches[0]); 759 | 760 | $remainder = substr($Line['text'], $length); 761 | 762 | if (trim($remainder) === '') 763 | { 764 | if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) 765 | { 766 | $Block['closed'] = true; 767 | 768 | $Block['void'] = true; 769 | } 770 | } 771 | else 772 | { 773 | if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) 774 | { 775 | return; 776 | } 777 | 778 | if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder)) 779 | { 780 | $Block['closed'] = true; 781 | } 782 | } 783 | 784 | return $Block; 785 | } 786 | } 787 | 788 | protected function blockMarkupContinue($Line, array $Block) 789 | { 790 | if (isset($Block['closed'])) 791 | { 792 | return; 793 | } 794 | 795 | if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open 796 | { 797 | $Block['depth'] ++; 798 | } 799 | 800 | if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close 801 | { 802 | if ($Block['depth'] > 0) 803 | { 804 | $Block['depth'] --; 805 | } 806 | else 807 | { 808 | $Block['closed'] = true; 809 | } 810 | } 811 | 812 | if (isset($Block['interrupted'])) 813 | { 814 | $Block['markup'] .= "\n"; 815 | 816 | unset($Block['interrupted']); 817 | } 818 | 819 | $Block['markup'] .= "\n".$Line['body']; 820 | 821 | return $Block; 822 | } 823 | 824 | # 825 | # Reference 826 | 827 | protected function blockReference($Line) 828 | { 829 | if (preg_match('/^\[(.+?)\]:[ ]*?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches)) 830 | { 831 | $id = strtolower($matches[1]); 832 | 833 | $Data = array( 834 | 'url' => $matches[2], 835 | 'title' => null, 836 | ); 837 | 838 | if (isset($matches[3])) 839 | { 840 | $Data['title'] = $matches[3]; 841 | } 842 | 843 | $this->DefinitionData['Reference'][$id] = $Data; 844 | 845 | $Block = array( 846 | 'hidden' => true, 847 | ); 848 | 849 | return $Block; 850 | } 851 | } 852 | 853 | # 854 | # Table 855 | 856 | protected function blockTable($Line, array $Block = null) 857 | { 858 | if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) 859 | { 860 | return; 861 | } 862 | 863 | if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '') 864 | { 865 | $alignments = array(); 866 | 867 | $divider = $Line['text']; 868 | 869 | $divider = trim($divider); 870 | $divider = trim($divider, '|'); 871 | 872 | $dividerCells = explode('|', $divider); 873 | 874 | foreach ($dividerCells as $dividerCell) 875 | { 876 | $dividerCell = trim($dividerCell); 877 | 878 | if ($dividerCell === '') 879 | { 880 | continue; 881 | } 882 | 883 | $alignment = null; 884 | 885 | if ($dividerCell[0] === ':') 886 | { 887 | $alignment = 'left'; 888 | } 889 | 890 | if (substr($dividerCell, - 1) === ':') 891 | { 892 | $alignment = $alignment === 'left' ? 'center' : 'right'; 893 | } 894 | 895 | $alignments []= $alignment; 896 | } 897 | 898 | # ~ 899 | 900 | $HeaderElements = array(); 901 | 902 | $header = $Block['element']['text']; 903 | 904 | $header = trim($header); 905 | $header = trim($header, '|'); 906 | 907 | $headerCells = explode('|', $header); 908 | 909 | foreach ($headerCells as $index => $headerCell) 910 | { 911 | $headerCell = trim($headerCell); 912 | 913 | $HeaderElement = array( 914 | 'name' => 'th', 915 | 'text' => $headerCell, 916 | 'handler' => 'line', 917 | ); 918 | 919 | if (isset($alignments[$index])) 920 | { 921 | $alignment = $alignments[$index]; 922 | 923 | $HeaderElement['attributes'] = array( 924 | 'style' => 'text-align: '.$alignment.';', 925 | ); 926 | } 927 | 928 | $HeaderElements []= $HeaderElement; 929 | } 930 | 931 | # ~ 932 | 933 | $Block = array( 934 | 'alignments' => $alignments, 935 | 'identified' => true, 936 | 'element' => array( 937 | 'name' => 'table', 938 | 'handler' => 'elements', 939 | ), 940 | ); 941 | 942 | $Block['element']['text'] []= array( 943 | 'name' => 'thead', 944 | 'handler' => 'elements', 945 | ); 946 | 947 | $Block['element']['text'] []= array( 948 | 'name' => 'tbody', 949 | 'handler' => 'elements', 950 | 'text' => array(), 951 | ); 952 | 953 | $Block['element']['text'][0]['text'] []= array( 954 | 'name' => 'tr', 955 | 'handler' => 'elements', 956 | 'text' => $HeaderElements, 957 | ); 958 | 959 | return $Block; 960 | } 961 | } 962 | 963 | protected function blockTableContinue($Line, array $Block) 964 | { 965 | if (isset($Block['interrupted'])) 966 | { 967 | return; 968 | } 969 | 970 | if ($Line['text'][0] === '|' or strpos($Line['text'], '|')) 971 | { 972 | $Elements = array(); 973 | 974 | $row = $Line['text']; 975 | 976 | $row = trim($row); 977 | $row = trim($row, '|'); 978 | 979 | preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches); 980 | 981 | foreach ($matches[0] as $index => $cell) 982 | { 983 | $cell = trim($cell); 984 | 985 | $Element = array( 986 | 'name' => 'td', 987 | 'handler' => 'line', 988 | 'text' => $cell, 989 | ); 990 | 991 | if (isset($Block['alignments'][$index])) 992 | { 993 | $Element['attributes'] = array( 994 | 'style' => 'text-align: '.$Block['alignments'][$index].';', 995 | ); 996 | } 997 | 998 | $Elements []= $Element; 999 | } 1000 | 1001 | $Element = array( 1002 | 'name' => 'tr', 1003 | 'handler' => 'elements', 1004 | 'text' => $Elements, 1005 | ); 1006 | 1007 | $Block['element']['text'][1]['text'] []= $Element; 1008 | 1009 | return $Block; 1010 | } 1011 | } 1012 | 1013 | # 1014 | # ~ 1015 | # 1016 | 1017 | protected function paragraph($Line) 1018 | { 1019 | $Block = array( 1020 | 'element' => array( 1021 | 'name' => 'p', 1022 | 'text' => $Line['text'], 1023 | 'handler' => 'line', 1024 | ), 1025 | ); 1026 | 1027 | return $Block; 1028 | } 1029 | 1030 | # 1031 | # Inline Elements 1032 | # 1033 | 1034 | protected $InlineTypes = array( 1035 | '"' => array('SpecialCharacter'), 1036 | '!' => array('Image'), 1037 | '&' => array('SpecialCharacter'), 1038 | '*' => array('Emphasis'), 1039 | ':' => array('Url'), 1040 | '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'), 1041 | '>' => array('SpecialCharacter'), 1042 | '[' => array('Link'), 1043 | '_' => array('Emphasis'), 1044 | '`' => array('Code'), 1045 | '~' => array('Strikethrough'), 1046 | '\\' => array('EscapeSequence'), 1047 | ); 1048 | 1049 | # ~ 1050 | 1051 | protected $inlineMarkerList = '!"*_&[:<>`~\\'; 1052 | 1053 | # 1054 | # ~ 1055 | # 1056 | 1057 | public function line($text, $nonNestables=array()) 1058 | { 1059 | $markup = ''; 1060 | 1061 | # $excerpt is based on the first occurrence of a marker 1062 | 1063 | while ($excerpt = strpbrk($text, $this->inlineMarkerList)) 1064 | { 1065 | $marker = $excerpt[0]; 1066 | 1067 | $markerPosition = strpos($text, $marker); 1068 | 1069 | $Excerpt = array('text' => $excerpt, 'context' => $text); 1070 | 1071 | foreach ($this->InlineTypes[$marker] as $inlineType) 1072 | { 1073 | # check to see if the current inline type is nestable in the current context 1074 | 1075 | if ( ! empty($nonNestables) and in_array($inlineType, $nonNestables)) 1076 | { 1077 | continue; 1078 | } 1079 | 1080 | $Inline = $this->{'inline'.$inlineType}($Excerpt); 1081 | 1082 | if ( ! isset($Inline)) 1083 | { 1084 | continue; 1085 | } 1086 | 1087 | # makes sure that the inline belongs to "our" marker 1088 | 1089 | if (isset($Inline['position']) and $Inline['position'] > $markerPosition) 1090 | { 1091 | continue; 1092 | } 1093 | 1094 | # sets a default inline position 1095 | 1096 | if ( ! isset($Inline['position'])) 1097 | { 1098 | $Inline['position'] = $markerPosition; 1099 | } 1100 | 1101 | # cause the new element to 'inherit' our non nestables 1102 | 1103 | foreach ($nonNestables as $non_nestable) 1104 | { 1105 | $Inline['element']['nonNestables'][] = $non_nestable; 1106 | } 1107 | 1108 | # the text that comes before the inline 1109 | $unmarkedText = substr($text, 0, $Inline['position']); 1110 | 1111 | # compile the unmarked text 1112 | $markup .= $this->unmarkedText($unmarkedText); 1113 | 1114 | # compile the inline 1115 | $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']); 1116 | 1117 | # remove the examined text 1118 | $text = substr($text, $Inline['position'] + $Inline['extent']); 1119 | 1120 | continue 2; 1121 | } 1122 | 1123 | # the marker does not belong to an inline 1124 | 1125 | $unmarkedText = substr($text, 0, $markerPosition + 1); 1126 | 1127 | $markup .= $this->unmarkedText($unmarkedText); 1128 | 1129 | $text = substr($text, $markerPosition + 1); 1130 | } 1131 | 1132 | $markup .= $this->unmarkedText($text); 1133 | 1134 | return $markup; 1135 | } 1136 | 1137 | # 1138 | # ~ 1139 | # 1140 | 1141 | protected function inlineCode($Excerpt) 1142 | { 1143 | $marker = $Excerpt['text'][0]; 1144 | 1145 | if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(? strlen($matches[0]), 1152 | 'element' => array( 1153 | 'name' => 'code', 1154 | 'text' => $text, 1155 | ), 1156 | ); 1157 | } 1158 | } 1159 | 1160 | protected function inlineEmailTag($Excerpt) 1161 | { 1162 | if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches)) 1163 | { 1164 | $url = $matches[1]; 1165 | 1166 | if ( ! isset($matches[2])) 1167 | { 1168 | $url = 'mailto:' . $url; 1169 | } 1170 | 1171 | return array( 1172 | 'extent' => strlen($matches[0]), 1173 | 'element' => array( 1174 | 'name' => 'a', 1175 | 'text' => $matches[1], 1176 | 'attributes' => array( 1177 | 'href' => $url, 1178 | ), 1179 | ), 1180 | ); 1181 | } 1182 | } 1183 | 1184 | protected function inlineEmphasis($Excerpt) 1185 | { 1186 | if ( ! isset($Excerpt['text'][1])) 1187 | { 1188 | return; 1189 | } 1190 | 1191 | $marker = $Excerpt['text'][0]; 1192 | 1193 | if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches)) 1194 | { 1195 | $emphasis = 'strong'; 1196 | } 1197 | elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches)) 1198 | { 1199 | $emphasis = 'em'; 1200 | } 1201 | else 1202 | { 1203 | return; 1204 | } 1205 | 1206 | return array( 1207 | 'extent' => strlen($matches[0]), 1208 | 'element' => array( 1209 | 'name' => $emphasis, 1210 | 'handler' => 'line', 1211 | 'text' => $matches[1], 1212 | ), 1213 | ); 1214 | } 1215 | 1216 | protected function inlineEscapeSequence($Excerpt) 1217 | { 1218 | if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters)) 1219 | { 1220 | return array( 1221 | 'markup' => $Excerpt['text'][1], 1222 | 'extent' => 2, 1223 | ); 1224 | } 1225 | } 1226 | 1227 | protected function inlineImage($Excerpt) 1228 | { 1229 | if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[') 1230 | { 1231 | return; 1232 | } 1233 | 1234 | $Excerpt['text']= substr($Excerpt['text'], 1); 1235 | 1236 | $Link = $this->inlineLink($Excerpt); 1237 | 1238 | if ($Link === null) 1239 | { 1240 | return; 1241 | } 1242 | 1243 | $Inline = array( 1244 | 'extent' => $Link['extent'] + 1, 1245 | 'element' => array( 1246 | 'name' => 'img', 1247 | 'attributes' => array( 1248 | 'src' => $Link['element']['attributes']['href'], 1249 | 'alt' => $Link['element']['text'], 1250 | ), 1251 | ), 1252 | ); 1253 | 1254 | $Inline['element']['attributes'] += $Link['element']['attributes']; 1255 | 1256 | unset($Inline['element']['attributes']['href']); 1257 | 1258 | return $Inline; 1259 | } 1260 | 1261 | protected function inlineLink($Excerpt) 1262 | { 1263 | $Element = array( 1264 | 'name' => 'a', 1265 | 'handler' => 'line', 1266 | 'nonNestables' => array('Url', 'Link'), 1267 | 'text' => null, 1268 | 'attributes' => array( 1269 | 'href' => null, 1270 | 'title' => null, 1271 | ), 1272 | ); 1273 | 1274 | $extent = 0; 1275 | 1276 | $remainder = $Excerpt['text']; 1277 | 1278 | if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) 1279 | { 1280 | $Element['text'] = $matches[1]; 1281 | 1282 | $extent += strlen($matches[0]); 1283 | 1284 | $remainder = substr($remainder, $extent); 1285 | } 1286 | else 1287 | { 1288 | return; 1289 | } 1290 | 1291 | if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches)) 1292 | { 1293 | $Element['attributes']['href'] = $matches[1]; 1294 | 1295 | if (isset($matches[2])) 1296 | { 1297 | $Element['attributes']['title'] = substr($matches[2], 1, - 1); 1298 | } 1299 | 1300 | $extent += strlen($matches[0]); 1301 | } 1302 | else 1303 | { 1304 | if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) 1305 | { 1306 | $definition = strlen($matches[1]) ? $matches[1] : $Element['text']; 1307 | $definition = strtolower($definition); 1308 | 1309 | $extent += strlen($matches[0]); 1310 | } 1311 | else 1312 | { 1313 | $definition = strtolower($Element['text']); 1314 | } 1315 | 1316 | if ( ! isset($this->DefinitionData['Reference'][$definition])) 1317 | { 1318 | return; 1319 | } 1320 | 1321 | $Definition = $this->DefinitionData['Reference'][$definition]; 1322 | 1323 | $Element['attributes']['href'] = $Definition['url']; 1324 | $Element['attributes']['title'] = $Definition['title']; 1325 | } 1326 | 1327 | return array( 1328 | 'extent' => $extent, 1329 | 'element' => $Element, 1330 | ); 1331 | } 1332 | 1333 | protected function inlineMarkup($Excerpt) 1334 | { 1335 | if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false) 1336 | { 1337 | return; 1338 | } 1339 | 1340 | if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches)) 1341 | { 1342 | return array( 1343 | 'markup' => $matches[0], 1344 | 'extent' => strlen($matches[0]), 1345 | ); 1346 | } 1347 | 1348 | if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches)) 1349 | { 1350 | return array( 1351 | 'markup' => $matches[0], 1352 | 'extent' => strlen($matches[0]), 1353 | ); 1354 | } 1355 | 1356 | if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches)) 1357 | { 1358 | return array( 1359 | 'markup' => $matches[0], 1360 | 'extent' => strlen($matches[0]), 1361 | ); 1362 | } 1363 | } 1364 | 1365 | protected function inlineSpecialCharacter($Excerpt) 1366 | { 1367 | if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text'])) 1368 | { 1369 | return array( 1370 | 'markup' => '&', 1371 | 'extent' => 1, 1372 | ); 1373 | } 1374 | 1375 | $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot'); 1376 | 1377 | if (isset($SpecialCharacter[$Excerpt['text'][0]])) 1378 | { 1379 | return array( 1380 | 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';', 1381 | 'extent' => 1, 1382 | ); 1383 | } 1384 | } 1385 | 1386 | protected function inlineStrikethrough($Excerpt) 1387 | { 1388 | if ( ! isset($Excerpt['text'][1])) 1389 | { 1390 | return; 1391 | } 1392 | 1393 | if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) 1394 | { 1395 | return array( 1396 | 'extent' => strlen($matches[0]), 1397 | 'element' => array( 1398 | 'name' => 'del', 1399 | 'text' => $matches[1], 1400 | 'handler' => 'line', 1401 | ), 1402 | ); 1403 | } 1404 | } 1405 | 1406 | protected function inlineUrl($Excerpt) 1407 | { 1408 | if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/') 1409 | { 1410 | return; 1411 | } 1412 | 1413 | if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) 1414 | { 1415 | $url = $matches[0][0]; 1416 | 1417 | $Inline = array( 1418 | 'extent' => strlen($matches[0][0]), 1419 | 'position' => $matches[0][1], 1420 | 'element' => array( 1421 | 'name' => 'a', 1422 | 'text' => $url, 1423 | 'attributes' => array( 1424 | 'href' => $url, 1425 | ), 1426 | ), 1427 | ); 1428 | 1429 | return $Inline; 1430 | } 1431 | } 1432 | 1433 | protected function inlineUrlTag($Excerpt) 1434 | { 1435 | if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches)) 1436 | { 1437 | $url = $matches[1]; 1438 | 1439 | return array( 1440 | 'extent' => strlen($matches[0]), 1441 | 'element' => array( 1442 | 'name' => 'a', 1443 | 'text' => $url, 1444 | 'attributes' => array( 1445 | 'href' => $url, 1446 | ), 1447 | ), 1448 | ); 1449 | } 1450 | } 1451 | 1452 | # ~ 1453 | 1454 | protected function unmarkedText($text) 1455 | { 1456 | if ($this->breaksEnabled) 1457 | { 1458 | $text = preg_replace('/[ ]*\n/', "
\n", $text); 1459 | } 1460 | else 1461 | { 1462 | $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "
\n", $text); 1463 | $text = str_replace(" \n", "\n", $text); 1464 | } 1465 | 1466 | return $text; 1467 | } 1468 | 1469 | # 1470 | # Handlers 1471 | # 1472 | 1473 | protected function element(array $Element) 1474 | { 1475 | if ($this->safeMode) 1476 | { 1477 | $Element = $this->sanitiseElement($Element); 1478 | } 1479 | 1480 | $markup = '<'.$Element['name']; 1481 | 1482 | if (isset($Element['attributes'])) 1483 | { 1484 | foreach ($Element['attributes'] as $name => $value) 1485 | { 1486 | if ($value === null) 1487 | { 1488 | continue; 1489 | } 1490 | 1491 | $markup .= ' '.$name.'="'.self::escape($value).'"'; 1492 | } 1493 | } 1494 | 1495 | $permitRawHtml = false; 1496 | 1497 | if (isset($Element['text'])) 1498 | { 1499 | $text = $Element['text']; 1500 | } 1501 | // very strongly consider an alternative if you're writing an 1502 | // extension 1503 | elseif (isset($Element['rawHtml'])) 1504 | { 1505 | $text = $Element['rawHtml']; 1506 | $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode']; 1507 | $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode; 1508 | } 1509 | 1510 | if (isset($text)) 1511 | { 1512 | $markup .= '>'; 1513 | 1514 | if (!isset($Element['nonNestables'])) 1515 | { 1516 | $Element['nonNestables'] = array(); 1517 | } 1518 | 1519 | if (isset($Element['handler'])) 1520 | { 1521 | $markup .= $this->{$Element['handler']}($text, $Element['nonNestables']); 1522 | } 1523 | elseif (!$permitRawHtml) 1524 | { 1525 | $markup .= self::escape($text, true); 1526 | } 1527 | else 1528 | { 1529 | $markup .= $text; 1530 | } 1531 | 1532 | $markup .= ''; 1533 | } 1534 | else 1535 | { 1536 | $markup .= ' />'; 1537 | } 1538 | 1539 | return $markup; 1540 | } 1541 | 1542 | protected function elements(array $Elements) 1543 | { 1544 | $markup = ''; 1545 | 1546 | foreach ($Elements as $Element) 1547 | { 1548 | $markup .= "\n" . $this->element($Element); 1549 | } 1550 | 1551 | $markup .= "\n"; 1552 | 1553 | return $markup; 1554 | } 1555 | 1556 | # ~ 1557 | 1558 | protected function li($lines) 1559 | { 1560 | $markup = $this->lines($lines); 1561 | 1562 | $trimmedMarkup = trim($markup); 1563 | 1564 | if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '

') 1565 | { 1566 | $markup = $trimmedMarkup; 1567 | $markup = substr($markup, 3); 1568 | 1569 | $position = strpos($markup, "

"); 1570 | 1571 | $markup = substr_replace($markup, '', $position, 4); 1572 | } 1573 | 1574 | return $markup; 1575 | } 1576 | 1577 | # 1578 | # Deprecated Methods 1579 | # 1580 | 1581 | function parse($text) 1582 | { 1583 | $markup = $this->text($text); 1584 | 1585 | return $markup; 1586 | } 1587 | 1588 | protected function sanitiseElement(array $Element) 1589 | { 1590 | static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/'; 1591 | static $safeUrlNameToAtt = array( 1592 | 'a' => 'href', 1593 | 'img' => 'src', 1594 | ); 1595 | 1596 | if (isset($safeUrlNameToAtt[$Element['name']])) 1597 | { 1598 | $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]); 1599 | } 1600 | 1601 | if ( ! empty($Element['attributes'])) 1602 | { 1603 | foreach ($Element['attributes'] as $att => $val) 1604 | { 1605 | # filter out badly parsed attribute 1606 | if ( ! preg_match($goodAttribute, $att)) 1607 | { 1608 | unset($Element['attributes'][$att]); 1609 | } 1610 | # dump onevent attribute 1611 | elseif (self::striAtStart($att, 'on')) 1612 | { 1613 | unset($Element['attributes'][$att]); 1614 | } 1615 | } 1616 | } 1617 | 1618 | return $Element; 1619 | } 1620 | 1621 | protected function filterUnsafeUrlInAttribute(array $Element, $attribute) 1622 | { 1623 | foreach ($this->safeLinksWhitelist as $scheme) 1624 | { 1625 | if (self::striAtStart($Element['attributes'][$attribute], $scheme)) 1626 | { 1627 | return $Element; 1628 | } 1629 | } 1630 | 1631 | $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]); 1632 | 1633 | return $Element; 1634 | } 1635 | 1636 | # 1637 | # Static Methods 1638 | # 1639 | 1640 | protected static function escape($text, $allowQuotes = false) 1641 | { 1642 | return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8'); 1643 | } 1644 | 1645 | protected static function striAtStart($string, $needle) 1646 | { 1647 | $len = strlen($needle); 1648 | 1649 | if ($len > strlen($string)) 1650 | { 1651 | return false; 1652 | } 1653 | else 1654 | { 1655 | return strtolower(substr($string, 0, $len)) === strtolower($needle); 1656 | } 1657 | } 1658 | 1659 | static function instance($name = 'default') 1660 | { 1661 | if (isset(self::$instances[$name])) 1662 | { 1663 | return self::$instances[$name]; 1664 | } 1665 | 1666 | $instance = new static(); 1667 | 1668 | self::$instances[$name] = $instance; 1669 | 1670 | return $instance; 1671 | } 1672 | 1673 | private static $instances = array(); 1674 | 1675 | # 1676 | # Fields 1677 | # 1678 | 1679 | protected $DefinitionData; 1680 | 1681 | # 1682 | # Read-Only 1683 | 1684 | protected $specialCharacters = array( 1685 | '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', 1686 | ); 1687 | 1688 | protected $StrongRegex = array( 1689 | '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s', 1690 | '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us', 1691 | ); 1692 | 1693 | protected $EmRegex = array( 1694 | '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', 1695 | '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us', 1696 | ); 1697 | 1698 | protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?'; 1699 | 1700 | protected $voidElements = array( 1701 | 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 1702 | ); 1703 | 1704 | protected $textLevelElements = array( 1705 | 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont', 1706 | 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing', 1707 | 'i', 'rp', 'del', 'code', 'strike', 'marquee', 1708 | 'q', 'rt', 'ins', 'font', 'strong', 1709 | 's', 'tt', 'kbd', 'mark', 1710 | 'u', 'xm', 'sub', 'nobr', 1711 | 'sup', 'ruby', 1712 | 'var', 'span', 1713 | 'wbr', 'time', 1714 | ); 1715 | } 1716 | --------------------------------------------------------------------------------