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

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