PluginProbe
Smart Grid-Layout Design for Contact Form 7 / 3.3.3
Smart Grid-Layout Design for Contact Form 7 v3.3.3
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.3.3, at public/class-cf7-grid-layout-public.php

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