GravityView  1.22.6
The best, easiest way to display Gravity Forms entries on your website.
class-search-widget.php
Go to the documentation of this file.
1 <?php
2 /**
3  * The GravityView New Search widget
4  *
5  * @package GravityView-DataTables-Ext
6  * @license GPL2+
7  * @author Katz Web Services, Inc.
8  * @link http://gravityview.co
9  * @copyright Copyright 2014, Katz Web Services, Inc.
10  */
11 
12 if ( ! defined( 'WPINC' ) ) {
13  die;
14 }
15 
17 
18  public static $file;
19  public static $instance;
20 
21  private $search_filters = array();
22 
23  /**
24  * whether search method is GET or POST ( default: GET )
25  * @since 1.16.4
26  * @var string
27  */
28  private $search_method = 'get';
29 
30  public function __construct() {
31 
32  $this->widget_description = esc_html__( 'Search form for searching entries.', 'gravityview' );
33 
34  self::$instance = &$this;
35 
36  self::$file = plugin_dir_path( __FILE__ );
37 
38  $default_values = array( 'header' => 0, 'footer' => 0 );
39 
40  $settings = array(
41  'search_layout' => array(
42  'type' => 'radio',
43  'full_width' => true,
44  'label' => esc_html__( 'Search Layout', 'gravityview' ),
45  'value' => 'horizontal',
46  'options' => array(
47  'horizontal' => esc_html__( 'Horizontal', 'gravityview' ),
48  'vertical' => esc_html__( 'Vertical', 'gravityview' ),
49  ),
50  ),
51  'search_clear' => array(
52  'type' => 'checkbox',
53  'label' => __( 'Show Clear button', 'gravityview' ),
54  'value' => false,
55  ),
56  'search_fields' => array(
57  'type' => 'hidden',
58  'label' => '',
59  'class' => 'gv-search-fields-value',
60  'value' => '[{"field":"search_all","input":"input_text"}]', // Default: Search Everything text box
61  ),
62  'search_mode' => array(
63  'type' => 'radio',
64  'full_width' => true,
65  'label' => esc_html__( 'Search Mode', 'gravityview' ),
66  'desc' => __('Should search results match all search fields, or any?', 'gravityview'),
67  'value' => 'any',
68  'class' => 'hide-if-js',
69  'options' => array(
70  'any' => esc_html__( 'Match Any Fields', 'gravityview' ),
71  'all' => esc_html__( 'Match All Fields', 'gravityview' ),
72  ),
73  ),
74  );
75  parent::__construct( esc_html__( 'Search Bar', 'gravityview' ), 'search_bar', $default_values, $settings );
76 
77  // frontend - filter entries
78  add_filter( 'gravityview_fe_search_criteria', array( $this, 'filter_entries' ), 10, 1 );
79 
80  // frontend - add template path
81  add_filter( 'gravityview_template_paths', array( $this, 'add_template_path' ) );
82 
83  // Add hidden fields for "Default" permalink structure
84  add_filter( 'gravityview_widget_search_filters', array( $this, 'add_no_permalink_fields' ), 10, 3 );
85 
86  // admin - add scripts - run at 1100 to make sure GravityView_Admin_Views::add_scripts_and_styles() runs first at 999
87  add_action( 'admin_enqueue_scripts', array( $this, 'add_scripts_and_styles' ), 1100 );
88  add_action( 'wp_enqueue_scripts', array( $this, 'register_scripts') );
89  add_filter( 'gravityview_noconflict_scripts', array( $this, 'register_no_conflict' ) );
90 
91  // ajax - get the searchable fields
92  add_action( 'wp_ajax_gv_searchable_fields', array( 'GravityView_Widget_Search', 'get_searchable_fields' ) );
93 
94  // calculate the search method (POST / GET)
95  $this->set_search_method();
96 
97  }
98 
99  /**
100  * @return GravityView_Widget_Search
101  */
102  public static function getInstance() {
103  if ( empty( self::$instance ) ) {
104  self::$instance = new GravityView_Widget_Search;
105  }
106  return self::$instance;
107  }
108 
109  /**
110  * Sets the search method to GET (default) or POST
111  * @since 1.16.4
112  */
113  private function set_search_method() {
114  /**
115  * @filter `gravityview/search/method` Modify the search form method (GET / POST)
116  * @since 1.16.4
117  * @param string $search_method Assign an input type according to the form field type. Defaults: `boolean`, `multi`, `select`, `date`, `text`
118  * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
119  */
120  $method = apply_filters( 'gravityview/search/method', $this->search_method );
121 
122  $method = strtolower( $method );
123 
124  $this->search_method = in_array( $method, array( 'get', 'post' ) ) ? $method : 'get';
125  }
126 
127  /**
128  * Returns the search method
129  * @since 1.16.4
130  * @return string
131  */
132  public function get_search_method() {
133  return $this->search_method;
134  }
135 
136  /**
137  * Get the input types available for different field types
138  *
139  * @since 1.17.5
140  *
141  * @return array [field type name] => (array|string) search bar input types
142  */
143  public static function get_input_types_by_field_type() {
144  /**
145  * Input Type groups
146  * @see admin-search-widget.js (getSelectInput)
147  * @var array
148  */
149  $input_types = array(
150  'text' => array( 'input_text' ),
151  'address' => array( 'input_text' ),
152  'number' => array( 'input_text' ),
153  'date' => array( 'date', 'date_range' ),
154  'boolean' => array( 'single_checkbox' ),
155  'select' => array( 'select', 'radio', 'link' ),
156  'multi' => array( 'select', 'multiselect', 'radio', 'checkbox', 'link' ),
157  );
158 
159  /**
160  * @filter `gravityview/search/input_types` Change the types of search fields available to a field type
161  * @see GravityView_Widget_Search::get_search_input_labels() for the available input types
162  * @param array $input_types Associative array: key is field `name`, value is array of GravityView input types (note: use `input_text` for `text`)
163  */
164  $input_types = apply_filters( 'gravityview/search/input_types', $input_types );
165 
166  return $input_types;
167  }
168 
169  /**
170  * Get labels for different types of search bar inputs
171  *
172  * @since 1.17.5
173  *
174  * @return array [input type] => input type label
175  */
176  public static function get_search_input_labels() {
177  /**
178  * Input Type labels l10n
179  * @see admin-search-widget.js (getSelectInput)
180  * @var array
181  */
182  $input_labels = array(
183  'input_text' => esc_html__( 'Text', 'gravityview' ),
184  'date' => esc_html__( 'Date', 'gravityview' ),
185  'select' => esc_html__( 'Select', 'gravityview' ),
186  'multiselect' => esc_html__( 'Select (multiple values)', 'gravityview' ),
187  'radio' => esc_html__( 'Radio', 'gravityview' ),
188  'checkbox' => esc_html__( 'Checkbox', 'gravityview' ),
189  'single_checkbox' => esc_html__( 'Checkbox', 'gravityview' ),
190  'link' => esc_html__( 'Links', 'gravityview' ),
191  'date_range' => esc_html__( 'Date range', 'gravityview' ),
192  );
193 
194  /**
195  * @filter `gravityview/search/input_types` Change the label of search field input types
196  * @param array $input_types Associative array: key is input type name, value is label
197  */
198  $input_labels = apply_filters( 'gravityview/search/input_labels', $input_labels );
199 
200  return $input_labels;
201  }
202 
203  public static function get_search_input_label( $input_type ) {
204  $labels = self::get_search_input_labels();
205 
206  return rgar( $labels, $input_type, false );
207  }
208 
209  /**
210  * Add script to Views edit screen (admin)
211  * @param mixed $hook
212  */
213  public function add_scripts_and_styles( $hook ) {
214  global $pagenow;
215 
216  // Don't process any scripts below here if it's not a GravityView page or the widgets screen
217  if ( ! gravityview_is_admin_page( $hook ) && ( 'widgets.php' !== $pagenow ) ) {
218  return;
219  }
220 
221  $script_min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
222  $script_source = empty( $script_min ) ? '/source' : '';
223 
224  wp_enqueue_script( 'gravityview_searchwidget_admin', plugins_url( 'assets/js'.$script_source.'/admin-search-widget'.$script_min.'.js', __FILE__ ), array( 'jquery', 'gravityview_views_scripts' ), GravityView_Plugin::version );
225 
226  wp_localize_script( 'gravityview_searchwidget_admin', 'gvSearchVar', array(
227  'nonce' => wp_create_nonce( 'gravityview_ajaxsearchwidget' ),
228  'label_nofields' => esc_html__( 'No search fields configured yet.', 'gravityview' ),
229  'label_addfield' => esc_html__( 'Add Search Field', 'gravityview' ),
230  'label_label' => esc_html__( 'Label', 'gravityview' ),
231  'label_searchfield' => esc_html__( 'Search Field', 'gravityview' ),
232  'label_inputtype' => esc_html__( 'Input Type', 'gravityview' ),
233  'label_ajaxerror' => esc_html__( 'There was an error loading searchable fields. Save the View or refresh the page to fix this issue.', 'gravityview' ),
234  'input_labels' => json_encode( self::get_search_input_labels() ),
235  'input_types' => json_encode( self::get_input_types_by_field_type() ),
236  ) );
237 
238  }
239 
240  /**
241  * Add admin script to the no-conflict scripts whitelist
242  * @param array $allowed Scripts allowed in no-conflict mode
243  * @return array Scripts allowed in no-conflict mode, plus the search widget script
244  */
245  public function register_no_conflict( $allowed ) {
246  $allowed[] = 'gravityview_searchwidget_admin';
247  return $allowed;
248  }
249 
250  /**
251  * Ajax
252  * Returns the form fields ( only the searchable ones )
253  *
254  * @access public
255  * @return void
256  */
257  public static function get_searchable_fields() {
258 
259  if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'gravityview_ajaxsearchwidget' ) ) {
260  exit( '0' );
261  }
262 
263  $form = '';
264 
265  // Fetch the form for the current View
266  if ( ! empty( $_POST['view_id'] ) ) {
267 
268  $form = gravityview_get_form_id( $_POST['view_id'] );
269 
270  } elseif ( ! empty( $_POST['formid'] ) ) {
271 
272  $form = (int) $_POST['formid'];
273 
274  } elseif ( ! empty( $_POST['template_id'] ) && class_exists( 'GravityView_Ajax' ) ) {
275 
276  $form = GravityView_Ajax::pre_get_form_fields( $_POST['template_id'] );
277 
278  }
279 
280  // fetch form id assigned to the view
281  $response = self::render_searchable_fields( $form );
282 
283  exit( $response );
284  }
285 
286  /**
287  * Generates html for the available Search Fields dropdown
288  * @param int $form_id
289  * @param string $current (for future use)
290  * @return string
291  */
292  public static function render_searchable_fields( $form_id = null, $current = '' ) {
293 
294  if ( is_null( $form_id ) ) {
295  return '';
296  }
297 
298  // start building output
299 
300  $output = '<select class="gv-search-fields">';
301 
302  $custom_fields = array(
303  'search_all' => array(
304  'text' => esc_html__( 'Search Everything', 'gravityview' ),
305  'type' => 'text',
306  ),
307  'entry_date' => array(
308  'text' => esc_html__( 'Entry Date', 'gravityview' ),
309  'type' => 'date',
310  ),
311  'entry_id' => array(
312  'text' => esc_html__( 'Entry ID', 'gravityview' ),
313  'type' => 'text',
314  ),
315  'created_by' => array(
316  'text' => esc_html__( 'Entry Creator', 'gravityview' ),
317  'type' => 'select',
318  )
319  );
320 
321  foreach( $custom_fields as $custom_field_key => $custom_field ) {
322  $output .= sprintf( '<option value="%s" %s data-inputtypes="%s" data-placeholder="%s">%s</option>', $custom_field_key, selected( $custom_field_key, $current, false ), $custom_field['type'], self::get_field_label( array('field' => $custom_field_key ) ), $custom_field['text'] );
323  }
324 
325  // Get fields with sub-inputs and no parent
326  $fields = gravityview_get_form_fields( $form_id, true, true );
327 
328  /**
329  * @filter `gravityview/search/searchable_fields` Modify the fields that are displayed as searchable in the Search Bar dropdown\n
330  * @since 1.17
331  * @see gravityview_get_form_fields() Used to fetch the fields
332  * @see GravityView_Widget_Search::get_search_input_types See this method to modify the type of input types allowed for a field
333  * @param array $fields Array of searchable fields, as fetched by gravityview_get_form_fields()
334  * @param int $form_id
335  */
336  $fields = apply_filters( 'gravityview/search/searchable_fields', $fields, $form_id );
337 
338  if ( ! empty( $fields ) ) {
339 
340  $blacklist_field_types = apply_filters( 'gravityview_blacklist_field_types', array( 'fileupload', 'post_image', 'post_id', 'section' ), null );
341 
342  foreach ( $fields as $id => $field ) {
343 
344  if ( in_array( $field['type'], $blacklist_field_types ) ) {
345  continue;
346  }
347 
348  $types = self::get_search_input_types( $id, $field['type'] );
349 
350  $output .= '<option value="'. $id .'" '. selected( $id, $current, false ).'data-inputtypes="'. esc_attr( $types ) .'">'. esc_html( $field['label'] ) .'</option>';
351  }
352  }
353 
354  $output .= '</select>';
355 
356  return $output;
357 
358  }
359 
360  /**
361  * Assign an input type according to the form field type
362  *
363  * @see admin-search-widget.js
364  *
365  * @param string|int|float $field_id Gravity Forms field ID
366  * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
367  *
368  * @return string GV field search input type ('multi', 'boolean', 'select', 'date', 'text')
369  */
370  public static function get_search_input_types( $field_id = '', $field_type = null ) {
371 
372  // @todo - This needs to be improved - many fields have . including products and addresses
373  if ( false !== strpos( (string) $field_id, '.' ) && in_array( $field_type, array( 'checkbox' ) ) || in_array( $field_id, array( 'is_fulfilled' ) ) ) {
374  $input_type = 'boolean'; // on/off checkbox
375  } elseif ( in_array( $field_type, array( 'checkbox', 'post_category', 'multiselect' ) ) ) {
376  $input_type = 'multi'; //multiselect
377  } elseif ( in_array( $field_type, array( 'select', 'radio' ) ) ) {
378  $input_type = 'select';
379  } elseif ( in_array( $field_type, array( 'date' ) ) || in_array( $field_id, array( 'payment_date' ) ) ) {
380  $input_type = 'date';
381  } elseif ( in_array( $field_type, array( 'number' ) ) || in_array( $field_id, array( 'payment_amount' ) ) ) {
382  $input_type = 'number';
383  } else {
384  $input_type = 'text';
385  }
386 
387  /**
388  * @filter `gravityview/extension/search/input_type` Modify the search form input type based on field type
389  * @since 1.2
390  * @since 1.19.2 Added $field_id parameter
391  * @param string $input_type Assign an input type according to the form field type. Defaults: `boolean`, `multi`, `select`, `date`, `text`
392  * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
393  * @param string|int|float $field_id ID of the field being processed
394  */
395  $input_type = apply_filters( 'gravityview/extension/search/input_type', $input_type, $field_type, $field_id );
396 
397  return $input_type;
398  }
399 
400  /**
401  * Display hidden fields to add support for sites using Default permalink structure
402  *
403  * @since 1.8
404  * @return array Search fields, modified if not using permalinks
405  */
406  public function add_no_permalink_fields( $search_fields, $object, $widget_args = array() ) {
407  /** @global WP_Rewrite $wp_rewrite */
408  global $wp_rewrite;
409 
410  // Support default permalink structure
411  if ( false === $wp_rewrite->using_permalinks() ) {
412 
413  // By default, use current post.
414  $post_id = 0;
415 
416  // We're in the WordPress Widget context, and an overriding post ID has been set.
417  if ( ! empty( $widget_args['post_id'] ) ) {
418  $post_id = absint( $widget_args['post_id'] );
419  }
420  // We're in the WordPress Widget context, and the base View ID should be used
421  else if ( ! empty( $widget_args['view_id'] ) ) {
422  $post_id = absint( $widget_args['view_id'] );
423  }
424 
425  $args = gravityview_get_permalink_query_args( $post_id );
426 
427  // Add hidden fields to the search form
428  foreach ( $args as $key => $value ) {
429  $search_fields[] = array(
430  'name' => $key,
431  'input' => 'hidden',
432  'value' => $value,
433  );
434  }
435  }
436 
437  return $search_fields;
438  }
439 
440 
441  /** --- Frontend --- */
442 
443  /**
444  * Calculate the search criteria to filter entries
445  * @param array $search_criteria
446  * @return array
447  */
448  public function filter_entries( $search_criteria ) {
449 
450  if( 'post' === $this->search_method ) {
451  $get = $_POST;
452  } else {
453  $get = $_GET;
454  }
455 
456  do_action( 'gravityview_log_debug', sprintf( '%s[filter_entries] Requested $_%s: ', get_class( $this ), $this->search_method ), $get );
457 
458  if ( empty( $get ) || ! is_array( $get ) ) {
459  return $search_criteria;
460  }
461 
462  $get = stripslashes_deep( $get );
463 
464  $get = gv_map_deep( $get, 'rawurldecode' );
465 
466  // Make sure array key is set up
467  $search_criteria['field_filters'] = rgar( $search_criteria, 'field_filters', array() );
468 
469  // add free search
470  if ( ! empty( $get['gv_search'] ) ) {
471 
472  $search_all_value = trim( $get['gv_search'] );
473 
474  /**
475  * @filter `gravityview/search-all-split-words` Search for each word separately or the whole phrase?
476  * @since 1.20.2
477  * @param bool $split_words True: split a phrase into words; False: search whole word only [Default: true]
478  */
479  $split_words = apply_filters( 'gravityview/search-all-split-words', true );
480 
481  if( $split_words ) {
482 
483  // Search for a piece
484  $words = explode( ' ', $search_all_value );
485 
486  $words = array_filter( $words );
487 
488  } else {
489 
490  // Replace multiple spaces with one space
491  $search_all_value = preg_replace( '/\s+/ism', ' ', $search_all_value );
492 
493  $words = array( $search_all_value );
494  }
495 
496  foreach ( $words as $word ) {
497  $search_criteria['field_filters'][] = array(
498  'key' => null, // The field ID to search
499  'value' => $word, // The value to search
500  'operator' => 'contains', // What to search in. Options: `is` or `contains`
501  );
502  }
503  }
504 
505  //start date & end date
506  $curr_start = !empty( $get['gv_start'] ) ? $get['gv_start'] : '';
507  $curr_end = !empty( $get['gv_start'] ) ? $get['gv_end'] : '';
508 
509  /**
510  * @filter `gravityview_date_created_adjust_timezone` Whether to adjust the timezone for entries. \n
511  * date_created is stored in UTC format. Convert search date into UTC (also used on templates/fields/date_created.php)
512  * @since 1.12
513  * @param[out,in] boolean $adjust_tz Use timezone-adjusted datetime? If true, adjusts date based on blog's timezone setting. If false, uses UTC setting. Default: true
514  * @param[in] string $context Where the filter is being called from. `search` in this case.
515  */
516  $adjust_tz = apply_filters( 'gravityview_date_created_adjust_timezone', true, 'search' );
517 
518 
519  /**
520  * Don't set $search_criteria['start_date'] if start_date is empty as it may lead to bad query results (GFAPI::get_entries)
521  */
522  if( !empty( $curr_start ) ) {
523  $search_criteria['start_date'] = $adjust_tz ? get_gmt_from_date( $curr_start ) : $curr_start;
524  }
525  if( !empty( $curr_end ) ) {
526  $search_criteria['end_date'] = $adjust_tz ? get_gmt_from_date( $curr_end ) : $curr_end;
527  }
528 
529  // search for a specific entry ID
530  if ( ! empty( $get[ 'gv_id' ] ) ) {
531  $search_criteria['field_filters'][] = array(
532  'key' => 'id',
533  'value' => absint( $get[ 'gv_id' ] ),
534  'operator' => '=',
535  );
536  }
537 
538  // search for a specific Created_by ID
539  if ( ! empty( $get[ 'gv_by' ] ) ) {
540  $search_criteria['field_filters'][] = array(
541  'key' => 'created_by',
542  'value' => absint( $get['gv_by'] ),
543  'operator' => '=',
544  );
545  }
546 
547 
548  // Get search mode passed in URL
549  $mode = isset( $get['mode'] ) && in_array( $get['mode'], array( 'any', 'all' ) ) ? $get['mode'] : 'any';
550 
551  // get the other search filters
552  foreach ( $get as $key => $value ) {
553 
554  if ( 0 !== strpos( $key, 'filter_' ) || gv_empty( $value, false, false ) || ( is_array( $value ) && count( $value ) === 1 && gv_empty( $value[0], false, false ) ) ) {
555  continue;
556  }
557 
558  // could return simple filter or multiple filters
559  $filter = $this->prepare_field_filter( $key, $value );
560 
561  if ( isset( $filter[0]['value'] ) ) {
562  $search_criteria['field_filters'] = array_merge( $search_criteria['field_filters'], $filter );
563 
564  // if date range type, set search mode to ALL
565  if ( ! empty( $filter[0]['operator'] ) && in_array( $filter[0]['operator'], array( '>=', '<=', '>', '<' ) ) ) {
566  $mode = 'all';
567  }
568  } elseif( !empty( $filter ) ) {
569  $search_criteria['field_filters'][] = $filter;
570  }
571  }
572 
573  /**
574  * @filter `gravityview/search/mode` Set the Search Mode (`all` or `any`)
575  * @since 1.5.1
576  * @param[out,in] string $mode Search mode (`any` vs `all`)
577  */
578  $search_criteria['field_filters']['mode'] = apply_filters( 'gravityview/search/mode', $mode );
579 
580  do_action( 'gravityview_log_debug', sprintf( '%s[filter_entries] Returned Search Criteria: ', get_class( $this ) ), $search_criteria );
581 
582  unset( $get );
583 
584  return $search_criteria;
585  }
586 
587  /**
588  * Prepare the field filters to GFAPI
589  *
590  * The type post_category, multiselect and checkbox support multi-select search - each value needs to be separated in an independent filter so we could apply the ANY search mode.
591  *
592  * Format searched values
593  * @param string $key $_GET/$_POST search key
594  * @param string $value $_GET/$_POST search value
595  * @return array 1 or 2 deph levels
596  */
597  public function prepare_field_filter( $key, $value ) {
598 
600 
601  $field_id = str_replace( 'filter_', '', $key );
602 
603  // calculates field_id, removing 'filter_' and for '_' for advanced fields ( like name or checkbox )
604  if ( preg_match('/^[0-9_]+$/ism', $field_id ) ) {
605  $field_id = str_replace( '_', '.', $field_id );
606  }
607 
608  // get form field array
609  $form = $gravityview_view->getForm();
610  $form_field = gravityview_get_field( $form, $field_id );
611 
612  // default filter array
613  $filter = array(
614  'key' => $field_id,
615  'value' => $value,
616  );
617 
618  switch ( $form_field['type'] ) {
619 
620  case 'select':
621  case 'radio':
622  $filter['operator'] = 'is';
623  break;
624 
625  case 'post_category':
626 
627  if ( ! is_array( $value ) ) {
628  $value = array( $value );
629  }
630 
631  // Reset filter variable
632  $filter = array();
633 
634  foreach ( $value as $val ) {
635  $cat = get_term( $val, 'category' );
636  $filter[] = array(
637  'key' => $field_id,
638  'value' => esc_attr( $cat->name ) . ':' . $val,
639  'operator' => 'is',
640  );
641  }
642 
643  break;
644 
645  case 'multiselect':
646 
647  if ( ! is_array( $value ) ) {
648  break;
649  }
650 
651  // Reset filter variable
652  $filter = array();
653 
654  foreach ( $value as $val ) {
655  $filter[] = array( 'key' => $field_id, 'value' => $val );
656  }
657 
658  break;
659 
660  case 'checkbox':
661  // convert checkbox on/off into the correct search filter
662  if ( false !== strpos( $field_id, '.' ) && ! empty( $form_field['inputs'] ) && ! empty( $form_field['choices'] ) ) {
663  foreach ( $form_field['inputs'] as $k => $input ) {
664  if ( $input['id'] == $field_id ) {
665  $filter['value'] = $form_field['choices'][ $k ]['value'];
666  $filter['operator'] = 'is';
667  break;
668  }
669  }
670  } elseif ( is_array( $value ) ) {
671 
672  // Reset filter variable
673  $filter = array();
674 
675  foreach ( $value as $val ) {
676  $filter[] = array(
677  'key' => $field_id,
678  'value' => $val,
679  'operator' => 'is',
680  );
681  }
682  }
683 
684  break;
685 
686  case 'name':
687  case 'address':
688 
689  if ( false === strpos( $field_id, '.' ) ) {
690 
691  $words = explode( ' ', $value );
692 
693  $filters = array();
694  foreach ( $words as $word ) {
695  if ( ! empty( $word ) && strlen( $word ) > 1 ) {
696  // Keep the same key for each filter
697  $filter['value'] = $word;
698  // Add a search for the value
699  $filters[] = $filter;
700  }
701  }
702 
703  $filter = $filters;
704  }
705 
706  break;
707 
708  case 'date':
709 
710  if ( is_array( $value ) ) {
711 
712  // Reset filter variable
713  $filter = array();
714 
715  foreach ( $value as $k => $date ) {
716  if ( empty( $date ) ) {
717  continue;
718  }
719  $operator = 'start' === $k ? '>=' : '<=';
720 
721  /**
722  * @hack
723  * @since 1.16.3
724  * Safeguard until GF implements '<=' operator
725  */
726  if( !GFFormsModel::is_valid_operator( $operator ) && $operator === '<=' ) {
727  $operator = '<';
728  $date = date( 'Y-m-d', strtotime( $date . ' +1 day' ) );
729  }
730 
731  $filter[] = array(
732  'key' => $field_id,
733  'value' => self::get_formatted_date( $date, 'Y-m-d' ),
734  'operator' => $operator,
735  );
736  }
737  } else {
738  $filter['value'] = self::get_formatted_date( $value, 'Y-m-d' );
739  }
740 
741  break;
742 
743 
744  } // switch field type
745 
746  return $filter;
747  }
748 
749  /**
750  * Get the Field Format form GravityForms
751  *
752  * @param GF_Field_Date $field The field object
753  * @since 1.10
754  *
755  * @return string Format of the date in the database
756  */
757  public static function get_date_field_format( GF_Field_Date $field ) {
758  $format = 'm/d/Y';
759  $datepicker = array(
760  'mdy' => 'm/d/Y',
761  'dmy' => 'd/m/Y',
762  'dmy_dash' => 'd-m-Y',
763  'dmy_dot' => 'm.d.Y',
764  'ymd_slash' => 'Y/m/d',
765  'ymd_dash' => 'Y-m-d',
766  'ymd_dot' => 'Y.m.d',
767  );
768 
769  if ( ! empty( $field->dateFormat ) && isset( $datepicker[ $field->dateFormat ] ) ){
770  $format = $datepicker[ $field->dateFormat ];
771  }
772 
773  return $format;
774  }
775 
776  /**
777  * Format a date value
778  *
779  * @param string $value Date value input
780  * @param string $format Wanted formatted date
781  * @return string
782  */
783  public static function get_formatted_date( $value = '', $format = 'Y-m-d' ) {
784  $date = date_create( $value );
785  if ( empty( $date ) ) {
786  do_action( 'gravityview_log_debug', sprintf( '%s[get_formatted_date] Date format not valid: ', get_class( self::$instance ) ), $value );
787  return '';
788  }
789  return $date->format( $format );
790  }
791 
792 
793  /**
794  * Include this extension templates path
795  * @param array $file_paths List of template paths ordered
796  */
797  public function add_template_path( $file_paths ) {
798 
799  // Index 100 is the default GravityView template path.
800  $file_paths[102] = self::$file . 'templates/';
801 
802  return $file_paths;
803  }
804 
805  /**
806  * Check whether the configured search fields have a date field
807  *
808  * @since 1.17.5
809  *
810  * @param array $search_fields
811  *
812  * @return bool True: has a `date` or `date_range` field
813  */
814  private function has_date_field( $search_fields ) {
815 
816  $has_date = false;
817 
818  foreach ( $search_fields as $k => $field ) {
819  if ( in_array( $field['input'], array( 'date', 'date_range', 'entry_date' ) ) ) {
820  $has_date = true;
821  break;
822  }
823  }
824 
825  return $has_date;
826  }
827 
828  /**
829  * Renders the Search Widget
830  * @param array $widget_args
831  * @param string $content
832  * @param string $context
833  *
834  * @return void
835  */
836  public function render_frontend( $widget_args, $content = '', $context = '' ) {
837  /** @var GravityView_View $gravityview_view */
839 
840  if ( empty( $gravityview_view ) ) {
841  do_action( 'gravityview_log_debug', sprintf( '%s[render_frontend]: $gravityview_view not instantiated yet.', get_class( $this ) ) );
842  return;
843  }
844 
845  // get configured search fields
846  $search_fields = ! empty( $widget_args['search_fields'] ) ? json_decode( $widget_args['search_fields'], true ) : '';
847 
848  if ( empty( $search_fields ) || ! is_array( $search_fields ) ) {
849  do_action( 'gravityview_log_debug', sprintf( '%s[render_frontend] No search fields configured for widget:', get_class( $this ) ), $widget_args );
850  return;
851  }
852 
853 
854  // prepare fields
855  foreach ( $search_fields as $k => $field ) {
856 
857  $updated_field = $field;
858 
859  $updated_field = $this->get_search_filter_details( $updated_field );
860 
861  switch ( $field['field'] ) {
862 
863  case 'search_all':
864  $updated_field['key'] = 'search_all';
865  $updated_field['input'] = 'search_all';
866  $updated_field['value'] = $this->rgget_or_rgpost( 'gv_search' );
867  break;
868 
869  case 'entry_date':
870  $updated_field['key'] = 'entry_date';
871  $updated_field['input'] = 'entry_date';
872  $updated_field['value'] = array(
873  'start' => $this->rgget_or_rgpost( 'gv_start' ),
874  'end' => $this->rgget_or_rgpost( 'gv_end' ),
875  );
876  break;
877 
878  case 'entry_id':
879  $updated_field['key'] = 'entry_id';
880  $updated_field['input'] = 'entry_id';
881  $updated_field['value'] = $this->rgget_or_rgpost( 'gv_id' );
882  break;
883 
884  case 'created_by':
885  $updated_field['key'] = 'created_by';
886  $updated_field['name'] = 'gv_by';
887  $updated_field['value'] = $this->rgget_or_rgpost( 'gv_by' );
888  $updated_field['choices'] = self::get_created_by_choices();
889  break;
890  }
891 
892  $search_fields[ $k ] = $updated_field;
893  }
894 
895  do_action( 'gravityview_log_debug', sprintf( '%s[render_frontend] Calculated Search Fields: ', get_class( $this ) ), $search_fields );
896 
897  /**
898  * @filter `gravityview_widget_search_filters` Modify what fields are shown. The order of the fields in the $search_filters array controls the order as displayed in the search bar widget.
899  * @param array $search_fields Array of search filters with `key`, `label`, `value`, `type`, `choices` keys
900  * @param GravityView_Widget_Search $this Current widget object
901  * @param array $widget_args Args passed to this method. {@since 1.8}
902  * @var array
903  */
904  $gravityview_view->search_fields = apply_filters( 'gravityview_widget_search_filters', $search_fields, $this, $widget_args );
905 
906  $gravityview_view->search_layout = ! empty( $widget_args['search_layout'] ) ? $widget_args['search_layout'] : 'horizontal';
907 
908  /** @since 1.14 */
909  $gravityview_view->search_mode = ! empty( $widget_args['search_mode'] ) ? $widget_args['search_mode'] : 'any';
910 
911  $custom_class = ! empty( $widget_args['custom_class'] ) ? $widget_args['custom_class'] : '';
912 
913  $gravityview_view->search_class = self::get_search_class( $custom_class );
914 
915  $gravityview_view->search_clear = ! empty( $widget_args['search_clear'] ) ? $widget_args['search_clear'] : false;
916 
917  if ( $this->has_date_field( $search_fields ) ) {
918  // enqueue datepicker stuff only if needed!
919  $this->enqueue_datepicker();
920  }
921 
922  $this->maybe_enqueue_flexibility();
923 
924  $gravityview_view->render( 'widget', 'search', false );
925  }
926 
927  /**
928  * Get the search class for a search form
929  *
930  * @since 1.5.4
931  *
932  * @return string Sanitized CSS class for the search form
933  */
934  public static function get_search_class( $custom_class = '' ) {
936 
937  $search_class = 'gv-search-'.$gravityview_view->search_layout;
938 
939  if ( ! empty( $custom_class ) ) {
940  $search_class .= ' '.$custom_class;
941  }
942 
943  /**
944  * @filter `gravityview_search_class` Modify the CSS class for the search form
945  * @param string $search_class The CSS class for the search form
946  */
947  $search_class = apply_filters( 'gravityview_search_class', $search_class );
948 
949  // Is there an active search being performed? Used by fe-views.js
950  $search_class .= GravityView_frontend::getInstance()->isSearch() ? ' gv-is-search' : '';
951 
952  return gravityview_sanitize_html_class( $search_class );
953  }
954 
955 
956  /**
957  * Calculate the search form action
958  * @since 1.6
959  *
960  * @return string
961  */
962  public static function get_search_form_action() {
964 
965  $post_id = $gravityview_view->getPostId() ? $gravityview_view->getPostId() : $gravityview_view->getViewId();
966 
967  $url = add_query_arg( array(), get_permalink( $post_id ) );
968 
969  return esc_url( $url );
970  }
971 
972  /**
973  * Get the label for a search form field
974  * @param array $field Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
975  * @param array $form_field Form field data, as fetched by `gravityview_get_field()`
976  * @return string Label for the search form
977  */
978  private static function get_field_label( $field, $form_field = array() ) {
979 
980  $label = rgget( 'label', $field );
981 
982  if( '' === $label ) {
983 
984  $label = isset( $form_field['label'] ) ? $form_field['label'] : '';
985 
986  switch( $field['field'] ) {
987  case 'search_all':
988  $label = __( 'Search Entries:', 'gravityview' );
989  break;
990  case 'entry_date':
991  $label = __( 'Filter by date:', 'gravityview' );
992  break;
993  case 'entry_id':
994  $label = __( 'Entry ID:', 'gravityview' );
995  break;
996  default:
997  // If this is a field input, not a field
998  if ( strpos( $field['field'], '.' ) > 0 && ! empty( $form_field['inputs'] ) ) {
999 
1000  // Get the label for the field in question, which returns an array
1001  $items = wp_list_filter( $form_field['inputs'], array( 'id' => $field['field'] ) );
1002 
1003  // Get the item with the `label` key
1004  $values = wp_list_pluck( $items, 'label' );
1005 
1006  // There will only one item in the array, but this is easier
1007  foreach ( $values as $value ) {
1008  $label = $value;
1009  break;
1010  }
1011  }
1012  }
1013  }
1014 
1015  /**
1016  * @filter `gravityview_search_field_label` Modify the label for a search field. Supports returning HTML
1017  * @since 1.17.3 Added $field parameter
1018  * @param[in,out] string $label Existing label text, sanitized.
1019  * @param[in] array $form_field Gravity Forms field array, as returned by `GFFormsModel::get_field()`
1020  * @param[in] array $field Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1021  */
1022  $label = apply_filters( 'gravityview_search_field_label', esc_attr( $label ), $form_field, $field );
1023 
1024  return $label;
1025  }
1026 
1027  /**
1028  * Prepare search fields to frontend render with other details (label, field type, searched values)
1029  *
1030  * @param array $field
1031  * @return array
1032  */
1033  private function get_search_filter_details( $field ) {
1034 
1036 
1037  $form = $gravityview_view->getForm();
1038 
1039  // for advanced field ids (eg, first name / last name )
1040  $name = 'filter_' . str_replace( '.', '_', $field['field'] );
1041 
1042  // get searched value from $_GET/$_POST (string or array)
1043  $value = $this->rgget_or_rgpost( $name );
1044 
1045  // get form field details
1046  $form_field = gravityview_get_field( $form, $field['field'] );
1047 
1048  $filter = array(
1049  'key' => $field['field'],
1050  'name' => $name,
1051  'label' => self::get_field_label( $field, $form_field ),
1052  'input' => $field['input'],
1053  'value' => $value,
1054  'type' => $form_field['type'],
1055  );
1056 
1057  // collect choices
1058  if ( 'post_category' === $form_field['type'] && ! empty( $form_field['displayAllCategories'] ) && empty( $form_field['choices'] ) ) {
1059  $filter['choices'] = gravityview_get_terms_choices();
1060  } elseif ( ! empty( $form_field['choices'] ) ) {
1061  $filter['choices'] = $form_field['choices'];
1062  }
1063 
1064  if ( 'date_range' === $field['input'] && empty( $value ) ) {
1065  $filter['value'] = array( 'start' => '', 'end' => '' );
1066  }
1067 
1068  return $filter;
1069 
1070  }
1071 
1072  /**
1073  * Calculate the search choices for the users
1074  *
1075  * @since 1.8
1076  *
1077  * @return array Array of user choices (value = ID, text = display name)
1078  */
1079  private static function get_created_by_choices() {
1080 
1081  /**
1082  * filter gravityview/get_users/search_widget
1083  * @see \GVCommon::get_users
1084  */
1085  $users = GVCommon::get_users( 'search_widget', array( 'fields' => array( 'ID', 'display_name' ) ) );
1086 
1087  $choices = array();
1088  foreach ( $users as $user ) {
1089  $choices[] = array(
1090  'value' => $user->ID,
1091  'text' => $user->display_name,
1092  );
1093  }
1094 
1095  return $choices;
1096  }
1097 
1098 
1099  /**
1100  * Output the Clear Search Results button
1101  * @since 1.5.4
1102  */
1103  public static function the_clear_search_button() {
1105 
1106  if ( $gravityview_view->search_clear ) {
1107 
1108  $url = strtok( add_query_arg( array() ), '?' );
1109 
1110  echo gravityview_get_link( $url, esc_html__( 'Clear', 'gravityview' ), 'class=button gv-search-clear' );
1111 
1112  }
1113  }
1114 
1115  /**
1116  * Based on the search method, fetch the value for a specific key
1117  *
1118  * @since 1.16.4
1119  *
1120  * @param string $name Name of the request key to fetch the value for
1121  *
1122  * @return mixed|string Value of request at $name key. Empty string if empty.
1123  */
1124  private function rgget_or_rgpost( $name ) {
1125  $value = 'get' === $this->search_method ? rgget( $name ) : rgpost( $name );
1126 
1127  $value = stripslashes_deep( $value );
1128 
1129  $value = gv_map_deep( $value, 'rawurldecode' );
1130 
1131  $value = gv_map_deep( $value, '_wp_specialchars' );
1132 
1133  return $value;
1134  }
1135 
1136 
1137  /**
1138  * Require the datepicker script for the frontend GV script
1139  * @param array $js_dependencies Array of existing required scripts for the fe-views.js script
1140  * @return array Array required scripts, with `jquery-ui-datepicker` added
1141  */
1142  public function add_datepicker_js_dependency( $js_dependencies ) {
1143 
1144  $js_dependencies[] = 'jquery-ui-datepicker';
1145 
1146  return $js_dependencies;
1147  }
1148 
1149  /**
1150  * Modify the array passed to wp_localize_script()
1151  *
1152  * @param array $js_localization The data padded to the Javascript file
1153  * @param array $view_data View data array with View settings
1154  *
1155  * @return array
1156  */
1157  public function add_datepicker_localization( $localizations = array(), $view_data = array() ) {
1158  global $wp_locale;
1159 
1160  /**
1161  * @filter `gravityview_datepicker_settings` Modify the datepicker settings
1162  * @see http://api.jqueryui.com/datepicker/ Learn what settings are available
1163  * @see http://www.renegadetechconsulting.com/tutorials/jquery-datepicker-and-wordpress-i18n Thanks for the helpful information on $wp_locale
1164  * @param array $js_localization The data padded to the Javascript file
1165  * @param array $view_data View data array with View settings
1166  */
1167  $datepicker_settings = apply_filters( 'gravityview_datepicker_settings', array(
1168  'yearRange' => '-5:+5',
1169  'changeMonth' => true,
1170  'changeYear' => true,
1171  'closeText' => esc_attr_x( 'Close', 'Close calendar', 'gravityview' ),
1172  'prevText' => esc_attr_x( 'Prev', 'Previous month in calendar', 'gravityview' ),
1173  'nextText' => esc_attr_x( 'Next', 'Next month in calendar', 'gravityview' ),
1174  'currentText' => esc_attr_x( 'Today', 'Today in calendar', 'gravityview' ),
1175  'weekHeader' => esc_attr_x( 'Week', 'Week in calendar', 'gravityview' ),
1176  'monthStatus' => __( 'Show a different month', 'gravityview' ),
1177  'monthNames' => array_values( $wp_locale->month ),
1178  'monthNamesShort' => array_values( $wp_locale->month_abbrev ),
1179  'dayNames' => array_values( $wp_locale->weekday ),
1180  'dayNamesShort' => array_values( $wp_locale->weekday_abbrev ),
1181  'dayNamesMin' => array_values( $wp_locale->weekday_initial ),
1182  // get the start of week from WP general setting
1183  'firstDay' => get_option( 'start_of_week' ),
1184  // is Right to left language? default is false
1185  'isRTL' => is_rtl(),
1186  ), $view_data );
1187 
1188  $localizations['datepicker'] = $datepicker_settings;
1189 
1190  return $localizations;
1191 
1192  }
1193 
1194  /**
1195  * Register search widget scripts, including Flexibility
1196  *
1197  * @see https://github.com/10up/flexibility
1198  *
1199  * @since 1.17
1200  *
1201  * @return void
1202  */
1203  public function register_scripts() {
1204 
1205  wp_register_script( 'gv-flexibility', plugins_url( 'assets/lib/flexibility/flexibility.js', GRAVITYVIEW_FILE ), array(), GravityView_Plugin::version, true );
1206 
1207  }
1208 
1209  /**
1210  * If the current visitor is running IE 8 or 9, enqueue Flexibility
1211  *
1212  * @since 1.17
1213  *
1214  * @return void
1215  */
1216  private function maybe_enqueue_flexibility() {
1217  if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && preg_match( '/MSIE [8-9]/', $_SERVER['HTTP_USER_AGENT'] ) ) {
1218  wp_enqueue_script( 'gv-flexibility' );
1219  }
1220  }
1221 
1222  /**
1223  * Enqueue the datepicker script
1224  *
1225  * It sets the $gravityview->datepicker_class parameter
1226  *
1227  * @todo Use own datepicker javascript instead of GF datepicker.js - that way, we can localize the settings and not require the changeMonth and changeYear pickers.
1228  * @return void
1229  */
1230  public function enqueue_datepicker() {
1232 
1233  wp_enqueue_script( 'jquery-ui-datepicker' );
1234 
1235  add_filter( 'gravityview_js_dependencies', array( $this, 'add_datepicker_js_dependency' ) );
1236  add_filter( 'gravityview_js_localization', array( $this, 'add_datepicker_localization' ), 10, 2 );
1237 
1238  $scheme = is_ssl() ? 'https://' : 'http://';
1239  wp_enqueue_style( 'jquery-ui-datepicker', $scheme.'ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/smoothness/jquery-ui.css' );
1240 
1241  /**
1242  * @filter `gravityview_search_datepicker_class`
1243  * Modify the CSS class for the datepicker, used by the CSS class is used by Gravity Forms' javascript to determine the format for the date picker. The `gv-datepicker` class is required by the GravityView datepicker javascript.
1244  * @param string $css_class CSS class to use. Default: `gv-datepicker datepicker mdy` \n
1245  * Options are:
1246  * - `mdy` (mm/dd/yyyy)
1247  * - `dmy` (dd/mm/yyyy)
1248  * - `dmy_dash` (dd-mm-yyyy)
1249  * - `dmy_dot` (dd.mm.yyyy)
1250  * - `ymp_slash` (yyyy/mm/dd)
1251  * - `ymd_dash` (yyyy-mm-dd)
1252  * - `ymp_dot` (yyyy.mm.dd)
1253  */
1254  $datepicker_class = apply_filters( 'gravityview_search_datepicker_class', 'gv-datepicker datepicker mdy' );
1255 
1256  $gravityview_view->datepicker_class = $datepicker_class;
1257 
1258  }
1259 
1260 
1261 } // end class
1262 
render_frontend( $widget_args, $content='', $context='')
Frontend logic.
new GravityView_Widget_Search
rgget_or_rgpost( $name)
Based on the search method, fetch the value for a specific key.
$url
Definition: post_image.php:25
static get_field_label( $field, $form_field=array())
Get the label for a search form field.
static get_created_by_choices()
Calculate the search choices for the users.
$labels
static get_searchable_fields()
Ajax Returns the form fields ( only the searchable ones )
add_datepicker_localization( $localizations=array(), $view_data=array())
Modify the array passed to wp_localize_script()
prepare_field_filter( $key, $value)
Prepare the field filters to GFAPI.
get_search_filter_details( $field)
Prepare search fields to frontend render with other details (label, field type, searched values) ...
static getInstance( $passed_post=NULL)
set_search_method()
Sets the search method to GET (default) or POST.
get_search_method()
Returns the search method.
Main GravityView widget class.
if(gv_empty( $field['value'], false, false)) $format
register_no_conflict( $allowed)
Add admin script to the no-conflict scripts whitelist.
enqueue_datepicker()
Enqueue the datepicker script.
gravityview_get_link( $href='', $anchor_text='', $atts=array())
Generate an HTML anchor tag with a list of supported attributes.
static get_search_form_action()
Calculate the search form action.
static get_formatted_date( $value='', $format='Y-m-d')
Format a date value.
if(empty( $field_settings['content'])) $content
Definition: custom.php:37
static get_search_input_label( $input_type)
static get_date_field_format(GF_Field_Date $field)
Get the Field Format form GravityForms.
gv_map_deep( $value, $callback)
Maps a function to all non-iterable elements of an array or an object.
has_date_field( $search_fields)
Check whether the configured search fields have a date field.
gravityview_get_field( $form, $field_id)
Returns the field details array of a specific form given the field id.
gravityview_get_form_fields( $form='', $add_default_properties=false, $include_parent_field=true)
Return array of fields&#39; id and label, for a given Form ID.
static pre_get_form_fields( $template_id='')
Get the the form fields for a preset (no form created yet)
Definition: class-ajax.php:303
register_scripts()
Register search widget scripts, including Flexibility.
add_scripts_and_styles( $hook)
Add script to Views edit screen (admin)
static get_users( $context='change_entry_creator', $args=array())
Get WordPress users with reasonable limits set.
gravityview_get_form_id( $view_id)
Get the connected form ID from a View ID.
filter_entries( $search_criteria)
— Frontend —
$field_id
Definition: time.php:17
static get_search_class( $custom_class='')
Get the search class for a search form.
maybe_enqueue_flexibility()
If the current visitor is running IE 8 or 9, enqueue Flexibility.
add_datepicker_js_dependency( $js_dependencies)
Require the datepicker script for the frontend GV script.
static get_search_input_types( $field_id='', $field_type=null)
Assign an input type according to the form field type.
if(empty( $created_by)) $form_id
gravityview_get_terms_choices( $args=array())
Get categories formatted in a way used by GravityView and Gravity Forms input choices.
gravityview_is_admin_page($hook='', $page=NULL)
Alias for GravityView_Admin::is_admin_page()
static render_searchable_fields( $form_id=null, $current='')
Generates html for the available Search Fields dropdown.
gv_empty( $value, $zero_is_empty=true, $allow_string_booleans=true)
Is the value empty?
const GRAVITYVIEW_FILE(! defined( 'ABSPATH'))
Plugin Name: GravityView Plugin URI: https://gravityview.co Description: The best, easiest way to display Gravity Forms entries on your website.
Definition: gravityview.php:26
gravityview_get_permalink_query_args( $id=0)
Get get_permalink() without the home_url() prepended to it.
add_no_permalink_fields( $search_fields, $object, $widget_args=array())
Display hidden fields to add support for sites using Default permalink structure. ...
add_template_path( $file_paths)
Include this extension templates path.
$field
Definition: gquiz_grade.php:11
static the_clear_search_button()
Output the Clear Search Results button.
static getInstance()
Get the one true instantiated self.