GravityView  2.1.1
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_id = 'search_bar';
33  $this->widget_description = esc_html__( 'Search form for searching entries.', 'gravityview' );
34 
35  self::$instance = &$this;
36 
37  self::$file = plugin_dir_path( __FILE__ );
38 
39  $default_values = array( 'header' => 0, 'footer' => 0 );
40 
41  $settings = array(
42  'search_layout' => array(
43  'type' => 'radio',
44  'full_width' => true,
45  'label' => esc_html__( 'Search Layout', 'gravityview' ),
46  'value' => 'horizontal',
47  'options' => array(
48  'horizontal' => esc_html__( 'Horizontal', 'gravityview' ),
49  'vertical' => esc_html__( 'Vertical', 'gravityview' ),
50  ),
51  ),
52  'search_clear' => array(
53  'type' => 'checkbox',
54  'label' => __( 'Show Clear button', 'gravityview' ),
55  'value' => false,
56  ),
57  'search_fields' => array(
58  'type' => 'hidden',
59  'label' => '',
60  'class' => 'gv-search-fields-value',
61  'value' => '[{"field":"search_all","input":"input_text"}]', // Default: Search Everything text box
62  ),
63  'search_mode' => array(
64  'type' => 'radio',
65  'full_width' => true,
66  'label' => esc_html__( 'Search Mode', 'gravityview' ),
67  'desc' => __('Should search results match all search fields, or any?', 'gravityview'),
68  'value' => 'any',
69  'class' => 'hide-if-js',
70  'options' => array(
71  'any' => esc_html__( 'Match Any Fields', 'gravityview' ),
72  'all' => esc_html__( 'Match All Fields', 'gravityview' ),
73  ),
74  ),
75  );
76 
77  if ( ! $this->is_registered() ) {
78  // frontend - filter entries
79  add_filter( 'gravityview_fe_search_criteria', array( $this, 'filter_entries' ), 10, 3 );
80 
81  // frontend - add template path
82  add_filter( 'gravityview_template_paths', array( $this, 'add_template_path' ) );
83 
84  // Add hidden fields for "Default" permalink structure
85  add_filter( 'gravityview_widget_search_filters', array( $this, 'add_no_permalink_fields' ), 10, 3 );
86 
87  // admin - add scripts - run at 1100 to make sure GravityView_Admin_Views::add_scripts_and_styles() runs first at 999
88  add_action( 'admin_enqueue_scripts', array( $this, 'add_scripts_and_styles' ), 1100 );
89  add_action( 'wp_enqueue_scripts', array( $this, 'register_scripts') );
90  add_filter( 'gravityview_noconflict_scripts', array( $this, 'register_no_conflict' ) );
91 
92  // ajax - get the searchable fields
93  add_action( 'wp_ajax_gv_searchable_fields', array( 'GravityView_Widget_Search', 'get_searchable_fields' ) );
94  }
95 
96  parent::__construct( esc_html__( 'Search Bar', 'gravityview' ), null, $default_values, $settings );
97 
98  // calculate the search method (POST / GET)
99  $this->set_search_method();
100  }
101 
102  /**
103  * @return GravityView_Widget_Search
104  */
105  public static function getInstance() {
106  if ( empty( self::$instance ) ) {
107  self::$instance = new GravityView_Widget_Search;
108  }
109  return self::$instance;
110  }
111 
112  /**
113  * Sets the search method to GET (default) or POST
114  * @since 1.16.4
115  */
116  private function set_search_method() {
117  /**
118  * @filter `gravityview/search/method` Modify the search form method (GET / POST)
119  * @since 1.16.4
120  * @param string $search_method Assign an input type according to the form field type. Defaults: `boolean`, `multi`, `select`, `date`, `text`
121  * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
122  */
123  $method = apply_filters( 'gravityview/search/method', $this->search_method );
124 
125  $method = strtolower( $method );
126 
127  $this->search_method = in_array( $method, array( 'get', 'post' ) ) ? $method : 'get';
128  }
129 
130  /**
131  * Returns the search method
132  * @since 1.16.4
133  * @return string
134  */
135  public function get_search_method() {
136  return $this->search_method;
137  }
138 
139  /**
140  * Get the input types available for different field types
141  *
142  * @since 1.17.5
143  *
144  * @return array [field type name] => (array|string) search bar input types
145  */
146  public static function get_input_types_by_field_type() {
147  /**
148  * Input Type groups
149  * @see admin-search-widget.js (getSelectInput)
150  * @var array
151  */
152  $input_types = array(
153  'text' => array( 'input_text' ),
154  'address' => array( 'input_text' ),
155  'number' => array( 'input_text' ),
156  'date' => array( 'date', 'date_range' ),
157  'boolean' => array( 'single_checkbox' ),
158  'select' => array( 'select', 'radio', 'link' ),
159  'multi' => array( 'select', 'multiselect', 'radio', 'checkbox', 'link' ),
160  );
161 
162  /**
163  * @filter `gravityview/search/input_types` Change the types of search fields available to a field type
164  * @see GravityView_Widget_Search::get_search_input_labels() for the available input types
165  * @param array $input_types Associative array: key is field `name`, value is array of GravityView input types (note: use `input_text` for `text`)
166  */
167  $input_types = apply_filters( 'gravityview/search/input_types', $input_types );
168 
169  return $input_types;
170  }
171 
172  /**
173  * Get labels for different types of search bar inputs
174  *
175  * @since 1.17.5
176  *
177  * @return array [input type] => input type label
178  */
179  public static function get_search_input_labels() {
180  /**
181  * Input Type labels l10n
182  * @see admin-search-widget.js (getSelectInput)
183  * @var array
184  */
185  $input_labels = array(
186  'input_text' => esc_html__( 'Text', 'gravityview' ),
187  'date' => esc_html__( 'Date', 'gravityview' ),
188  'select' => esc_html__( 'Select', 'gravityview' ),
189  'multiselect' => esc_html__( 'Select (multiple values)', 'gravityview' ),
190  'radio' => esc_html__( 'Radio', 'gravityview' ),
191  'checkbox' => esc_html__( 'Checkbox', 'gravityview' ),
192  'single_checkbox' => esc_html__( 'Checkbox', 'gravityview' ),
193  'link' => esc_html__( 'Links', 'gravityview' ),
194  'date_range' => esc_html__( 'Date range', 'gravityview' ),
195  );
196 
197  /**
198  * @filter `gravityview/search/input_types` Change the label of search field input types
199  * @param array $input_types Associative array: key is input type name, value is label
200  */
201  $input_labels = apply_filters( 'gravityview/search/input_labels', $input_labels );
202 
203  return $input_labels;
204  }
205 
206  public static function get_search_input_label( $input_type ) {
207  $labels = self::get_search_input_labels();
208 
209  return \GV\Utils::get( $labels, $input_type, false );
210  }
211 
212  /**
213  * Add script to Views edit screen (admin)
214  * @param mixed $hook
215  */
216  public function add_scripts_and_styles( $hook ) {
217  global $pagenow;
218 
219  // Don't process any scripts below here if it's not a GravityView page or the widgets screen
220  if ( ! gravityview()->request->is_admin( $hook, 'single' ) && ( 'widgets.php' !== $pagenow ) ) {
221  return;
222  }
223 
224  $script_min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
225  $script_source = empty( $script_min ) ? '/source' : '';
226 
227  wp_enqueue_script( 'gravityview_searchwidget_admin', plugins_url( 'assets/js'.$script_source.'/admin-search-widget'.$script_min.'.js', __FILE__ ), array( 'jquery', 'gravityview_views_scripts' ), \GV\Plugin::$version );
228 
229  wp_localize_script( 'gravityview_searchwidget_admin', 'gvSearchVar', array(
230  'nonce' => wp_create_nonce( 'gravityview_ajaxsearchwidget' ),
231  'label_nofields' => esc_html__( 'No search fields configured yet.', 'gravityview' ),
232  'label_addfield' => esc_html__( 'Add Search Field', 'gravityview' ),
233  'label_label' => esc_html__( 'Label', 'gravityview' ),
234  'label_searchfield' => esc_html__( 'Search Field', 'gravityview' ),
235  'label_inputtype' => esc_html__( 'Input Type', 'gravityview' ),
236  'label_ajaxerror' => esc_html__( 'There was an error loading searchable fields. Save the View or refresh the page to fix this issue.', 'gravityview' ),
237  'input_labels' => json_encode( self::get_search_input_labels() ),
238  'input_types' => json_encode( self::get_input_types_by_field_type() ),
239  ) );
240 
241  }
242 
243  /**
244  * Add admin script to the no-conflict scripts whitelist
245  * @param array $allowed Scripts allowed in no-conflict mode
246  * @return array Scripts allowed in no-conflict mode, plus the search widget script
247  */
248  public function register_no_conflict( $allowed ) {
249  $allowed[] = 'gravityview_searchwidget_admin';
250  return $allowed;
251  }
252 
253  /**
254  * Ajax
255  * Returns the form fields ( only the searchable ones )
256  *
257  * @access public
258  * @return void
259  */
260  public static function get_searchable_fields() {
261 
262  if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'gravityview_ajaxsearchwidget' ) ) {
263  exit( '0' );
264  }
265 
266  $form = '';
267 
268  // Fetch the form for the current View
269  if ( ! empty( $_POST['view_id'] ) ) {
270 
271  $form = gravityview_get_form_id( $_POST['view_id'] );
272 
273  } elseif ( ! empty( $_POST['formid'] ) ) {
274 
275  $form = (int) $_POST['formid'];
276 
277  } elseif ( ! empty( $_POST['template_id'] ) && class_exists( 'GravityView_Ajax' ) ) {
278 
279  $form = GravityView_Ajax::pre_get_form_fields( $_POST['template_id'] );
280 
281  }
282 
283  // fetch form id assigned to the view
284  $response = self::render_searchable_fields( $form );
285 
286  exit( $response );
287  }
288 
289  /**
290  * Generates html for the available Search Fields dropdown
291  * @param int $form_id
292  * @param string $current (for future use)
293  * @return string
294  */
295  public static function render_searchable_fields( $form_id = null, $current = '' ) {
296 
297  if ( is_null( $form_id ) ) {
298  return '';
299  }
300 
301  // start building output
302 
303  $output = '<select class="gv-search-fields">';
304 
305  $custom_fields = array(
306  'search_all' => array(
307  'text' => esc_html__( 'Search Everything', 'gravityview' ),
308  'type' => 'text',
309  ),
310  'entry_date' => array(
311  'text' => esc_html__( 'Entry Date', 'gravityview' ),
312  'type' => 'date',
313  ),
314  'entry_id' => array(
315  'text' => esc_html__( 'Entry ID', 'gravityview' ),
316  'type' => 'text',
317  ),
318  'created_by' => array(
319  'text' => esc_html__( 'Entry Creator', 'gravityview' ),
320  'type' => 'select',
321  ),
322  'is_starred' => array(
323  'text' => esc_html__( 'Is Starred', 'gravityview' ),
324  'type' => 'boolean',
325  ),
326  );
327 
328  foreach( $custom_fields as $custom_field_key => $custom_field ) {
329  $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'] );
330  }
331 
332  // Get fields with sub-inputs and no parent
333  $fields = gravityview_get_form_fields( $form_id, true, true );
334 
335  /**
336  * @filter `gravityview/search/searchable_fields` Modify the fields that are displayed as searchable in the Search Bar dropdown\n
337  * @since 1.17
338  * @see gravityview_get_form_fields() Used to fetch the fields
339  * @see GravityView_Widget_Search::get_search_input_types See this method to modify the type of input types allowed for a field
340  * @param array $fields Array of searchable fields, as fetched by gravityview_get_form_fields()
341  * @param int $form_id
342  */
343  $fields = apply_filters( 'gravityview/search/searchable_fields', $fields, $form_id );
344 
345  if ( ! empty( $fields ) ) {
346 
347  $blacklist_field_types = apply_filters( 'gravityview_blacklist_field_types', array( 'fileupload', 'post_image', 'post_id', 'section' ), null );
348 
349  foreach ( $fields as $id => $field ) {
350 
351  if ( in_array( $field['type'], $blacklist_field_types ) ) {
352  continue;
353  }
354 
355  $types = self::get_search_input_types( $id, $field['type'] );
356 
357  $output .= '<option value="'. $id .'" '. selected( $id, $current, false ).'data-inputtypes="'. esc_attr( $types ) .'">'. esc_html( $field['label'] ) .'</option>';
358  }
359  }
360 
361  $output .= '</select>';
362 
363  return $output;
364 
365  }
366 
367  /**
368  * Assign an input type according to the form field type
369  *
370  * @see admin-search-widget.js
371  *
372  * @param string|int|float $field_id Gravity Forms field ID
373  * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
374  *
375  * @return string GV field search input type ('multi', 'boolean', 'select', 'date', 'text')
376  */
377  public static function get_search_input_types( $field_id = '', $field_type = null ) {
378 
379  // @todo - This needs to be improved - many fields have . including products and addresses
380  if ( false !== strpos( (string) $field_id, '.' ) && in_array( $field_type, array( 'checkbox' ) ) || in_array( $field_id, array( 'is_fulfilled' ) ) ) {
381  $input_type = 'boolean'; // on/off checkbox
382  } elseif ( in_array( $field_type, array( 'checkbox', 'post_category', 'multiselect' ) ) ) {
383  $input_type = 'multi'; //multiselect
384  } elseif ( in_array( $field_type, array( 'select', 'radio' ) ) ) {
385  $input_type = 'select';
386  } elseif ( in_array( $field_type, array( 'date' ) ) || in_array( $field_id, array( 'payment_date' ) ) ) {
387  $input_type = 'date';
388  } elseif ( in_array( $field_type, array( 'number' ) ) || in_array( $field_id, array( 'payment_amount' ) ) ) {
389  $input_type = 'number';
390  } else {
391  $input_type = 'text';
392  }
393 
394  /**
395  * @filter `gravityview/extension/search/input_type` Modify the search form input type based on field type
396  * @since 1.2
397  * @since 1.19.2 Added $field_id parameter
398  * @param string $input_type Assign an input type according to the form field type. Defaults: `boolean`, `multi`, `select`, `date`, `text`
399  * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
400  * @param string|int|float $field_id ID of the field being processed
401  */
402  $input_type = apply_filters( 'gravityview/extension/search/input_type', $input_type, $field_type, $field_id );
403 
404  return $input_type;
405  }
406 
407  /**
408  * Display hidden fields to add support for sites using Default permalink structure
409  *
410  * @since 1.8
411  * @return array Search fields, modified if not using permalinks
412  */
413  public function add_no_permalink_fields( $search_fields, $object, $widget_args = array() ) {
414  /** @global WP_Rewrite $wp_rewrite */
415  global $wp_rewrite;
416 
417  // Support default permalink structure
418  if ( false === $wp_rewrite->using_permalinks() ) {
419 
420  // By default, use current post.
421  $post_id = 0;
422 
423  // We're in the WordPress Widget context, and an overriding post ID has been set.
424  if ( ! empty( $widget_args['post_id'] ) ) {
425  $post_id = absint( $widget_args['post_id'] );
426  }
427  // We're in the WordPress Widget context, and the base View ID should be used
428  else if ( ! empty( $widget_args['view_id'] ) ) {
429  $post_id = absint( $widget_args['view_id'] );
430  }
431 
432  $args = gravityview_get_permalink_query_args( $post_id );
433 
434  // Add hidden fields to the search form
435  foreach ( $args as $key => $value ) {
436  $search_fields[] = array(
437  'name' => $key,
438  'input' => 'hidden',
439  'value' => $value,
440  );
441  }
442  }
443 
444  return $search_fields;
445  }
446 
447  /**
448  * Get the fields that are searchable for a View
449  *
450  * @since 2.0
451  * @since 2.0.9 Added $with_full_field parameter
452  *
453  * @param \GV\View|null $view
454  * @param bool $with_full_field Return full field array, or just field ID? Default: false (just field ID)
455  *
456  * TODO: Move to \GV\View, perhaps? And return a Field_Collection
457  * TODO: Use in gravityview()->request->is_search() to calculate whether a valid search
458  *
459  * @return array If no View, returns empty array. Otherwise, returns array of fields configured in widgets and Search Bar for a View
460  */
461  private function get_view_searchable_fields( $view, $with_full_field = false ) {
462 
463  /**
464  * Find all search widgets on the view and get the searchable fields settings.
465  */
466  $searchable_fields = array();
467 
468  if ( ! $view ) {
469  return $searchable_fields;
470  }
471 
472  /**
473  * Include the sidebar Widgets.
474  */
475  $widgets = (array) get_option( 'widget_gravityview_search', array() );
476 
477  foreach ( $widgets as $widget ) {
478  if ( ! empty( $widget['view_id'] ) && $widget['view_id'] == $view->ID ) {
479  if( $_fields = json_decode( $widget['search_fields'], true ) ) {
480  foreach ( $_fields as $field ) {
481  $searchable_fields [] = $with_full_field ? $field : $field['field'];
482  }
483  }
484  }
485  }
486 
487  foreach ( $view->widgets->by_id( $this->get_widget_id() )->all() as $widget ) {
488  if( $_fields = json_decode( $widget->configuration->get( 'search_fields' ), true ) ) {
489  foreach ( $_fields as $field ) {
490  $searchable_fields [] = $with_full_field ? $field : $field['field'];
491  }
492  }
493  }
494 
495  return $searchable_fields;
496  }
497 
498  /** --- Frontend --- */
499 
500  /**
501  * Calculate the search criteria to filter entries
502  * @param array $search_criteria The search criteria
503  * @param int $form_id The form ID
504  * @param array $args Some args
505  *
506  * @param bool $force_search_criteria Whether to suppress GF_Query filter, internally used in self::gf_query_filter
507  *
508  * @return array
509  */
510  public function filter_entries( $search_criteria, $form_id = null, $args = array(), $force_search_criteria = false ) {
511  if ( ! $force_search_criteria && gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
512  /**
513  * If GF_Query is available, we can construct custom conditions with nested
514  * booleans on the query, giving up the old ways of flat search_criteria field_filters.
515  */
516  add_action( 'gravityview/view/query', array( $this, 'gf_query_filter' ), 10, 3 );
517  return $search_criteria; // Return the original criteria, GF_Query modification kicks in later
518  }
519 
520  if( 'post' === $this->search_method ) {
521  $get = $_POST;
522  } else {
523  $get = $_GET;
524  }
525 
526  $view = \GV\View::by_id( \GV\Utils::get( $args, 'id' ) );
527 
528  gravityview()->log->debug( 'Requested $_{method}: ', array( 'method' => $this->search_method, 'data' => $get ) );
529 
530  if ( empty( $get ) || ! is_array( $get ) ) {
531  return $search_criteria;
532  }
533 
534  $get = stripslashes_deep( $get );
535 
536  $get = gv_map_deep( $get, 'rawurldecode' );
537 
538  // Make sure array key is set up
539  $search_criteria['field_filters'] = \GV\Utils::get( $search_criteria, 'field_filters', array() );
540 
541  $searchable_fields = $this->get_view_searchable_fields( $view );
542 
543  // add free search
544  if ( isset( $get['gv_search'] ) && '' !== $get['gv_search'] && in_array( 'search_all', $searchable_fields ) ) {
545 
546  $search_all_value = trim( $get['gv_search'] );
547 
548  /**
549  * @filter `gravityview/search-all-split-words` Search for each word separately or the whole phrase?
550  * @since 1.20.2
551  * @param bool $split_words True: split a phrase into words; False: search whole word only [Default: true]
552  */
553  $split_words = apply_filters( 'gravityview/search-all-split-words', true );
554 
555  if ( $split_words ) {
556 
557  // Search for a piece
558  $words = explode( ' ', $search_all_value );
559 
560  $words = array_filter( $words );
561 
562  } else {
563 
564  // Replace multiple spaces with one space
565  $search_all_value = preg_replace( '/\s+/ism', ' ', $search_all_value );
566 
567  $words = array( $search_all_value );
568  }
569 
570  foreach ( $words as $word ) {
571  $search_criteria['field_filters'][] = array(
572  'key' => null, // The field ID to search
573  'value' => $word, // The value to search
574  'operator' => 'contains', // What to search in. Options: `is` or `contains`
575  );
576  }
577  }
578 
579  // start date & end date
580  if ( in_array( 'entry_date', $searchable_fields ) ) {
581  /**
582  * Get and normalize the dates according to the input format.
583  */
584  if ( $curr_start = ! empty( $get['gv_start'] ) ? $get['gv_start'] : '' ) {
585  if( $curr_start_date = date_create_from_format( $this->get_datepicker_format( true ), $curr_start ) ) {
586  $curr_start = $curr_start_date->format( 'Y-m-d' );
587  }
588  }
589 
590  if ( $curr_end = ! empty( $get['gv_start'] ) ? ( ! empty( $get['gv_end'] ) ? $get['gv_end'] : '' ) : '' ) {
591  if( $curr_end_date = date_create_from_format( $this->get_datepicker_format( true ), $curr_end ) ) {
592  $curr_end = $curr_end_date->format( 'Y-m-d' );
593  }
594  }
595 
596  if ( $view ) {
597  /**
598  * Override start and end dates if View is limited to some already.
599  */
600  if ( $start_date = $view->settings->get( 'start_date' ) ) {
601  if ( $start_timestamp = strtotime( $curr_start ) ) {
602  $curr_start = $start_timestamp < strtotime( $start_date ) ? $start_date : $curr_start;
603  }
604  }
605  if ( $end_date = $view->settings->get( 'end_date' ) ) {
606  if ( $end_timestamp = strtotime( $curr_end ) ) {
607  $curr_end = $end_timestamp > strtotime( $end_date ) ? $end_date : $curr_end;
608  }
609  }
610  }
611 
612  /**
613  * @filter `gravityview_date_created_adjust_timezone` Whether to adjust the timezone for entries. \n
614  * date_created is stored in UTC format. Convert search date into UTC (also used on templates/fields/date_created.php)
615  * @since 1.12
616  * @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
617  * @param[in] string $context Where the filter is being called from. `search` in this case.
618  */
619  $adjust_tz = apply_filters( 'gravityview_date_created_adjust_timezone', true, 'search' );
620 
621  /**
622  * Don't set $search_criteria['start_date'] if start_date is empty as it may lead to bad query results (GFAPI::get_entries)
623  */
624  if ( ! empty( $curr_start ) ) {
625  $curr_start = date( 'Y-m-d H:i:s', strtotime( $curr_start ) );
626  $search_criteria['start_date'] = $adjust_tz ? get_gmt_from_date( $curr_start ) : $curr_start;
627  }
628 
629  if ( ! empty( $curr_end ) ) {
630  // Fast-forward 24 hour on the end time
631  $curr_end = date( 'Y-m-d H:i:s', strtotime( $curr_end ) + DAY_IN_SECONDS );
632  $search_criteria['end_date'] = $adjust_tz ? get_gmt_from_date( $curr_end ) : $curr_end;
633  if ( strpos( $search_criteria['end_date'], '00:00:00' ) ) { // See https://github.com/gravityview/GravityView/issues/1056
634  $search_criteria['end_date'] = date( 'Y-m-d H:i:s', strtotime( $search_criteria['end_date'] ) - 1 );
635  }
636  }
637  }
638 
639  // search for a specific entry ID
640  if ( ! empty( $get[ 'gv_id' ] ) && in_array( 'entry_id', $searchable_fields ) ) {
641  $search_criteria['field_filters'][] = array(
642  'key' => 'id',
643  'value' => absint( $get[ 'gv_id' ] ),
644  'operator' => '=',
645  );
646  }
647 
648  // search for a specific Created_by ID
649  if ( ! empty( $get[ 'gv_by' ] ) && in_array( 'created_by', $searchable_fields ) ) {
650  $search_criteria['field_filters'][] = array(
651  'key' => 'created_by',
652  'value' => absint( $get['gv_by'] ),
653  'operator' => '=',
654  );
655  }
656 
657 
658  // Get search mode passed in URL
659  $mode = isset( $get['mode'] ) && in_array( $get['mode'], array( 'any', 'all' ) ) ? $get['mode'] : 'any';
660 
661  // get the other search filters
662  foreach ( $get as $key => $value ) {
663 
664  if ( 0 !== strpos( $key, 'filter_' ) || gv_empty( $value, false, false ) || ( is_array( $value ) && count( $value ) === 1 && gv_empty( $value[0], false, false ) ) ) {
665  continue;
666  }
667 
668  $filter_key = $this->convert_request_key_to_filter_key( $key );
669 
670  // could return simple filter or multiple filters
671  if ( ! in_array( 'search_all', $searchable_fields ) && ! in_array( $filter_key , $searchable_fields ) ) {
672  continue;
673  }
674 
675  $filter = $this->prepare_field_filter( $filter_key, $value, $view );
676 
677  if ( isset( $filter[0]['value'] ) ) {
678  $search_criteria['field_filters'] = array_merge( $search_criteria['field_filters'], $filter );
679 
680  // if date range type, set search mode to ALL
681  if ( ! empty( $filter[0]['operator'] ) && in_array( $filter[0]['operator'], array( '>=', '<=', '>', '<' ) ) ) {
682  $mode = 'all';
683  }
684  } elseif( !empty( $filter ) ) {
685  $search_criteria['field_filters'][] = $filter;
686  }
687  }
688 
689  /**
690  * @filter `gravityview/search/mode` Set the Search Mode (`all` or `any`)
691  * @since 1.5.1
692  * @param[out,in] string $mode Search mode (`any` vs `all`)
693  */
694  $search_criteria['field_filters']['mode'] = apply_filters( 'gravityview/search/mode', $mode );
695 
696  gravityview()->log->debug( 'Returned Search Criteria: ', array( 'data' => $search_criteria ) );
697 
698  unset( $get );
699 
700  return $search_criteria;
701  }
702 
703  /**
704  * Filters the \GF_Query with advanced logic.
705  *
706  * Dropin for the legacy flat filters when \GF_Query is available.
707  *
708  * @param \GF_Query $query The current query object reference
709  * @param \GV\View $this The current view object
710  * @param \GV\Request $request The request object
711  */
712  public function gf_query_filter( &$query, $view, $request ) {
713  /**
714  * This is a shortcut to get all the needed search criteria.
715  * We feed these into an new GF_Query and tack them onto the current object.
716  */
717  $search_criteria = $this->filter_entries( array(), null, array( 'id' => $view->ID ), true /** force search_criteria */ );
718 
719  if ( empty( $search_criteria['field_filters'] ) ) {
720  return;
721  }
722 
723  foreach ( $search_criteria['field_filters'] as &$filter ) {
724  if ( ! is_array( $filter ) ) {
725  continue;
726  }
727 
728  // By default, we want searches to be wildcard for each field.
729  $filter['operator'] = empty( $filter['operator'] ) ? 'contains' : $filter['operator'];
730 
731  /**
732  * @filter `gravityview_search_operator` Modify the search operator for the field (contains, is, isnot, etc)
733  * @param string $operator Existing search operator
734  * @param array $filter array with `key`, `value`, `operator`, `type` keys
735  */
736  $filter['operator'] = apply_filters( 'gravityview_search_operator', $filter['operator'], $filter );
737  }
738 
739  /**
740  * Parse the filter criteria to generate the needed
741  * WHERE clauses. This is a trick to not write our own generation
742  * code by reusing what's inside GF_Query already.
743  */
744  $_tmp_query = new GF_Query( $view->form->ID, $search_criteria );
745  $_tmp_query_parts = $_tmp_query->_introspect();
746 
747  /**
748  * Grab the current clauses. We'll be combining them shortly.
749  */
750  $query_parts = $query->_introspect();
751 
752  /**
753  * Combine the parts as a new WHERE clause.
754  */
755  $query->where( GF_Query_Condition::_and( $query_parts['where'], $_tmp_query_parts['where'] ) );
756  }
757 
758  /**
759  * Convert $_GET/$_POST key to the field/meta ID
760  *
761  * Examples:
762  * - `filter_is_starred` => `is_starred`
763  * - `filter_1_2` => `1.2`
764  * - `filter_5` => `5`
765  *
766  * @since 2.0
767  *
768  * @param string $key $_GET/_$_POST search key
769  *
770  * @return string
771  */
772  private function convert_request_key_to_filter_key( $key ) {
773 
774  $field_id = str_replace( 'filter_', '', $key );
775 
776  // calculates field_id, removing 'filter_' and for '_' for advanced fields ( like name or checkbox )
777  if ( preg_match('/^[0-9_]+$/ism', $field_id ) ) {
778  $field_id = str_replace( '_', '.', $field_id );
779  }
780 
781  return $field_id;
782  }
783 
784  /**
785  * Prepare the field filters to GFAPI
786  *
787  * 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.
788  *
789  * Format searched values
790  *
791  * @param string $filter_key ID of the field, or entry meta key
792  * @param string $value $_GET/$_POST search value
793  * @param \GV\View $view The view we're looking at
794  *
795  * @return array 1 or 2 deph levels
796  */
797  public function prepare_field_filter( $filter_key, $value, $view ) {
798 
799  // get form field array
800  $form_field = is_numeric( $filter_key ) ? \GV\GF_Field::by_id( $view->form, $filter_key ) : \GV\Internal_Field::by_id( $filter_key );
801 
802  // default filter array
803  $filter = array(
804  'key' => $filter_key,
805  'value' => $value,
806  );
807 
808  switch ( $form_field->type ) {
809 
810  case 'select':
811  case 'radio':
812  $filter['operator'] = 'is';
813  break;
814 
815  case 'post_category':
816 
817  if ( ! is_array( $value ) ) {
818  $value = array( $value );
819  }
820 
821  // Reset filter variable
822  $filter = array();
823 
824  foreach ( $value as $val ) {
825  $cat = get_term( $val, 'category' );
826  $filter[] = array(
827  'key' => $filter_key,
828  'value' => esc_attr( $cat->name ) . ':' . $val,
829  'operator' => 'is',
830  );
831  }
832 
833  break;
834 
835  case 'multiselect':
836 
837  if ( ! is_array( $value ) ) {
838  break;
839  }
840 
841  // Reset filter variable
842  $filter = array();
843 
844  foreach ( $value as $val ) {
845  $filter[] = array( 'key' => $filter_key, 'value' => $val );
846  }
847 
848  break;
849 
850  case 'checkbox':
851  // convert checkbox on/off into the correct search filter
852  if ( false !== strpos( $filter_key, '.' ) && ! empty( $form_field->inputs ) && ! empty( $form_field->choices ) ) {
853  foreach ( $form_field->inputs as $k => $input ) {
854  if ( $input['id'] == $filter_key ) {
855  $filter['value'] = $form_field->choices[ $k ]['value'];
856  $filter['operator'] = 'is';
857  break;
858  }
859  }
860  } elseif ( is_array( $value ) ) {
861 
862  // Reset filter variable
863  $filter = array();
864 
865  foreach ( $value as $val ) {
866  $filter[] = array(
867  'key' => $filter_key,
868  'value' => $val,
869  'operator' => 'is',
870  );
871  }
872  }
873 
874  break;
875 
876  case 'name':
877  case 'address':
878 
879  if ( false === strpos( $filter_key, '.' ) ) {
880 
881  $words = explode( ' ', $value );
882 
883  $filters = array();
884  foreach ( $words as $word ) {
885  if ( ! empty( $word ) && strlen( $word ) > 1 ) {
886  // Keep the same key for each filter
887  $filter['value'] = $word;
888  // Add a search for the value
889  $filters[] = $filter;
890  }
891  }
892 
893  $filter = $filters;
894  }
895 
896  // State/Province should be exact matches
897  if ( 'address' === $form_field->field->type ) {
898 
899  $searchable_fields = $this->get_view_searchable_fields( $view, true );
900 
901  foreach ( $searchable_fields as $searchable_field ) {
902 
903  if( $form_field->ID !== $searchable_field['field'] ) {
904  continue;
905  }
906 
907  // Only exact-match dropdowns, not text search
908  if( in_array( $searchable_field['input'], array( 'text', 'search' ), true ) ) {
909  continue;
910  }
911 
912  $input_id = gravityview_get_input_id_from_id( $form_field->ID );
913 
914  if ( 4 === $input_id ) {
915  $filter['operator'] = 'is';
916  };
917  }
918  }
919 
920  break;
921 
922  case 'date':
923 
924  $date_format = $this->get_datepicker_format( true );
925 
926  if ( is_array( $value ) ) {
927 
928  // Reset filter variable
929  $filter = array();
930 
931  foreach ( $value as $k => $date ) {
932  if ( empty( $date ) ) {
933  continue;
934  }
935  $operator = 'start' === $k ? '>=' : '<=';
936 
937  /**
938  * @hack
939  * @since 1.16.3
940  * Safeguard until GF implements '<=' operator
941  */
942  if( !GFFormsModel::is_valid_operator( $operator ) && $operator === '<=' ) {
943  $operator = '<';
944  $date = date( 'Y-m-d', strtotime( self::get_formatted_date( $date, 'Y-m-d', $date_format ) . ' +1 day' ) );
945  }
946 
947  $filter[] = array(
948  'key' => $filter_key,
949  'value' => self::get_formatted_date( $date, 'Y-m-d', $date_format ),
950  'operator' => $operator,
951  );
952  }
953  } else {
954  $date = $value;
955  $filter['value'] = self::get_formatted_date( $date, 'Y-m-d', $date_format );
956  }
957 
958  break;
959 
960 
961  } // switch field type
962 
963  return $filter;
964  }
965 
966  /**
967  * Get the Field Format form GravityForms
968  *
969  * @param GF_Field_Date $field The field object
970  * @since 1.10
971  *
972  * @return string Format of the date in the database
973  */
974  public static function get_date_field_format( GF_Field_Date $field ) {
975  $format = 'm/d/Y';
976  $datepicker = array(
977  'mdy' => 'm/d/Y',
978  'dmy' => 'd/m/Y',
979  'dmy_dash' => 'd-m-Y',
980  'dmy_dot' => 'd.m.Y',
981  'ymd_slash' => 'Y/m/d',
982  'ymd_dash' => 'Y-m-d',
983  'ymd_dot' => 'Y.m.d',
984  );
985 
986  if ( ! empty( $field->dateFormat ) && isset( $datepicker[ $field->dateFormat ] ) ){
987  $format = $datepicker[ $field->dateFormat ];
988  }
989 
990  return $format;
991  }
992 
993  /**
994  * Format a date value
995  *
996  * @param string $value Date value input
997  * @param string $format Wanted formatted date
998  *
999  * @since 2.1.2
1000  * @param string $value_format The value format. Default: Y-m-d
1001  *
1002  * @return string
1003  */
1004  public static function get_formatted_date( $value = '', $format = 'Y-m-d', $value_format = 'Y-m-d' ) {
1005 
1006  $date = date_create_from_format( $value_format, $value );
1007 
1008  if ( empty( $date ) ) {
1009  gravityview()->log->debug( 'Date format not valid: {value}', array( 'value' => $value ) );
1010  return '';
1011  }
1012  return $date->format( $format );
1013  }
1014 
1015 
1016  /**
1017  * Include this extension templates path
1018  * @param array $file_paths List of template paths ordered
1019  */
1020  public function add_template_path( $file_paths ) {
1021 
1022  // Index 100 is the default GravityView template path.
1023  $file_paths[102] = self::$file . 'templates/';
1024 
1025  return $file_paths;
1026  }
1027 
1028  /**
1029  * Check whether the configured search fields have a date field
1030  *
1031  * @since 1.17.5
1032  *
1033  * @param array $search_fields
1034  *
1035  * @return bool True: has a `date` or `date_range` field
1036  */
1037  private function has_date_field( $search_fields ) {
1038 
1039  $has_date = false;
1040 
1041  foreach ( $search_fields as $k => $field ) {
1042  if ( in_array( $field['input'], array( 'date', 'date_range', 'entry_date' ) ) ) {
1043  $has_date = true;
1044  break;
1045  }
1046  }
1047 
1048  return $has_date;
1049  }
1050 
1051  /**
1052  * Renders the Search Widget
1053  * @param array $widget_args
1054  * @param string $content
1055  * @param string $context
1056  *
1057  * @return void
1058  */
1059  public function render_frontend( $widget_args, $content = '', $context = '' ) {
1060  /** @var GravityView_View $gravityview_view */
1062 
1063  if ( empty( $gravityview_view ) ) {
1064  gravityview()->log->debug( '$gravityview_view not instantiated yet.' );
1065  return;
1066  }
1067 
1068  // get configured search fields
1069  $search_fields = ! empty( $widget_args['search_fields'] ) ? json_decode( $widget_args['search_fields'], true ) : '';
1070 
1071  if ( empty( $search_fields ) || ! is_array( $search_fields ) ) {
1072  gravityview()->log->debug( 'No search fields configured for widget:', array( 'data' => $widget_args ) );
1073  return;
1074  }
1075 
1076 
1077  // prepare fields
1078  foreach ( $search_fields as $k => $field ) {
1079 
1080  $updated_field = $field;
1081 
1082  $updated_field = $this->get_search_filter_details( $updated_field );
1083 
1084  switch ( $field['field'] ) {
1085 
1086  case 'search_all':
1087  $updated_field['key'] = 'search_all';
1088  $updated_field['input'] = 'search_all';
1089  $updated_field['value'] = $this->rgget_or_rgpost( 'gv_search' );
1090  break;
1091 
1092  case 'entry_date':
1093  $updated_field['key'] = 'entry_date';
1094  $updated_field['input'] = 'entry_date';
1095  $updated_field['value'] = array(
1096  'start' => $this->rgget_or_rgpost( 'gv_start' ),
1097  'end' => $this->rgget_or_rgpost( 'gv_end' ),
1098  );
1099  break;
1100 
1101  case 'entry_id':
1102  $updated_field['key'] = 'entry_id';
1103  $updated_field['input'] = 'entry_id';
1104  $updated_field['value'] = $this->rgget_or_rgpost( 'gv_id' );
1105  break;
1106 
1107  case 'created_by':
1108  $updated_field['key'] = 'created_by';
1109  $updated_field['name'] = 'gv_by';
1110  $updated_field['value'] = $this->rgget_or_rgpost( 'gv_by' );
1111  $updated_field['choices'] = self::get_created_by_choices();
1112  break;
1113  }
1114 
1115  $search_fields[ $k ] = $updated_field;
1116  }
1117 
1118  gravityview()->log->debug( 'Calculated Search Fields: ', array( 'data' => $search_fields ) );
1119 
1120  /**
1121  * @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.
1122  * @param array $search_fields Array of search filters with `key`, `label`, `value`, `type`, `choices` keys
1123  * @param GravityView_Widget_Search $this Current widget object
1124  * @param array $widget_args Args passed to this method. {@since 1.8}
1125  * @param \GV\Template_Context $context {@since 2.0}
1126  * @var array
1127  */
1128  $gravityview_view->search_fields = apply_filters( 'gravityview_widget_search_filters', $search_fields, $this, $widget_args, $context );
1129 
1130  $gravityview_view->search_layout = ! empty( $widget_args['search_layout'] ) ? $widget_args['search_layout'] : 'horizontal';
1131 
1132  /** @since 1.14 */
1133  $gravityview_view->search_mode = ! empty( $widget_args['search_mode'] ) ? $widget_args['search_mode'] : 'any';
1134 
1135  $custom_class = ! empty( $widget_args['custom_class'] ) ? $widget_args['custom_class'] : '';
1136 
1137  $gravityview_view->search_class = self::get_search_class( $custom_class );
1138 
1139  $gravityview_view->search_clear = ! empty( $widget_args['search_clear'] ) ? $widget_args['search_clear'] : false;
1140 
1141  if ( $this->has_date_field( $search_fields ) ) {
1142  // enqueue datepicker stuff only if needed!
1143  $this->enqueue_datepicker();
1144  }
1145 
1146  $this->maybe_enqueue_flexibility();
1147 
1148  $gravityview_view->render( 'widget', 'search', false );
1149  }
1150 
1151  /**
1152  * Get the search class for a search form
1153  *
1154  * @since 1.5.4
1155  *
1156  * @return string Sanitized CSS class for the search form
1157  */
1158  public static function get_search_class( $custom_class = '' ) {
1160 
1161  $search_class = 'gv-search-'.$gravityview_view->search_layout;
1162 
1163  if ( ! empty( $custom_class ) ) {
1164  $search_class .= ' '.$custom_class;
1165  }
1166 
1167  /**
1168  * @filter `gravityview_search_class` Modify the CSS class for the search form
1169  * @param string $search_class The CSS class for the search form
1170  */
1171  $search_class = apply_filters( 'gravityview_search_class', $search_class );
1172 
1173  // Is there an active search being performed? Used by fe-views.js
1174  $search_class .= GravityView_frontend::getInstance()->isSearch() ? ' gv-is-search' : '';
1175 
1176  return gravityview_sanitize_html_class( $search_class );
1177  }
1178 
1179 
1180  /**
1181  * Calculate the search form action
1182  * @since 1.6
1183  *
1184  * @return string
1185  */
1186  public static function get_search_form_action() {
1188 
1189  $post_id = $gravityview_view->getPostId() ? $gravityview_view->getPostId() : $gravityview_view->getViewId();
1190 
1191  $url = add_query_arg( array(), get_permalink( $post_id ) );
1192 
1193  return esc_url( $url );
1194  }
1195 
1196  /**
1197  * Get the label for a search form field
1198  * @param array $field Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1199  * @param array $form_field Form field data, as fetched by `gravityview_get_field()`
1200  * @return string Label for the search form
1201  */
1202  private static function get_field_label( $field, $form_field = array() ) {
1203 
1204  $label = \GV\Utils::_GET( 'label', \GV\Utils::get( $field, 'label' ) );
1205 
1206  if ( ! $label ) {
1207 
1208  $label = isset( $form_field['label'] ) ? $form_field['label'] : '';
1209 
1210  switch( $field['field'] ) {
1211  case 'search_all':
1212  $label = __( 'Search Entries:', 'gravityview' );
1213  break;
1214  case 'entry_date':
1215  $label = __( 'Filter by date:', 'gravityview' );
1216  break;
1217  case 'entry_id':
1218  $label = __( 'Entry ID:', 'gravityview' );
1219  break;
1220  default:
1221  // If this is a field input, not a field
1222  if ( strpos( $field['field'], '.' ) > 0 && ! empty( $form_field['inputs'] ) ) {
1223 
1224  // Get the label for the field in question, which returns an array
1225  $items = wp_list_filter( $form_field['inputs'], array( 'id' => $field['field'] ) );
1226 
1227  // Get the item with the `label` key
1228  $values = wp_list_pluck( $items, 'label' );
1229 
1230  // There will only one item in the array, but this is easier
1231  foreach ( $values as $value ) {
1232  $label = $value;
1233  break;
1234  }
1235  }
1236  }
1237  }
1238 
1239  /**
1240  * @filter `gravityview_search_field_label` Modify the label for a search field. Supports returning HTML
1241  * @since 1.17.3 Added $field parameter
1242  * @param[in,out] string $label Existing label text, sanitized.
1243  * @param[in] array $form_field Gravity Forms field array, as returned by `GFFormsModel::get_field()`
1244  * @param[in] array $field Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1245  */
1246  $label = apply_filters( 'gravityview_search_field_label', esc_attr( $label ), $form_field, $field );
1247 
1248  return $label;
1249  }
1250 
1251  /**
1252  * Prepare search fields to frontend render with other details (label, field type, searched values)
1253  *
1254  * @param array $field
1255  * @return array
1256  */
1257  private function get_search_filter_details( $field ) {
1258 
1260 
1261  $form = $gravityview_view->getForm();
1262 
1263  // for advanced field ids (eg, first name / last name )
1264  $name = 'filter_' . str_replace( '.', '_', $field['field'] );
1265 
1266  // get searched value from $_GET/$_POST (string or array)
1267  $value = $this->rgget_or_rgpost( $name );
1268 
1269  // get form field details
1270  $form_field = gravityview_get_field( $form, $field['field'] );
1271 
1272  $filter = array(
1273  'key' => $field['field'],
1274  'name' => $name,
1275  'label' => self::get_field_label( $field, $form_field ),
1276  'input' => $field['input'],
1277  'value' => $value,
1278  'type' => $form_field['type'],
1279  );
1280 
1281  // collect choices
1282  if ( 'post_category' === $form_field['type'] && ! empty( $form_field['displayAllCategories'] ) && empty( $form_field['choices'] ) ) {
1283  $filter['choices'] = gravityview_get_terms_choices();
1284  } elseif ( ! empty( $form_field['choices'] ) ) {
1285  $filter['choices'] = $form_field['choices'];
1286  }
1287 
1288  if ( 'date_range' === $field['input'] && empty( $value ) ) {
1289  $filter['value'] = array( 'start' => '', 'end' => '' );
1290  }
1291 
1292  return $filter;
1293 
1294  }
1295 
1296  /**
1297  * Calculate the search choices for the users
1298  *
1299  * @since 1.8
1300  *
1301  * @return array Array of user choices (value = ID, text = display name)
1302  */
1303  private static function get_created_by_choices() {
1304 
1305  /**
1306  * filter gravityview/get_users/search_widget
1307  * @see \GVCommon::get_users
1308  */
1309  $users = GVCommon::get_users( 'search_widget', array( 'fields' => array( 'ID', 'display_name' ) ) );
1310 
1311  $choices = array();
1312  foreach ( $users as $user ) {
1313  $choices[] = array(
1314  'value' => $user->ID,
1315  'text' => $user->display_name,
1316  );
1317  }
1318 
1319  return $choices;
1320  }
1321 
1322 
1323  /**
1324  * Output the Clear Search Results button
1325  * @since 1.5.4
1326  */
1327  public static function the_clear_search_button() {
1329 
1330  if ( $gravityview_view->search_clear ) {
1331 
1332  $url = strtok( add_query_arg( array() ), '?' );
1333 
1334  echo gravityview_get_link( $url, esc_html__( 'Clear', 'gravityview' ), 'class=button gv-search-clear' );
1335 
1336  }
1337  }
1338 
1339  /**
1340  * Based on the search method, fetch the value for a specific key
1341  *
1342  * @since 1.16.4
1343  *
1344  * @param string $name Name of the request key to fetch the value for
1345  *
1346  * @return mixed|string Value of request at $name key. Empty string if empty.
1347  */
1348  private function rgget_or_rgpost( $name ) {
1350 
1351  $value = stripslashes_deep( $value );
1352 
1353  $value = gv_map_deep( $value, 'rawurldecode' );
1354 
1355  $value = gv_map_deep( $value, '_wp_specialchars' );
1356 
1357  return $value;
1358  }
1359 
1360 
1361  /**
1362  * Require the datepicker script for the frontend GV script
1363  * @param array $js_dependencies Array of existing required scripts for the fe-views.js script
1364  * @return array Array required scripts, with `jquery-ui-datepicker` added
1365  */
1366  public function add_datepicker_js_dependency( $js_dependencies ) {
1367 
1368  $js_dependencies[] = 'jquery-ui-datepicker';
1369 
1370  return $js_dependencies;
1371  }
1372 
1373  /**
1374  * Modify the array passed to wp_localize_script()
1375  *
1376  * @param array $js_localization The data padded to the Javascript file
1377  * @param array $view_data View data array with View settings
1378  *
1379  * @return array
1380  */
1381  public function add_datepicker_localization( $localizations = array(), $view_data = array() ) {
1382  global $wp_locale;
1383 
1384  /**
1385  * @filter `gravityview_datepicker_settings` Modify the datepicker settings
1386  * @see http://api.jqueryui.com/datepicker/ Learn what settings are available
1387  * @see http://www.renegadetechconsulting.com/tutorials/jquery-datepicker-and-wordpress-i18n Thanks for the helpful information on $wp_locale
1388  * @param array $js_localization The data padded to the Javascript file
1389  * @param array $view_data View data array with View settings
1390  */
1391  $datepicker_settings = apply_filters( 'gravityview_datepicker_settings', array(
1392  'yearRange' => '-5:+5',
1393  'changeMonth' => true,
1394  'changeYear' => true,
1395  'closeText' => esc_attr_x( 'Close', 'Close calendar', 'gravityview' ),
1396  'prevText' => esc_attr_x( 'Prev', 'Previous month in calendar', 'gravityview' ),
1397  'nextText' => esc_attr_x( 'Next', 'Next month in calendar', 'gravityview' ),
1398  'currentText' => esc_attr_x( 'Today', 'Today in calendar', 'gravityview' ),
1399  'weekHeader' => esc_attr_x( 'Week', 'Week in calendar', 'gravityview' ),
1400  'monthStatus' => __( 'Show a different month', 'gravityview' ),
1401  'monthNames' => array_values( $wp_locale->month ),
1402  'monthNamesShort' => array_values( $wp_locale->month_abbrev ),
1403  'dayNames' => array_values( $wp_locale->weekday ),
1404  'dayNamesShort' => array_values( $wp_locale->weekday_abbrev ),
1405  'dayNamesMin' => array_values( $wp_locale->weekday_initial ),
1406  // get the start of week from WP general setting
1407  'firstDay' => get_option( 'start_of_week' ),
1408  // is Right to left language? default is false
1409  'isRTL' => is_rtl(),
1410  ), $view_data );
1411 
1412  $localizations['datepicker'] = $datepicker_settings;
1413 
1414  return $localizations;
1415 
1416  }
1417 
1418  /**
1419  * Register search widget scripts, including Flexibility
1420  *
1421  * @see https://github.com/10up/flexibility
1422  *
1423  * @since 1.17
1424  *
1425  * @return void
1426  */
1427  public function register_scripts() {
1428  wp_register_script( 'gv-flexibility', plugins_url( 'assets/lib/flexibility/flexibility.js', GRAVITYVIEW_FILE ), array(), \GV\Plugin::$version, true );
1429  }
1430 
1431  /**
1432  * If the current visitor is running IE 8 or 9, enqueue Flexibility
1433  *
1434  * @since 1.17
1435  *
1436  * @return void
1437  */
1438  private function maybe_enqueue_flexibility() {
1439  if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && preg_match( '/MSIE [8-9]/', $_SERVER['HTTP_USER_AGENT'] ) ) {
1440  wp_enqueue_script( 'gv-flexibility' );
1441  }
1442  }
1443 
1444  /**
1445  * Enqueue the datepicker script
1446  *
1447  * It sets the $gravityview->datepicker_class parameter
1448  *
1449  * @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.
1450  * @return void
1451  */
1452  public function enqueue_datepicker() {
1454 
1455  wp_enqueue_script( 'jquery-ui-datepicker' );
1456 
1457  add_filter( 'gravityview_js_dependencies', array( $this, 'add_datepicker_js_dependency' ) );
1458  add_filter( 'gravityview_js_localization', array( $this, 'add_datepicker_localization' ), 10, 2 );
1459 
1460  $scheme = is_ssl() ? 'https://' : 'http://';
1461  wp_enqueue_style( 'jquery-ui-datepicker', $scheme.'ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/smoothness/jquery-ui.css' );
1462 
1463  /**
1464  * @filter `gravityview_search_datepicker_class`
1465  * 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.
1466  * @param string $css_class CSS class to use. Default: `gv-datepicker datepicker mdy` \n
1467  * Options are:
1468  * - `mdy` (mm/dd/yyyy)
1469  * - `dmy` (dd/mm/yyyy)
1470  * - `dmy_dash` (dd-mm-yyyy)
1471  * - `dmy_dot` (dd.mm.yyyy)
1472  * - `ymd_slash` (yyyy/mm/dd)
1473  * - `ymd_dash` (yyyy-mm-dd)
1474  * - `ymd_dot` (yyyy.mm.dd)
1475  */
1476  $datepicker_class = apply_filters( 'gravityview_search_datepicker_class', "gv-datepicker datepicker " . $this->get_datepicker_format() );
1477 
1478  $gravityview_view->datepicker_class = $datepicker_class;
1479  }
1480 
1481  /**
1482  * Retrieve the datepicker format.
1483  *
1484  * @param bool $date_format Whether to return the PHP date format or the datpicker class name. Default: false.
1485  *
1486  * @see https://docs.gravityview.co/article/115-changing-the-format-of-the-search-widgets-date-picker
1487  *
1488  * @return string The datepicker format placeholder, or the PHP date format.
1489  */
1490  private function get_datepicker_format( $date_format = false ) {
1491 
1492  $default_format = 'mdy';
1493 
1494  /**
1495  * @filter `gravityview/widgets/search/datepicker/format`
1496  * @since 2.1.1
1497  * @param string $format Default: mdy
1498  * Options are:
1499  * - `mdy` (mm/dd/yyyy)
1500  * - `dmy` (dd/mm/yyyy)
1501  * - `dmy_dash` (dd-mm-yyyy)
1502  * - `dmy_dot` (dd.mm.yyyy)
1503  * - `ymd_slash` (yyyy/mm/dd)
1504  * - `ymd_dash` (yyyy-mm-dd)
1505  * - `ymd_dot` (yyyy.mm.dd)
1506  */
1507  $format = apply_filters( 'gravityview/widgets/search/datepicker/format', $default_format );
1508 
1509  $gf_date_formats = array(
1510  'mdy' => 'm/d/Y',
1511 
1512  'dmy_dash' => 'd-m-Y',
1513  'dmy_dot' => 'd.m.Y',
1514  'dmy' => 'd/m/Y',
1515 
1516  'ymd_slash' => 'Y/m/d',
1517  'ymd_dash' => 'Y-m-d',
1518  'ymd_dot' => 'Y.m.d',
1519  );
1520 
1521  if ( ! $date_format ) {
1522  // If the format key isn't valid, return default format key
1523  return isset( $gf_date_formats[ $format ] ) ? $format : $default_format;
1524  }
1525 
1526  // If the format key isn't valid, return default format value
1527  return \GV\Utils::get( $gf_date_formats, $format, $gf_date_formats[ $default_format ] );
1528  }
1529 
1530 
1531 } // end class
1532 
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.
all()
Get all the settings.
$labels
static get_searchable_fields()
Ajax Returns the form fields ( only the searchable ones )
static _GET( $name, $default=null)
Grab a value from the _GET superglobal or default.
add_datepicker_localization( $localizations=array(), $view_data=array())
Modify the array passed to wp_localize_script()
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.
if(empty( $value)) $user
static _REQUEST( $name, $default=null)
Grab a value from the _REQUEST superglobal or default.
if(gv_empty( $field['value'], false, false)) $format
register_no_conflict( $allowed)
Add admin script to the no-conflict scripts whitelist.
static get_formatted_date( $value='', $format='Y-m-d', $value_format='Y-m-d')
Format a date value.
enqueue_datepicker()
Enqueue the datepicker script.
render_frontend( $widget_args, $content='', $context='')
Frontend logic.
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.
gravityview()
Definition: _stubs.php:26
get( $key, $default=null)
Retrieve a setting.
gravityview_get_input_id_from_id( $field_id='')
Very commonly needed: get the # of the input based on a full field ID.
static by_id( $form, $field_id)
Get a by and Field ID.
If this file is called directly, abort.
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.
if(empty( $created_by)) $search_criteria
There was no logged in user who created this entry.
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:304
register_scripts()
Register search widget scripts, including Flexibility.
prepare_field_filter( $filter_key, $value, $view)
Prepare the field filters to GFAPI.
add_scripts_and_styles( $hook)
Add script to Views edit screen (admin)
static by_id( $post_id)
Construct a instance from a post ID.
static get_users( $context='change_entry_creator', $args=array())
Get WordPress users with reasonable limits set.
is_registered()
Whether this Widget&#39;s been registered already or not.
gravityview_get_form_id( $view_id)
Get the connected form ID from a View ID.
const GRAVITYVIEW_FILE
Full path to the GravityView file "GRAVITYVIEW_FILE" "./gravityview.php".
Definition: gravityview.php:31
$field_id
Definition: time.php:17
static get_search_class( $custom_class='')
Get the search class for a search form.
gf_query_filter(&$query, $view, $request)
Filters the with advanced logic.
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.
static by_id( $field_id)
Get a from an internal Gravity Forms field ID.
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.
get_datepicker_format( $date_format=false)
Retrieve the datepicker format.
convert_request_key_to_filter_key( $key)
Convert $_GET/$_POST key to the field/meta ID.
filter_entries( $search_criteria, $form_id=null, $args=array(), $force_search_criteria=false)
— Frontend —
static get( $array, $key, $default=null)
Grab a value from an array or an object or default.
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?
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.
get_view_searchable_fields( $view, $with_full_field=false)
Get the fields that are searchable for a View.
$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.