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

2,186 lines 89.2 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 4.0.0
76 * @access private
77 * @var Array $array_tabbed_toggles The form toggles within tabbed sections.
78 */
79 static private $array_tabbed_toggles = array();
80 /**
81 * The cf7 array fields.
82 *
83 * @since 2.5.0
84 * @access private
85 * @var Array $array_toggled_panels The form toggled sections used.
86 */
87 static private $array_toggled_panels = array();
88 /**
89 * The cf7 array fields.
90 *
91 * @since 2.6.0
92 * @access private
93 * @var Array $localised_data Localised data parameters.
94 */
95 private $localised_data = array();
96 /**
97 * The wp_errors returned by the CF7 v5.4 wpcf7_unship_uploaded_file() function.
98 *
99 * @since 4.10.1
100 * @access private
101 * @var Array $file_wp_errors track file errors.
102 */
103 private $file_wp_errors = array();
104 /**
105 * Initialize the class and set its properties.
106 *
107 * @since 1.0.0
108 * @param string $plugin_name The name of the plugin.
109 * @param string $version The version of this plugin.
110 */
111 public function __construct( $plugin_name, $version ) {
112 $this->plugin_name = $plugin_name;
113 $this->version = $version;
114 }
115 /**
116 *
117 *
118 * @since 1.0.0
119 * @param string $field field name to check
120 * @param string $form_id form id for which to check the
121 * @return string 'singular' for non-grid fields, 'tab' for tabbed fields, 'table' for tablled fields, 'both' for fields in tables in tabs.
122 */
123 static public function field_type($field, $form_id){
124 if(!isset(self::$array_grid_fields[$form_id])){
125 //try to load the fields.
126 self::get_grid_fields($form_id);
127 }
128 $type = 'singular';
129 if(isset(self::$array_grid_fields[$form_id][$field])){
130 $type = self::$array_grid_fields[$form_id][$field][1]; //array(origin_id,type);
131 }
132 return $type;
133 }
134
135 /**
136 * Check if a field is in a toggled section.
137 *
138 *@since 2.5.0
139 *@param string $field field nav_menu_link_attributes.
140 *@return array null or array of panel ids.
141 */
142 protected function get_toggle($field){
143 return isset(self::$array_toggle_fields[$this->form_id][$field]) ? self::$array_toggle_fields[$this->form_id][$field]: null;
144 }
145 /**
146 * Check if a toggled section was used and its fields submitted.
147 *
148 *@since 2.5.0
149 *@param string $toggle toogle id
150 *@return boolean was the toggle section submitted.
151 */
152 protected function is_submitted($toggle){
153 return !empty( array_intersect( array_keys(self::$array_toggled_panels[$this->form_id]), $toggle) );
154 }
155 /**
156 * Check if a toggled section is within a tabbed section.
157 *
158 *@since 4.0.0
159 *@param string $toggle toogle id
160 *@return boolean was the toggle section inside a tabbed section.
161 */
162 protected function is_tabbed($toggle){
163 /* if the array is not set for this form_id,
164 * then it is because the form is an older version and
165 * should return true for backward compatibility */
166 if(isset(self::$array_tabbed_toggles[$this->form_id])){
167 return isset(self::$array_tabbed_toggles[$this->form_id][$toggle]);
168 }else return true;
169 }
170 /**
171 * Register the stylesheets for the public-facing side of the site.
172 *
173 * @since 1.0.0
174 */
175 public function register_styles_and_scripts() {
176 global $wp_scripts, $post;
177 $cf7id = $cf7key = '';
178 if ( is_a( $post, 'WP_Post' ) ) { //&& has_shortcode( , 'cf7form')
179 preg_match_all( '/' . get_shortcode_regex() . '/', $post->post_content, $matches, PREG_SET_ORDER );
180 foreach($matches as $sc){
181 switch($sc[2]){
182 case 'contact-form-7':
183 $attrs = shortcode_parse_atts($sc[3]);
184 if(is_array($attrs) && isset($attrs['id'])) $cf7id = $attrs['id'];
185 break;
186 case 'cf7form':
187 $attrs = shortcode_parse_atts($sc[3]);
188 if(is_array($attrs) && isset($attrs['cf7key'])){
189 $cf7key = $attrs['cf7key'];
190 $cf7id = get_cf7form_id($cf7key);
191 }
192 }
193 }
194 }
195 $resources = array();
196 if(!empty($cf7id)){
197 $resources = get_post_meta($cf7id, '_cf7sg_script_classes', true);
198 if(empty($resources)) $resources = array();
199 }
200
201 $airplane=false;
202 if( class_exists( 'Airplane_Mode_Core' ) && Airplane_Mode_Core::getInstance()->enabled()){
203 $airplane=true;
204 }
205 $plugin_dir = plugin_dir_url( __DIR__ );
206 //default style for cf7 grid forms (row buttons and tables mainly).
207 //others
208 // get registered script object for jquery-ui
209 $ui_ver = '1.12.1';
210 if(!empty($wp_scripts)) $ui_ver = $wp_scripts->query('jquery-ui-core')->ver;
211 // tell WordPress to load the Smoothness theme from Google CDN
212 if( !$airplane ){
213 $protocol = is_ssl() ? 'https' : 'http';
214 $url_path = "$protocol://ajax.googleapis.com/ajax/libs/jqueryui/{$ui_ver}/";
215 wp_register_style('cf7-jquery-ui', $url_path . 'themes/smoothness/jquery-ui.min.css', array(), $ui_ver , 'all');
216 wp_register_style( 'cf7-jquery-ui-theme', $url_path . 'jquery-ui.theme.min.css', array(), $ui_ver, 'all');
217 wp_register_style( 'cf7-jquery-ui-structure', $url_path . 'jquery-ui.structure.min.css', array(), $ui_ver, 'all');
218 }
219
220
221 /** @since 3.1,0 improve live loading of resources */
222 $ff = '';
223 $pf='';
224 if((!defined('WP_DEBUG') || !WP_DEBUG) && !isset($_GET['cf7sgdbg'])){
225 $ff = '.min';
226 $pf = '/min';
227 }
228 wp_register_style( 'cf7-benchmark-css', $plugin_dir . "public/css{$pf}/cf7-benchmark.css", array(), $this->version, 'all' );
229
230 wp_register_style( 'smart-grid', $plugin_dir . "assets/css.gs/smart-grid{$ff}.css", array(), $this->version, 'all' );
231 wp_register_style('jquery-toggles-css', $plugin_dir . "assets/jquery-toggles/css/toggles{$ff}.css", array(), $this->version, 'all' );
232 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' );
233
234 /** @since 4.2.0 enable Gliderliders for slider sections */
235 $min = '';
236 if(!defined('WP_GURUS_DEBUG') || !WP_GURUS_DEBUG) $min = '.min';
237 wp_register_style('glider-style', $plugin_dir . "assets/glider-js/glider{$min}.css", array(), '1.7.4','all');
238
239 $dep = array();
240 foreach($resources as $class){
241 switch($class){
242 case 'has-hybriddd':
243 $dep[] ='hybriddd-style';
244 break;
245 case 'has-select2':
246 $dep[] ='select2-style';
247 break;
248 case 'has-nice-select':
249 $dep[] ='jquery-nice-select-css';
250 break;
251 case 'has-slider':
252 $dep[] ='glider-style';
253 break;
254 case 'has-toggles':
255 $dep[] = 'jquery-toggles-light-css';
256 $dep[] = 'cf7-jquery-ui';
257 $dep[] = 'cf7-jquery-ui-theme';
258 $dep[] = 'cf7-jquery-ui-structure';
259 break;
260 case 'has-date':
261 $dep[] = 'cf7-jquery-ui';
262 case 'has-tabs':
263 $dep[] = 'cf7-jquery-ui';
264 $dep[] = 'cf7-jquery-ui-theme';
265 $dep[] = 'cf7-jquery-ui-structure';
266 break;
267 case 'has-accodrion':
268 $dep[] = 'cf7-jquery-ui';
269 $dep[] = 'cf7-jquery-ui-theme';
270 $dep[] = 'cf7-jquery-ui-structure';
271 break;
272
273 }
274 }
275 wp_register_style( $this->plugin_name, $plugin_dir . "public/css{$pf}/cf7-grid-layout-public.css", $dep, $this->version, 'all' );
276
277 do_action('smart_grid_register_styles',$airplane, $min, $resources, $cf7key, $cf7id);
278
279 //script registration
280 //load custom css/js script from theme css folder.
281 // $themepath = get_stylesheet_directory();
282 // $themeuri = get_stylesheet_directory_uri();
283 $dep = array( 'jquery','contact-form-7' );
284
285 // if( !empty($cf7key) && file_exists($themepath.'/js/'.$cf7key.'.js') ){
286 // $dep[] = $cf7key.'-js';
287 // wp_register_script( $cf7key.'-js' , $themeuri.'/js/'.$cf7key.'.js', array('jquery', $this->plugin_name), null, true);
288 // }
289 wp_register_script( $this->plugin_name, $plugin_dir . "public/js{$pf}/cf7-grid-layout-public.js", $dep, $this->version, true );
290 wp_register_script('jquery-toggles', $plugin_dir . 'assets/jquery-toggles/toggles.min.js', array( 'jquery' ), $this->version, true );
291 wp_register_script('js-cf7sg-benchmarking', $plugin_dir . "public/js{$pf}/cf7-benchmark.js", array( 'jquery' ), $this->version, true );
292
293 wp_register_script('glider-js', $plugin_dir . "assets/glider-js/glider{$min}.js", null, '1.7.4',true);
294 //allow custom script registration
295 do_action('smart_grid_register_scripts', $airplane, $min, $resources, $cf7key, $cf7id);
296 }
297 /**
298 * Dequeue script 'contact-form-7'
299 * hooked on 'wp_print_scripts', this canbe re-enqeued on specific cf7 shortcode calls
300 * @since 1.0.0
301 **/
302 public function dequeue_cf7_scripts(){
303 wp_dequeue_script('contact-form-7');
304 }
305 /**
306 * Dequeue script 'contact-form-7'
307 * hooked on 'wp_print_style', this canbe re-enqeued on specific cf7 shortcode calls
308 * @since 1.0.0
309 **/
310 public function dequeue_cf7_styles(){
311 wp_dequeue_style('contact-form-7');
312 }
313 /**
314 * Add the cf7 key to the hidden fields so as not to have to load it after each submission.
315 * Hooked to 'wpcf7_form_hidden_fields'
316 * @since 1.0.0
317 * @param Array $hidden hidden fields to add to cf7 form.
318 * @return Array hidden fields to add to cf7 form.
319 **/
320 public function add_hidden_fields($hidden){
321 $form = wpcf7_get_current_contact_form();
322 $post = get_post($form->id());
323 $hidden['_wpcf7_key'] = $post->post_name;
324 $hidden['_cf7sg_toggles'] = '';
325 $hidden['_cf7sg_version'] = $this->version;
326 /** @since 4.4.0 enable rest authentication for logged in users. */
327 $hidden['_wpnonce'] = wp_create_nonce('wp_rest');
328 return $hidden;
329 }
330 /**
331 * Filter autop off for grid forms.
332 * hooked to 'wpcf7_autop_or_not'
333 *@since 4.0.0
334 *@param boolean $autop true or false.
335 *@return boolean true or false
336 */
337 public function disable_autop_for_grid($autop){
338 $form = WPCF7_ContactForm::get_current();
339 if(isset($form) && $form->id()>0){
340 $is_grid_form = get_post_meta($form->id(), '_cf7sg_managed_form', true);
341 $autop = ( !$is_grid_form || ''==$is_grid_form );
342 }
343 return $autop;
344 }
345 /**
346 * Enqueue scripts requried for cf7 shortcode
347 * hooked on 'do_shortcode_tag',
348 * @since 1.0.0
349 **/
350 public function cf7_shortcode_request($output, $tag, $attr){
351 //if not a cf7 shortcode, then exit.
352 if('contact-form-7' !== $tag){
353 return $output;
354 }
355 //wp_enqueue_script('contact-form-7'); //default cf7 plugin script.
356 wp_enqueue_script('contact-form-7'); //default cf7 plugin script.
357 $cf7_id = $attr['id'];
358 //validate version number.
359 /**
360 * @since 1.2.3 disable cf7sg styling/js for non-cf7sg forms.
361 */
362 $is_form = get_post_meta($cf7_id, '_cf7sg_managed_form', true);
363 $form_version = get_post_meta($cf7_id, '_cf7sg_version', true);
364 if($is_form and (empty($form_version) or version_compare($form_version, CF7SG_VERSION_FORM_UPDATE, '<')) ){
365 return '<p><em>'.__('Form is deprecated, please contact the webmaster to <a href="https://wordpress.org/support/topic/upgrade-your-form-message-instead-of-form-being-displayed/">upgrade</a> this form.', 'cf7-grid-layout').'</em></p>';
366 }
367 //get the key
368 $cf7post = get_post($cf7_id);
369 if(empty($cf7post) || WPCF7_ContactForm::post_type != $cf7post->post_type){ //not a form.
370 return $output;
371 }
372 $cf7_key = $cf7post->post_name;
373
374 /** @since 3.0.0 load scripts only for required classes */
375 $class = get_post_meta($cf7_id, '_cf7sg_script_classes', true);
376 if(empty($class)){
377 $class = array();
378 }
379 // debug_msg($class, "$cf7_id form classes ");
380 //check classes required for sub-forms.
381 $sub_forms = array();
382 $use_grid_js = false;
383 $sub_form_keys = get_post_meta($cf7_id, '_cf7sg_sub_forms', true);
384 if(!empty($sub_form_keys)){
385 $args = array(
386 'post_type' => 'wpcf7_contact_form',
387 'post_name__in' => $sub_form_keys
388 );
389 $sub_forms = get_posts($args);
390 foreach($sub_forms as $form){
391 $sub_class = get_post_meta($form->ID, '_cf7sg_script_classes', true);
392 if(!empty($sub_class)) $class = array_unique(array_merge($class, $sub_class));
393 }
394 }
395 //select2
396 if(array_search('has-select2',$class, true)!==false){
397 wp_enqueue_script('jquery-select2');
398 wp_enqueue_style('select2-style');
399 $use_grid_js=true;
400 }
401 //nice-select
402 if(array_search('has-nice-select',$class, true)!==false){
403 wp_enqueue_script('jquery-nice-select');
404 wp_enqueue_style('jquery-nice-select-css');
405 $use_grid_js=true;
406 }
407 //benchmark
408 if(array_search('has-benchmark',$class, true)!==false){
409 wp_enqueue_script('js-cf7sg-benchmarking');
410 $use_grid_js=true;
411 }
412 if(array_search('has-date',$class, true)!==false){
413 wp_enqueue_script('jquery-ui-datepicker');
414 wp_enqueue_style('cf7-jquery-ui');
415 $use_grid_js=true;
416 }
417
418 $use_grid_js = ($use_grid_js or $is_form);
419
420 //cf7 plugin styles.
421 wp_enqueue_style('contact-form-7');
422
423 /** @since 2.6.0 disabled button message*/
424 $form = WPCF7_ContactForm::get_instance($cf7_id);
425 $messages = array();
426 if( !empty($form) ) $messages = $form->prop('messages');
427 else debug_msg("CF7SG FROM ERROR: unable to retrieve cf7 form $cf7_id");
428 //setup classes and id for wrapper.
429 $css_id ='';
430 apply_filters_deprecated('cf7_smart_grid_form_id', array($css_id, $attr), '4.6.0','', __('this filter is no longer available', 'cf7-grid-layout'));
431 $css_id = $this->form_css_id($cf7_key);
432 /** @since 4.6.0 allow redirect on submit */
433 $redirect = get_post_meta($cf7_id, '_cf7sg_page_redirect',true);
434 if(!empty($redirect)){
435 $cache = get_post_meta($cf7_id, '_cf7sg_cache_redirect_data',true);
436 if(!empty($cache)){
437 $cache = wp_create_nonce($css_id);
438 }
439 $redirect = get_permalink($redirect).( empty($cache) ? '':"?cf7sg=$cache");
440 }else $redirect='';
441 /** @since 4.4.0 enable prefilling of form fields*/
442 $prefill = apply_filters('cf7sg_prefill_form_fields', array(), $cf7_key);
443 if( !empty($redirect) or !empty($prefill) ) $use_grid_js = true;
444 if($use_grid_js){
445 $this->localise_script( array(
446 'url' => admin_url( 'admin-ajax.php' ),
447 'debug'=>( defined('WP_DEBUG') && WP_DEBUG ),
448 $css_id => array(
449 'prefill'=>$prefill,
450 'submit_disabled'=> isset($messages['submit_disabled']) ? $messages['submit_disabled']: __( "Disabled! To enable, check the acceptance field.", 'cf7-grid-layout' ),
451 'max_table_rows' => isset($messages['max_table_rows']) ? $messages['max_table_rows']: __( "You have reached the maximum number of rows.", 'cf7-grid-layout' ),
452 'table_labels' => apply_filters('cf7sg_remove_table_row_labels',true,$cf7_key),
453 'redirect'=>$redirect,
454 'slider_auto_scroll' => apply_filters('cf7sg_slider_auto_scroll', true, $cf7_key)
455 )
456 ));
457 //cf7sg script & style.
458 wp_enqueue_script($this->plugin_name);
459 //wp_add_inline_script( $this->plugin_name, 'cf7sg', json_encode($this->localise_script()), 'before' );
460 $localise = json_encode($this->localise_script());
461 add_action('wp_footer', function() use ($localise){
462 printf('<script type="text/javascript">var cf7sg = %s</script>', $localise);
463 });
464 }
465 //load custom css/js script from theme css folder.
466 $themepath = get_stylesheet_directory();
467 $themeuri = get_stylesheet_directory_uri();
468 if( file_exists($themepath.'/css/'.$cf7_key.'.css') ){
469 $dep = array();
470 if($is_form) $dep =array($this->plugin_name);
471 wp_enqueue_style( $cf7_key.'-css' , $themeuri.'/css/'.$cf7_key.'.css', $dep, null, 'all');
472 }
473 if( file_exists($themepath.'/js/'.$cf7_key.'.js') ){
474 wp_enqueue_script( $cf7_key.'-js', $themeuri.'/js/'.$cf7_key.'.js', array('jquery', $this->plugin_name), null, true);
475 do_action_deprecated('smart_grid_register_custom_script', array($cf7_key), '4.6.0', 'cf7sg_enqueue_custom_script-$form_key', __('deprecated action hook','cf7-grid-layout'));
476 do_action("cf7sg_enqueue_custom_script-{$cf7_key}",$cf7_key.'-js');
477 }
478
479 if(empty($is_form) or !$is_form){
480 do_action('smart_grid_enqueue_scripts', $cf7_key, $attr, $class);
481 $classes = implode(' ', $class) .' key_'.$cf7_key;
482 $output = '<div class="cf7sg-container cf7sg-not-grid"><div id="' . $css_id . '" class="'.($use_grid_js?'cf7-smart-grid ':''). $classes . '">' . $output . '</div></div>';
483 return $output;
484 }
485 //grid styling.
486 wp_enqueue_style($this->plugin_name);
487 //load required dependencies for grid form.
488 wp_enqueue_style('smart-grid');
489 /** @since 4.4.0 set max-width of forms dynamically */
490 $max_width = apply_filters('cf7sg_max_form_width', 940, $cf7_key);
491 $mobile_cutoff = apply_filters('cf7sg_responsive_width', 480, $cf7_key);
492 wp_add_inline_style('smart-grid', ":root {--max-cf7sg-form-width: {$max_width}px } @media (max-width: {$mobile_cutoff}px) {.cf7sg-container .cf7-smart-grid.has-grid form .container .row .columns {float: none;margin: 0;width: 100%;}}");
493 wp_enqueue_style('dashicons');
494 //slider introduced in 4.0.
495 if(array_search('has-slider',$class, true)!==false){
496 wp_enqueue_script('glider-js');
497 wp_enqueue_style('glider-style');
498 }
499 //jquery accordion for collapsible rows.
500 $has_section = array_search('has-accordion',$class, true) !==false;
501 if($has_section){
502 wp_enqueue_script('jquery-ui-accordion');
503 }
504 $has_tabs = array_search('has-tabs',$class, true) !==false;
505 $has_tables = array_search('has-table',$class, true) !==false;
506 $has_toggles = array_search('has-toggles',$class, true) !==false;
507
508 $form_time = strtotime($cf7post->post_modified);
509 if(!empty($sub_forms)){
510 $cf7_form = $form_raw = '';
511 foreach($sub_forms as $post_obj){
512 //check form saved date, if sub-form is newer, we need to udpate it.
513 if(strtotime($post_obj->post_modified ) > $form_time){
514 if(empty($cf7_form)){
515 $cf7_form = WPCF7_ContactForm::get_instance($cf7_id);
516 $form_raw = '';
517 if( !empty($cf7_form) ) $form_raw = $cf7_form->prop( 'form' );
518 }
519 $form_raw = $this->update_sub_form($form_raw, $post_obj);
520 }
521 }
522 if(!empty($cf7_form)){ //redraw the form.
523 $cf7_form = wpcf7_save_contact_form(array('id'=>$cf7_id, 'form'=>$form_raw));
524 //reload the form
525 $output = $cf7_form->form_html($attr);
526 $class[]= 'has-update';
527 //actino for other plugin notification.
528 do_action('cf7sg_subform_uddate',$cf7_id, $cf7_form, $attr );
529 }
530 }
531 //required for tables/tabs/accordion
532 if($has_tabs || $has_tables || $has_toggles || $has_section){
533 wp_enqueue_script('jquery-effects-core');
534 }
535
536 if($has_tabs || $has_toggles || $has_section){
537 wp_enqueue_style('cf7-jquery-ui-theme');
538 wp_enqueue_style('cf7-jquery-ui-structure');
539 wp_enqueue_style('cf7-jquery-ui');
540 }
541 if($has_tabs) wp_enqueue_script('jquery-ui-tabs');
542 if($has_toggles){
543 wp_enqueue_script('jquery-toggles');
544 wp_enqueue_style('jquery-toggles-css');
545 wp_enqueue_style('jquery-toggles-light-css');
546 }
547 //$cf7_key = get_post_meta($cf7_id, '_smart_grid_cf7_form_key', true);
548 //allow custom script print
549 do_action('smart_grid_enqueue_scripts', $cf7_key, $attr, $class);
550
551 $classes = implode(' ', $class) .' key_'.$cf7_key;
552 $output = '<div class="cf7sg-container"><div id="' . $css_id . '" class="cf7-smart-grid ' . $classes . '">' . $output . '</div></div>';
553 return $output;
554 }
555 /**
556 *
557 *
558 *@since 4.6.0
559 *@param string $param text_description
560 *@return string text_description
561 */
562 public function form_css_id($cf7_key){
563 return 'cf7sg-form-'.$cf7_key;
564 }
565 /**
566 * Function hooked on 'cf7_2_post_form_values' to load toggle status for saved submissions
567 *
568 *@since 1.1.0
569 *@param string $post_id saved submission post ID
570 */
571 public function load_saved_toggled_status($field_values){
572 if(!isset($field_values['map_post_id']) || empty($field_values['map_post_id'])){
573 return $field_values;
574 }
575 $post_id = $field_values['map_post_id'];
576 $toggles = get_post_meta($post_id, 'cf7sg_toggles_status', true);
577 if(!empty($toggles)){
578 $cf7post = get_post($post_id);
579 $cf7_key = $cf7post->post_name;
580 wp_enqueue_script($this->plugin_name);
581 wp_localize_script( $this->plugin_name, 'cf7sg', $this->localise_script( array('toggles_status' => $toggles)), $this->form_css_id($cf7_key));
582 }
583 return $field_values;
584 }
585 /**
586 * Single source for localised script.
587 *
588 *@since 2.6.0
589 *@param array $params additional parameter to send.
590 *@return array data array to localise
591 */
592 private function localise_script($params=array(), $css_id=null){
593 if( empty($css_id) ) $this->localised_data += $params;
594 else{
595 if( !isset($this->localised_data[$css_id]) ) $this->localised_data[$css_id] = array();
596 $this->localised_data[$css_id] += $params;
597 }
598 return $this->localised_data;
599 }
600 /**
601 * Update sub-forms in cf7 forms
602 * Hooked to 'do_shortcode'
603 * @since 1.0.0
604 * @param String $form_raw HTML form.
605 * @param WP_Post $sub_form_post CF7 sub-form post object.
606 * @return String HTML for new form.
607 **/
608 public function update_sub_form($form_raw, $sub_form_post){
609 //Create a new DOM document
610 $cf7_key = $sub_form_post->post_name;
611 $sub_form_raw = get_post_meta($sub_form_post->ID, '_form', true);
612 //PHP DOM plugin.
613 /** @since 3.2.0 use Simple HTML Dom library */
614 require_once plugin_dir_path( __DIR__ ) . 'assets/simple-html-dom/autoload.php';
615
616 $dom = HtmlDomParser::str_get_html($form_raw);
617 //reset the inner form.
618 $element = $dom->find('#cf7sg-form-'.$cf7_key);
619
620 $element[0]->innertext = $sub_form_raw;
621
622 return $dom->outertext;
623 }
624 /**
625 * Function to load custom js script for Post My CF7 Form loading of form field values
626 * Hooked to 'cf7_2_post_echo_field_mapping_script'
627 * @since 1.0.0
628 * @param boolean $default_script whether to use the default script or not, default is true.
629 * @param string $field cf7 form field name
630 * @param string $type field type (number, text, select...)
631 * @param string $json_value the json value loaded for this field in the form.
632 * @param string $$js_form the javascript variable in which the form is loaded.
633 * @return boolean false to print a custom script from the called function, true for the default script printed by this plugin.
634 **/
635 public function load_tabs_table_field($default_script, $post_id, $field, $type, $json_value, $js_form){
636 $grid = self::field_type($field, $post_id);
637 switch($grid){
638 case 'tab':
639 case 'table':
640 case 'both':
641 include( plugin_dir_path( __FILE__ ) . '/partials/cf7sg-field-load-script.php');
642 $default_script = false;
643 break;
644 default:
645 break;
646 }
647 return $default_script;
648 }
649 /**
650 * Register a [save] shortcode with CF7.
651 * Hooked on 'wpcf7_init'
652 * This function registers a callback function to expand the shortcode for the save button field.
653 * @since 2.0.0
654 */
655 public function register_cf7_shortcode() {
656 if( function_exists('wpcf7_add_form_tag') ) {
657 //benchmark
658 wpcf7_add_form_tag(
659 array( 'benchmark', 'benchmark*' ),
660 array($this,'cf7_benchmark_shortcode'),
661 true //has name
662 );
663 /** @since 4.10.0 abstract out dynamic lists */
664 do_action('cf7sg_register_dynamic_lists');
665 $lists = cf7sg_get_dynamic_lists();
666 // debug_msg($lists, 'dynamic lists ');
667 foreach($lists as $l) $l->register_cf7_shortcode();
668 }
669 }
670 /**
671 * Register a [benchmark] shortcode with CF7.
672 * called by funciton above
673 * This function registers a callback function to expand the shortcode for the googleMap form fields.
674 * @since 1.0.0
675 * @param strng $tag the tag name designated in the tag help screen
676 * @return string a set of html fields to capture the googleMap information
677 */
678
679 public function cf7_benchmark_shortcode($tag){
680 $tag = new WPCF7_FormTag( $tag );
681 wp_enqueue_script('js-cf7sg-benchmarking');
682 wp_enqueue_style( 'cf7-benchmark-css' );
683 ob_start();
684 include( plugin_dir_path( __FILE__ ) . '/partials/cf7-benchmark-tag.php');
685 $html = ob_get_contents ();
686 ob_end_clean();
687 return $html;
688 }
689
690 /**
691 * Function to save ajax submitted grid fields, grid fields are any input/select fields used in the table/tabs constructs
692 * 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.
693 * @since 1.0.0
694 **/
695 public function save_grid_fields(){
696 if( !isset($_POST['nonce']) ){
697 wp_send_json_error(array('message'=>'nonce failed'));
698 wp_die();
699 }
700 $cf7_id = sanitize_text_field($_POST['id']);
701
702 if(!wpcf7_verify_nonce($_POST['nonce'], $cf7_id)){
703 wp_send_json_error(array('message'=>'nonce failed'));
704 wp_die();
705 }
706
707 if(isset($_POST['tabs_fields']) && isset($_POST['table_fields'])){
708 $tabs_fields = json_decode(stripslashes($_POST['tabs_fields']));
709 if(empty($tabs_fields)) $tabs_fields = array();
710 if(!is_array($tabs_fields)) $tabs_fields = array($tabs_fields);
711 //validate each field name as text, sanitize.
712 $sanitised_tab_fields = array();
713 foreach($tabs_fields as $field){
714 $sanitised_tab_fields[] = sanitize_text_field($field);
715 }
716 $table_fields = json_decode(stripslashes($_POST['table_fields']));
717 if(empty($table_fields)) $table_fields = array();
718 if(!is_array($table_fields)) $table_fields = array($table_fields);
719 $sanitised_table_fields = array();
720 foreach($table_fields as $field){
721 $sanitised_table_fields[] = sanitize_text_field($field);
722 }
723 //debug_msg($grid_fields, $cf7_id);
724 update_post_meta($cf7_id, '_cf7sg_grid_tabs_names', $sanitised_tab_fields);
725 update_post_meta($cf7_id, '_cf7sg_grid_table_names', $sanitised_table_fields);
726 wp_send_json_success(array('message'=>'saved fields'));
727 }else{
728 wp_send_json_error(array('message'=>'no data received'));
729 }
730 wp_die();
731 }
732 /**
733 * This function filters submitted data and consolidates grid field values into arrays.
734 * Function hooked on 'wpcf7_posted_data'
735 * @since 1.0.0
736 * @param Array $data unvalidated submitted data.
737 * @return Array filtered submitted data.
738 **/
739 public function setup_grid_values($data){
740 // debug_msg($data, 'CF7 processed data ');
741 $cf7form = WPCF7_ContactForm::get_current();
742 // debug_msg($_POST);
743 if(empty($cf7form) ){
744 if(isset($_POST['_wpcf7']) ){
745 $cf7_id = $_POST['_wpcf7'];
746 $cf7form = WPCF7_ContactForm::get_instance($cf7_id);
747 if(empty($cf7form) ){
748 debug_msg("CF7SG ERROR: fn setup_grid_values() is unable to load submitted form");
749 return $data;
750 }
751 }
752 }
753 $cf7_id = $cf7form->id();
754 $this->form_id = $cf7_id;
755 //debug_msg($grid_fields, 'grid fields...');
756
757 foreach($cf7form->scan_form_tags() as $tag){
758 if(empty($tag->name)) continue;
759 $field_name = $tag['name'];
760 $field_type = self::field_type($field_name, $cf7_id);
761 switch($field_type){
762 case 'tab':
763 case 'table':
764 case 'both':
765 // the $data array is passed by reference and will be consolidated.
766 /** @since 2.5.0 */
767 $this->consolidate_grid_submissions($tag, $field_type, $data);
768 break;
769 default:
770 $toggle = $this->get_toggle($field_name);
771 /** @since 4.8.1 preserve cf7 data schema for some plugins */
772 if( apply_filters('cf7sg_preserve_cf7_data_schema', false) ){
773 if(!empty($toggle) && !$this->is_submitted($toggle)) $data[$field_name] = null;
774 }else{
775 if(!empty($toggle)){
776 $data[$field_name] = $this->is_submitted($toggle) ? $this->get_field_value($field_name,$field_type, $tag) : null;
777 }else $data[$field_name] = $this->get_field_value($field_name, $tag['basetype'], $tag);
778 }
779 break;
780 }
781 }
782 // debug_msg($data, 'data consolidated');
783 //add toggled sections as submitted values.
784 $groups = get_post_meta($this->form_id, '_cf7sg_grid_grouped_toggles', true);
785 if(!empty($groups)){
786 foreach($groups as $group=>$grp_toggles){
787 foreach($grp_toggles as $toggle){
788 if( isset(self::$array_toggled_panels[$this->form_id][$toggle]) ){
789 self::$array_toggled_panels[$this->form_id][$group]=self::$array_toggled_panels[$this->form_id][$toggle];
790 break;
791 }
792 }
793 }
794 $data += self::$array_toggled_panels[$this->form_id];
795 }
796 // debug_msg($data, 'consolidated + submitted: ');
797 // debug_msg($_POST);
798 return $data;
799 }
800 /**
801 * 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 sections.
802 *
803 *@since 2.5.0
804 *@param array $field_tag a cf7 form field tag which is part of tabs or table grid section.
805 *@param array $type field type, should be 'tab'/'table'/'both'.
806 *@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.
807 *@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.
808 */
809 private function consolidate_grid_submissions($field_tag, $type, &$data){
810 //get_post_meta($post_id, '_cf7sg_has_tables', true);
811 if(isset($_POST['_wpcf7']) ) $cf7_id = $_POST['_wpcf7'];
812 else{
813 debug_msg("CF7SG ERROR: fn consolidate_grid_submissions() is unable to load submitted form");
814 }
815 $is_used = false;
816 $field_name = $field_tag['name'];
817 $field_type = $field_tag['basetype'];
818 $origin = self::$array_grid_fields[$cf7_id][$field_name][0];//array(origin,type);
819 // debug_msg($origin, "$field_name origin ");
820 $values = array();
821 $regex = '';
822 $submitted_fields=array();
823 $max_fields =0;
824 $purge_fields=array();
825 $toggle = $this->get_toggle($field_name);
826 switch($type){
827 case 'tab':
828 case 'table':
829 $index_suffix = ('table'==$type)?'_row-':'_tab-';
830
831 //find the number of rows submitted.
832 $max_fields = isset($_POST[$origin]) ? $_POST[$origin]:0;
833
834 if(!empty($toggle) && !$this->is_submitted($toggle)){ //check if submitted.
835 $values[''] = null;
836 }else{
837 $values['']= $this->get_field_value($field_name,$field_type, $field_tag);
838 $is_used = true;
839 }
840
841 for($idx=1;$idx<$max_fields;$idx++){
842 $fid=$index_suffix.$idx;
843 if(isset($toggle)) $toggle_id = $this->is_tabbed($toggle[0]) ? preg_filter('/$/',$fid, $toggle) : $toggle;
844 if(!empty($toggle) && !$this->is_submitted($toggle_id)){ //check if submitted.
845 $values[$fid] = null;
846 }else{
847 $values[$fid] = $this->get_field_value($field_name.$fid,$field_type, $field_tag);
848 $is_used = true;
849 }
850 $purge_fields[$field_name.$fid] = true;
851 }
852 break;
853 case 'both':
854 $origins = explode(':', $origin);
855 $table_origin = $origins[0];
856 $tab_origin = $origins[1];
857 $max_tabs = isset($_POST[$tab_origin])?$_POST[$tab_origin]:0;
858 $values[''] = array(); //init multi-dimensional array
859
860 for($idx=0;$idx<$max_tabs;$idx++){ //tables in other tabs.
861 $max_fields = 0;
862 if(0==$idx && isset($_POST[$table_origin])){
863 $max_fields = $_POST[$table_origin];
864 }else if($idx>0 && isset($_POST[$table_origin.'_tab-'.$idx])){
865 $max_fields = $_POST[$table_origin.'_tab-'.$idx];
866 }
867 $tab_suffix= ($idx>0)? '_tab-'.$idx:'';
868 for($jdx=0;$jdx<$max_fields;$jdx++){
869 $row_suffix= ($jdx>0)?'_row-'.$jdx:'';
870 $fid = $tab_suffix.$row_suffix;
871 if(isset($toggle)) $toggle_id = $this->is_tabbed($toggle[0]) ? preg_filter('/$/',$tab_suffix,$toggle) : $toggle;
872
873 if(!empty($toggle) && !$this->is_submitted($toggle_id)){ //check if submitted, tab<-toggle<-table.
874 $values[$tab_suffix][$row_suffix] = null;
875 }else{
876 $values[$tab_suffix][$row_suffix] = $this->get_field_value($field_name.$fid,$field_type, $field_tag);
877 $is_used = true;
878 }
879 $purge_fields[$field_name.$fid] = true; //remove from the original $data.
880 }
881 }
882 break;
883 }
884 $purge_fields[$field_name] = true;
885 //debug_msg($purge_fields, 'purging ');
886 $data = array_udiff_uassoc($data, $purge_fields, function($a, $b){return 0;}, function($a, $b){
887 if ($a === $b) return 0;
888 return ($a > $b)? 1:-1;
889 }); //this will remove all the surplus fields
890 if( $is_used ) $data[$field_name] = $values;
891 }
892 /**
893 * Extract either a file name or a field value
894 *
895 *@since 2.5.0
896 *@param array $data submitted data.
897 *@return string text_description
898 */
899 private function get_field_value($field_name, $field_type, $field_tag){
900 $value = '';
901 if('file' == $field_type) { /** @since 4.9.0 handle files in posted data due to CF7 5.4 changes */
902 if(empty($_FILES[$field_name])) return;
903 $file = $_FILES[$field_name];
904
905 $args = array(
906 'tag' => $field_tag,
907 'name' => $field_name,
908 'required' => $field_tag->is_required(),
909 'filetypes' => $field_tag->get_option( 'filetypes' ),
910 'limit' => $field_tag->get_limit_option(),
911 );
912 $value = wpcf7_unship_uploaded_file( $file, $args );
913 if(is_wp_error($value)) {
914 $this->file_wp_errors[$field_name]=$value;
915 $value='';
916 }
917 }else{
918 $pipes = $field_tag->pipes;
919
920 $value = $posted = isset($_POST[$field_name]) ? $_POST[$field_name]:'';
921
922 if(function_exists('wpcf7_form_tag_supports') and wpcf7_form_tag_supports( $field_type, 'selectable-values' )){
923 $value = (array) $value;
924 }
925
926 if ( WPCF7_USE_PIPE and $pipes instanceof WPCF7_Pipes and ! $pipes->zero() and !in_array( $field_type, array('map','dynamic_select')) ){
927 if(is_array($posted)){
928 $value = array();
929 foreach($posted as $v){
930 $value[] = $pipes->do_pipe($v);
931 }
932 }else $value = $pipes->do_pipe($posted);
933 }
934
935 if ( $field_tag->has_option( 'free_text' ) and isset( $_POST[$field_name . '_free_text'] ) ){
936 if(is_array($value)){
937 $v = array_pop($value).' '.sanitize_text_field($_POST[$field_name . '_free_text']);
938 $value[] = $v;
939 }else{
940 $value = $value.' '.sanitize_text_field($_POST[$field_name . '_free_text']);
941 }
942 }
943 }
944 return $value;
945 }
946 /**
947 * function returns an array of fields as keys and value which are eitehr 'tab' or 'table', or 'both'.
948 *
949 * @since 1.0.0
950 * @param string $form_id form id for which to return fields.
951 * @return array empty array if no fields found..
952 **/
953 static public function get_grid_fields($form_id){
954 if(isset(self::$array_grid_fields[$form_id])){
955 return self::$array_grid_fields[$form_id];
956 }
957 $grid_fields = array();
958 //tables
959 $fields = get_post_meta($form_id , '_cf7sg_grid_table_names', true);
960 if(!empty($fields)){
961 if( is_array( $fields[key($fields)] ) ){
962 foreach($fields as $table=>$table_fields) $grid_fields += array_fill_keys($table_fields, array($table,'table'));
963 }else $grid_fields += array_fill_keys($fields, array('cf7-sg-table-000','table'));
964 }
965 //tabs
966 $fields = get_post_meta($form_id , '_cf7sg_grid_tabs_names', true);
967 if(!empty($fields)){
968 if(is_array( $fields[key( $fields)] ) ){ /** @since 2.4.2 */
969 foreach($fields as $tab=>$tab_fields){
970 foreach($tab_fields as $field){
971 if(isset($grid_fields[$field])){
972 $grid_fields[$field] = array($grid_fields[$field][0].':'.$tab,'both');
973 }else $grid_fields[$field] = array($tab,'tab');
974 }
975 }
976 }else{
977 foreach($fields as $field){
978 if(isset($grid_fields[$field])) $grid_fields[$field] = array($grid_fields[$field][0].':cf7-sg-tab-000','both');
979 else $grid_fields[$field] = array('cf7-sg-tab-000','tab');
980 }
981 }
982 }
983 $subform_keys = get_post_meta($form_id, '_cf7sg_sub_forms', true);
984 if(!empty($subform_keys)){
985 foreach($subform_keys as $cf7Key){
986 $post_id = get_cf7form_id($cf7Key);
987 $grid_fields += self::get_grid_fields($post_id);
988 }
989 }
990 self::$array_grid_fields[$form_id]=$grid_fields;
991 //load panel fields.
992 self::$array_toggled_panels[$form_id]=array();
993 $toggled_panels = array();
994 if(isset($_POST['_cf7sg_toggles'])){
995 $toggled_panels = json_decode( stripslashes($_POST['_cf7sg_toggles']));
996 if(!empty($toggled_panels)) $toggled_panels = get_object_vars($toggled_panels);
997 else $toggled_panels = array();
998 }
999 self::$array_toggled_panels[$form_id]=$toggled_panels;
1000
1001 $fields = get_post_meta($form_id, '_cf7sg_grid_toggled_names', true);
1002 self::$array_toggle_fields[$form_id]=array();
1003 if(!empty($fields)){
1004 foreach($fields as $panel=>$pfields){
1005 foreach($pfields as $field){
1006 if( isset(self::$array_toggle_fields[$form_id][$field]) ){
1007 self::$array_toggle_fields[$form_id][$field][] = $panel;
1008 }else{
1009 self::$array_toggle_fields[$form_id][$field]= array($panel);
1010 }
1011 }
1012 }
1013 }
1014 $tabtgls = get_post_meta($form_id, '_cf7sg_grid_tabbed_toggles', true);
1015 if(!empty($tabtgls)) $tabtgls = array_fill_keys($tabtgls,true);
1016 else $tabtgls = array();
1017 self::$array_tabbed_toggles[$form_id]=$tabtgls;
1018 return $grid_fields;
1019 }
1020
1021 /**
1022 * Validates required benchmark and dynamic_select tags
1023 *
1024 * @since 1.0.0
1025 * @param WPCF7_Validation $result validation object
1026 * @param Array $tag an array of cf7 tag attributes and values
1027 * @return WPCF7_Validation validation result
1028 **/
1029 public function validate_required($result, $tag){
1030 if(!isset( $_POST[$tag->name] ) ){
1031 return $result; //not submitted hence disabled.
1032 }
1033 $tag = new WPCF7_FormTag( $tag );
1034
1035 $name = $tag->name;
1036 $value = isset( $_POST[$name] )
1037 ? trim( strtr( (string) sanitize_text_field($_POST[$name]), "\n", " " ) ): '';
1038
1039 if ( $tag->is_required() && '' == $value ) {
1040 $result->invalidate( $tag, wpcf7_get_message( 'invalid_required' ) );
1041 }
1042 return $result;
1043 }
1044 /**
1045 * New validation for CF7 5.6 onwards hooked to 'wpcf7_swv_create_schema'
1046 * this function will look for repetitive fields that need to be added to the validation schema,
1047 * it will also remove any rules in the schema
1048 * @since 4.14.0
1049 * @param WPCF7_SWV_Schema $schema itself a WPCF7_SWV_Rule
1050 * @param WPCF7_ContactForm $form form object.
1051 */
1052 function validate_swv_schemas($schema, $form){
1053 // debug_msg($schema, 'schema...');
1054 //setup the form id
1055 $this->form_id = $form->id();
1056 $submitted = null;
1057 $cloned_schemas=array();
1058 //check if we have a submission
1059 if(method_exists('WPCF7_Submission','get_instance')){
1060 $submitted = WPCF7_Submission::get_instance();
1061 }else debug_msg('WPCF7_Submission::get_instance() method no longer available');
1062 if(empty($submitted)) return;
1063 //get the consolidated submitted data.
1064 $data = $submitted->get_posted_data();
1065 // 1. run through the tags that are tabbed/tabled or both,
1066 // 2. ignore those that are toggled and unused.
1067 // 3. check for hidden WPCF conditional fields in tabs/tables/toggles
1068 $tags = $form->scan_form_tags();
1069 //cf7 plugin is unable to handle dynamic fields (added or removed).
1070 $added_tags = array();
1071 $removed_tags = array();
1072 $valid_tags = array();
1073 foreach ( $tags as $tag ) {
1074 //check to see if this field is an array (table or tab or both).
1075 $field_type = self::field_type( $tag['name'], $this->form_id );
1076 // the $data array is passed by reference and will be consolidated.
1077 if(!isset($data[$tag['name']])){ /** @todo verify tabbed toggled fields */
1078 $removed_tags[] = $tag['name'];
1079 continue; //skip this tag.
1080 }
1081
1082 $values = $data[$tag['name']];
1083 switch($field_type){
1084 case 'tab':
1085 case 'table':
1086 $added_tags[$tag['name']] = array();
1087 $valid_tags[$tag['name']]=array();
1088 foreach($values as $index=>$value){
1089 if(!isset($value)){ //if no value then skip validation.
1090 $removed_tags[] = $tag['name'].$index;
1091 }else{//additional tag
1092 if(empty($index)){
1093 $valid_tags[$tag['name']][$index] = $tag;
1094 continue; //first tab or row will be taken care by cf7 plugin.
1095 }
1096 $sg_field_tag = clone $tag;
1097 $sg_field_tag['name'] = $tag['name'].$index;
1098 $added_tags[$tag['name']][] = array($sg_field_tag,$index);
1099 $valid_tags[$tag['name']][$index] = $sg_field_tag; //used for recreating WPCF7_Validation object.
1100 }
1101 }
1102 break;
1103 case 'both':
1104 $added_tags[$tag['name']] = array();
1105 $valid_tags[$tag['name']] = array();
1106 foreach($values as $index=>$value){
1107 $valid_tags[$tag['name']][$index]=array();
1108 foreach($value as $row=>$row_value){
1109 if( !isset($row_value) ) { //if no value then skip validation.
1110 $removed_tags[] = $tag['name'].$index.$row;
1111 } else { //additional tag
1112 if(empty($index) && empty($row)){
1113 $valid_tags[$tag['name']][$index][$row]=$tag;
1114 continue; //first row will be taken care by cf7 plugin.
1115 }
1116 $sg_field_tag = clone $tag;
1117 $sg_field_tag['name'] = $tag['name'].$index.$row;
1118 $added_tags[$tag['name']][] = array($sg_field_tag,$index,$row);
1119 $valid_tags[$tag['name']][$index][$row]=$sg_field_tag;
1120 }
1121 }
1122 }
1123 break;
1124 default:
1125 //verify if data was submitted for this field
1126 if(!isset($data[$tag['name']])){ //it was removed by some plugin.
1127 $removed_tags[] = $tag['name'];
1128 }
1129 $valid_tags[$tag['name']] = $tag; //used for recreating WPCF7_Validation object.
1130
1131 break;
1132 }
1133 }
1134 $rules = array();
1135 foreach($schema->rules() as $r){ //$rule is a WPCF7_SWV_Rule object.
1136 $rule = $r->to_array();
1137 if( !isset($rules[$rule['field']])) $rules[$rule['field']] = array(); //can have multiple rules.
1138 $rules[$rule['field']][] = $r;
1139 }
1140 if(!class_exists('WPCF7_SWV_Schema') ){
1141 debug_msg('CF7SG PUBLIC: Cannot find class WPCF7_SWV_Schema, quitting validation');
1142 return;
1143 }
1144
1145 // debug_msg($schema, 'new schema...');
1146 //validate the extra schemas once validation by CF7 is done.
1147 add_filter('wpcf7_validate', function($result) use ($rules,$added_tags, $removed_tags, $valid_tags){
1148
1149 //remove the errors for removed tags
1150 $invalids = $result->get_invalid_fields();
1151 $validation = array();
1152 foreach($invalids as $f=>$err){
1153 $validation[$f]=$err['reason'];
1154 }
1155 foreach($removed_tags as $field){
1156 if(isset($validation[$field])) unset($validation[$field]);
1157 }
1158
1159 //validate the extra schema.
1160 //$field = original field name which has cloned $tags.
1161 foreach($added_tags as $field=>$tagset){
1162
1163 foreach($tagset as $tag_array){
1164 $tag = $tag_array[0];
1165 $err='';
1166 if(!isset($rules[$field])){ //maybe non-swv field, try with a filter
1167 $result = apply_filters("wpcf7_validate_{$tag['type']}", new WPCF7_Validation(), $tag); //for other plugins to add extra validation.
1168 if(!$result->is_valid()){
1169 $invalids = $result->get_invalid_fields();
1170 $err = $invalids[$tag['name']]['reason'];
1171 }
1172 }else{
1173 foreach($rules[$field] as $rule){
1174 $rule_class = get_class($rule);
1175 $_rule = $rule->to_array();
1176 $_rule['field'] = $tag['name'];
1177 $_rule = new $rule_class($_rule); //cloned rule object for new field.
1178 $v = $_rule->validate(
1179 array(
1180 'text' => true,
1181 'file' => false,
1182 'field' => array(),
1183 )
1184 ); //validate
1185 if(is_wp_error($v)){
1186 $err = $v->get_error_message();
1187 break 1; //move to the next tag.
1188 }
1189 }
1190 }
1191 if(!empty($err)){
1192 $validation[$tag['name']] = $err;
1193 }
1194 }
1195 }
1196 //reformat validation for filter/final WPCF7_Validation object.
1197 $f_validation= array();
1198 foreach($valid_tags as $field=>$tag){
1199 switch(true){
1200 case is_object($tag):
1201 if(isset($validation[$tag['name']])){
1202 $f_validation[$field] = $validation[$tag['name']];
1203 }
1204 break;
1205 case is_array($tag):
1206 $f_validation[$field] = $this->repeat_field_err_messages($tag, $validation);
1207 break;
1208 }
1209 }
1210 // debug_msg($validation, 'validation ');
1211 // debug_msg($f_validation, 'f validation ');
1212 // debug_msg($valid_tags, 'valid tags ');
1213 return $this->filter_validation($f_validation, $valid_tags, null);
1214 },1,1);//call it early.
1215 }
1216 private function repeat_field_err_messages($tags, $validation, $d=1){
1217 $f_validation=array();
1218 foreach($tags as $idx=>$tag){
1219 if(!is_object($tag)){
1220 $f_validation[$idx] = $this->repeat_field_err_messages($tag, $validation, 2);
1221 }else if(isset($validation[$tag['name']])){
1222 $f_validation[$idx] = $validation[$tag['name']];
1223 }
1224 }
1225 return $f_validation;
1226 }
1227 /**
1228 * Final validation with all values submitted for inter dependent validation
1229 * Hooked to filter 'wpcf7_validate', sets up the final $result object
1230 * @since 1.0.0
1231 * @param WPCF7_Validation $result validation object
1232 * @param Array $tags an array of cf7 tag used in this form
1233 * @return WPCF7_Validation validation result
1234 **/
1235 public function filter_wpcf7_validate($result){
1236 /** @since 4.14.0 check cf7 version to maintain backward compatibility */
1237 if(defined('WPCF7_VERSION') && version_compare(WPCF7_VERSION,'5.6','>=')) return $result;
1238
1239 /** @since 3.3.3 fix for captch field validation*/
1240 $invalids = $result->get_invalid_fields();
1241 /**
1242 *@since 1.1.0
1243 *reset the validation result.
1244 * this is required to dynamically disable required form fields & stop their validation.
1245 * 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.
1246 * this bug was reported in the cf7 support forum:
1247 * https://wordpress.org/support/topic/bug-javascript-disabled-fields-flagged-as-invalid/
1248 **/
1249
1250 //rebuild the default validation result.
1251 $cf7form = $submitted = null;
1252 if(method_exists('WPCF7_ContactForm','get_current')){ /** @since 4.12.8 */
1253 $cf7form = WPCF7_ContactForm::get_current();
1254 }else debug_msg('WPCF7_ContactForm::get_current() method no longer available');
1255 if(empty($cf7form)) return $result;
1256 //check if we have a submission
1257 if(method_exists('WPCF7_Submission','get_instance')){
1258 $submitted = WPCF7_Submission::get_instance();
1259 }else debug_msg('WPCF7_Submission::get_instance() method no longer available');
1260 if(empty($submitted)) return $result;
1261 //check if we have a validation object constructor.
1262 if(class_exists('WPCF7_Validation')){
1263 $result = new WPCF7_Validation();
1264 }else{
1265 debug_msg('new WPCF7_Validation() constructor no longer available');
1266 return $result;
1267 }
1268 $data = $submitted->get_posted_data();
1269 $tag_types = array(); //store all form tags, including cloned tags for array fields.
1270
1271 /** @since 3.1.3 allow validation of unvalidated data in custom validation filter */
1272 $validation = array(); //holds all the validation messages.
1273 $validated = array();
1274 $submission = array();
1275 /**
1276 *@since 2.1.0 fix issue with Conditional Field plugin.
1277 */
1278 $data = $this->remove_hidden_fields_from_conditional_plugin($data);
1279 $toggle_status = '';
1280 if(isset($_POST['_cf7sg_toggles'])){
1281 $toggle_status = json_decode( stripslashes($_POST['_cf7sg_toggles']));
1282 }
1283
1284 foreach ( $cf7form->scan_form_tags() as $tag ) {
1285 //debug_msg($data);
1286 /**
1287 *@since 1.9.0 fix issue with Conditional Field plugin.
1288 */
1289 //validation for non-deprecated plugins (v2.5.0+).
1290 if(!isset($data[$tag['name']])) continue; //it was removed by some plugin.
1291 $type = $tag['type'];
1292 //check to see if this field is an array (table or tab or both).
1293 $tag_types[$tag['name']] = $tag['type'];
1294 $field_type = self::field_type($tag['name'], $cf7form->id());
1295 switch($field_type){
1296 case 'tab':
1297 case 'table':
1298 case 'both':
1299 // the $data array is passed by reference and will be consolidated.
1300 $values = $data[$tag['name']];
1301 /** @since 3.1.3 track all validations */
1302 $validation[$tag['name']] = array();
1303 $validated[$tag['name']] = array();
1304 $submission[$tag['name']] = array();
1305 $idx = 0;
1306 foreach($values as $index=>$value){
1307 if(is_array($value) && 'both'==$field_type){
1308 $validation[$tag['name']][$idx]=array();
1309 $validated[$tag['name']][$idx] = array();
1310 $submission[$tag['name']][$idx] = array();
1311 $rdx = 0;
1312 foreach($value as $row=>$row_value){
1313 if(!isset($row_value)) continue;
1314 $sg_field_tag = clone $tag;
1315 $sg_field_tag['name'] = $tag['name'].$index.$row;
1316 if($tag['basetype'] === 'file' ){
1317 $result = apply_filters("wpcf7_validate_{$sg_field_tag->type}", $result, $sg_field_tag,
1318 array(
1319 'uploaded_files' => isset($this->file_wp_errors[$sg_field_tag['name']]) ? $this->file_wp_errors[$sg_field_tag['name']]: $row_value,
1320 )
1321 );
1322 }else{
1323 $result = apply_filters("wpcf7_validate_{$tag['type']}", $result, $sg_field_tag);
1324 }
1325 /** @since 3.1.3 */
1326 $validation[$tag['name']][$idx][$rdx] = $this->strip_cf7_validation($result, $sg_field_tag->name);
1327 $validated[$tag['name']][$idx][$rdx] = $sg_field_tag;
1328 $submission[$tag['name']][$idx][$rdx] = $row_value;
1329 $rdx++;
1330 }
1331 }else{
1332 if(!isset($value)) continue;
1333 $sg_field_tag = clone $tag;
1334 $sg_field_tag['name'] = $tag['name'].$index;
1335 if($tag['basetype'] === 'file' ){
1336 $result = apply_filters("wpcf7_validate_{$sg_field_tag->type}", $result, $sg_field_tag,
1337 array(
1338 'uploaded_files' => isset($this->file_wp_errors[$sg_field_tag['name']]) ? $this->file_wp_errors[$sg_field_tag['name']]: $value,
1339 )
1340 );
1341 }else{
1342 $result= apply_filters("wpcf7_validate_{$tag['type']}", $result, $sg_field_tag);
1343 }
1344 /** @since 3.1.3 */
1345 $validation[$tag['name']][$idx] = $this->strip_cf7_validation($result, $sg_field_tag->name);
1346 $validated[$tag['name']][$idx] = $sg_field_tag;
1347 $submission[$tag['name']][$idx] = $value;
1348 }
1349 $idx++;
1350 }
1351 // debug_msg($values, 'values '.$tag['name'].'....');
1352 // debug_msg($result, 'validation '.$tag['name'].'....');
1353 break;
1354 default:
1355 switch($type) {
1356 case 'captchar': //cannot be called twice, hence see if already invalidated.
1357 /** @since 3.3.3 check if invalidated previously */
1358 if( isset($invalids[$tag['name']]) ){
1359 $result->invalidate( $tag, $invalids[$tag['name']]['reason']);
1360 $validation[$tag['name']] = $invalids[$tag['name']]['reason'];
1361 $validated[$tag['name']] = $tag;
1362 }
1363 break;
1364 default:
1365 if($tag['basetype'] === 'file' ){
1366 $result = apply_filters("wpcf7_validate_{$tag->type}", $result, $tag,
1367 array(
1368 'uploaded_files' => isset($this->file_wp_errors[$tag['name']]) ? $this->file_wp_errors[$tag['name']]: $data[$tag['name']],
1369 )
1370 );
1371 }else{
1372 $result= apply_filters( "wpcf7_validate_{$type}", $result, $tag );
1373 }
1374 /** @since 3.1.3 */
1375 $validation[$tag['name']] = $this->strip_cf7_validation($result, $tag['name']);
1376 $validated[$tag['name']] = $tag;
1377 $submission[$tag['name']] = $data[$tag['name']];
1378 break;
1379 }
1380 break;
1381 }
1382 }
1383 return $this->filter_validation($validation, $validated, $submission);
1384 }
1385 /**
1386 * Filter validation using entire submitted data set.
1387 * @since 1.0.0
1388 * @param Array $validation array of field=>error msg
1389 * @param Array $validated array of field=>tags
1390 * @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.
1391 * @return WPCF7_Validation validation object
1392 */
1393 private function filter_validation($validation, $validated, $submission){
1394 $form_key = '';
1395 if(isset($_POST['_wpcf7_key'])){
1396 $form_key = $_POST['_wpcf7_key'];
1397 }
1398
1399 //allow for more complex validation.
1400 if(has_filter('cf7sg_validate_submission')){
1401 if(is_null($submission)){
1402 if(method_exists('WPCF7_Submission','get_instance')){
1403 $submitted = WPCF7_Submission::get_instance();
1404 $submission = $submitted->get_posted_data();
1405 }
1406 }
1407 /**
1408 * filter to validate the entire submission and check interdependency of fields. The $validation array are error messages coming from the default CF7 validation process.
1409 * However, you can unset those messages if your cross-data validation logic demands it, or you can add additional errors to fields that have passed the default validation.
1410 * For repeat fields, you need to follow the same array contruct at the $submission data array, namely
1411 * - $field => <error message> for single fields.
1412 * - $field => array( $idx => <error message> ) for tabled or tabbed fields. ($idx for first row/tab is empty string); $idx for subsequent table rows 2,3.. is _row-1, _row-2...; and $idx for tabs 2,3... is _tab-1, _tab-2, ...
1413 * - $field => array( $tab_idx => array( $row_idx => <error message> ) ) for tabbed tabled fields.
1414 * NOTE: failing to follow this structure in your filtered validation array will result in spurious messages on the form fields.
1415 * @since 1.0.0
1416 * @param Array $validation an array of $field_name=>$msg for single fields, $field_name=>array( $idx=>$msg ) for table or tabbed fields, $field_name=>array( $tab_idx=> array( $row_idx => $msg ) ) for tabbed tabled fields.
1417 * @param Array $submission submitted data, $field_name => $value pairs ($value can be an array especially for fields in tables and tabs using the same array construct at the $validation array) null values are fields which have not been enabled and not submitted such as those in toggled sections.
1418 * @param String $form_key unique form key to identify current form.
1419 * @param int $form_id cf7 form post ID.
1420 * @return Array an array of errors messages, $field_name => $error_msg for single values, $field_name=>array( $idx=>$msg ) for table or tabbed fields, $field_name=>array( $tab_idx=> array( $row_idx => $msg ) ) for tabbed tabled fields.
1421 */
1422 $validation = apply_filters('cf7sg_validate_submission', $validation, $submission, $form_key, $_POST['_wpcf7']);
1423 }
1424 //now that the user has filtered the validatoin, reset the in cf7 inalids.
1425 $result = new WPCF7_Validation();
1426
1427 if(!empty($validation)){
1428 // debug_msg($validation, 'validation ');
1429
1430 foreach($validation as $name=>$msg){
1431 switch(true){
1432 case is_array($msg):
1433 foreach($msg as $idx=>$value){
1434 switch(true){
1435 case is_array($value):
1436 foreach($value as $rdx=>$message){
1437 if(empty($message)) continue;
1438 $result->invalidate($validated[$name][$idx][$rdx], $message);
1439 }
1440 break;
1441 case is_array($validated[$name][$idx]): //error, expecting array..
1442 debug_msg('Filtered cf7sg_validate_submission validation ERROR, expecting array for table field within tab: '.$name.'['.$idx.']');
1443 break;
1444 case empty($value): //no message, just continue.
1445 break;
1446 default:
1447 $result->invalidate($validated[$name][$idx], $value);
1448 break;
1449 }
1450 }
1451 break;
1452 case is_array($validated[$name]): //error, we should have an array.
1453 debug_msg('Filtered cf7sg_validate_submission validation ERROR, expecting array for field '.$name);
1454 break;
1455 case empty($msg): //no message, just continue.
1456 break;
1457 default:
1458 $result->invalidate($validated[$name], $msg);
1459 break;
1460 }
1461 }
1462 }
1463 if($result->is_valid()){
1464 /**
1465 * Once data is valid fire action to expose final submitted data.
1466 * @since 4.9.0
1467 * @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.
1468 * @param String $form_key unique form key to identify current form.
1469 * @param int $form_id cf7 form post ID.
1470 */
1471 do_action('cf7sg_valid_form_submission',$submission, $form_key, $_POST['_wpcf7']);
1472 }
1473 return $result;
1474 }
1475
1476 /**
1477 * Function to strip the submitted data for a given field.
1478 * Handles base type files whose data is stored in the $_FILES global array.
1479 *@since 3.1.3
1480 *@param string $name field name.
1481 *@param string $type base type.
1482 *@param mixed $data $_POST data.
1483 *@return mixed value actually submitted.
1484 */
1485 public function get_submitted_data($name, $type, $data){
1486 $value = $data;
1487 switch($type){
1488 case 'file':
1489 case 'file*':
1490 $value = array();
1491 if(isset($_FILES[$name])) $value = $_FILES[$name];
1492 break;
1493 }
1494 return $value;
1495 }
1496 /**
1497 * This function strips the validation message from cf7 WPCF7_Validation object for storage and custom modification using the hook 'cf7sg_validate_submission'.
1498 *
1499 *@since 3.1.3
1500 *@param WPCF7_Validation $result cf7 validation object for the field being validated.
1501 *@param WPCF7_FormTag $tag cf7 tag object for the field being validated.
1502 *@return string text_description
1503 */
1504 protected function strip_cf7_validation($result, $name){
1505 $invalid = $result->get_invalid_fields();
1506 $msg = '';
1507 if( !empty($invalid) && isset($invalid[$name]['reason']) ){
1508 $msg = $invalid[$name]['reason'];
1509 }
1510 return $msg;
1511 }
1512 /**
1513 * Function to save toggled collapsible sections status to open them up again for draft forms.
1514 * Hooks action 'cf7_2_post_form_posted'
1515 * @since 1.0.0
1516 * @param string $key form unique key.
1517 * @param Array $submitted_data array of field-name=>value pairs submitted in form
1518 *@since 1.1.0
1519 *@param string $param text_description
1520 *@return string text_description
1521 */
1522 public function save_toggle_status($post_id, $key, $post_fields, $post_meta_fields, $submitted_data){
1523 if(isset($submitted_data['_cf7sg_toggles'])){
1524 $toggles = json_decode( stripslashes( $submitted_data['_cf7sg_toggles'] ) );
1525 $toggles_array = array();
1526 if(!empty($toggles)){
1527 foreach($toggles as $key=>$value){
1528 $toggles_array[$key] = sanitize_text_field($value);
1529 }
1530 }
1531 //debug_msg($toggles_array, 'toggles status saved, ');
1532 update_post_meta($post_id, 'cf7sg_toggles_status', $toggles_array);
1533 }
1534 }
1535 /**
1536 * Function to save custom options values added to tagged select2 fields.
1537 * Hooks action 'cf7_2_post_form_posted'
1538 * @since 1.0.0
1539 * @param string $key form unique key.
1540 * @param Array $submitted_data array of field-name=>value pairs submitted in form
1541 **/
1542 public function save_select2_custom_options($post_id, $cf7_key, $post_fields, $post_meta_fields, $submitted_data){
1543 $form_id = get_cf7form_id($cf7_key);
1544 $tagged_fields = get_post_meta($form_id, '_cf7sg_select2_tagged_fields', true);
1545 if(empty($tagged_fields)){
1546 return;
1547 }
1548
1549 foreach($tagged_fields as $field_name=>$source){
1550 $value = $submitted_data[$field_name];
1551 $is_array = true;
1552 if(!is_array($value)){
1553 $value = array($value);
1554 $is_array = false;
1555 }
1556 switch($source['source']){
1557 case 'taxonomy':
1558 $taxonomy = $source['taxonomy'];
1559 $idx=0;
1560 foreach($value as $term){
1561 if(!term_exists($term, $taxonomy)){
1562 /**
1563 * Filter custom options from tag enabled select2 dynamic-dropdown fields
1564 * 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.
1565 * @param string $term the new term submitted by the user.
1566 * @param string $field_name the name of the form field.
1567 * @param string $taxonomy the taxonomy to which this is added.
1568 * @param array $submitted_data array of other submitted $field=>$value pairs.
1569 * @param string $cf7_key the form unique key.
1570 * @return string the new term name to insert.
1571 * @since 2.0.0
1572 */
1573 $term = apply_filters('cf7sg_dynamic_dropdown_new_term', $term, $field_name, $taxonomy, $submitted_data, $cf7_key);
1574 if(!empty($term)){
1575 $new_term = wp_insert_term($term, $taxonomy);
1576 if(!is_wp_error($new_term)){
1577 $new_term = get_term($new_term['term_id'], $taxonomy);
1578 $value[$idx] = $new_term->slug;
1579 }
1580 }
1581 }
1582 $idx++;
1583 }
1584 break;
1585 case 'post':
1586 $args = array(
1587 'post_type' => $source['post'],
1588 'post_status' => 'publish',
1589 'posts_per_page' => -1
1590 );
1591 if(!empty($source['taxonomy'])){
1592 $tax = array();
1593 if(sizeof($source['taxonomy']) > 1){
1594 $tax['relation'] = 'AND';
1595 }
1596 foreach($source['taxonomy'] as $term => $taxonomy){
1597 $tax[]=array(
1598 'taxonomy' => $taxonomy,
1599 'field' => 'slug',
1600 'terms' => $term
1601 );
1602 }
1603 $args['tax_query'] = $tax;
1604 }
1605 apply_filters_deprecated('cf7sg_dynamic_dropdown_post_query',
1606 array(
1607 $args,
1608 $tag->name,
1609 $cf7_key
1610 ), '4.11.0', 'cf7sg_dynamic_list_post_query' );
1611 $args = apply_filters('cf7sg_dynamic_list_post_query', $args, $tag->name, $cf7_key);
1612 $posts = get_posts($args);
1613 $options = array();
1614 if(!empty($posts)){
1615 foreach($posts as $post){
1616 $options[$post->post_name] = $post->post_title;
1617 }
1618 }
1619 $idx=0;
1620 foreach($value as $slug){
1621 if(!isset($options[$slug])){
1622 $args = $source;
1623 $post_type = $source['post'];
1624 $title = $slug;
1625 $post_name = '';
1626 /**
1627 * Filter custom options from tag enabled select2 dynamic-dropdown fields
1628 * where the source of options come from post titles. Filter is fired when a new value is submitted.
1629 * 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.
1630 * @param string $post_name the new post slug.
1631 * @param string $field_name the name of the form field.
1632 * @param string $title new value being submitted for a new post title.
1633 * @param string $post_type the post type from which this dropdown was built
1634 * @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.
1635 * @param array $submitted_data array of other submitted $field=>$value pairs.
1636 * @param string $cf7_key the form unique key.
1637 */
1638 $value[$idx] = apply_filters('cf7sg_dynamic_dropdown_new_post', $post_name, $field_name, $title, $post_type, $args, $submitted_data, $cf7_key);
1639 }
1640 $idx++;
1641 }
1642 break;
1643 case 'filter':
1644 $values = $value;
1645 /**
1646 * Filter custom otions from tag enabled select2 dynamic-dropdown fields
1647 * where the source of options come from a filter. Filter is fired when values are submitted.
1648 * Return updated values if any are custom values so that saved/draft submissions will reflect the correct value saved in the DB,
1649 * @param array $values an array submitted values (several values can be submitted in the case of a tabbed/table input field).
1650 * @param string $field_name the name of the form field.
1651 * @param array $submitted_data array of other submitted $field=>$value pairs.
1652 * @param string $cf7_key the form unique key.
1653 */
1654 $value = apply_filters('cf7sg_dynamic_dropdown_filter_select2_submission', $values, $field_name, $submitted_data, $cf7_key);
1655 break;
1656 }
1657 //Save the modified value, find which post field the field is mapped to
1658 if(!$is_array){
1659 $value = $value[0];
1660 }
1661 if( isset($post_fields[$field_name]) ){
1662 $post_key = $post_fields[$field_name];
1663 switch($post_key){
1664 case 'title':
1665 case 'author':
1666 case 'excerpt':
1667 $post_key = 'post_'.$post_key;
1668 break;
1669 case 'editor':
1670 $post_key ='post_content';
1671 break;
1672 case 'slug':
1673 $post_key ='post_name';
1674 break;
1675 }
1676 wp_update_post(array(
1677 'ID' => $post_id,
1678 $post_key => $value
1679 ));
1680 }else if( isset($post_meta_fields[$field_name]) ){
1681 update_post_meta($post_id, $post_meta_fields[$field_name], $value);
1682 }
1683 }
1684 }
1685
1686 /**
1687 * Function to temporarily fix the conditional fields plugin issue.
1688 * this is is called by the vlidation function 'filter_wpcf7_validate' above.
1689 *@since 2.1
1690 *@param array $posted_data submitted data
1691 *@return array submitted data without fields that remained hidden.
1692 */
1693 private function remove_hidden_fields_from_conditional_plugin($posted_data){
1694 /*code taken from cf7cf.php file in cf7-conditional-fields*/
1695 if(!isset($_POST['_wpcf7cf_hidden_group_fields'])){
1696 return $posted_data;
1697 }
1698
1699 $hidden_fields = json_decode(stripslashes($_POST['_wpcf7cf_hidden_group_fields']));
1700
1701 if (is_array($hidden_fields) && count($hidden_fields) > 0) {
1702 foreach ($hidden_fields as $field) {
1703 $field = str_replace('[]', '', $field);
1704 // if (wpcf7cf_endswith($field, '[]')) {
1705 // $field = substr($field,0,strlen($field)-2);
1706 // }
1707 unset( $posted_data[$field] );
1708 }
1709 }
1710 return $posted_data;
1711 }
1712 /**
1713 * Filter mail tags which are table or tab fields.
1714 * Hooked on 'wpcf7_mail_tag_replaced'.
1715 *@since 2.1
1716 *@param string $replaced mail tag string to replace.
1717 *@param mixed $submitted value of submitted field from cf7 posted data..
1718 *@param boolea $html mail if mail body is using html.
1719 *@param WPCF7_MailTag $mail_tag mail tag object of field being replaced.
1720 *@return string replacement string.
1721 */
1722 public function filter_table_tab_mail_tag($replaced, $submitted, $html=false, $mail_tag=null ){
1723 $cf7form = WPCF7_ContactForm::get_current();
1724 if(empty($cf7form)){
1725 debug_msg(mail_tag, 'SMART GRID (ERR): unable to retrieve current form while filtering mail tag: ');
1726 return $replaced; //no form object.
1727 }
1728 if(empty($mail_tag) || !is_a($mail_tag,'WPCF7_MailTag')) return $replaced;
1729
1730 $cf7form_key = get_cf7form_key($cf7form->id());
1731 $submitted_cf7 = WPCF7_Submission::get_instance();
1732 $submitted_data = array();
1733 if(!empty($submitted_cf7)) $submitted_data = $submitted_cf7->get_posted_data();
1734
1735 switch(true){
1736 case 0===strpos($mail_tag->field_name(), 'cf7sg-toggle-'):
1737 $toggle = str_replace('cf7sg-toggle-', '', $mail_tag->field_name());
1738 if(isset($submitted_data[$toggle])) $replaced = $submitted_data[$toggle];
1739 break;
1740 case 0===strpos($mail_tag->field_name(), 'cf7sg-form-'):
1741 $replaced ="";
1742 break;
1743 }
1744
1745 $field_type = self::field_type($mail_tag->field_name(), $cf7form->id());
1746 $label = '';
1747 $build = false;
1748
1749 switch($field_type){
1750 case 'tab':
1751 case 'table':
1752 if($html){
1753 $filtered = apply_filters_deprecated('cf7sg_mailtag_grid_fields', array('', $mail_tag->field_name(), $submitted, $cf7form_key, false), '4.6.2','cf7sg_mailtag_{$field_name}', __('this filter is no longer available', 'cf7-grid-layout'));
1754 $filtered = apply_filters('cf7sg_mailtag_'.$mail_tag->field_name(), $filtered, $submitted_data, $cf7form_key);
1755 if(!empty($filtered)){
1756 $replaced = $filtered;
1757 break;
1758 }
1759 }
1760 $label=('table'==$field_type)?'row':$field_type;
1761 if($html && is_array($submitted)) {
1762 $idx=0;
1763 $replaced='';
1764 foreach($submitted as $index=>$value){
1765 $idx++;
1766 if(is_array($value)) $value = implode(' | ', $value);
1767 $replaced .= '<div><label>'.$label.'('.$idx.'):</label><span>'.$value.'</span></div>';
1768 }
1769 }
1770 break;
1771 case 'both':
1772 if($html){
1773 $filtered = apply_filters_deprecated('cf7sg_mailtag_grid_fields', array('', $mail_tag->field_name(), $submitted, $cf7form_key, false), '4.6.2','cf7sg_mailtag_{$field_name}', __('this filter is no longer available', 'cf7-grid-layout'));
1774 $filtered = apply_filters('cf7sg_mailtag_'.$mail_tag->field_name(), $filtered, $submitted_data, $cf7form_key);
1775 if(!empty($filtered)){
1776 $replaced = $filtered;
1777 break;
1778 }
1779 }
1780 // build table.
1781 $replaced = '';
1782 if( is_array($submitted)) {
1783 $tab = 0;
1784 foreach($submitted as $index=>$value){
1785 $tab++;
1786 $idx=0;
1787 if($html) $replaced .= '<div><span>tab('.$tab.')</span>&nbsp;';
1788 else $replaced .= 'tab('.$tab.') = ';
1789 if(is_array($value)){
1790 foreach($value as $row=>$row_value){
1791 $idx++;
1792 if(is_array($row_value)) $row_value = implode('|', $row_value);
1793 if($html) $replaced .='<div><label>row('.$idx.'):</label><span>' . $row_value . '</span></div>';
1794 else $replaced .= $row_value.',';
1795 }
1796 }
1797 if($html) $replaced .='</div>';
1798 else $replaced .= PHP_EOL;
1799 }
1800 }
1801 break;
1802 default: //general fix for cf7 mail tags.
1803 $tag = $mail_tag->corresponding_form_tag();
1804
1805 /**
1806 * Filter the value inserted in the mail tag.
1807 * @since 2.9.0.
1808 * @param string $replaced value to filter.
1809 * @param string $field_name name of the field being inserted in the mail.
1810 * @param array $submitted submitted form data.
1811 * @param string $form_key unique form key identifier.
1812 * @return string a value to replace.
1813 */
1814 if($tag instanceof WPCF7_FormTag){
1815 $replaced = apply_filters('cf7sg_mailtag_'.$tag->basetype, $replaced, $mail_tag->field_name(), $submitted_data, $cf7form_key);
1816 }
1817 /**
1818 * Filter the value inserted in the mail tag.
1819 * @since 3.1.6.
1820 * @param string $replaced value to filter.
1821 * @param string $field_name name of the field being inserted in the mail.
1822 * @param array $submitted submitted form data.
1823 * @param string $form_key unique form key identifier.
1824 * @return string a value to replace.
1825 */
1826 $replaced = apply_filters('cf7sg_mailtag_'.$mail_tag->field_name(), $replaced, $submitted_data, $cf7form_key);
1827 break;
1828 }
1829
1830 return $replaced;
1831 }
1832 /**
1833 * We need to add the missing attachments just before the mail is sent.
1834 * Hooked to filter 'wpcf7_mail_components', sets up the final $components object
1835 * @since 2.4.1
1836 * @param Array $components an array of mail parts
1837 * @param WPCF7_ContactForm $cf7form cf7 form object
1838 * @param WPCF7_Mail $cf7mail cf7 mail object
1839 * @return Array an array of mail parts
1840 */
1841 public function wpcf7_mail_components($components, $cf7form, $cf7mail) {
1842 $submission = WPCF7_Submission::get_instance();
1843 /** @since 4.9 file validation changes in CF7 v5.4 */
1844 // $uploaded_files = $submission->uploaded_files(); //get file path from data subission itself.
1845 //get the tags that are file uploads.
1846 $tags = $cf7form->scan_form_tags(array(
1847 'feature' => 'file-uploading',
1848 ) );
1849 /** @since 2.5.6 fix issue with non-attached files */
1850 $template = $cf7mail->get( 'attachments' );
1851 $row = $tab = null;
1852 /** @since 2.8.1 fix for send pdf */
1853 $attachments = $components['attachments'];
1854 foreach ( $tags as $tag ) {
1855 $name = $tag['name'];
1856 if ( false === strpos( $template, "[${name}]" ) ) continue; //not attached.
1857 $field_type = self::field_type($name, $cf7form->id());
1858 $data = $submission->get_posted_data($name);
1859 switch($field_type){
1860 case 'tab':
1861 case 'table':
1862 foreach($data as $field_idx=>$file_path){
1863 if(empty($file_path)) continue 1;
1864 foreach($file_path as $path){
1865 $row = ('table'==$field_type)? str_replace('_row-', '', $field_idx):null;
1866 $row = (isset($row) && empty($row))? 1:1+(int)$row;
1867 $tab = ('tab'==$field_type)? str_replace('_tab-', '', $field_idx):null;
1868 $tab = (isset($tab) && empty($tab))? 1:1+(int)$tab;
1869 // $file = $uploaded_files[$name.$field_idx];
1870
1871 if(!in_array($path,$attachments)) $attachments[] = $path;
1872 $file_name = explode('/',$path);
1873 $file_name = $file_name[count($file_name)-1];
1874 $components['body'].= apply_filters('cf7sg_annotate_mail_attach_grid_files','', $name, $file_name, $tab, $row, $_POST['_wpcf7_key']);
1875 }
1876 }
1877 break;
1878 case 'both':
1879 $row = $tab = 1;
1880 foreach($data as $field_tab=>$tab_files){
1881 if(empty($tab_files)) continue 1;
1882 $tab = str_replace('_tab-', '', $field_tab);
1883 $tab = empty($tab)?1:1+(int) $tab;
1884 foreach($tab_files as $field_row=>$file_path){
1885 if(empty($file_path)) continue;
1886 foreach($file_path as $path){
1887 $row = str_replace('_row-', '', $field_row);
1888 // $file = $uploaded_files[$name.$field_tab.$field_row];
1889 if(!in_array($path,$attachments)) $attachments[] = $path;
1890 $file_name = explode('/',$path);
1891 $file_name = $file_name[count($file_name)-1];
1892 $components['body'].= apply_filters('cf7sg_annotate_mail_attach_grid_files','', $name, $file_name, $tab, $row, $_POST['_wpcf7_key']);
1893 }
1894 }
1895 }
1896 break;
1897 default: //singular fields.
1898 if(empty($data)) continue 2;
1899 foreach($data as $path){
1900 if(!in_array($path, $attachments)) $attachments[] = $path;
1901 $file_name = explode('/',$path);
1902 $file_name = $file_name[count($file_name)-1];
1903 $components['body'].= apply_filters('cf7sg_annotate_mail_attach_grid_files','', $name, $file_name, null, null, $_POST['_wpcf7_key']);
1904 }
1905 break;
1906 }
1907 }
1908 $components['attachments'] = $attachments;
1909 return $components;
1910 }
1911
1912 /**
1913 * Track form field value submissions for preview forms.
1914 * Hooked to action 'wpcf7_before_send_mail'
1915 *@since 4.4.0
1916 *@param WPCF7_Contact_Form $form cf7 form object.
1917 */
1918 public function on_submit_success($form){
1919 if(empty($form)) return;
1920 /** @since 4.6.0 check if the form is being redirected and data cached */
1921 $redirect = get_post_meta($form->id(), '_cf7sg_page_redirect',true);
1922 if(!empty($redirect)){
1923 $cache = get_post_meta($form->id(), '_cf7sg_cache_redirect_data',true);
1924 if(is_array($cache)){
1925 $submission = WPCF7_Submission::get_instance();
1926 $data = array();
1927 if(!empty($submission)){
1928 $data['fields'] = $submission->get_posted_data();
1929 $files = $submission->uploaded_files();
1930 if(!empty($files)) $data['files'] = $files;
1931 }
1932 $data = apply_filters('cf7sg_form_redirect_cached_data',$data, $_POST['_wpcf7_key'], $form->id());
1933 $transient = '_cf7sg_'.wp_create_nonce( $this->form_css_id($_POST['_wpcf7_key']) );
1934 set_transient( $transient, $data, $cache[0]*$cache[1]);
1935 // debug_msg($data, "setting transient $transient, expiring in ".$cache[0]*$cache[1]);
1936 }
1937 }
1938 //for preview forms...
1939 if( !isset($_POST['_cf7sg_preview']) ) return;
1940 $prefill = array();
1941 foreach( $form->scan_form_tags() as $tag){
1942 if( isset($_POST[$tag->name]) and !empty($_POST[$tag->name]) ){
1943 $prefill[$tag->name] = $_POST[$tag->name];
1944 }
1945 $prefill['_cf7sg_toggles'] = self::$array_toggled_panels[$form->id()];
1946 }
1947
1948 if(!empty($prefill)) setcookie('_cf7sg_'. sanitize_text_field( $_POST['_wpcf7_key'] ), json_encode($prefill),0,'/');
1949 }
1950 /**
1951 * Register a [dynamic_display] shortcode with CF7.
1952 * hooked on 'cf7sg_dynamic_select_html_field'
1953 * This function registers a callback function to expand the shortcode for the googleMap form fields.
1954 * @since 4.11.0
1955 * @param Array $attrs array of attribute key=>value pairs to be included in the html element tag.
1956 * @param Array $options array of value=>label pairs of options.
1957 * @param Array $other_attrs array of other attributes selected in tag field as $attr=>true.
1958 * @return String an html string representing the input field to a=be added to the field wrapper and into the form.
1959 */
1960 public function build_dynamic_select_field( $html, $attrs, $options, $other_attrs, $selected){
1961 $attributes ='';
1962 foreach($attrs as $key=>$value){
1963 if('name'==$key && isset($other_attrs['multiple'])){
1964 $value.='[]';
1965 $attributes .= ' multiple';
1966 }
1967 $attributes .= ' '.$key.'="'.$value.'"';
1968 }
1969 $html = '<select value="'.$selected.'"'.$attributes.'>'.PHP_EOL;
1970 if(is_array($options)){
1971 foreach($options as $value=>$details){
1972 $attributes ='';
1973 foreach($details[1] as $name=>$attval){
1974 $attributes .= ' '.$this->format_attribute($name,$attval);
1975 }
1976 if($value==$selected) $attributes .=' selected="selected"';
1977 $html .= '<option value="'.$value.'"'.$attributes.'>'.$details[0].'</option>'.PHP_EOL;
1978 }
1979 }else $html .= $options; //pre 4.10.0 backward compatibility.
1980 $html .='</select>'.PHP_EOL;
1981 return $html;
1982 }
1983 /**
1984 * Register a [dynamic_display] shortcode with CF7.
1985 * hooked on 'cf7sg_dynamic_checkbox_html_field'
1986 * This function registers a callback function to expand the shortcode for the googleMap form fields.
1987 * @since 4.11.0
1988 * @param Array $attrs array of attribute key=>value pairs to be included in the html element tag.
1989 * @param Array $options array of value=>label pairs of options.
1990 * @param Array $other_attrs array of other attributes selected in tag field as $attr=>true.
1991 * @return String an html string representing the input field to a=be added to the field wrapper and into the form.
1992 */
1993 public function build_dynamic_checkbox_field( $html, $attrs, $options, $other_attrs, $selected){
1994
1995 if( !isset($attrs['class']) ) $attrs['class'] = '';
1996 $classes = explode(' ',$attrs['class']);
1997
1998 $type = 'radio';
1999 $name_attr='';
2000 $isImageGrid = isset($other_attrs['imagegrid']);
2001 if(isset($other_attrs['nolimit'])) $attrs['data-limit-selection']="-1";
2002 $attributes = '';
2003 $hybrid_data = array();
2004 switch(true){
2005 case array_search('cf7sg-hybriddd',$classes)!==false:
2006 break;
2007 case array_search('cf7sg-imagehdd',$classes)!==false:
2008 $attrs['data-checkboxes']='false';
2009 $attrs['data-dropdown']='landscape';
2010 break;
2011 case array_search('cf7sg-treeview',$classes)!==false:
2012 $attrs['class'] .=' cf7sg-hybriddd';
2013 $attrs['data-tree-view']='true';
2014 break;
2015 case array_search('cf7sg-imagegrid',$classes)!==false:
2016 $attrs['class'] .=' cf7sg-imagehdd';
2017 $attrs['data-dropdown']='none';
2018 break;
2019 }
2020 $default = apply_filters('cf7sg_hybrid_dynamic_checkbox_default_value','',$attrs['name']);
2021 if(!empty($default)) $hybrid_data['']=$default;
2022 if(!empty($attrs['name'])) $attributes .= ' data-field-name="'.$attrs['name'].'"';
2023 if( isset($attrs['name'])){
2024 $name_attr = 'name="'.$attrs['name'];
2025 if(in_array('checkbox',$classes)){
2026 $name_attr.='[]';
2027 $type = 'checkbox';
2028 }
2029 $name_attr.='"';
2030 }
2031 $hasId = false;
2032 foreach($attrs as $key=>$value){
2033 if('name'==$key) continue;
2034 if('id'==$key) $hasId = true;
2035 if( 'data-maxcheck'==$key ) $key = 'data-limit-selection';
2036 $attributes .= ' '.$key.'="'.$value.'"';
2037 }
2038 if(!$hasId) $attributes = 'id="dl-'.$attrs['name'].'" '.$attributes;
2039
2040 $html = '<span '.$attributes.'>'.PHP_EOL;
2041 $html .= '<script type="application/json">'.PHP_EOL;
2042 $html .= json_encode($this->build_hybrid_list($options)).PHP_EOL;
2043 $html .= '</script>'.PHP_EOL;
2044 $html .='</span>'.PHP_EOL;
2045 return $html;
2046 }
2047
2048 /**
2049 * Build recursively nested list.
2050 *
2051 * @since 4.11.0
2052 * @param Array $options array of value=>label pairs of options.
2053 * @return String an html string representing the input field to a=be added to the field wrapper and into the form.
2054 */
2055 protected function build_hybrid_list($options){
2056 $hybrid_data = array();
2057
2058 foreach($options as $value=>$details){
2059 $attributes = array($details[0]); //option label
2060 // if($value==$selected) $attributes .=' checked="true"';
2061 foreach($details[1] as $name=>$aval){
2062 $attributes[] = $this->format_attribute($name,$aval);
2063 }
2064 $children = array();
2065 if(isset($details[2])) $children = $this->build_hybrid_list($details[2]);
2066 $hybrid_data[$value] = array('label'=>$attributes) + $children;
2067 }
2068 return $hybrid_data;
2069 }
2070 /**
2071 * format html attribute as $name="$value".
2072 * @since 4.11.0
2073 * @param String $name
2074 * @param Mixed $value either a string or an array.
2075 * @return String
2076 */
2077 protected function format_attribute($name, $value){
2078 if(is_array($value)){
2079 $separator = ' ';
2080 if('style' === $name ) $separator = ';';
2081 $value = implode( $separator, $value);
2082 }
2083 return $name.'="'.$value.'"';
2084 }
2085 /**
2086 * register scripts for dynamic select
2087 * hooked on 'smart_grid_register_styles'
2088 * @since 4.11.0
2089 * @param Boolean $airplane if airplane mode is on, do not load remote scripts/styles.
2090 * @param String $min set to '.min' by default, empty if in WP_DEBUG mode.
2091 */
2092 public function register_dynamic_list_styles($airplane, $min){
2093 $ff = '';
2094 if(!defined('WP_DEBUG') || !WP_DEBUG){
2095 $ff = '.min';
2096 }
2097 $plugin_dir = plugin_dir_url( __DIR__ );
2098 wp_register_style('jquery-nice-select-css', "{$plugin_dir}assets/jquery-nice-select/css/nice-select{$ff}.css", array(), '1.1.0', 'all' );
2099 /** @since 3.2.1 use cloudflare for live sites */
2100 if( $airplane || (defined('WP_DEBUG') && WP_DEBUG || apply_filters('cf7sg_use_local_select2', false) ) ){
2101 wp_register_style('select2-style', "{$plugin_dir}assets/select2/css/select2.min.css", array(), '4.0.13', 'all' );
2102 }else{
2103 wp_register_style('select2-style', '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css', array(), '4.0.13','all');
2104 }
2105 //hybrid style for the dynamic checkbox.
2106 wp_register_style('hybriddd-style', $plugin_dir . "assets/hybrid-html-dropdown/hybrid-dropdown{$min}.css", array(), $this->version,'all');
2107 }
2108 /**
2109 * register scripts for dynamic select
2110 * hooked on 'smart_grid_register_scripts'
2111 * @since 4.11.0
2112 * @param Boolean $airplane if airplane mode is on, do not load remote scripts/styles.
2113 * @param String $min set to '.min' by default, empty if in WP_DEBUG mode.
2114 */
2115 public function register_dynamic_list_scripts($airplane, $min){
2116 /** @since 3.2.1 use cloudflare for live sites */
2117 $plugin_dir = plugin_dir_url( __DIR__ );
2118 if( $airplane || (defined('WP_DEBUG') && WP_DEBUG) || apply_filters('cf7sg_use_local_select2', false) ){
2119 wp_register_script('jquery-select2', "{$plugin_dir}assets/select2/js/select2.min.js", array( 'jquery' ), '4.0.13', true );
2120 }else{
2121 wp_register_script('jquery-select2', '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js', array( 'jquery' ), '4.0.13', true );
2122 }
2123 wp_register_script('jquery-nice-select', "{$plugin_dir}assets/jquery-nice-select/js/jquery.nice-select.min.js", array( 'jquery' ), '1.1.0', true );
2124 wp_register_script('hybriddd-js', $plugin_dir . "assets/hybrid-html-dropdown/hybrid-dropdown{$min}.js", null, $this->version, true);
2125
2126
2127 //listen for script enqueue action.
2128 add_action('smart_grid_enqueue_scripts', function($cf7_key, $atts, $classes){
2129 //check for classes set in get_form_classes() method in CF7SG_Dynamic_list class.
2130 if(in_array('has-select2', $classes)){
2131 wp_enqueue_style('select2-style');
2132 wp_enqueue_script('jquery-select2');
2133 }
2134 if(in_array('has-nice-select', $classes)){
2135 wp_enqueue_style('jquery-nice-select-css');
2136 wp_enqueue_script('jquery-nice-select');
2137 }
2138 if( in_array('has-hybriddd', $classes) ){
2139 wp_enqueue_style('hybriddd-style');
2140 wp_enqueue_script('hybriddd-js');
2141 }
2142 },10,3);
2143 }
2144 /**
2145 * Enable HTML messages in cf7 submission messages.
2146 * Hooked on 'wpcf7_mail_sent' action fired after the submission response has been set.
2147 *@since 4.11.0
2148 *@param WPCF7_ContactForm $form cfy form
2149 */
2150 public function change_submission_msg($form){
2151 if(has_filter('cf7sg_submission_success_message') || has_filter('cf7sg_redirect_on_success')){
2152 if(is_a($form, 'WPCF7_ContactForm')){
2153 $submitted = WPCF7_Submission::get_instance();
2154 $data = $submitted->get_posted_data();
2155 $cf7key = get_cf7form_key( $form->id() );
2156 $message = apply_filters('cf7sg_submission_success_message', $form->message( 'mail_sent_ok' ), $data, $cf7key);
2157 $url = apply_filters('cf7sg_redirect_on_success', '', $data, $cf7key);
2158 if(wp_http_validate_url($url)) $message = 'cf7sg->redirect:' . $url;
2159 $submitted->set_response( $message );
2160 }
2161 }
2162 }
2163 }
2164
2165 if(!function_exists('cf7sg_extract_submitted_files')){
2166 /**
2167 * Extract submitted files. Used by other plugins to extract files.
2168 *
2169 *@since 4.9.0
2170 *@param Array $files array of files.
2171 *@return Array $file_name=>$file_path.
2172 */
2173 function cf7sg_extract_submitted_files(Array $files){
2174 $results=array();
2175 foreach($files as $file){
2176 if(is_array($file)) $results = array_merge($results, cf7sg_extract_submitted_files($file));
2177 else{
2178 $filename = explode('/',$file);
2179 $filename = $filename[count($filename)-1];
2180 $results[$filename] = $file;
2181 }
2182 }
2183 return $results;
2184 }
2185 }
2186