PluginProbe
Smart Grid-Layout Design for Contact Form 7 / 3.2.0
Smart Grid-Layout Design for Contact Form 7 v3.2.0
4.18.0 4.17.0 3.2.0 3.2.1 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 4.0.0 4.0.1 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.10 4.11 4.12 4.13 4.14 All 119 releases
cf7-grid-layout / public / class-cf7-grid-layout-public.php

class-cf7-grid-layout-public.php in Smart Grid-Layout Design for Contact Form 7 3.2.0, at public/class-cf7-grid-layout-public.php

1,624 lines 66.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 use voku\helper\HtmlDomParser;
3
4 /**
5 * The public-facing functionality of the plugin.
6 *
7 * @link http://syllogic.in
8 * @since 1.0.0
9 *
10 * @package Cf7_Grid_Layout
11 * @subpackage Cf7_Grid_Layout/public
12 */
13
14 /**
15 * The public-facing functionality of the plugin.
16 *
17 * Defines the plugin name, version, and two examples hooks for how to
18 * enqueue the admin-specific stylesheet and JavaScript.
19 *
20 * @package Cf7_Grid_Layout
21 * @subpackage Cf7_Grid_Layout/public
22 * @author Aurovrata V. <vrata@syllogic.in>
23 */
24 class Cf7_Grid_Layout_Public {
25
26 /**
27 * The ID of this plugin.
28 *
29 * @since 1.0.0
30 * @access private
31 * @var string $plugin_name The ID of this plugin.
32 */
33 private $plugin_name;
34 private $registered = false;
35
36 /**
37 * The version of this plugin.
38 *
39 * @since 1.0.0
40 * @access private
41 * @var string $version The current version of this plugin.
42 */
43 private $version;
44
45 /**
46 * The version of this plugin.
47 *
48 * @since 2.6.0
49 * @access private
50 * @var string $form_id The current form id.
51 */
52 private $form_id='';
53
54 /**
55 * The cf7 array fields.
56 *
57 * @since 1.0.0
58 * @access private
59 * @var Array $array_grid_fields The form fields which were converted to arrays by the plugin.
60 */
61 static private $array_grid_fields = array();
62
63 /**
64 * The cf7 array fields.
65 *
66 * @since 2.5.0
67 * @access private
68 * @var Array $array_toggle_fields The form fields which are within toggle sections.
69 */
70 static private $array_toggle_fields = array();
71
72 /**
73 * The cf7 array fields.
74 *
75 * @since 2.5.0
76 * @access private
77 * @var Array $array_toggled_panels The form toggled sections used.
78 */
79 static private $array_toggled_panels = array();
80 /**
81 * The cf7 array fields.
82 *
83 * @since 2.6.0
84 * @access private
85 * @var Array $localised_data Localised data parameters.
86 */
87 private $localised_data = array();
88
89 /**
90 * Initialize the class and set its properties.
91 *
92 * @since 1.0.0
93 * @param string $plugin_name The name of the plugin.
94 * @param string $version The version of this plugin.
95 */
96 public function __construct( $plugin_name, $version ) {
97
98 $this->plugin_name = $plugin_name;
99 $this->version = $version;
100 }
101 /**
102 *
103 *
104 * @since 1.0.0
105 * @param string $field field name to check
106 * @param string $form_id form id for which to check the
107 * @return string 'singular' for non-grid fields, 'tab' for tabbed fields, 'table' for tablled fields, 'both' for fields in tables in tabs.
108 */
109 static public function field_type($field, $form_id){
110 if(!isset(self::$array_grid_fields[$form_id])){
111 //try to load the fields.
112 self::get_grid_fields($form_id);
113 }
114 $type = 'singular';
115 if(isset(self::$array_grid_fields[$form_id][$field])){
116 $type = self::$array_grid_fields[$form_id][$field];
117 switch(true){ /** @since 2.4.2 tables have unique id with time in milliseconds since 1/1/70 */
118 case preg_match('/^cf7-sg-table-[0-9]{3,13}$/', $type):
119 $type = 'table';
120 break;
121 case preg_match('/^cf7-sg-tab-[0-9]{3,13}$/', $type):
122 $type = 'tab';
123 break;
124 default:
125 $type = 'both';
126 break;
127 }
128 }
129 return $type;
130 }
131
132 /**
133 * Check if a field is in a toggled section.
134 *
135 *@since 2.5.0
136 *@param string $field field nav_menu_link_attributes.
137 *@return boolean check if the field is in a toggle section.
138 */
139 protected function get_toggle($field){
140 return isset(self::$array_toggle_fields[$this->form_id][$field]) ? self::$array_toggle_fields[$this->form_id][$field]: null;
141 }
142 /**
143 * Check if a toggled section was used and its fields submitted.
144 *
145 *@since 2.5.0
146 *@param string $toggle toogle id
147 *@return boolean was the toggle section submitted.
148 */
149 protected function is_submitted($toggle){
150 return isset(self::$array_toggled_panels[$this->form_id][$toggle]);
151 }
152 /**
153 * Register the stylesheets for the public-facing side of the site.
154 *
155 * @since 1.0.0
156 */
157 public function register_styles() {
158 $plugin_dir = plugin_dir_url( __DIR__ );
159 //default style for cf7 grid forms (row buttons and tables mainly).
160 //others
161 // get registered script object for jquery-ui
162 global $wp_scripts;
163 $ui = $wp_scripts->query('jquery-ui-core');
164
165 // tell WordPress to load the Smoothness theme from Google CDN
166 $protocol = is_ssl() ? 'https' : 'http';
167 $url_path = "$protocol://cdnjs.cloudflare.com/ajax/libs/jqueryui/{$ui->ver}/";
168 wp_register_style('cf7-jquery-ui', $url_path . 'themes/smoothness/jquery-ui.min.css', array(), $ui->ver , 'all');
169
170 wp_register_style( 'cf7-jquery-ui-theme', $url_path . 'jquery-ui.theme.min.css', array(), $ui->ver, 'all');
171 wp_register_style( 'cf7-jquery-ui-structure', $url_path . 'jquery-ui.structure.min.css', array(), $ui->ver, 'all');
172 /** @since 3.1,0 improve live loading of resources */
173 $ff = '';
174 $pf='';
175 if(!defined('WP_DEBUG') || !WP_DEBUG){
176 $ff = '.min';
177 $pf = '/min';
178 }
179 wp_register_style( 'cf7-benchmark-css', $plugin_dir . "public/css{$pf}/cf7-benchmark.css", array(), $this->version, 'all' );
180 wp_register_style( $this->plugin_name, $plugin_dir . "public/css{$pf}/cf7-grid-layout-public.css", array(), $this->version, 'all' );
181 wp_register_style( 'smart-grid', $plugin_dir . "assets/css.gs/smart-grid{$ff}.css", array(), $this->version, 'all' );
182 wp_register_style('jquery-nice-select-css', $plugin_dir . "assets/jquery-nice-select/css/nice-select{$ff}.css", array(), $this->version, 'all' );
183 wp_register_style('jquery-toggles-css', $plugin_dir . "assets/jquery-toggles/css/toggles{$ff}.css", array(), $this->version, 'all' );
184 wp_register_style('jquery-toggles-light-css', $plugin_dir . "assets/jquery-toggles/css/themes/toggles-light{$ff}.css", array('jquery-toggles-css'), $this->version, 'all' );
185
186 wp_register_style('select2-style', $plugin_dir . 'assets/select2/css/select2.min.css', array(), $this->version, 'all' );
187
188 //allow custom script registration
189 do_action('smart_grid_register_styles');
190 }
191
192 /**
193 * Register the JavaScript for the public-facing side of the site.
194 *
195 * @since 1.0.0
196 */
197 public function register_scripts() {
198 /** @since 3.1,0 improve live loading of resources */
199 $pf='';
200 if(!defined('WP_DEBUG') || !WP_DEBUG){
201 $pf = '/min';
202 }
203 $plugin_dir = plugin_dir_url( __DIR__ );
204 wp_register_script( $this->plugin_name, $plugin_dir . "public/js{$pf}/cf7-grid-layout-public.js", array( 'jquery','contact-form-7' ), $this->version, true );
205 wp_register_script('jquery-select2', $plugin_dir . 'assets/select2/js/select2.min.js', array( 'jquery' ), $this->version, true );
206 wp_register_script('jquery-nice-select', $plugin_dir . 'assets/jquery-nice-select/js/jquery.nice-select.min.js', array( 'jquery' ), $this->version, true );
207 wp_register_script('jquery-toggles', $plugin_dir . 'assets/jquery-toggles/toggles.min.js', array( 'jquery' ), $this->version, true );
208 wp_register_script('js-cf7sg-benchmarking', $plugin_dir . "public/js{$pf}/cf7-benchmark.js", array( 'jquery' ), $this->version, true );
209 //allow custom script registration
210 do_action('smart_grid_register_scripts');
211 }
212 /**
213 * Dequeue script 'contact-form-7'
214 * hooked on 'wp_print_scripts', this canbe re-enqeued on specific cf7 shortcode calls
215 * @since 1.0.0
216 **/
217 public function dequeue_cf7_scripts(){
218 wp_dequeue_script('contact-form-7');
219 }
220 /**
221 * Dequeue script 'contact-form-7'
222 * hooked on 'wp_print_style', this canbe re-enqeued on specific cf7 shortcode calls
223 * @since 1.0.0
224 **/
225 public function dequeue_cf7_styles(){
226 wp_dequeue_style('contact-form-7');
227 }
228 /**
229 * Add the cf7 key to the hidden fields so as not to have to load it after each submission.
230 * Hooked to 'wpcf7_form_hidden_fields'
231 * @since 1.0.0
232 * @param Array $hidden hidden fields to add to cf7 form.
233 * @return Array hidden fields to add to cf7 form.
234 **/
235 public function add_hidden_fields($hidden){
236 $form = wpcf7_get_current_contact_form();
237 $post = get_post($form->id());
238 $hidden['_wpcf7_key'] = $post->post_name;
239 $hidden['_cf7sg_toggles'] = '';
240 $hidden['_cf7sg_version'] = $this->version;
241 return $hidden;
242 }
243 /**
244 * Enqueue scripts requried for cf7 shortcode
245 * hooked on 'do_shortcode_tag',
246 * @since 1.0.0
247 **/
248 public function cf7_shortcode_request($output, $tag, $attr){
249 //if not a cf7 shortcode, then exit.
250 if('contact-form-7' !== $tag){
251 return $output;
252 }
253 //wp_enqueue_script('contact-form-7'); //default cf7 plugin script.
254 wp_enqueue_script('contact-form-7'); //default cf7 plugin script.
255 $cf7_id = $attr['id'];
256 //get the key
257 $cf7post = get_post($cf7_id);
258 $cf7_key = $cf7post->post_name;
259 //load custom css/js script from theme css folder.
260 $themepath = get_stylesheet_directory();
261 $themeuri = get_stylesheet_directory_uri();
262 if( file_exists($themepath.'/css/'.$cf7_key.'.css') ){
263 wp_enqueue_style( $cf7_key.'-css' , $themeuri.'/css/'.$cf7_key.'.css', array($this->plugin_name), null, 'all');
264 }
265 if( file_exists($themepath.'/js/'.$cf7_key.'.js') ){
266 wp_enqueue_script( $cf7_key.'-js' , $themeuri.'/js/'.$cf7_key.'.js', array($this->plugin_name), null, true);
267 do_action('smart_grid_register_custom_script', $cf7_key);
268 }
269 /** @since 3.0.0 load scripts only for required classes */
270 $class = get_post_meta($cf7_id, '_cf7sg_script_classes', true);
271 if(empty($class)){
272 $class = array();
273 }
274 //check classes required for sub-forms.
275 $sub_forms = array();
276 $sub_form_keys = get_post_meta($cf7_id, '_cf7sg_sub_forms', true);
277 if(!empty($sub_form_keys)){
278 $args = array(
279 'post_type' => 'wpcf7_contact_form',
280 'post_name__in' => $sub_form_keys
281 );
282 $sub_forms = get_posts($args);
283 foreach($sub_forms as $form){
284 $sub_class = get_post_meta($form->ID, '_cf7sg_script_classes', true);
285 if(!empty($sub_class)) $class = array_unique(array_merge($class, $sub_class));
286 }
287 }
288 //select2
289 if(array_search('has-select2',$class, true)!==false){
290 wp_enqueue_script('jquery-select2');
291 wp_enqueue_style('select2-style');
292 }
293 //nice-select
294 if(array_search('has-nice-select',$class, true)!==false){
295 wp_enqueue_script('jquery-nice-select');
296 wp_enqueue_style('jquery-nice-select-css');
297 }
298 //benchmark
299 if(array_search('has-benchmark',$class, true)!==false){
300 wp_enqueue_script('js-cf7sg-benchmarking');
301 }
302 if(array_search('has-date',$class, true)!==false){
303 wp_enqueue_script('jquery-ui-datepicker');
304 wp_enqueue_style('cf7-jquery-ui');
305 }
306 //cf7 plugin styles
307 wp_enqueue_style('contact-form-7');
308 //cf7sg script & style.
309 wp_enqueue_style($this->plugin_name);
310 wp_enqueue_script($this->plugin_name);
311 /** @since 2.6.0 disabled button message*/
312 $form = wpcf7_get_current_contact_form();
313 $messages = $form->prop('messages');
314 $this->localised_data = array(
315 'url' => admin_url( 'admin-ajax.php' ),
316 'submit_disabled'=> isset($messages['submit_disabled']) ? $messages['submit_disabled']: __( "Disabled! To enable, check the acceptance field.", 'cf7-grid-layout' ),
317 'max_table_rows' => isset($messages['max_table_rows']) ? $messages['max_table_rows']: __( "You have reached the maximum number of rows.", 'cf7-grid-layout' )
318 );
319 wp_localize_script( $this->plugin_name, 'cf7sg', $this->localise_script() );
320 //setup classes and id for wrapper.
321 $css_id = apply_filters('cf7_smart_grid_form_id', 'cf7sg-form-'.$cf7_key, $attr);
322 /**
323 * @since 1.2.3 disable cf7sg styling/js for non-cf7sg forms.
324 */
325 $is_form = get_post_meta($cf7_id, '_cf7sg_managed_form', true);
326 if(''===$is_form || !$is_form){
327 wp_enqueue_style('contact-form-7'); //default cf7 plugin css.
328 do_action('smart_grid_enqueue_scripts', $cf7_key, $attr);
329 $classes = implode(' ', $class) .' key_'.$cf7_key;
330 $output = '<div class="cf7sg-container"><div id="' . $css_id . '" class="cf7-smart-grid ' . $classes . '">' . $output . '</div></div>';
331 return $output;
332 }
333 //load required dependencies for grid form.
334 wp_enqueue_style('smart-grid');
335 wp_enqueue_style('dashicons');
336
337 //jquery accordion for collapsible rows.
338 $has_section = array_search('has-accordion',$class, true) !==false;
339 if($has_section){
340 wp_enqueue_script('jquery-ui-accordion');
341 }
342 $has_tabs = array_search('has-tabs',$class, true) !==false;
343 $has_tables = array_search('has-table',$class, true) !==false;
344 $has_toggles = array_search('has-toggles',$class, true) !==false;
345
346 $form_time = strtotime($cf7post->post_modified);
347 if(!empty($sub_forms)){
348 $cf7_form = $form_raw = '';
349 foreach($sub_forms as $post_obj){
350 //check form saved date, if sub-form is newer, we need to udpate it.
351 if(strtotime($post_obj->post_modified ) > $form_time){
352 if(empty($cf7_form)){
353 $cf7_form = wpcf7_contact_form($cf7_id);
354 $form_raw = $cf7_form->prop( 'form' );
355 }
356 $form_raw = $this->update_sub_form($form_raw, $post_obj);
357 }
358 }
359 if(!empty($cf7_form)){ //redraw the form.
360 $cf7_form = wpcf7_save_contact_form(array('id'=>$cf7_id, 'form'=>$form_raw));
361 //reload the form
362 //$cf7_form = wpcf7_contact_form($cf7_id);
363 $output = $cf7_form->form_html($attr);
364 $class[]= 'has-update';
365 //actino for other plugin notification.
366 do_action('cf7sg_subform_uddate',$cf7_id, $cf7_form, $attr );
367 }
368 }
369 //required for tables/tabs/accordion
370 if($has_tabs || $has_tables || $has_toggles || $has_section){
371 wp_enqueue_script('jquery-effects-core');
372 }
373
374 if($has_tabs || $has_toggles || $has_section){
375 wp_enqueue_style('cf7-jquery-ui-theme');
376 wp_enqueue_style('cf7-jquery-ui-structure');
377 wp_enqueue_style('cf7-jquery-ui');
378 }
379 if($has_tabs) wp_enqueue_script('jquery-ui-tabs');
380 if($has_toggles){
381 wp_enqueue_script('jquery-toggles');
382 wp_enqueue_style('jquery-toggles-css');
383 wp_enqueue_style('jquery-toggles-light-css');
384 }
385 //$cf7_key = get_post_meta($cf7_id, '_smart_grid_cf7_form_key', true);
386 //allow custom script print
387 do_action('smart_grid_enqueue_scripts', $cf7_key, $attr);
388
389 $classes = implode(' ', $class) .' key_'.$cf7_key;
390 $output = '<div class="cf7sg-container"><div id="' . $css_id . '" class="cf7-smart-grid ' . $classes . '">' . $output . '</div></div>';
391 return $output;
392 }
393 /**
394 * Function hooked on 'cf7_2_post_form_values' to load toggle status for saed submissions
395 *
396 *@since 1.1.0
397 *@param string $post_id saved submission post ID
398 */
399 public function load_saved_toggled_status($field_values){
400 if(!isset($field_values['map_post_id']) || empty($field_values['map_post_id'])){
401 return $field_values;
402 }
403 $post_id = $field_values['map_post_id'];
404 $toggles = get_post_meta($post_id, 'cf7sg_toggles_status', true);
405 //debug_msg($toggles, $post_id.' status ');
406 if(empty($toggles)) $toggles = array();
407 wp_enqueue_script($this->plugin_name);
408 wp_localize_script( $this->plugin_name, 'cf7sg', $this->localise_script( array('toggles_status' => $toggles)) );
409 return $field_values;
410 }
411 /**
412 * Single source for localised script.
413 *
414 *@since 2.6.0
415 *@param array $params additional parameter to send.
416 *@return array data array to localise
417 */
418 private function localise_script($params=array()){
419 $this->localised_data += $params;
420 return $this->localised_data;
421 }
422 /**
423 * Update sub-forms in cf7 forms
424 * Hooked to 'do_shortcode'
425 * @since 1.0.0
426 * @param String $form_raw HTML form.
427 * @param WP_Post $sub_form_post CF7 sub-form post object.
428 * @return String HTML for new form.
429 **/
430 public function update_sub_form($form_raw, $sub_form_post){
431 //Create a new DOM document
432 $cf7_key = $sub_form_post->post_name;
433 $sub_form_raw = get_post_meta($sub_form_post->ID, '_form', true);
434 //PHP DOM plugin.
435 /** @since 3.2.0 use Simple HTML Dom library */
436 require_once plugin_dir_path( __DIR__ ) . 'assets/simple-html-dom/autoload.php';
437
438 $dom = HtmlDomParser::str_get_html($form_raw);
439 //reset the inner form.
440 $element = $dom->find('#cf7sg-form-'.$cf7_key);
441
442 $element[0]->innertext = $sub_form_raw;
443
444 return $dom->outertext;
445 }
446 /**
447 * Function to load custom js script for Post My CF7 Form loading of form field values
448 * Hooked to 'cf7_2_post_echo_field_mapping_script'
449 * @since 1.0.0
450 * @param boolean $default_script whether to use the default script or not, default is true.
451 * @param string $field cf7 form field name
452 * @param string $type field type (number, text, select...)
453 * @param string $json_value the json value loaded for this field in the form.
454 * @param string $$js_form the javascript variable in which the form is loaded.
455 * @return boolean false to print a custom script from the called function, true for the default script printed by this plugin.
456 **/
457 public function load_tabs_table_field($default_script, $post_id, $field, $type, $json_value, $js_form){
458 $grid = self::field_type($field, $post_id);
459 switch($grid){
460 case 'tab':
461 case 'table':
462 case 'both':
463 include( plugin_dir_path( __FILE__ ) . '/partials/cf7sg-field-load-script.php');
464 $default_script = false;
465 break;
466 default:
467 break;
468 }
469 return $default_script;
470 }
471 /**
472 * Register a [save] shortcode with CF7.
473 * Hooked on 'wpcf7_init'
474 * This function registers a callback function to expand the shortcode for the save button field.
475 * @since 2.0.0
476 */
477 public function register_cf7_shortcode() {
478 if( function_exists('wpcf7_add_form_tag') ) {
479 //dynamic select
480 wpcf7_add_form_tag(
481 array( 'dynamic_select', 'dynamic_select*' ),
482 array($this,'cf7_taxonomy_shortcode'),
483 true //has name
484 );
485 //benchmark
486 wpcf7_add_form_tag(
487 array( 'benchmark', 'benchmark*' ),
488 array($this,'cf7_benchmark_shortcode'),
489 true //has name
490 );
491 }
492 }
493 /**
494 * Register a [benchmark] shortcode with CF7.
495 * called by funciton above
496 * This function registers a callback function to expand the shortcode for the googleMap form fields.
497 * @since 1.0.0
498 * @param strng $tag the tag name designated in the tag help screen
499 * @return string a set of html fields to capture the googleMap information
500 */
501
502 public function cf7_benchmark_shortcode($tag){
503 $tag = new WPCF7_FormTag( $tag );
504 wp_enqueue_script('js-cf7sg-benchmarking');
505 wp_enqueue_style( 'cf7-benchmark-css' );
506 ob_start();
507 include( plugin_dir_path( __FILE__ ) . '/partials/cf7-benchmark-tag.php');
508 $html = ob_get_contents ();
509 ob_end_clean();
510 $this->update_form_classes('has-benchmark');
511 return $html;
512 }
513 /**
514 * Register a [dynamic_display] shortcode with CF7.
515 * called by funciton above
516 * This function registers a callback function to expand the shortcode for the googleMap form fields.
517 * @since 1.0.0
518 * @param strng $tag the tag name designated in the tag help screen
519 * @return string a set of html fields to capture the googleMap information
520 */
521
522 public function cf7_taxonomy_shortcode($tag){
523 $tag = new WPCF7_FormTag( $tag );
524 $source = self::get_dynamic_drodown_attributes($tag);
525 ob_start();
526 include( plugin_dir_path( __FILE__ ) . '/partials/cf7-taxonomy-tag-display.php');
527 $html = ob_get_contents ();
528 ob_end_clean();
529 return $html;
530 }
531 /**
532 *
533 *
534 * @since 1.0.0
535 * @param mixed $class array of classes or string to set class for form and dependency loading .
536 * @param Object $cf7_form cf7 post id against which set the c$lass .
537 **/
538 public function update_form_classes($class, $cf7_id=null){
539 if(empty($cf7_id)){
540 //get latest form
541 if(function_exists('wpcf7_get_current_contact_form')){
542 $cf7_form = wpcf7_get_current_contact_form();
543 if(!empty($cf7_form)) $cf7_id = $cf7_form->id();
544 }
545 }
546 if(empty($cf7_id)){
547 debug_msg('Unable to get Contact Form 7 ID to set class: '.$class);
548 return;
549 }
550 $classes = get_post_meta($cf7_id, '_cf7sg_classes', true);
551 if(empty($classes)){
552 $classes = array();
553 }
554 if(is_array($class)){
555 foreach($class as $class_name){
556 $classes[$class_name] = true;
557 }
558 }else{
559 $classes[$class] = true;
560 }
561 update_post_meta($cf7_id, '_cf7sg_classes', $classes);
562 return;
563 }
564 /**
565 * Function to save ajax submitted grid fields, grid fields are any input/select fields used in the table/tabs constructs
566 * hooked on wp_ajax_nopriv_save_grid_fields and wp_ajax_save_grid_fields. The ajax is only fired in case a sub-form has been updated.
567 * @since 1.0.0
568 **/
569 public function save_grid_fields(){
570 if( !isset($_POST['nonce']) ){
571 wp_send_json_error(array('message'=>'nonce failed'));
572 wp_die();
573 }
574 $cf7_id = sanitize_text_field($_POST['id']);
575
576 if(!wpcf7_verify_nonce($_POST['nonce'], $cf7_id)){
577 wp_send_json_error(array('message'=>'nonce failed'));
578 wp_die();
579 }
580
581 if(isset($_POST['tabs_fields']) && isset($_POST['table_fields'])){
582 $tabs_fields = json_decode(stripslashes($_POST['tabs_fields']));
583 if(empty($tabs_fields)) $tabs_fields = array();
584 if(!is_array($tabs_fields)) $tabs_fields = array($tabs_fields);
585 //validate each field name as text, sanitize.
586 $sanitised_tab_fields = array();
587 foreach($tabs_fields as $field){
588 $sanitised_tab_fields[] = sanitize_text_field($field);
589 }
590 $table_fields = json_decode(stripslashes($_POST['table_fields']));
591 if(empty($table_fields)) $table_fields = array();
592 if(!is_array($table_fields)) $table_fields = array($table_fields);
593 $sanitised_table_fields = array();
594 foreach($table_fields as $field){
595 $sanitised_table_fields[] = sanitize_text_field($field);
596 }
597 //debug_msg($grid_fields, $cf7_id);
598 update_post_meta($cf7_id, '_cf7sg_grid_tabs_names', $sanitised_tab_fields);
599 update_post_meta($cf7_id, '_cf7sg_grid_table_names', $sanitised_table_fields);
600 wp_send_json_success(array('message'=>'saved fields'));
601 }else{
602 wp_send_json_error(array('message'=>'no data received'));
603 }
604 wp_die();
605 }
606 /**
607 * This function filters submitted data and consolidates grid field values into arrays.
608 * Function hooked on 'wpcf7_posted_data'
609 * @since 1.0.0
610 * @param Array $data unvalidated submitted data.
611 * @return Array filtered submitted data.
612 **/
613 public function setup_grid_values($data){
614 $cf7form = WPCF7_ContactForm::get_current();
615 if(empty($cf7form) ){
616 if(isset($data['_wpcf7']) ){
617 $cf7_id = $data['_wpcf7'];
618 $cf7form = WPCF7_ContactForm::get_instance($cf7_id);
619 if(empty($cf7form) ){
620 debug_msg("CF7SG ERROR: fn setup_grid_values() is unable to load submitted form");
621 return $data;
622 }
623 }
624 }
625 $cf7_id = $cf7form->id();
626 $this->form_id = $cf7_id;
627 //debug_msg($grid_fields, 'grid fields...');
628 $tags = $cf7form->scan_form_tags();
629 foreach($tags as $tag){
630 $field_name = $tag['name'];
631 $field_type = self::field_type($field_name, $cf7_id);
632 switch($field_type){
633 case 'tab':
634 case 'table':
635 case 'both':
636 // the $data array is passed by reference and will be consolidated.
637 /** @since 2.5.0 */
638 if( isset($_POST['_cf7sg_version']) && version_compare($_POST['_cf7sg_version'],'2.5.0','>=') ){
639 $this->consolidate_grid_submissions_v2($tag, $field_type, $data);
640 }else{
641 $this->consolidate_grid_submissions($field_name, $field_type, $data);
642 }
643 break;
644 default:
645 $toggle = $this->get_toggle($field_name);
646 if('unit-contact' ==$field_name) debug_msg($toggle, 'unit contact toggle ');
647 if(!empty($toggle)) $data[$field_name] = $this->is_submitted($toggle) ? $this->get_field_value($field_name, $data,$field_type) : null;
648 else $data[$field_name]= $this->get_field_value($field_name, $data, $tag['basetype']);
649 break;
650 }
651 }
652 return $data;
653 }
654 /**
655 * new function to consolidate submitted data for improved handling of optional toggled fields as well as special fields such as files and checkboxes in tabbed sectoins.
656 *
657 *@since 2.5.0
658 *@param array $field_tag a cf7 form field tag which is part of tabs or table grid section.
659 *@param array $type field type, should be 'tab'/'table'/'both'.
660 *@param array $data cf7 form submissed data passed by reference as consolidated grid values will be removed and the original field value replaced with an array.
661 *@return array a filtered array of $index_suffix=>$value pairs for tabs or rows fields, The index suffix is '.row-<index>' for tables and '.tab-<index>' for tabs. This method returns a 2 dimensional array for fields which are both within rowns and tabs. The 2 dimensional array will list [<tab_suffix>][<row_suffix>]=>$value. The original field name that was submitted can be reconstructed as $field_name.$index_suffix. The first field will have an empty string as its $index_suffix.
662 */
663 private function consolidate_grid_submissions_v2($field_tag, $type, &$data){
664 //get_post_meta($post_id, '_cf7sg_has_tables', true);
665 if(isset($data['_wpcf7']) ) $cf7_id = $data['_wpcf7'];
666 else{
667 debug_msg("CF7SG ERROR: fn consolidate_grid_submissions_v2() is unable to load submitted form");
668 }
669 $field_name = $field_tag['name'];
670 $field_type = $field_tag['basetype'];
671 $origin = self::$array_grid_fields[$cf7_id][$field_name];
672 $values = array();
673 $regex = '';
674 $submitted_fields=array();
675 $max_fields =0;
676 $purge_fields=array();
677 $toggle = $this->get_toggle($field_name);
678 switch($type){
679 case 'tab':
680 case 'table':
681 $index_suffix = ('table'==$type)?'_row-':'_tab-';
682 //find the number of rows submitted.
683 $max_fields = isset($_POST[$origin]) ? $_POST[$origin]:0;
684 if(!empty($toggle)){ //check if submitted.
685 $values[''] = $this->is_submitted($toggle) ? $this->get_field_value($field_name, $data,$field_type) : null;
686 }else $values['']= $this->get_field_value($field_name, $data,$field_type);
687
688 for($idx=1;$idx<$max_fields;$idx++){
689 $fid=$index_suffix.$idx;
690 if(!empty($toggle)){ //check if submitted.
691 $values[$fid] = $this->is_submitted($toggle.$fid) ? $this->get_field_value($field_name.$fid, $data,$field_type) : null;
692 }else $values[$fid] = $this->get_field_value($field_name.$fid, $data,$field_type);
693 $purge_fields[$field_name.$fid] = true;
694 }
695 break;
696 case 'both':
697 $origins = explode(':', $origin);
698 $table_origin = $origins[0];
699 $tab_origin = $origins[1];
700 $max_tabs = isset($_POST[$tab_origin])?$_POST[$tab_origin]:0;
701 $values[''] = array(); //init multi-dimensional array
702 if(!empty($toggle)){ //check if submitted, tab<-toggle<-table.
703 $values[''][''] = $this->is_submitted($toggle) ? $this->get_field_value($field_name, $data,$field_type) : null;
704 }else $values[''][''] = $this->get_field_value($field_name, $data,$field_type);
705
706 for($idx=0;$idx<$max_tabs;$idx++){ //tables in other tabs.
707 $max_fields = ($idx>0) ? $_POST[$table_origin.'_tab-'.$idx]:$_POST[$table_origin];
708 $tab_suffix= ($idx>0)? '_tab-'.$idx:'';
709 for($jdx=0;$jdx<$max_fields;$jdx++){
710 $row_suffix= ($jdx>0)?'_row-'.$jdx:'';
711 $fid = $tab_suffix.$row_suffix;
712 if(!empty($toggle)){ //check if submitted, tab<-toggle<-table.
713 $values[$tab_suffix][$row_suffix] = $this->is_submitted($toggle.$tab_suffix) ? $this->get_field_value( $field_name.$fid, $data, $field_type):null;
714 }else $values[$tab_suffix][$row_suffix] = $this->get_field_value($field_name.$fid, $data,$field_type);
715 $purge_fields[$field_name.$fid] = true; //remove from the origina $data.
716 }
717 }
718 break;
719 }
720 //debug_msg($purge_fields, 'purging ');
721 //debug_msg($data);
722 $data = array_udiff_uassoc($data, $purge_fields, function($a, $b){return 0;}, function($a, $b){
723 if ($a === $b) return 0;
724 return ($a > $b)? 1:-1;
725 }); //this will remove all the surplus fields
726 $data[$field_name] = $values;
727 return $values;
728 }
729 /**
730 * Extract either a file name or a field value
731 *
732 *@since 2.5.0
733 *@param array $data submitted data.
734 *@return string text_description
735 */
736 private function get_field_value($field_name, $data, $field_type){
737 $value = '';
738 if('file' == $field_type) {
739 $value = isset($_FILES[$field_name]) ? $_FILES[$field_name]['name']:'';
740 }else{
741 $value = isset($data[$field_name]) ? $data[$field_name]:'';
742 }
743 return $value;
744 }
745 /**
746 * function returns an array of fields as keys and value which are eitehr 'tab' or 'table', or 'both'.
747 *
748 * @since 1.0.0
749 * @param string $form_id form id for which to return fields.
750 * @return array empty array if no fields found..
751 **/
752 static public function get_grid_fields($form_id){
753 if(isset(self::$array_grid_fields[$form_id])){
754 return self::$array_grid_fields[$form_id];
755 }
756 $grid_fields = array();
757 //tables
758 $fields = get_post_meta($form_id , '_cf7sg_grid_table_names', true);
759 if(!empty($fields)){
760 if( is_array( $fields[key($fields)] ) ){
761 foreach($fields as $table=>$table_fields) $grid_fields += array_fill_keys($table_fields, $table);
762 }else $grid_fields += array_fill_keys($fields, 'cf7-sg-table-000');
763 }
764 //tabs
765 $fields = get_post_meta($form_id , '_cf7sg_grid_tabs_names', true);
766 if(!empty($fields)){
767 if(is_array( $fields[key( $fields)] ) ){ /** @since 2.4.2 */
768 foreach($fields as $tab=>$tab_fields){
769 foreach($tab_fields as $field){
770 if(isset($grid_fields[$field])) $grid_fields[$field] = $grid_fields[$field].':'.$tab;
771 else $grid_fields[$field] = $tab;
772 }
773 }
774 }else{
775 foreach($fields as $field){
776 if(isset($grid_fields[$field])) $grid_fields[$field] = $grid_fields[$field].':'.$tab;
777 else $grid_fields[$field] = 'cf7-sg-tab-000';
778 }
779 }
780 }
781 $subform_keys = get_post_meta($form_id, '_cf7sg_sub_forms', true);
782 if(!empty($subform_keys)){
783 foreach($subform_keys as $cf7Key){
784 $post_id = Cf7_WP_Post_Table::form_id($cf7Key);
785 $grid_fields += self::get_grid_fields($post_id);
786 }
787 }
788 self::$array_grid_fields[$form_id]=$grid_fields;
789 //load panel fields.
790 if( isset($_POST['_cf7sg_version']) && version_compare($_POST['_cf7sg_version'],'2.5.0','>=') ){
791 self::$array_toggled_panels[$form_id]=array();
792 $toggled_panels = array();
793 if(isset($_POST['_cf7sg_toggles'])){
794 $toggled_panels = json_decode( stripslashes($_POST['_cf7sg_toggles']));
795 if(!empty($toggled_panels)) $toggled_panels = get_object_vars($toggled_panels);
796 else $toggled_panels = array();
797 }
798 self::$array_toggled_panels[$form_id]=$toggled_panels;
799
800 $fields = get_post_meta($form_id, '_cf7sg_grid_toggled_names', true);
801 self::$array_toggle_fields[$form_id]=array();
802 if(!empty($fields)){
803 foreach($fields as $panel=>$pfields){
804 foreach($pfields as $field) self::$array_toggle_fields[$form_id][$field]=$panel;
805 }
806 }
807 }
808 return $grid_fields;
809 }
810 /**
811 * Consolidates submitted data from table and tab fields into arrays.
812 *
813 *@since 1.0.0
814 *@param string $field_name a cf7 form field name which is part of tabs or table grid section.
815 *@param array $type field type, should be 'tab'/'table'/'both'.
816 *@param array $data cf7 form submissed data passed by reference as consolidated grid values will be removed and the original field value replaced with an array.
817 *@return array a filtered array of $index_suffix=>$value pairs for tabs or rows fields, The index suffix is '.row-<index>' for tables and '.tab-<index>' for tabs. This method returns a 2 dimensional array for fields which are both within rowns and tabs. The 2 dimensional array will list [<tab_suffix>][<row_suffix>]=>$value. The original field name that was submitted can be reconstructed as $field_name.$index_suffix. The first field will have an empty string as its $index_suffix.
818 */
819 private function consolidate_grid_submissions($field_name, $type, &$data){
820 $values = array();
821 $regex = '';
822 $submitted_fields=array();
823 $max_fields =0;
824 $purge_fields=array();
825 switch($type){
826 case 'table':
827 $index_suffix = '_row-';
828 $regex = '/^'.preg_quote($field_name).'_row-[0-9]+$/';
829 $values['']=$data[$field_name];
830 //extract all relevant fields
831 $submitted_fields = preg_grep($regex, array_keys($data));
832 $max_fields = sizeof($submitted_fields);
833 break;
834 case 'tab':
835 $index_suffix = '_tab-';
836 $regex = '/^'.preg_quote($field_name).'_tab-[0-9]+$/';
837 $values['']=$data[$field_name];
838 //extract all relevant fields
839 $submitted_fields = preg_grep($regex, array_keys($data));
840 foreach($submitted_fields as $field){
841 $idx = 1.0 * str_replace($field_name.'_tab-', '', $field);
842 if($max_fields < $idx) $max_fields = $idx;
843 }
844 break;
845 case 'both':
846 $regex = '/^'.preg_quote($field_name);
847 $values[''] = array(); //init multi-dimensional array
848 if(isset($data[$field_name])){
849 $values[''][''] = $data[$field_name];
850 }else{
851 $values[''][''] = null;
852 }
853 //extract all relevant fields
854 $submitted_fields = preg_grep($regex.'_tab-[0-9]+_row-[0-9]+$/', array_keys($data));
855 //we also need the rows with tab index=0
856 $submitted_fields += preg_grep($regex.'_row-[0-9]+$/', array_keys($data));
857 //.. and tabs with row index=0
858 $submitted_fields += preg_grep($regex.'_tab-[0-9]+$/', array_keys($data));
859 //if('other-rm-qty'==$field_name){
860 // debug_msg($submitted_fields, 'Found fields '.$field_name);
861 //}
862 $max_fields = sizeof($submitted_fields);
863 break;
864 }
865
866 $row_idx=1; //[0][0] already set above is this is tab table field
867 $tab_idx=0;
868 $error_loop=false;
869 $loop_counter_limit = apply_filters('cf7sg_set_max_tabs_limit', 10, $data['_wpcf7_key'], $data['_wpcf7']);
870 for($idx=1; ($idx <= $max_fields && !$error_loop); $idx++){
871 switch($type){
872 case 'table':
873 case 'tab':
874 if(!isset($data[$field_name.$index_suffix.$idx])){
875 /**
876 *assuming this field was not submitted as it was disabled, hence set to null.
877 * @since 1.0.0
878 */
879 $values[$index_suffix.$idx] = null;
880 }else{
881 $values[$index_suffix.$idx] = $data[$field_name.$index_suffix.$idx];
882 $purge_fields[$field_name.$index_suffix.$idx] = true;
883 }
884 break;
885 case 'both':
886 //first attempt to look for the frist tab rows
887 $row_suffix = ($row_idx>0)?'_row-'.$row_idx:'';
888 $tab_suffix = ($tab_idx>0)?'_tab-'.$tab_idx:'';
889 //if('other-rm-qty'==$field_name){
890 // debug_msg($idx.'Looking for: '.$field_name.$tab_suffix.$row_suffix);
891 //}
892 if(isset($data[$field_name.$tab_suffix.$row_suffix])){
893 $values[$tab_suffix][$row_suffix] = $data[$field_name.$tab_suffix.$row_suffix];
894 $purge_fields[$field_name.$tab_suffix.$row_suffix] = true;
895 //next loop, look for the next row within the same tab,
896 $row_idx +=1;
897 }else{
898
899 $loop_counter=$tab_idx;
900 $loop = true;
901 do{//move to next tab, and reset the row counter
902 // debug_msg('loop:'.$loop_counter.', max: '.$loop_counter_limit);
903 $loop_counter +=1;
904 if($loop_counter > $loop_counter_limit) $error_loop=true;
905 $tab_idx +=1;
906 $row_idx = 0;
907 $row_suffix = '';
908 $tab_suffix = '_tab-'.$tab_idx;
909 //init new tab row values array
910 $values[$tab_suffix] = array();
911 //keep looking for next tab if we don't find a value;
912 /**
913 *if the frist row is not submitted then this may be in a toggled section which has been disabled.
914 * but we still need to keep looking for values in other tabs.
915 *@since 1.1.0
916 */
917 $values[$tab_suffix][$row_suffix] = null;
918 $loop = !isset($data[$field_name.$tab_suffix.$row_suffix]) && !$error_loop;
919 }while($loop);
920 if(!$error_loop) {
921 $values[$tab_suffix][$row_suffix] = $data[$field_name.$tab_suffix.$row_suffix];
922 $purge_fields[$field_name.$tab_suffix.$row_suffix] = true;
923 $row_idx +=1; //next loop, keep searching in thsi tab.
924 }else{
925 debug_msg($submitted_fields, 'CF7SG ERROR: Reached max tabs search loop for table field '.$field_name.' (cannot find any new rows above tab '.$tab_idx.' therefor abandoning search for remaining '.sizeof($submitted_fields)-$idx.' regex match in preg_grep result array listed below)');
926 //for loop will end here
927 }
928 }
929 break;
930 }//end switch.
931 }//end for loop preg_grep array.
932 if(!$error_loop){
933 //debug_msg($purge_fields, 'purging ');
934 //debug_msg($data);
935 $data = array_udiff_uassoc($data, $purge_fields, function($a, $b){return 0;}, function($a, $b){
936 if ($a === $b) return 0;
937 return ($a > $b)? 1:-1;
938 }); //this will remove all the surplus fields
939 $data[$field_name] = $values;
940 }
941 return $values;
942 }
943 /**
944 * Validates required benchmark and dynamic_select tags
945 *
946 * @since 1.0.0
947 * @param WPCF7_Validation $result validation object
948 * @param Array $tag an array of cf7 tag attributes and values
949 * @return WPCF7_Validation validation result
950 **/
951 public function validate_required($result, $tag){
952 if(!isset( $_POST[$tag->name] ) ){
953 return $result; //not submitted hence disabled.
954 }
955 $tag = new WPCF7_FormTag( $tag );
956
957 $name = $tag->name;
958 $value = isset( $_POST[$name] )
959 ? trim( strtr( (string) sanitize_text_field($_POST[$name]), "\n", " " ) ): '';
960
961 if ( $tag->is_required() && '' == $value ) {
962 $result->invalidate( $tag, wpcf7_get_message( 'invalid_required' ) );
963 }
964 return $result;
965 }
966
967 /**
968 * Final validation with all values submitted for inter dependent validation
969 * Hooked to filter 'wpcf7_validate', sets up the final $result object
970 * @since 1.0.0
971 * @param WPCF7_Validation $result validation object
972 * @param Array $tags an array of cf7 tag used in this form
973 * @return WPCF7_Validation validation result
974 **/
975 public function filter_wpcf7_validate($result, $tags){
976 /**
977 *@since 1.1.0
978 *reset the validation result.
979 * this is required to dynamically disable required form fields & stop their validation.
980 * Disabled fields are not submitted but CF7 forces their values to empty and therefore flags requried fields as invalid at submission even if they are disabled.
981 * this bug was reported in the cf7 support forum:
982 * https://wordpress.org/support/topic/bug-javascript-disabled-fields-flagged-as-invalid/
983 **/
984 $result = new WPCF7_Validation();
985
986 //rebuild the default validation result.
987 $cf7form = WPCF7_ContactForm::get_current();
988 $tags = $cf7form->scan_form_tags();
989 $submitted = WPCF7_Submission::get_instance();
990 $data = $submitted->get_posted_data();
991 $tag_types = array(); //store all form tags, including cloned tags for array fields.
992
993 /** @since 3.1.3 allow validation of unvalidated data in custom validation filter */
994 $validation = array(); //holds all the validation messages.
995 $validated = array();
996 $submission = array();
997 /**
998 *@since 2.1.0 fix issue with Conditional Field plugin.
999 */
1000 $data = $this->remove_hidden_fields_from_conditional_plugin($data);
1001 $toggle_status = '';
1002 if(isset($_POST['_cf7sg_toggles'])){
1003 $toggle_status = json_decode( stripslashes($_POST['_cf7sg_toggles']));
1004 }
1005 /** @since 2.5.0 new data consolidation enables simpler validation */
1006 $deprecated = true; //check if the form being submitted was created with an older version of the plugin.
1007 if( isset($_POST['_cf7sg_version']) && version_compare($_POST['_cf7sg_version'],'2.5.0','>=') ){
1008 $deprecated = false;
1009 }
1010 foreach ( $tags as $tag ) {
1011 //debug_msg($data);
1012 if($deprecated){
1013 /**
1014 * @since 2.1.5 fix validation of non-toggled checkox/radio. Toggled fields are now tracked in the tag itself as a class.
1015 */
1016 if(!isset($_POST[$tag['name']])){
1017 $isRequired = false;
1018 switch($tag['type']){
1019 case 'checkbox*':
1020 case 'radio':
1021 case 'file*': /** @since 2.3.1 fix as file field has no values in $_POST.*/
1022 $isRequired = true;
1023 $tag_options = $tag->options;
1024 if(empty($tag_options)) break; //break from switch, not toggled, we need to validate.
1025 $toggle='';
1026 foreach($tag_options as $option){
1027 $match = array();
1028 preg_match('/class:cf7sg-toggle-(.[^\s]+)/i',$option, $match);
1029 if(!empty($match)){
1030 $toggle=$match[1];
1031 break; //break fromeach loop.
1032 }
1033 }
1034 if(empty($toggle)) break; //break from switch, not toggled, we need to validate.
1035 //check if the toggle is open. only open toggles are registered.
1036 if(!empty($toggle_status) && property_exists($toggle_status, $toggle)) break; //break from switch, is toggled and in use, we need to validate.
1037 //if we reached here, then the checkbox/radio is toggled and not in use, so do not validate.
1038 $isRequired = false;
1039 break;
1040 }
1041 if(!$isRequired) continue;//not submitted==disabled, or not used.
1042 }
1043 }
1044 /**
1045 *@since 1.9.0 fix issue with Conditional Field plugin.
1046 */
1047 //validation for non-deprecated plugins (v2.5.0+).
1048 if(!isset($data[$tag['name']])) continue; //it was removed by some plugin.
1049 $type = $tag['type'];
1050 //check to see if this field is an array (table or tab or both).
1051 $tag_types[$tag['name']] = $tag['type'];
1052 $field_type = self::field_type($tag['name'], $cf7form->id());
1053 switch($field_type){
1054 case 'tab':
1055 case 'table':
1056 case 'both':
1057 // the $data array is passed by reference and will be consolidated.
1058 $values = $data[$tag['name']];
1059 /** @since 3.1.3 track all validations */
1060 $validation[$tag['name']] = array();
1061 $validated[$tag['name']] = array();
1062 $submission[$tag['name']] = array();
1063 $idx = 0;
1064 foreach($values as $index=>$value){
1065 if(is_array($value) && 'both'==$field_type){
1066 $validation[$tag['name']][$idx]=array();
1067 $validated[$tag['name']][$idx] = array();
1068 $submission[$tag['name']][$idx] = array();
1069 $rdx = 0;
1070 foreach($value as $row=>$row_value){
1071 if($deprecated && !isset($_POST[$tag['name'].$index.$row])) continue;
1072 elseif(!isset($row_value)) continue;
1073 $sg_field_tag = clone $tag;
1074 $sg_field_tag['name'] = $tag['name'].$index.$row;
1075 $result = apply_filters("wpcf7_validate_{$tag['type']}", $result, $sg_field_tag);
1076 /** @since 3.1.3 */
1077 $validation[$tag['name']][$idx][$rdx] = $this->strip_cf7_validation($result, $sg_field_tag->name);
1078 $validated[$tag['name']][$idx][$rdx] = $sg_field_tag;
1079 $submission[$tag['name']][$idx][$rdx] = $this->get_submitted_data($tag['name'], $type, $row_value);
1080 $rdx++;
1081 }
1082 }else{
1083 if($deprecated && !isset($_POST[$tag['name'].$index])) continue;
1084 elseif(!isset($value)) continue;
1085 $sg_field_tag = clone $tag;
1086 $sg_field_tag['name'] = $tag['name'].$index;
1087 $result= apply_filters("wpcf7_validate_{$tag['type']}", $result, $sg_field_tag);
1088 /** @since 3.1.3 */
1089 $validation[$tag['name']][$idx] = $this->strip_cf7_validation($result, $sg_field_tag->name);
1090 $validated[$tag['name']][$idx] = $sg_field_tag;
1091 $submission[$tag['name']][$idx] = $this->get_submitted_data($tag['name'], $type, $value);
1092 }
1093 $idx++;
1094 }
1095 // debug_msg($values, 'values '.$tag['name'].'....');
1096 // debug_msg($result, 'validation '.$tag['name'].'....');
1097 if($deprecated){ //if deprecated no need to bother with validation/validated arrays.
1098 /** @since 2.3.1 fix the files tag validation for files in tabs/tables*/
1099 switch($type){
1100 case 'file':
1101 case 'file*':
1102 // Search for $_FILES where name match a tabbed file field
1103 $regex = '/^'.preg_quote($tag['name']);
1104 $submitted_fields = preg_grep($regex.'$/', array_keys($_FILES));
1105 //extract all relevant fields
1106 $submitted_fields += preg_grep($regex.'_tab-[0-9]+_row-[0-9]+$/', array_keys($_FILES));
1107 //we also need the rows with tab index=0
1108 $row_fields = preg_grep($regex.'_row-[0-9]+$/', array_keys($_FILES));
1109 //.. and tabs with row index=0
1110 $submitted_fields += preg_grep($regex.'_tab-[0-9]+$/', array_keys($_FILES));
1111 foreach($submitted_fields as $index => $field){
1112 // For each tabbed file, call the filter validate to add uploaded_files
1113 $sg_field_tag = clone $tag;
1114 $sg_field_tag['name'] = $field;
1115 $result = apply_filters("wpcf7_validate_{$sg_field_tag['type']}", $result, $sg_field_tag);
1116 }
1117 }
1118 }
1119 break;
1120 default:
1121 /** @since 2.11.0 recaptcha fix constributed by @netzgestaltung */
1122 if ( $type !== 'captchar' ) { // really simple captcha gets called twice otherwise - and does not validate
1123 if(!isset($data[$tag['name']])) continue 2; /*likely toggled and unused*/
1124 $result= apply_filters( "wpcf7_validate_{$type}", $result, $tag );
1125 /** @since 3.1.3 */
1126 $validation[$tag['name']] = $this->strip_cf7_validation($result, $tag['name']);
1127 $validated[$tag['name']] = $tag;
1128 $submission[$tag['name']] = $this->get_submitted_data($tag['name'], $type, $data[$tag['name']]);
1129 }
1130 break;
1131 }
1132 }
1133
1134 $form_key = '';
1135 if(isset($data['_wpcf7_key'])){
1136 $form_key = $data['_wpcf7_key'];
1137 }
1138 /**
1139 * filter to validate the entire submission and check interdependency of fields
1140 * @since 1.0.0
1141 * @param Array $validation an array of $field_name=>$validaton_message.
1142 * @param Array $submission submitted data, $field_name => $value pairs ($value can be an array especially for fields in tables and tabs) null values are fields which have not been disabled and not submitted such as those in toggled sections.
1143 * @param String $form_key unique form key to identify current form.
1144 * @param int $form_id cf7 form post ID.
1145 * @return Array an array of errors messages, $field_name => $error_msg for single values, and $field_name => [0=>$error_msg, 1=>$error_msg,...] for array values
1146 */
1147 $validation = apply_filters('cf7sg_validate_submission', $validation, $submission, $form_key, $data['_wpcf7']);
1148
1149 $result = new WPCF7_Validation();
1150
1151 if(!empty($validation)){
1152 foreach($validation as $name=>$msg){
1153 switch(true){
1154 case is_array($msg):
1155 foreach($msg as $idx=>$value){
1156 switch(true){
1157 case is_array($value):
1158 foreach($value as $rdx=>$message){
1159 if(empty($message)) continue;
1160 $result->invalidate($validated[$name][$idx][$rdx], $message);
1161 }
1162 break;
1163 case is_array($validated[$name][$idx]): //error, expecting array..
1164 debug_msg('Filtered cf7sg_validate_submission validation ERROR, expecting array for table field within tab: '.$name.'['.$idx.']');
1165 break;
1166 case empty($value): //no message, just continue.
1167 break;
1168 default:
1169 $result->invalidate($validated[$name][$idx], $value);
1170 break;
1171 }
1172 }
1173 break;
1174 case is_array($validated[$name]): //error, we should have an array.
1175 debug_msg('Filtered cf7sg_validate_submission validation ERROR, expecting array for field '.$name);
1176 break;
1177 case empty($msg): //no message, just continue.
1178 break;
1179 default:
1180 $result->invalidate($validated[$name], $msg);
1181 break;
1182 }
1183 }
1184 }
1185 return $result;
1186 }
1187 /**
1188 * Function to strip the submitted data for a given field.
1189 * Handles base type files whose data is stored in the $_FILES global array.
1190 *@since 3.1.3
1191 *@param string $name field name.
1192 *@param string $type base type.
1193 *@param mixed $data $_POST data.
1194 *@return mixed value actually submitted.
1195 */
1196 public function get_submitted_data($name, $type, $data){
1197 $value = $data;
1198 switch($type){
1199 case 'file':
1200 case 'file*':
1201 $value = array();
1202 if(isset($_FILES[$name])) $value = $_FILES[$name];
1203 break;
1204 }
1205 return $value;
1206 }
1207 /**
1208 * This function strips the validation message from cf7 WPCF7_Validation object for storage and custom modification using the hook 'cf7sg_validate_submission'.
1209 *
1210 *@since 3.1.3
1211 *@param WPCF7_Validation $result cf7 validation object for the field being validated.
1212 *@param WPCF7_FormTag $tag cf7 tag object for the field being validated.
1213 *@return string text_description
1214 */
1215 protected function strip_cf7_validation($result, $name){
1216 $invalid = $result->get_invalid_fields();
1217 $msg = '';
1218 if( !empty($invalid) && isset($invalid[$name]['reason']) ){
1219 $msg = $invalid[$name]['reason'];
1220 }
1221 return $msg;
1222 }
1223 /**
1224 * Function to save toggled collapsible sections status to open them up again for draft forms.
1225 * Hooks action 'cf7_2_post_form_posted'
1226 * @since 1.0.0
1227 * @param string $key form unique key.
1228 * @param Array $submitted_data array of field-name=>value pairs submitted in form
1229 *@since 1.1.0
1230 *@param string $param text_description
1231 *@return string text_description
1232 */
1233 public function save_toggle_status($post_id, $key, $post_fields, $post_meta_fields, $submitted_data){
1234 if(isset($submitted_data['_cf7sg_toggles'])){
1235 $toggles = json_decode( stripslashes( $submitted_data['_cf7sg_toggles'] ) );
1236 $toggles_array = array();
1237 if(!empty($toggles)){
1238 foreach($toggles as $key=>$value){
1239 $toggles_array[$key] = sanitize_text_field($value);
1240 }
1241 }
1242 //debug_msg($toggles_array, 'toggles status saved, ');
1243 update_post_meta($post_id, 'cf7sg_toggles_status', $toggles_array);
1244 }
1245 }
1246 /**
1247 * Function to save custom options values added to tagged select2 fields.
1248 * Hooks action 'cf7_2_post_form_posted'
1249 * @since 1.0.0
1250 * @param string $key form unique key.
1251 * @param Array $submitted_data array of field-name=>value pairs submitted in form
1252 **/
1253 public function save_select2_custom_options($post_id, $key, $post_fields, $post_meta_fields, $submitted_data){
1254 $form_id = Cf7_WP_Post_Table::form_id($key);
1255 $tagged_fields = get_post_meta($form_id, '_cf7sg_select2_tagged_fields', true);
1256 if(empty($tagged_fields)){
1257 return;
1258 }
1259
1260 foreach($tagged_fields as $field_name=>$source){
1261 $value = $submitted_data[$field_name];
1262 $is_array = true;
1263 if(!is_array($value)){
1264 $value = array($value);
1265 $is_array = false;
1266 }
1267 switch($source['source']){
1268 case 'taxonomy':
1269 $taxonomy = $source['taxonomy'];
1270 $idx=0;
1271 foreach($value as $term){
1272 if(!term_exists($term, $taxonomy)){
1273 /**
1274 * Filter custom options from tag enabled select2 dynamic-dropdown fields
1275 * where the source of options come from taxonomy terms. Filter is fired when a new value is submitted. The pluign inserts a new term by default as per submitted value.
1276 * @param string $term the new term submitted by the user.
1277 * @param string $field_name the name of the form field.
1278 * @param string $taxonomy the taxonomy to which this is added.
1279 * @param array $submitted_data array of other submitted $field=>$value pairs.
1280 * @param string $key the form unique key.
1281 * @return string the new term name to insert.
1282 * @since 2.0.0
1283 */
1284 $term = apply_filters('cf7sg_dynamic_dropdown_new_term', $term, $field_name, $taxonomy, $submitted_data, $key);
1285 $new_term = wp_insert_term($term, $taxonomy);
1286 if(!is_wp_error($new_term)){
1287 $new_term = get_term($new_term['term_id'], $taxonomy);
1288 $value[$idx] = $new_term->slug;
1289 }
1290 }
1291 $idx++;
1292 }
1293 break;
1294 case 'post':
1295 $args = array(
1296 'post_type' => $source['post'],
1297 'post_status' => 'publish',
1298 'posts_per_page' => -1
1299 );
1300 if(!empty($source['taxonomy'])){
1301 $tax = array();
1302 if(sizeof($source['taxonomy']) > 1){
1303 $tax['relation'] = 'AND';
1304 }
1305 foreach($source['taxonomy'] as $term => $taxonomy){
1306 $tax[]=array(
1307 'taxonomy' => $taxonomy,
1308 'field' => 'slug',
1309 'terms' => $term
1310 );
1311 }
1312 $args['tax_query'] = $tax;
1313 }
1314 $args = apply_filters('cf7sg_dynamic_dropdown_post_query', $args, $tag->name, $key);
1315 $posts = get_posts($args);
1316 $options = array();
1317 if(!empty($posts)){
1318 foreach($posts as $post){
1319 $options[$post->post_name] = $post->post_title;
1320 }
1321 }
1322 $idx=0;
1323 foreach($value as $slug){
1324 if(!isset($options[$slug])){
1325 $args = $source;
1326 $post_type = $source['post'];
1327 $title = $slug;
1328 $post_name = '';
1329 /**
1330 * Filter custom options from tag enabled select2 dynamic-dropdown fields
1331 * where the source of options come from post titles. Filter is fired when a new value is submitted.
1332 * This plugin does not take any further action, ie no post of $post_type will be created. It is upto you to do so and return the slug of the newly created post.
1333 * @param string $post_name the new post slug.
1334 * @param string $field_name the name of the form field.
1335 * @param string $title new value being submitted for a new post title.
1336 * @param string $post_type the post type from which this dropdown was built
1337 * @param array $args an array of additional parameters that was set in the tag, for example the taxonomy and terms from which to filter the posts for the dynamic list.
1338 * @param array $submitted_data array of other submitted $field=>$value pairs.
1339 * @param string $key the form unique key.
1340 */
1341 $value[$idx] = apply_filters('cf7sg_dynamic_dropdown_new_post', $post_name, $field_name, $title, $post_type, $args, $submitted_data, $key);
1342 }
1343 $idx++;
1344 }
1345 break;
1346 case 'filter':
1347 $values = $value;
1348 /**
1349 * Filter custom otions from tag enabled select2 dynamic-dropdown fields
1350 * where the source of options come from a filter. Filter is fired when values are submitted.
1351 * Return updated values if any are custom values so that saved/draft submissions will reflect the correct value saved in the DB,
1352 * @param array $values an array submitted values (several values can be submitted in the case of a tabbed/table input field).
1353 * @param string $field_name the name of the form field.
1354 * @param array $submitted_data array of other submitted $field=>$value pairs.
1355 * @param string $key the form unique key.
1356 */
1357 $value = apply_filters('cf7sg_dynamic_dropdown_filter_select2_submission', $values, $field_name, $submitted_data, $key);
1358 break;
1359 }
1360 //Save the modified value, find which post field the field is mapped to
1361 if(!$is_array){
1362 $value = $value[0];
1363 }
1364 if( isset($post_fields[$field_name]) ){
1365 $post_key = $post_fields[$field_name];
1366 switch($post_key){
1367 case 'title':
1368 case 'author':
1369 case 'excerpt':
1370 $post_key = 'post_'.$post_key;
1371 break;
1372 case 'editor':
1373 $post_key ='post_content';
1374 break;
1375 case 'slug':
1376 $post_key ='post_name';
1377 break;
1378 }
1379 wp_update_post(array(
1380 'ID' => $post_id,
1381 $post_key => $value
1382 ));
1383 }else if( isset($post_meta_fields[$field_name]) ){
1384 update_post_meta($post_id, $post_meta_fields[$field_name], $value);
1385 }
1386 }
1387 }
1388 /**
1389 * Function to retrieve dynamic-dropdown attributes
1390 *
1391 * @since 1.0.0
1392 * @param WPCF7_FormTag $tag cf7 tag object of basetype dynamic_select.
1393 * @return array an array of attributes with 'source'=>[post|taxonomy|filter], .
1394 **/
1395 static public function get_dynamic_drodown_attributes($tag){
1396 if(is_array($tag) && isset($tag['basetype']) && 'dynamic_select' === $tag['basetype']){
1397 $tag = new WPCF7_FormTag($tag);
1398 }
1399 if( !($tag instanceof WPCF7_FormTag) || 'dynamic_select' !== $tag['basetype']){
1400 return false;
1401 }
1402 $source = array();
1403 foreach($tag->values as $values){
1404 if(0 === strpos($values, 'slug:') ){
1405 $source['source'] = "taxonomy";
1406 $source['taxonomy'] = str_replace('slug:', '', $values);
1407 }
1408 if(0 === strpos($values, 'source:post')){
1409 $source['source'] = "post";
1410 $source['post'] = str_replace('source:post:', '', $values);
1411 }
1412 if(0 === strpos($values, 'taxonomy:')){
1413 if(empty($source['taxonomy'])){
1414 $source['taxonomy'] = array();
1415 }
1416 $values = str_replace('taxonomy:', '', $values);
1417 $exp = explode(":", $values);
1418 if(!empty($exp) && is_array($exp)){
1419 $source['taxonomy'][$exp[1]] = $exp[0];
1420 }
1421 }
1422 if(0 === strpos($values, 'source:filter')){
1423 $source['source'] = "filter";
1424 }
1425 }
1426 return $source;
1427 }
1428 /**
1429 * Function to temporarily fix the conditional fields plugin issue.
1430 * this is is called by the vlidation function 'filter_wpcf7_validate' above.
1431 *@since 2.1
1432 *@param array $posted_data submitted data
1433 *@return array submitted data without fields that remained hidden.
1434 */
1435 private function remove_hidden_fields_from_conditional_plugin($posted_data){
1436 /*code taken from cf7cf.php file in cf7-conditional-fields*/
1437 if(!isset($posted_data['_wpcf7cf_hidden_group_fields'])){
1438 return $posted_data;
1439 }
1440 $hidden_fields = json_decode(stripslashes($posted_data['_wpcf7cf_hidden_group_fields']));
1441 $conditonal_fields = array();
1442 if (is_array($hidden_fields) && count($hidden_fields) > 0) {
1443 foreach ($hidden_fields as $field) {
1444 if (wpcf7cf_endswith($field, '[]')) {
1445 $field = substr($field,0,strlen($field)-2);
1446 }
1447 unset( $posted_data[$field] );
1448 }
1449 }
1450 return $posted_data;
1451 }
1452 /**
1453 * Filter mail tags which are table or tab fields.
1454 * Hooked on 'wpcf7_mail_tag_replaced'.
1455 *@since 2.1
1456 *@param string $replaced mail tag string to replace.
1457 *@param mixed $submitted value of submitted field from cf7 posted data..
1458 *@param boolea $html mail if mail body is using html.
1459 *@param WPCF7_MailTag $mail_tag mail tag object of field being replaced.
1460 *@return string replacement string.
1461 */
1462 public function filter_table_tab_mail_tag($replaced, $submitted, $html=false, $mail_tag=null ){
1463 $cf7form = WPCF7_ContactForm::get_current();
1464 $cf7form_key = Cf7_WP_Post_Table::form_key($cf7form->id());
1465 $submitted_cf7 = WPCF7_Submission::get_instance();
1466 $submitted_data = $submitted_cf7->get_posted_data();
1467
1468 if(empty($mail_tag)) return $replaced;
1469 $field_type = self::field_type($mail_tag->field_name(), $cf7form->id());
1470 $label = '';
1471 $build = false;
1472 switch($field_type){
1473 case 'tab':
1474 case 'table':
1475 if($html){
1476 $filtered = apply_filters('cf7sg_mailtag_grid_fields', '', $mail_tag->field_name(), $submitted, $cf7form_key);
1477 if(!empty($filtered)){
1478 $replaced = $filtered;
1479 break;
1480 }
1481 }
1482 $label=('table'==$field_type)?'row':$field_type;
1483 if($html && is_array($submitted)) {
1484 $idx=0;
1485 $replaced='';
1486 foreach($submitted as $index=>$value){
1487 $idx++;
1488 if(is_array($value)) $value = implode(' | ', $value);
1489 $replaced .= '<div><label>'.$label.'('.$idx.'):</label><span>'.$value.'</span></div>';
1490 }
1491 }
1492 break;
1493 case 'both':
1494 if($html){
1495 $filtered = apply_filters('cf7sg_mailtag_grid_fields', '', $mail_tag->field_name(), $submitted, $cf7form_key);
1496 if(!empty($filtered)){
1497 $replaced = $filtered;
1498 break;
1499 }
1500 }
1501 // build table.
1502 $replaced = '';
1503 if( is_array($submitted)) {
1504 $tab = 0;
1505 foreach($submitted as $index=>$value){
1506 $tab++;
1507 $idx=0;
1508 if($html) $replaced .= '<div><span>tab('.$tab.')</span>&nbsp;';
1509 else $replaced .= 'tab('.$tab.') = ';
1510 if(is_array($value)){
1511 foreach($value as $row=>$row_value){
1512 $idx++;
1513 if(is_array($row_value)) $row_value = implode('|', $row_value);
1514 if($html) $replaced .='<div><label>row('.$idx.'):</label><span>' . $row_value . '</span></div>';
1515 else $replaced .= $row_value.',';
1516 }
1517 }
1518 if($html) $replaced .='</div>';
1519 else $replaced .= PHP_EOL;
1520 }
1521 }
1522 break;
1523 default: //general fix for cf7 mail tags.
1524 $tag = $mail_tag->corresponding_form_tag();
1525 /**
1526 * Filter the value inserted in the mail tag.
1527 * @since 2.9.0.
1528 * @param string $replaced value to filter.
1529 * @param string $field_name name of the field being inserted in the mail.
1530 * @param array $submitted submitted form data.
1531 * @param string $form_key unique form key identifier.
1532 * @return string a value to replace.
1533 */
1534 if($tag instanceof WPCF7_FormTag){
1535 $replaced = apply_filters('cf7sg_mailtag_'.$tag->basetype, $replaced, $mail_tag->field_name(), $submitted_data, $cf7form_key);
1536 }
1537 /**
1538 * Filter the value inserted in the mail tag.
1539 * @since 3.1.6.
1540 * @param string $replaced value to filter.
1541 * @param string $field_name name of the field being inserted in the mail.
1542 * @param array $submitted submitted form data.
1543 * @param string $form_key unique form key identifier.
1544 * @return string a value to replace.
1545 */
1546 $replaced = apply_filters('cf7sg_mailtag_'.$mail_tag->field_name(), $replaced, $submitted_data, $cf7form_key);
1547 break;
1548 }
1549
1550 return $replaced;
1551 }
1552 /**
1553 * We need to add the missing attachments just before the mail is sent.
1554 * Hooked to filter 'wpcf7_mail_components', sets up the final $components object
1555 * @since 2.4.1
1556 * @param Array $components an array of mail parts
1557 * @param WPCF7_ContactForm $cf7form cf7 form object
1558 * @param WPCF7_Mail $cf7mail cf7 mail object
1559 * @return Array an array of mail parts
1560 */
1561 public function wpcf7_mail_components($components, $cf7form, $cf7mail) {
1562 //$tags = $cf7form->scan_form_tags();
1563 $submission = WPCF7_Submission::get_instance();
1564 $uploaded_files = $submission->uploaded_files();
1565 $cf7_mail = WPCF7_Mail::get_current();
1566 /** @since 2.5.6 fix issue with non-attached files */
1567 $template = $cf7_mail->get( 'attachments' );
1568 $row = $tab = null;
1569 $idx = 0;
1570 /** @since 2.8.1 fix for send pdf */
1571 $attachments = $components['attachments'];
1572 foreach ( (array) $uploaded_files as $name => $path ) {
1573 if ( false === strpos( $template, "[${name}]" ) ) continue; //not attached.
1574 $field_type = self::field_type($name, $cf7form->id());
1575 $data = $submission->get_posted_data($name);
1576 switch($field_type){
1577 case 'tab':
1578 case 'table':
1579 foreach($data as $field_idx=>$file_name){
1580 if(empty($file_name)) continue;
1581 $idx++;
1582 $row = ('table'==$field_type)? str_replace('_row-', '', $field_idx):null;
1583 $row = (isset($row) && empty($row))? 1:1+(int)$row;
1584 $tab = ('tab'==$field_type)? str_replace('_tab-', '', $field_idx):null;
1585 $tab = (isset($tab) && empty($tab))? 1:1+(int)$tab;
1586 $file = $uploaded_files[$name.$field_idx];
1587 if(!in_array($attachments,$file)) $attachments[] = $file;
1588 $file = explode('/',$file);
1589 $filename = $file[count($file)-1];
1590 $components['body'].= apply_filters('cf7sg_annotate_mail_attach_grid_files', '', $name, $row, $tab, $idx,$filename, $_POST['_wpcf7_key']);
1591 }
1592 break;
1593 case 'both':
1594 $row = $tab = 1;
1595 foreach($data as $field_tab=>$tab_files){
1596 $tab = str_replace('_tab-', '', $field_tab);
1597 $tab = empty($tab)?1:1+(int) $tab;
1598 foreach($tab_files as $field_row=>$file_name){
1599 if(empty($file_name)) continue;
1600 $idx++;
1601 $row = str_replace('_row-', '', $field_row);
1602 $file = $uploaded_files[$name.$field_tab.$field_row];
1603 if(!in_array($attachments,$file)) $attachments[] = $file;
1604 $file = explode('/',$file);
1605 $filename = $file[count($file)-1];
1606 $components['body'].= apply_filters('cf7sg_annotate_mail_attach_grid_files','', $name, $row, $tab, $idx,$filename, $_POST['_wpcf7_key']);
1607 }
1608 }
1609 break;
1610 default: //singular fields.
1611 if(empty($path)) continue 2;
1612 $idx++;
1613 if(!in_array($path, $attachments)) $attachments[] = $path;
1614 $path = explode('/',$path);
1615 $filename = $path[count($path)-1];
1616 $components['body'].= apply_filters('cf7sg_annotate_mail_attach_grid_files','', $name, null, null, $idx,$filename, $_POST['_wpcf7_key']);
1617 break;
1618 }
1619 }
1620 $components['attachments'] = $attachments;
1621 return $components;
1622 }
1623 }
1624