GravityView  2.10.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 GravityView <[email protected]>
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 $icon = 'dashicons-search';
19 
20  public static $file;
21  public static $instance;
22 
23  private $search_filters = array();
24 
25  /**
26  * whether search method is GET or POST ( default: GET )
27  * @since 1.16.4
28  * @var string $search_method
29  */
30  private $search_method = 'get';
31 
32  public function __construct() {
33 
34  $this->widget_id = 'search_bar';
35  $this->widget_description = esc_html__( 'Search form for searching entries.', 'gravityview' );
36  $this->widget_subtitle = '';
37 
38  self::$instance = &$this;
39 
40  self::$file = plugin_dir_path( __FILE__ );
41 
42  $default_values = array( 'header' => 0, 'footer' => 0 );
43 
44  $settings = array(
45  'search_layout' => array(
46  'type' => 'radio',
47  'full_width' => true,
48  'label' => esc_html__( 'Search Layout', 'gravityview' ),
49  'value' => 'horizontal',
50  'options' => array(
51  'horizontal' => esc_html__( 'Horizontal', 'gravityview' ),
52  'vertical' => esc_html__( 'Vertical', 'gravityview' ),
53  ),
54  ),
55  'search_clear' => array(
56  'type' => 'checkbox',
57  'label' => __( 'Show Clear button', 'gravityview' ),
58  'desc' => __( 'When a search is performed, display a button that removes all search values.', 'gravityview'),
59  'value' => true,
60  ),
61  'search_fields' => array(
62  'type' => 'hidden',
63  'label' => '',
64  'class' => 'gv-search-fields-value',
65  'value' => '[{"field":"search_all","input":"input_text"}]', // Default: Search Everything text box
66  ),
67  'search_mode' => array(
68  'type' => 'radio',
69  'full_width' => true,
70  'label' => esc_html__( 'Search Mode', 'gravityview' ),
71  'desc' => __('Should search results match all search fields, or any?', 'gravityview'),
72  'value' => 'any',
73  'class' => 'hide-if-js',
74  'options' => array(
75  'any' => esc_html__( 'Match Any Fields', 'gravityview' ),
76  'all' => esc_html__( 'Match All Fields', 'gravityview' ),
77  ),
78  ),
79  );
80 
81  if ( ! $this->is_registered() ) {
82  // frontend - filter entries
83  add_filter( 'gravityview_fe_search_criteria', array( $this, 'filter_entries' ), 10, 3 );
84 
85  // frontend - add template path
86  add_filter( 'gravityview_template_paths', array( $this, 'add_template_path' ) );
87 
88  // admin - add scripts - run at 1100 to make sure GravityView_Admin_Views::add_scripts_and_styles() runs first at 999
89  add_action( 'admin_enqueue_scripts', array( $this, 'add_scripts_and_styles' ), 1100 );
90  add_action( 'wp_enqueue_scripts', array( $this, 'register_scripts') );
91  add_filter( 'gravityview_noconflict_scripts', array( $this, 'register_no_conflict' ) );
92 
93  // ajax - get the searchable fields
94  add_action( 'wp_ajax_gv_searchable_fields', array( 'GravityView_Widget_Search', 'get_searchable_fields' ) );
95 
96  add_action( 'gravityview_search_widget_fields_after', array( $this, 'add_preview_inputs' ) );
97 
98  add_filter( 'gravityview/api/reserved_query_args', array( $this, 'add_reserved_args' ) );
99  }
100 
101  parent::__construct( esc_html__( 'Search Bar', 'gravityview' ), null, $default_values, $settings );
102 
103  // calculate the search method (POST / GET)
104  $this->set_search_method();
105  }
106 
107  /**
108  * @return GravityView_Widget_Search
109  */
110  public static function getInstance() {
111  if ( empty( self::$instance ) ) {
112  self::$instance = new GravityView_Widget_Search;
113  }
114  return self::$instance;
115  }
116 
117  /**
118  * @since 2.10
119  *
120  * @param $args
121  *
122  * @return mixed
123  */
124  public function add_reserved_args( $args ) {
125 
126  $args[] = 'gv_search';
127  $args[] = 'gv_start';
128  $args[] = 'gv_end';
129  $args[] = 'gv_id';
130  $args[] = 'gv_by';
131  $args[] = 'mode';
132 
133  $get = (array) $_GET;
134 
135  // If the fields being searched as reserved; not to be considered user-passed variables
136  foreach ( $get as $key => $value ) {
137  if ( $key !== $this->convert_request_key_to_filter_key( $key ) ) {
138  $args[] = $key;
139  }
140  }
141 
142  return $args;
143  }
144 
145  /**
146  * Sets the search method to GET (default) or POST
147  * @since 1.16.4
148  */
149  private function set_search_method() {
150  /**
151  * @filter `gravityview/search/method` Modify the search form method (GET / POST)
152  * @since 1.16.4
153  * @param string $search_method Assign an input type according to the form field type. Defaults: `boolean`, `multi`, `select`, `date`, `text`
154  * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
155  */
156  $method = apply_filters( 'gravityview/search/method', $this->search_method );
157 
158  $method = strtolower( $method );
159 
160  $this->search_method = in_array( $method, array( 'get', 'post' ) ) ? $method : 'get';
161  }
162 
163  /**
164  * Returns the search method
165  * @since 1.16.4
166  * @return string
167  */
168  public function get_search_method() {
169  return $this->search_method;
170  }
171 
172  /**
173  * Get the input types available for different field types
174  *
175  * @since 1.17.5
176  *
177  * @return array [field type name] => (array|string) search bar input types
178  */
179  public static function get_input_types_by_field_type() {
180  /**
181  * Input Type groups
182  * @see admin-search-widget.js (getSelectInput)
183  */
184  $input_types = array(
185  'text' => array( 'input_text' ),
186  'address' => array( 'input_text' ),
187  'number' => array( 'input_text' ),
188  'date' => array( 'date', 'date_range' ),
189  'boolean' => array( 'single_checkbox' ),
190  'select' => array( 'select', 'radio', 'link' ),
191  'multi' => array( 'select', 'multiselect', 'radio', 'checkbox', 'link' ),
192 
193  // hybrids
194  'created_by' => array( 'select', 'radio', 'checkbox', 'multiselect', 'link', 'input_text' ),
195  'product' => array( 'select', 'radio', 'link', 'input_text' ),
196  );
197 
198  /**
199  * @filter `gravityview/search/input_types` Change the types of search fields available to a field type
200  * @see GravityView_Widget_Search::get_search_input_labels() for the available input types
201  * @param array $input_types Associative array: key is field `name`, value is array of GravityView input types (note: use `input_text` for `text`)
202  */
203  $input_types = apply_filters( 'gravityview/search/input_types', $input_types );
204 
205  return $input_types;
206  }
207 
208  /**
209  * Get labels for different types of search bar inputs
210  *
211  * @since 1.17.5
212  *
213  * @return array [input type] => input type label
214  */
215  public static function get_search_input_labels() {
216  /**
217  * Input Type labels l10n
218  * @see admin-search-widget.js (getSelectInput)
219  */
220  $input_labels = array(
221  'input_text' => esc_html__( 'Text', 'gravityview' ),
222  'date' => esc_html__( 'Date', 'gravityview' ),
223  'select' => esc_html__( 'Select', 'gravityview' ),
224  'multiselect' => esc_html__( 'Select (multiple values)', 'gravityview' ),
225  'radio' => esc_html__( 'Radio', 'gravityview' ),
226  'checkbox' => esc_html__( 'Checkbox', 'gravityview' ),
227  'single_checkbox' => esc_html__( 'Checkbox', 'gravityview' ),
228  'link' => esc_html__( 'Links', 'gravityview' ),
229  'date_range' => esc_html__( 'Date range', 'gravityview' ),
230  );
231 
232  /**
233  * @filter `gravityview/search/input_types` Change the label of search field input types
234  * @param array $input_types Associative array: key is input type name, value is label
235  */
236  $input_labels = apply_filters( 'gravityview/search/input_labels', $input_labels );
237 
238  return $input_labels;
239  }
240 
241  public static function get_search_input_label( $input_type ) {
242  $labels = self::get_search_input_labels();
243 
244  return \GV\Utils::get( $labels, $input_type, false );
245  }
246 
247  /**
248  * Add script to Views edit screen (admin)
249  * @param mixed $hook
250  */
251  public function add_scripts_and_styles( $hook ) {
252  global $pagenow;
253 
254  // Don't process any scripts below here if it's not a GravityView page or the widgets screen
255  if ( ! gravityview()->request->is_admin( $hook, 'single' ) && ( 'widgets.php' !== $pagenow ) ) {
256  return;
257  }
258 
259  $script_min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
260  $script_source = empty( $script_min ) ? '/source' : '';
261 
262  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 );
263 
264  wp_localize_script( 'gravityview_searchwidget_admin', 'gvSearchVar', array(
265  'nonce' => wp_create_nonce( 'gravityview_ajaxsearchwidget' ),
266  'label_nofields' => esc_html__( 'No search fields configured yet.', 'gravityview' ),
267  'label_addfield' => esc_html__( 'Add Search Field', 'gravityview' ),
268  'label_label' => esc_html__( 'Label', 'gravityview' ),
269  'label_searchfield' => esc_html__( 'Search Field', 'gravityview' ),
270  'label_inputtype' => esc_html__( 'Input Type', 'gravityview' ),
271  'label_ajaxerror' => esc_html__( 'There was an error loading searchable fields. Save the View or refresh the page to fix this issue.', 'gravityview' ),
272  'input_labels' => json_encode( self::get_search_input_labels() ),
273  'input_types' => json_encode( self::get_input_types_by_field_type() ),
274  ) );
275 
276  }
277 
278  /**
279  * Add admin script to the no-conflict scripts whitelist
280  * @param array $allowed Scripts allowed in no-conflict mode
281  * @return array Scripts allowed in no-conflict mode, plus the search widget script
282  */
283  public function register_no_conflict( $allowed ) {
284  $allowed[] = 'gravityview_searchwidget_admin';
285  return $allowed;
286  }
287 
288  /**
289  * Ajax
290  * Returns the form fields ( only the searchable ones )
291  *
292  * @return void
293  */
294  public static function get_searchable_fields() {
295 
296  if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'gravityview_ajaxsearchwidget' ) ) {
297  exit( '0' );
298  }
299 
300  $form = '';
301 
302  // Fetch the form for the current View
303  if ( ! empty( $_POST['view_id'] ) ) {
304 
305  $form = gravityview_get_form_id( $_POST['view_id'] );
306 
307  } elseif ( ! empty( $_POST['formid'] ) ) {
308 
309  $form = (int) $_POST['formid'];
310 
311  } elseif ( ! empty( $_POST['template_id'] ) && class_exists( 'GravityView_Ajax' ) ) {
312 
313  $form = GravityView_Ajax::pre_get_form_fields( $_POST['template_id'] );
314 
315  }
316 
317  // fetch form id assigned to the view
318  $response = self::render_searchable_fields( $form );
319 
320  exit( $response );
321  }
322 
323  /**
324  * Generates html for the available Search Fields dropdown
325  * @param int $form_id
326  * @param string $current (for future use)
327  * @return string
328  */
329  public static function render_searchable_fields( $form_id = null, $current = '' ) {
330 
331  if ( is_null( $form_id ) ) {
332  return '';
333  }
334 
335  // start building output
336 
337  $output = '<select class="gv-search-fields">';
338 
339  $custom_fields = array(
340  'search_all' => array(
341  'text' => esc_html__( 'Search Everything', 'gravityview' ),
342  'type' => 'text',
343  ),
344  'entry_date' => array(
345  'text' => esc_html__( 'Entry Date', 'gravityview' ),
346  'type' => 'date',
347  ),
348  'entry_id' => array(
349  'text' => esc_html__( 'Entry ID', 'gravityview' ),
350  'type' => 'text',
351  ),
352  'created_by' => array(
353  'text' => esc_html__( 'Entry Creator', 'gravityview' ),
354  'type' => 'created_by',
355  ),
356  'is_starred' => array(
357  'text' => esc_html__( 'Is Starred', 'gravityview' ),
358  'type' => 'boolean',
359  ),
360  );
361 
362  if ( gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
363  $custom_fields['is_approved'] = array(
364  'text' => esc_html__( 'Approval Status', 'gravityview' ),
365  'type' => 'multi',
366  );
367  }
368 
369  foreach( $custom_fields as $custom_field_key => $custom_field ) {
370  $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'] );
371  }
372 
373  // Get fields with sub-inputs and no parent
374  $fields = gravityview_get_form_fields( $form_id, true, true );
375 
376  /**
377  * @filter `gravityview/search/searchable_fields` Modify the fields that are displayed as searchable in the Search Bar dropdown\n
378  * @since 1.17
379  * @see gravityview_get_form_fields() Used to fetch the fields
380  * @see GravityView_Widget_Search::get_search_input_types See this method to modify the type of input types allowed for a field
381  * @param array $fields Array of searchable fields, as fetched by gravityview_get_form_fields()
382  * @param int $form_id
383  */
384  $fields = apply_filters( 'gravityview/search/searchable_fields', $fields, $form_id );
385 
386  if ( ! empty( $fields ) ) {
387 
388  $blacklist_field_types = apply_filters( 'gravityview_blacklist_field_types', array( 'fileupload', 'post_image', 'post_id', 'section' ), null );
389 
390  foreach ( $fields as $id => $field ) {
391 
392  if ( in_array( $field['type'], $blacklist_field_types ) ) {
393  continue;
394  }
395 
396  $types = self::get_search_input_types( $id, $field['type'] );
397 
398  $output .= '<option value="'. $id .'" '. selected( $id, $current, false ).'data-inputtypes="'. esc_attr( $types ) .'">'. esc_html( $field['label'] ) .'</option>';
399  }
400  }
401 
402  $output .= '</select>';
403 
404  return $output;
405 
406  }
407 
408  /**
409  * Assign an input type according to the form field type
410  *
411  * @see admin-search-widget.js
412  *
413  * @param string|int|float $field_id Gravity Forms field ID
414  * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
415  *
416  * @return string GV field search input type ('multi', 'boolean', 'select', 'date', 'text')
417  */
418  public static function get_search_input_types( $field_id = '', $field_type = null ) {
419 
420  // @todo - This needs to be improved - many fields have . including products and addresses
421  if ( false !== strpos( (string) $field_id, '.' ) && in_array( $field_type, array( 'checkbox' ) ) || in_array( $field_id, array( 'is_fulfilled' ) ) ) {
422  $input_type = 'boolean'; // on/off checkbox
423  } elseif ( in_array( $field_type, array( 'checkbox', 'post_category', 'multiselect' ) ) ) {
424  $input_type = 'multi'; //multiselect
425  } elseif ( in_array( $field_type, array( 'select', 'radio' ) ) ) {
426  $input_type = 'select';
427  } elseif ( in_array( $field_type, array( 'date' ) ) || in_array( $field_id, array( 'payment_date' ) ) ) {
428  $input_type = 'date';
429  } elseif ( in_array( $field_type, array( 'number' ) ) || in_array( $field_id, array( 'payment_amount' ) ) ) {
430  $input_type = 'number';
431  } elseif ( in_array( $field_type, array( 'product' ) ) ) {
432  $input_type = 'product';
433  } else {
434  $input_type = 'text';
435  }
436 
437  /**
438  * @filter `gravityview/extension/search/input_type` Modify the search form input type based on field type
439  * @since 1.2
440  * @since 1.19.2 Added $field_id parameter
441  * @param string $input_type Assign an input type according to the form field type. Defaults: `boolean`, `multi`, `select`, `date`, `text`
442  * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
443  * @param string|int|float $field_id ID of the field being processed
444  */
445  $input_type = apply_filters( 'gravityview/extension/search/input_type', $input_type, $field_type, $field_id );
446 
447  return $input_type;
448  }
449 
450  /**
451  * Display hidden fields to add support for sites using Default permalink structure
452  *
453  * @since 1.8
454  * @return array Search fields, modified if not using permalinks
455  */
456  public function add_no_permalink_fields( $search_fields, $object, $widget_args = array() ) {
457  /** @global WP_Rewrite $wp_rewrite */
458  global $wp_rewrite;
459 
460  // Support default permalink structure
461  if ( false === $wp_rewrite->using_permalinks() ) {
462 
463  // By default, use current post.
464  $post_id = 0;
465 
466  // We're in the WordPress Widget context, and an overriding post ID has been set.
467  if ( ! empty( $widget_args['post_id'] ) ) {
468  $post_id = absint( $widget_args['post_id'] );
469  }
470  // We're in the WordPress Widget context, and the base View ID should be used
471  else if ( ! empty( $widget_args['view_id'] ) ) {
472  $post_id = absint( $widget_args['view_id'] );
473  }
474 
476 
477  // Add hidden fields to the search form
478  foreach ( $args as $key => $value ) {
479  $search_fields[] = array(
480  'name' => $key,
481  'input' => 'hidden',
482  'value' => $value,
483  );
484  }
485  }
486 
487  return $search_fields;
488  }
489 
490  /**
491  * Get the fields that are searchable for a View
492  *
493  * @since 2.0
494  * @since 2.0.9 Added $with_full_field parameter
495  *
496  * @param \GV\View|null $view
497  * @param bool $with_full_field Return full field array, or just field ID? Default: false (just field ID)
498  *
499  * TODO: Move to \GV\View, perhaps? And return a Field_Collection
500  * TODO: Use in gravityview()->request->is_search() to calculate whether a valid search
501  *
502  * @return array If no View, returns empty array. Otherwise, returns array of fields configured in widgets and Search Bar for a View
503  */
504  private function get_view_searchable_fields( $view, $with_full_field = false ) {
505 
506  /**
507  * Find all search widgets on the view and get the searchable fields settings.
508  */
509  $searchable_fields = array();
510 
511  if ( ! $view ) {
512  return $searchable_fields;
513  }
514 
515  /**
516  * Include the sidebar Widgets.
517  */
518  $widgets = (array) get_option( 'widget_gravityview_search', array() );
519 
520  foreach ( $widgets as $widget ) {
521  if ( ! empty( $widget['view_id'] ) && $widget['view_id'] == $view->ID ) {
522  if( $_fields = json_decode( $widget['search_fields'], true ) ) {
523  foreach ( $_fields as $field ) {
524  if ( empty( $field['form_id'] ) ) {
525  $field['form_id'] = $view->form ? $view->form->ID : 0;
526  }
527  $searchable_fields[] = $with_full_field ? $field : $field['field'];
528  }
529  }
530  }
531  }
532 
533  foreach ( $view->widgets->by_id( $this->get_widget_id() )->all() as $widget ) {
534  if( $_fields = json_decode( $widget->configuration->get( 'search_fields' ), true ) ) {
535  foreach ( $_fields as $field ) {
536  if ( empty( $field['form_id'] ) ) {
537  $field['form_id'] = $view->form ? $view->form->ID : 0;
538  }
539  $searchable_fields[] = $with_full_field ? $field : $field['field'];
540  }
541  }
542  }
543 
544  /**
545  * @filter `gravityview/search/searchable_fields/whitelist` Modifies the fields able to be searched using the Search Bar
546  * @since 2.5.1
547  *
548  * @param array $searchable_fields Array of GravityView-formatted fields or only the field ID? Example: [ '1.2', 'created_by' ]
549  * @param \GV\View $view Object of View being searched.
550  * @param bool $with_full_field Does $searchable_fields contain the full field array or just field ID? Default: false (just field ID)
551  */
552  return apply_filters( 'gravityview/search/searchable_fields/whitelist', $searchable_fields, $view, $with_full_field );
553  }
554 
555  /** --- Frontend --- */
556 
557  /**
558  * Calculate the search criteria to filter entries
559  * @param array $search_criteria The search criteria
560  * @param int $form_id The form ID
561  * @param array $args Some args
562  *
563  * @param bool $force_search_criteria Whether to suppress GF_Query filter, internally used in self::gf_query_filter
564  *
565  * @return array
566  */
567  public function filter_entries( $search_criteria, $form_id = null, $args = array(), $force_search_criteria = false ) {
568  if ( ! $force_search_criteria && gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
569  /**
570  * If GF_Query is available, we can construct custom conditions with nested
571  * booleans on the query, giving up the old ways of flat search_criteria field_filters.
572  */
573  add_action( 'gravityview/view/query', array( $this, 'gf_query_filter' ), 10, 3 );
574  return $search_criteria; // Return the original criteria, GF_Query modification kicks in later
575  }
576 
577  if( 'post' === $this->search_method ) {
578  $get = $_POST;
579  } else {
580  $get = $_GET;
581  }
582 
583  $view = \GV\View::by_id( \GV\Utils::get( $args, 'id' ) );
584 
585  gravityview()->log->debug( 'Requested $_{method}: ', array( 'method' => $this->search_method, 'data' => $get ) );
586 
587  if ( empty( $get ) || ! is_array( $get ) ) {
588  return $search_criteria;
589  }
590 
591  $get = stripslashes_deep( $get );
592 
593  $get = gv_map_deep( $get, 'rawurldecode' );
594 
595  // Make sure array key is set up
596  $search_criteria['field_filters'] = \GV\Utils::get( $search_criteria, 'field_filters', array() );
597 
598  $searchable_fields = $this->get_view_searchable_fields( $view );
599  $searchable_field_objects = $this->get_view_searchable_fields( $view, true );
600 
601  /**
602  * @filter `gravityview/search-all-split-words` Search for each word separately or the whole phrase?
603  * @since 1.20.2
604  * @param bool $split_words True: split a phrase into words; False: search whole word only [Default: true]
605  */
606  $split_words = apply_filters( 'gravityview/search-all-split-words', true );
607 
608  /**
609  * @filter `gravityview/search-trim-input` Remove leading/trailing whitespaces from search value
610  * @since 2.9.3
611  * @param bool $trim_search_value True: remove whitespace; False: keep as is [Default: true]
612  */
613  $trim_search_value = apply_filters( 'gravityview/search-trim-input', true );
614 
615  // add free search
616  if ( isset( $get['gv_search'] ) && '' !== $get['gv_search'] && in_array( 'search_all', $searchable_fields ) ) {
617 
618  $search_all_value = $trim_search_value ? trim( $get['gv_search'] ) : $get['gv_search'];
619 
620  if ( $split_words ) {
621  // Search for a piece
622  $words = explode( ' ', $search_all_value );
623 
624  $words = array_filter( $words );
625 
626  } else {
627  // Replace multiple spaces with one space
628  $search_all_value = preg_replace( '/\s+/ism', ' ', $search_all_value );
629 
630  $words = array( $search_all_value );
631  }
632 
633  foreach ( $words as $word ) {
634  $search_criteria['field_filters'][] = array(
635  'key' => null, // The field ID to search
636  'value' => $word, // The value to search
637  'operator' => 'contains', // What to search in. Options: `is` or `contains`
638  );
639  }
640  }
641 
642  // start date & end date
643  if ( in_array( 'entry_date', $searchable_fields ) ) {
644  /**
645  * Get and normalize the dates according to the input format.
646  */
647  if ( $curr_start = ! empty( $get['gv_start'] ) ? $get['gv_start'] : '' ) {
648  if( $curr_start_date = date_create_from_format( $this->get_datepicker_format( true ), $curr_start ) ) {
649  $curr_start = $curr_start_date->format( 'Y-m-d' );
650  }
651  }
652 
653  if ( $curr_end = ! empty( $get['gv_start'] ) ? ( ! empty( $get['gv_end'] ) ? $get['gv_end'] : '' ) : '' ) {
654  if( $curr_end_date = date_create_from_format( $this->get_datepicker_format( true ), $curr_end ) ) {
655  $curr_end = $curr_end_date->format( 'Y-m-d' );
656  }
657  }
658 
659  if ( $view ) {
660  /**
661  * Override start and end dates if View is limited to some already.
662  */
663  if ( $start_date = $view->settings->get( 'start_date' ) ) {
664  if ( $start_timestamp = strtotime( $curr_start ) ) {
665  $curr_start = $start_timestamp < strtotime( $start_date ) ? $start_date : $curr_start;
666  }
667  }
668  if ( $end_date = $view->settings->get( 'end_date' ) ) {
669  if ( $end_timestamp = strtotime( $curr_end ) ) {
670  $curr_end = $end_timestamp > strtotime( $end_date ) ? $end_date : $curr_end;
671  }
672  }
673  }
674 
675  /**
676  * @filter `gravityview_date_created_adjust_timezone` Whether to adjust the timezone for entries. \n
677  * `date_created` is stored in UTC format. Convert search date into UTC (also used on templates/fields/date_created.php). \n
678  * This is for backward compatibility before \GF_Query started to automatically apply the timezone offset.
679  * @since 1.12
680  * @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 is `false`.
681  * @param[in] string $context Where the filter is being called from. `search` in this case.
682  */
683  $adjust_tz = apply_filters( 'gravityview_date_created_adjust_timezone', false, 'search' );
684 
685  /**
686  * Don't set $search_criteria['start_date'] if start_date is empty as it may lead to bad query results (GFAPI::get_entries)
687  */
688  if ( ! empty( $curr_start ) ) {
689  $curr_start = date( 'Y-m-d H:i:s', strtotime( $curr_start ) );
690  $search_criteria['start_date'] = $adjust_tz ? get_gmt_from_date( $curr_start ) : $curr_start;
691  }
692 
693  if ( ! empty( $curr_end ) ) {
694  // Fast-forward 24 hour on the end time
695  $curr_end = date( 'Y-m-d H:i:s', strtotime( $curr_end ) + DAY_IN_SECONDS );
696  $search_criteria['end_date'] = $adjust_tz ? get_gmt_from_date( $curr_end ) : $curr_end;
697  if ( strpos( $search_criteria['end_date'], '00:00:00' ) ) { // See https://github.com/gravityview/GravityView/issues/1056
698  $search_criteria['end_date'] = date( 'Y-m-d H:i:s', strtotime( $search_criteria['end_date'] ) - 1 );
699  }
700  }
701  }
702 
703  // search for a specific entry ID
704  if ( ! empty( $get[ 'gv_id' ] ) && in_array( 'entry_id', $searchable_fields ) ) {
705  $search_criteria['field_filters'][] = array(
706  'key' => 'id',
707  'value' => absint( $get[ 'gv_id' ] ),
708  'operator' => $this->get_operator( $get, 'gv_id', array( '=' ), '=' ),
709  );
710  }
711 
712  // search for a specific Created_by ID
713  if ( ! empty( $get[ 'gv_by' ] ) && in_array( 'created_by', $searchable_fields ) ) {
714  $search_criteria['field_filters'][] = array(
715  'key' => 'created_by',
716  'value' => $get['gv_by'],
717  'operator' => $this->get_operator( $get, 'gv_by', array( '=' ), '=' ),
718  );
719  }
720 
721  // Get search mode passed in URL
722  $mode = isset( $get['mode'] ) && in_array( $get['mode'], array( 'any', 'all' ) ) ? $get['mode'] : 'any';
723 
724  // get the other search filters
725  foreach ( $get as $key => $value ) {
726 
727  if ( $trim_search_value ) {
728  $value = is_array( $value ) ? array_map( 'trim', $value ) : trim( $value );
729  }
730 
731  if ( 0 !== strpos( $key, 'filter_' ) || gv_empty( $value, false, false ) || ( is_array( $value ) && count( $value ) === 1 && gv_empty( $value[0], false, false ) ) ) {
732  continue; // Not a filter, or empty
733  }
734 
735  if ( strpos( $key, '|op' ) !== false ) {
736  continue; // This is an operator
737  }
738 
739  $filter_key = $this->convert_request_key_to_filter_key( $key );
740 
741  if ( ! $filter = $this->prepare_field_filter( $filter_key, $value, $view, $searchable_field_objects, $get ) ) {
742  continue;
743  }
744 
745  if ( ! isset( $filter['operator'] ) ) {
746  $filter['operator'] = $this->get_operator( $get, $key, array( 'contains' ), 'contains' );
747  }
748 
749  if ( isset( $filter[0]['value'] ) ) {
750  $filter[0]['value'] = $trim_search_value ? trim( $filter[0]['value'] ) : $filter[0]['value'];
751 
752  $search_criteria['field_filters'] = array_merge( $search_criteria['field_filters'], $filter );
753 
754  // if date range type, set search mode to ALL
755  if ( ! empty( $filter[0]['operator'] ) && in_array( $filter[0]['operator'], array( '>=', '<=', '>', '<' ) ) ) {
756  $mode = 'all';
757  }
758  } elseif( !empty( $filter ) ) {
759  $search_criteria['field_filters'][] = $filter;
760  }
761  }
762 
763  /**
764  * @filter `gravityview/search/mode` Set the Search Mode (`all` or `any`)
765  * @since 1.5.1
766  * @param[out,in] string $mode Search mode (`any` vs `all`)
767  */
768  $search_criteria['field_filters']['mode'] = apply_filters( 'gravityview/search/mode', $mode );
769 
770  gravityview()->log->debug( 'Returned Search Criteria: ', array( 'data' => $search_criteria ) );
771 
772  unset( $get );
773 
774  return $search_criteria;
775  }
776 
777  /**
778  * Filters the \GF_Query with advanced logic.
779  *
780  * Dropin for the legacy flat filters when \GF_Query is available.
781  *
782  * @param \GF_Query $query The current query object reference
783  * @param \GV\View $this The current view object
784  * @param \GV\Request $request The request object
785  */
786  public function gf_query_filter( &$query, $view, $request ) {
787  /**
788  * This is a shortcut to get all the needed search criteria.
789  * We feed these into an new GF_Query and tack them onto the current object.
790  */
791  $search_criteria = $this->filter_entries( array(), null, array( 'id' => $view->ID ), true /** force search_criteria */ );
792 
793  /**
794  * Call any userland filters that they might have.
795  */
796  remove_filter( 'gravityview_fe_search_criteria', array( $this, 'filter_entries' ), 10, 3 );
797  $search_criteria = apply_filters( 'gravityview_fe_search_criteria', $search_criteria, $view->form->ID, $view->settings->as_atts() );
798  add_filter( 'gravityview_fe_search_criteria', array( $this, 'filter_entries' ), 10, 3 );
799 
800  $query_class = $view->get_query_class();
801 
802  if ( empty( $search_criteria['field_filters'] ) ) {
803  return;
804  }
805 
806  $widgets = $view->widgets->by_id( $this->widget_id );
807  if ( $widgets->count() ) {
808  $widgets = $widgets->all();
809  $widget = $widgets[0];
810 
811  $search_fields = json_decode( $widget->configuration->get( 'search_fields' ), true );
812 
813  foreach ( (array) $search_fields as $search_field ) {
814  if ( 'created_by' === $search_field['field'] && 'input_text' === $search_field['input'] ) {
815  $created_by_text_mode = true;
816  }
817  }
818  }
819 
820  $extra_conditions = array();
821  $mode = 'any';
822 
823  foreach ( $search_criteria['field_filters'] as &$filter ) {
824  if ( ! is_array( $filter ) ) {
825  if ( in_array( strtolower( $filter ), array( 'any', 'all' ) ) ) {
826  $mode = $filter;
827  }
828  continue;
829  }
830 
831  // Construct a manual query for unapproved statuses
832  if ( 'is_approved' === $filter['key'] && in_array( \GravityView_Entry_Approval_Status::UNAPPROVED, (array) $filter['value'] ) ) {
833  $_tmp_query = new $query_class( $view->form->ID, array(
834  'field_filters' => array(
835  array(
836  'operator' => 'in',
837  'key' => 'is_approved',
838  'value' => (array) $filter['value'],
839  ),
840  array(
841  'operator' => 'is',
842  'key' => 'is_approved',
843  'value' => '',
844  ),
845  'mode' => 'any'
846  ),
847  ) );
848  $_tmp_query_parts = $_tmp_query->_introspect();
849 
850  $extra_conditions[] = $_tmp_query_parts['where'];
851 
852  $filter = false;
853  continue;
854  }
855 
856  // Construct manual query for text mode creator search
857  if ( 'created_by' === $filter['key'] && ! empty( $created_by_text_mode ) ) {
858  $extra_conditions[] = new GravityView_Widget_Search_Author_GF_Query_Condition( $filter, $view );
859  $filter = false;
860  continue;
861  }
862 
863  // By default, we want searches to be wildcard for each field.
864  $filter['operator'] = empty( $filter['operator'] ) ? 'contains' : $filter['operator'];
865 
866  // For multichoice, let's have an in (OR) search.
867  if ( is_array( $filter['value'] ) ) {
868  $filter['operator'] = 'in'; // @todo what about in contains (OR LIKE chains)?
869  }
870 
871  // Default form with joins functionality
872  if ( empty( $filter['form_id'] ) ) {
873  $filter['form_id'] = $view->form ? $view->form->ID : 0;
874  }
875 
876  /**
877  * @filter `gravityview_search_operator` Modify the search operator for the field (contains, is, isnot, etc)
878  * @param string $operator Existing search operator
879  * @param array $filter array with `key`, `value`, `operator`, `type` keys
880  * @since develop
881  * @param \GV\View $view The View we're operating on.
882  */
883  $filter['operator'] = apply_filters( 'gravityview_search_operator', $filter['operator'], $filter, $view );
884  }
885 
886  if ( ! empty( $search_criteria['start_date'] ) || ! empty( $search_criteria['end_date'] ) ) {
887  $date_criteria = array();
888 
889  if ( isset( $search_criteria['start_date'] ) ) {
890  $date_criteria['start_date'] = $search_criteria['start_date'];
891  }
892 
893  if ( isset( $search_criteria['end_date'] ) ) {
894  $date_criteria['end_date'] = $search_criteria['end_date'];
895  }
896 
897  $_tmp_query = new $query_class( $view->form->ID, $date_criteria );
898  $_tmp_query_parts = $_tmp_query->_introspect();
899  $extra_conditions[] = $_tmp_query_parts['where'];
900  }
901 
902  $search_conditions = array();
903 
904  if ( $filters = array_filter( $search_criteria['field_filters'] ) ) {
905  foreach ( $filters as &$filter ) {
906  if ( ! is_array( $filter ) ) {
907  continue;
908  }
909 
910  /**
911  * Parse the filter criteria to generate the needed
912  * WHERE condition. This is a trick to not write our own generation
913  * code by reusing what's inside GF_Query already as they
914  * take care of many small things like forcing numeric, etc.
915  */
916  $_tmp_query = new $query_class( $filter['form_id'], array( 'mode' => 'any', 'field_filters' => array( $filter ) ) );
917  $_tmp_query_parts = $_tmp_query->_introspect();
918  $search_condition = $_tmp_query_parts['where'];
919 
920  if ( empty( $filter['key'] ) && $search_condition->expressions ) {
921  $search_conditions[] = $search_condition;
922  } else {
923  $left = $search_condition->left;
924  $alias = $query->_alias( $left->field_id, $left->source, $left->is_entry_column() ? 't' : 'm' );
925 
926  if ( $view->joins && $left->field_id == GF_Query_Column::META ) {
927  foreach ( $view->joins as $_join ) {
928  $on = $_join->join_on;
929  $join = $_join->join;
930 
931  $search_conditions[] = GF_Query_Condition::_or(
932  // Join
933  new GF_Query_Condition(
934  new GF_Query_Column( GF_Query_Column::META, $join->ID, $query->_alias( GF_Query_Column::META, $join->ID, 'm' ) ),
935  $search_condition->operator,
936  $search_condition->right
937  ),
938  // On
939  new GF_Query_Condition(
940  new GF_Query_Column( GF_Query_Column::META, $on->ID, $query->_alias( GF_Query_Column::META, $on->ID, 'm' ) ),
941  $search_condition->operator,
942  $search_condition->right
943  )
944  );
945  }
946  } else {
947  $search_conditions[] = new GF_Query_Condition(
948  new GF_Query_Column( $left->field_id, $left->source, $alias ),
949  $search_condition->operator,
950  $search_condition->right
951  );
952  }
953  }
954  }
955 
956  if ( $search_conditions ) {
957  $search_conditions = array( call_user_func_array( '\GF_Query_Condition::' . ( $mode == 'all' ? '_and' : '_or' ), $search_conditions ) );
958  }
959  }
960 
961  /**
962  * Grab the current clauses. We'll be combining them shortly.
963  */
964  $query_parts = $query->_introspect();
965 
966  /**
967  * Combine the parts as a new WHERE clause.
968  */
969  $where = call_user_func_array( '\GF_Query_Condition::_and', array_merge( array( $query_parts['where'] ), $search_conditions, $extra_conditions ) );
970  $query->where( $where );
971  }
972 
973  /**
974  * Convert $_GET/$_POST key to the field/meta ID
975  *
976  * Examples:
977  * - `filter_is_starred` => `is_starred`
978  * - `filter_1_2` => `1.2`
979  * - `filter_5` => `5`
980  *
981  * @since 2.0
982  *
983  * @param string $key $_GET/_$_POST search key
984  *
985  * @return string
986  */
987  private function convert_request_key_to_filter_key( $key ) {
988 
989  $field_id = str_replace( 'filter_', '', $key );
990 
991  // calculates field_id, removing 'filter_' and for '_' for advanced fields ( like name or checkbox )
992  if ( preg_match('/^[0-9_]+$/ism', $field_id ) ) {
993  $field_id = str_replace( '_', '.', $field_id );
994  }
995 
996  return $field_id;
997  }
998 
999  /**
1000  * Prepare the field filters to GFAPI
1001  *
1002  * 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.
1003  *
1004  * Format searched values
1005  *
1006  * @param string $filter_key ID of the field, or entry meta key
1007  * @param string $value $_GET/$_POST search value
1008  * @param \GV\View $view The view we're looking at
1009  * @param array[] $searchable_fields The searchable fields as configured by the widget.
1010  * @param string[] $get The $_GET/$_POST array.
1011  *
1012  * @since develop Added 5th $get parameter for operator overrides.
1013  * @todo Set function as private.
1014  *
1015  * @return array|false 1 or 2 deph levels, false if not allowed
1016  */
1017  public function prepare_field_filter( $filter_key, $value, $view, $searchable_fields, $get = array() ) {
1018  $key = $filter_key;
1019  $filter_key = explode( ':', $filter_key ); // field_id, form_id
1020 
1021  $form = null;
1022 
1023  if ( count( $filter_key ) > 1 ) {
1024  // form is specified
1025  list( $field_id, $form_id ) = $filter_key;
1026 
1027  if ( $forms = \GV\View::get_joined_forms( $view->ID ) ) {
1028  if ( ! $form = \GV\GF_Form::by_id( $form_id ) ) {
1029  return false;
1030  }
1031  }
1032 
1033  // form is allowed
1034  $found = false;
1035  foreach ( $forms as $form ) {
1036  if ( $form->ID == $form_id ) {
1037  $found = true;
1038  break;
1039  }
1040  }
1041 
1042  if ( ! $found ) {
1043  return false;
1044  }
1045 
1046  // form is in searchable fields
1047  $found = false;
1048  foreach ( $searchable_fields as $field ) {
1049  if ( $field_id == $field['field'] && $form->ID == $field['form_id'] ) {
1050  $found = true;
1051  break;
1052  }
1053  }
1054 
1055  if ( ! $found ) {
1056  return false;
1057  }
1058  } else {
1059  $field_id = reset( $filter_key );
1060  $searchable_fields = wp_list_pluck( $searchable_fields, 'field' );
1061  if ( ! in_array( 'search_all', $searchable_fields ) && ! in_array( $field_id, $searchable_fields ) ) {
1062  return false;
1063  }
1064  }
1065 
1066  if ( ! $form ) {
1067  // fallback
1068  $form = $view->form;
1069  }
1070 
1071  // get form field array
1072  $form_field = is_numeric( $field_id ) ? \GV\GF_Field::by_id( $form, $field_id ) : \GV\Internal_Field::by_id( $field_id );
1073 
1074  if ( ! $form_field ) {
1075  return false;
1076  }
1077 
1078  // default filter array
1079  $filter = array(
1080  'key' => $field_id,
1081  'value' => $value,
1082  'form_id' => $form->ID,
1083  );
1084 
1085  switch ( $form_field->type ) {
1086 
1087  case 'select':
1088  case 'radio':
1089  $filter['operator'] = $this->get_operator( $get, $key, array( 'is' ), 'is' );
1090  break;
1091 
1092  case 'post_category':
1093 
1094  if ( ! is_array( $value ) ) {
1095  $value = array( $value );
1096  }
1097 
1098  // Reset filter variable
1099  $filter = array();
1100 
1101  foreach ( $value as $val ) {
1102  $cat = get_term( $val, 'category' );
1103  $filter[] = array(
1104  'key' => $field_id,
1105  'value' => esc_attr( $cat->name ) . ':' . $val,
1106  'operator' => $this->get_operator( $get, $key, array( 'is' ), 'is' ),
1107  );
1108  }
1109 
1110  break;
1111 
1112  case 'multiselect':
1113 
1114  if ( ! is_array( $value ) ) {
1115  break;
1116  }
1117 
1118  // Reset filter variable
1119  $filter = array();
1120 
1121  foreach ( $value as $val ) {
1122  $filter[] = array( 'key' => $field_id, 'value' => $val );
1123  }
1124 
1125  break;
1126 
1127  case 'checkbox':
1128  // convert checkbox on/off into the correct search filter
1129  if ( false !== strpos( $field_id, '.' ) && ! empty( $form_field->inputs ) && ! empty( $form_field->choices ) ) {
1130  foreach ( $form_field->inputs as $k => $input ) {
1131  if ( $input['id'] == $field_id ) {
1132  $filter['value'] = $form_field->choices[ $k ]['value'];
1133  $filter['operator'] = $this->get_operator( $get, $key, array( 'is' ), 'is' );
1134  break;
1135  }
1136  }
1137  } elseif ( is_array( $value ) ) {
1138 
1139  // Reset filter variable
1140  $filter = array();
1141 
1142  foreach ( $value as $val ) {
1143  $filter[] = array(
1144  'key' => $field_id,
1145  'value' => $val,
1146  'operator' => $this->get_operator( $get, $key, array( 'is' ), 'is' ),
1147  );
1148  }
1149  }
1150 
1151  break;
1152 
1153  case 'name':
1154  case 'address':
1155 
1156  if ( false === strpos( $field_id, '.' ) ) {
1157 
1158  $words = explode( ' ', $value );
1159 
1160  $filters = array();
1161  foreach ( $words as $word ) {
1162  if ( ! empty( $word ) && strlen( $word ) > 1 ) {
1163  // Keep the same key for each filter
1164  $filter['value'] = $word;
1165  // Add a search for the value
1166  $filters[] = $filter;
1167  }
1168  }
1169 
1170  $filter = $filters;
1171  }
1172 
1173  // State/Province should be exact matches
1174  if ( 'address' === $form_field->field->type ) {
1175 
1176  $searchable_fields = $this->get_view_searchable_fields( $view, true );
1177 
1178  foreach ( $searchable_fields as $searchable_field ) {
1179 
1180  if( $form_field->ID !== $searchable_field['field'] ) {
1181  continue;
1182  }
1183 
1184  // Only exact-match dropdowns, not text search
1185  if( in_array( $searchable_field['input'], array( 'text', 'search' ), true ) ) {
1186  continue;
1187  }
1188 
1189  $input_id = gravityview_get_input_id_from_id( $form_field->ID );
1190 
1191  if ( 4 === $input_id ) {
1192  $filter['operator'] = $this->get_operator( $get, $key, array( 'is' ), 'is' );
1193  };
1194  }
1195  }
1196 
1197  break;
1198 
1199  case 'payment_date':
1200  case 'date':
1201 
1202  $date_format = $this->get_datepicker_format( true );
1203 
1204  if ( is_array( $value ) ) {
1205 
1206  // Reset filter variable
1207  $filter = array();
1208 
1209  foreach ( $value as $k => $date ) {
1210  if ( empty( $date ) ) {
1211  continue;
1212  }
1213  $operator = 'start' === $k ? '>=' : '<=';
1214 
1215  /**
1216  * @hack
1217  * @since 1.16.3
1218  * Safeguard until GF implements '<=' operator
1219  */
1220  if( !GFFormsModel::is_valid_operator( $operator ) && $operator === '<=' ) {
1221  $operator = '<';
1222  $date = date( 'Y-m-d', strtotime( self::get_formatted_date( $date, 'Y-m-d', $date_format ) . ' +1 day' ) );
1223  }
1224 
1225  $filter[] = array(
1226  'key' => $field_id,
1227  'value' => self::get_formatted_date( $date, 'Y-m-d', $date_format ),
1228  'operator' => $this->get_operator( $get, $key, array( $operator ), $operator ),
1229  );
1230  }
1231  } else {
1232  $date = $value;
1233  $filter['value'] = self::get_formatted_date( $date, 'Y-m-d', $date_format );
1234  $filter['operator'] = $this->get_operator( $get, $key, array( 'is' ), 'is' );
1235  }
1236 
1237  if ('payment_date' === $key) {
1238  $filter['operator'] = 'contains';
1239  }
1240 
1241  break;
1242  } // switch field type
1243 
1244  return $filter;
1245  }
1246 
1247  /**
1248  * Get the Field Format form GravityForms
1249  *
1250  * @param GF_Field_Date $field The field object
1251  * @since 1.10
1252  *
1253  * @return string Format of the date in the database
1254  */
1255  public static function get_date_field_format( GF_Field_Date $field ) {
1256  $format = 'm/d/Y';
1257  $datepicker = array(
1258  'mdy' => 'm/d/Y',
1259  'dmy' => 'd/m/Y',
1260  'dmy_dash' => 'd-m-Y',
1261  'dmy_dot' => 'd.m.Y',
1262  'ymd_slash' => 'Y/m/d',
1263  'ymd_dash' => 'Y-m-d',
1264  'ymd_dot' => 'Y.m.d',
1265  );
1266 
1267  if ( ! empty( $field->dateFormat ) && isset( $datepicker[ $field->dateFormat ] ) ){
1268  $format = $datepicker[ $field->dateFormat ];
1269  }
1270 
1271  return $format;
1272  }
1273 
1274  /**
1275  * Format a date value
1276  *
1277  * @param string $value Date value input
1278  * @param string $format Wanted formatted date
1279  *
1280  * @since 2.1.2
1281  * @param string $value_format The value format. Default: Y-m-d
1282  *
1283  * @return string
1284  */
1285  public static function get_formatted_date( $value = '', $format = 'Y-m-d', $value_format = 'Y-m-d' ) {
1286 
1287  $date = date_create_from_format( $value_format, $value );
1288 
1289  if ( empty( $date ) ) {
1290  gravityview()->log->debug( 'Date format not valid: {value}', array( 'value' => $value ) );
1291  return '';
1292  }
1293  return $date->format( $format );
1294  }
1295 
1296 
1297  /**
1298  * Include this extension templates path
1299  * @param array $file_paths List of template paths ordered
1300  */
1301  public function add_template_path( $file_paths ) {
1302 
1303  // Index 100 is the default GravityView template path.
1304  $file_paths[102] = self::$file . 'templates/';
1305 
1306  return $file_paths;
1307  }
1308 
1309  /**
1310  * Check whether the configured search fields have a date field
1311  *
1312  * @since 1.17.5
1313  *
1314  * @param array $search_fields
1315  *
1316  * @return bool True: has a `date` or `date_range` field
1317  */
1318  private function has_date_field( $search_fields ) {
1319 
1320  $has_date = false;
1321 
1322  foreach ( $search_fields as $k => $field ) {
1323  if ( in_array( $field['input'], array( 'date', 'date_range', 'entry_date' ) ) ) {
1324  $has_date = true;
1325  break;
1326  }
1327  }
1328 
1329  return $has_date;
1330  }
1331 
1332  /**
1333  * Renders the Search Widget
1334  * @param array $widget_args
1335  * @param string $content
1336  * @param string|\GV\Template_Context $context
1337  *
1338  * @return void
1339  */
1340  public function render_frontend( $widget_args, $content = '', $context = '' ) {
1341 
1343 
1344  if ( empty( $gravityview_view ) ) {
1345  gravityview()->log->debug( '$gravityview_view not instantiated yet.' );
1346  return;
1347  }
1348 
1349  $view = \GV\View::by_id( $gravityview_view->view_id );
1350 
1351  // get configured search fields
1352  $search_fields = ! empty( $widget_args['search_fields'] ) ? json_decode( $widget_args['search_fields'], true ) : '';
1353 
1354  if ( empty( $search_fields ) || ! is_array( $search_fields ) ) {
1355  gravityview()->log->debug( 'No search fields configured for widget:', array( 'data' => $widget_args ) );
1356  return;
1357  }
1358 
1359  // prepare fields
1360  foreach ( $search_fields as $k => $field ) {
1361 
1362  $updated_field = $field;
1363 
1364  $updated_field = $this->get_search_filter_details( $updated_field, $context );
1365 
1366  switch ( $field['field'] ) {
1367 
1368  case 'search_all':
1369  $updated_field['key'] = 'search_all';
1370  $updated_field['input'] = 'search_all';
1371  $updated_field['value'] = $this->rgget_or_rgpost( 'gv_search' );
1372  break;
1373 
1374  case 'entry_date':
1375  $updated_field['key'] = 'entry_date';
1376  $updated_field['input'] = 'entry_date';
1377  $updated_field['value'] = array(
1378  'start' => $this->rgget_or_rgpost( 'gv_start' ),
1379  'end' => $this->rgget_or_rgpost( 'gv_end' ),
1380  );
1381  break;
1382 
1383  case 'entry_id':
1384  $updated_field['key'] = 'entry_id';
1385  $updated_field['input'] = 'entry_id';
1386  $updated_field['value'] = $this->rgget_or_rgpost( 'gv_id' );
1387  break;
1388 
1389  case 'created_by':
1390  $updated_field['key'] = 'created_by';
1391  $updated_field['name'] = 'gv_by';
1392  $updated_field['value'] = $this->rgget_or_rgpost( 'gv_by' );
1393  $updated_field['choices'] = self::get_created_by_choices( $view );
1394  break;
1395 
1396  case 'is_approved':
1397  $updated_field['key'] = 'is_approved';
1398  $updated_field['value'] = $this->rgget_or_rgpost( 'filter_is_approved' );
1399  $updated_field['choices'] = self::get_is_approved_choices();
1400  break;
1401  }
1402 
1403  $search_fields[ $k ] = $updated_field;
1404  }
1405 
1406  gravityview()->log->debug( 'Calculated Search Fields: ', array( 'data' => $search_fields ) );
1407 
1408  /**
1409  * @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.
1410  * @param array $search_fields Array of search filters with `key`, `label`, `value`, `type`, `choices` keys
1411  * @param GravityView_Widget_Search $this Current widget object
1412  * @param array $widget_args Args passed to this method. {@since 1.8}
1413  * @param \GV\Template_Context $context {@since 2.0}
1414  * @type array
1415  */
1416  $gravityview_view->search_fields = apply_filters( 'gravityview_widget_search_filters', $search_fields, $this, $widget_args, $context );
1417 
1418  $gravityview_view->permalink_fields = $this->add_no_permalink_fields( array(), $this, $widget_args );
1419 
1420  $gravityview_view->search_layout = ! empty( $widget_args['search_layout'] ) ? $widget_args['search_layout'] : 'horizontal';
1421 
1422  /** @since 1.14 */
1423  $gravityview_view->search_mode = ! empty( $widget_args['search_mode'] ) ? $widget_args['search_mode'] : 'any';
1424 
1425  $custom_class = ! empty( $widget_args['custom_class'] ) ? $widget_args['custom_class'] : '';
1426 
1427  $gravityview_view->search_class = self::get_search_class( $custom_class );
1428 
1429  $gravityview_view->search_clear = ! empty( $widget_args['search_clear'] ) ? $widget_args['search_clear'] : false;
1430 
1431  if ( $this->has_date_field( $search_fields ) ) {
1432  // enqueue datepicker stuff only if needed!
1433  $this->enqueue_datepicker();
1434  }
1435 
1436  $this->maybe_enqueue_flexibility();
1437 
1438  $gravityview_view->render( 'widget', 'search', false );
1439  }
1440 
1441  /**
1442  * Get the search class for a search form
1443  *
1444  * @since 1.5.4
1445  *
1446  * @return string Sanitized CSS class for the search form
1447  */
1448  public static function get_search_class( $custom_class = '' ) {
1450 
1451  $search_class = 'gv-search-'.$gravityview_view->search_layout;
1452 
1453  if ( ! empty( $custom_class ) ) {
1454  $search_class .= ' '.$custom_class;
1455  }
1456 
1457  /**
1458  * @filter `gravityview_search_class` Modify the CSS class for the search form
1459  * @param string $search_class The CSS class for the search form
1460  */
1461  $search_class = apply_filters( 'gravityview_search_class', $search_class );
1462 
1463  // Is there an active search being performed? Used by fe-views.js
1464  $search_class .= GravityView_frontend::getInstance()->isSearch() ? ' gv-is-search' : '';
1465 
1466  return gravityview_sanitize_html_class( $search_class );
1467  }
1468 
1469 
1470  /**
1471  * Calculate the search form action
1472  * @since 1.6
1473  *
1474  * @return string
1475  */
1476  public static function get_search_form_action() {
1478 
1479  $post_id = $gravityview_view->getPostId() ? $gravityview_view->getPostId() : $gravityview_view->getViewId();
1480 
1481  $url = add_query_arg( array(), get_permalink( $post_id ) );
1482 
1483  /**
1484  * @filter `gravityview/widget/search/form/action` Override the search URL.
1485  * @param[in,out] string $action Where the form submits to.
1486  *
1487  * Further parameters will be added once adhoc context is added.
1488  * Use gravityview()->request until then.
1489  */
1490  return apply_filters( 'gravityview/widget/search/form/action', $url );
1491  }
1492 
1493  /**
1494  * Get the label for a search form field
1495  * @param array $field Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1496  * @param array $form_field Form field data, as fetched by `gravityview_get_field()`
1497  * @return string Label for the search form
1498  */
1499  private static function get_field_label( $field, $form_field = array() ) {
1500 
1501  $label = \GV\Utils::_GET( 'label', \GV\Utils::get( $field, 'label' ) );
1502 
1503  if ( ! $label ) {
1504 
1505  $label = isset( $form_field['label'] ) ? $form_field['label'] : '';
1506 
1507  switch( $field['field'] ) {
1508  case 'search_all':
1509  $label = __( 'Search Entries:', 'gravityview' );
1510  break;
1511  case 'entry_date':
1512  $label = __( 'Filter by date:', 'gravityview' );
1513  break;
1514  case 'entry_id':
1515  $label = __( 'Entry ID:', 'gravityview' );
1516  break;
1517  default:
1518  // If this is a field input, not a field
1519  if ( strpos( $field['field'], '.' ) > 0 && ! empty( $form_field['inputs'] ) ) {
1520 
1521  // Get the label for the field in question, which returns an array
1522  $items = wp_list_filter( $form_field['inputs'], array( 'id' => $field['field'] ) );
1523 
1524  // Get the item with the `label` key
1525  $values = wp_list_pluck( $items, 'label' );
1526 
1527  // There will only one item in the array, but this is easier
1528  foreach ( $values as $value ) {
1529  $label = $value;
1530  break;
1531  }
1532  }
1533  }
1534  }
1535 
1536  /**
1537  * @filter `gravityview_search_field_label` Modify the label for a search field. Supports returning HTML
1538  * @since 1.17.3 Added $field parameter
1539  * @param[in,out] string $label Existing label text, sanitized.
1540  * @param[in] array $form_field Gravity Forms field array, as returned by `GFFormsModel::get_field()`
1541  * @param[in] array $field Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1542  */
1543  $label = apply_filters( 'gravityview_search_field_label', esc_attr( $label ), $form_field, $field );
1544 
1545  return $label;
1546  }
1547 
1548  /**
1549  * Prepare search fields to frontend render with other details (label, field type, searched values)
1550  *
1551  * @param array $field
1552  * @param \GV\Context $context
1553  *
1554  * @return array
1555  */
1556  private function get_search_filter_details( $field, $context ) {
1557 
1559 
1560  $form = $gravityview_view->getForm();
1561 
1562  // for advanced field ids (eg, first name / last name )
1563  $name = 'filter_' . str_replace( '.', '_', $field['field'] );
1564 
1565  // get searched value from $_GET/$_POST (string or array)
1566  $value = $this->rgget_or_rgpost( $name );
1567 
1568  // get form field details
1569  $form_field = gravityview_get_field( $form, $field['field'] );
1570 
1571  $form_field_type = \GV\Utils::get( $form_field, 'type' );
1572 
1573  $filter = array(
1574  'key' => \GV\Utils::get( $field, 'field' ),
1575  'name' => $name,
1576  'label' => self::get_field_label( $field, $form_field ),
1577  'input' => \GV\Utils::get( $field, 'input' ),
1578  'value' => $value,
1579  'type' => $form_field_type,
1580  );
1581 
1582  // collect choices
1583  if ( 'post_category' === $form_field_type && ! empty( $form_field['displayAllCategories'] ) && empty( $form_field['choices'] ) ) {
1584  $filter['choices'] = gravityview_get_terms_choices();
1585  } elseif ( ! empty( $form_field['choices'] ) ) {
1586  $filter['choices'] = $form_field['choices'];
1587  }
1588 
1589  if ( 'date_range' === $field['input'] && empty( $value ) ) {
1590  $filter['value'] = array( 'start' => '', 'end' => '' );
1591  }
1592 
1593  if ( ! empty( $filter['choices'] ) ) {
1594  /**
1595  * @filter `gravityview/search/sieve_choices` Only output used choices for this field.
1596  * @param[in,out] bool Yes or no.
1597  * @param array $field The field configuration.
1598  * @param \GV\Context The context.
1599  */
1600  if ( apply_filters( 'gravityview/search/sieve_choices', false, $field, $context ) ) {
1601  $filter['choices'] = $this->sieve_filter_choices( $filter, $context );
1602  }
1603  }
1604 
1605  /**
1606  * @filter `gravityview/search/filter_details` Filter the output filter details for the Search widget.
1607  * @param[in,out] array $filter The filter details
1608  * @param array $field The search field configuration
1609  * @param \GV\Context The context
1610  * @since develop
1611  */
1612  $filter = apply_filters( 'gravityview/search/filter_details', $filter, $field, $context );
1613 
1614  return $filter;
1615 
1616  }
1617 
1618  /**
1619  * Sieve filter choices to only ones that are used.
1620  *
1621  * @param array $filter The filter configuration.
1622  * @param \GV\Context $context The context
1623  *
1624  * @since develop
1625  * @internal
1626  *
1627  * @return array The filter choices.
1628  */
1629  private function sieve_filter_choices( $filter, $context ) {
1630  if ( empty( $filter['key'] ) || empty( $filter['choices'] ) ) {
1631  return $filter; // @todo Populate plugins might give us empty choices
1632  }
1633 
1634  if ( ! is_numeric( $filter['key'] ) ) {
1635  return $filter;
1636  }
1637 
1638  $form_id = $context->view->form->ID; // @todo Support multiple forms (joins)
1639 
1640  global $wpdb;
1641 
1642  $table = GFFormsModel::get_entry_meta_table_name();
1643 
1644  $key_like = $wpdb->esc_like( $filter['key'] ) . '.%';
1645 
1646  switch ( \GV\Utils::get( $filter, 'type' ) ):
1647  case 'post_category':
1648  $choices = $wpdb->get_col( $wpdb->prepare(
1649  "SELECT DISTINCT SUBSTRING_INDEX(meta_value, ':', 1) FROM $table WHERE (meta_key LIKE %s OR meta_key = %d) AND form_id = %d",
1650  $key_like, $filter['key'], $form_id
1651  ) );
1652  break;
1653  default:
1654  $choices = $wpdb->get_col( $wpdb->prepare(
1655  "SELECT DISTINCT meta_value FROM $table WHERE (meta_key LIKE %s OR meta_key = %d) AND form_id = %d",
1656  $key_like, $filter['key'], $form_id
1657  ) );
1658 
1659  if ( ( $field = gravityview_get_field( $form_id, $filter['key'] ) ) && 'json' === $field->storageType ) {
1660  $choices = array_map( 'json_decode', $choices );
1661  $_choices_array = array();
1662  foreach ( $choices as $choice ) {
1663  if ( is_array( $choice ) ) {
1664  $_choices_array = array_merge( $_choices_array, $choice );
1665  } else {
1666  $_choices_array []= $choice;
1667  }
1668  }
1669  $choices = array_unique( $_choices_array );
1670  }
1671 
1672  break;
1673  endswitch;
1674 
1675  $filter_choices = array();
1676  foreach ( $filter['choices'] as $choice ) {
1677  if ( in_array( $choice['text'], $choices, true ) || in_array( $choice['value'], $choices, true ) ) {
1678  $filter_choices[] = $choice;
1679  }
1680  }
1681 
1682  return $filter_choices;
1683  }
1684 
1685  /**
1686  * Calculate the search choices for the users
1687  *
1688  * @param \GV\View $view The view
1689  * @since develop
1690  *
1691  * @since 1.8
1692  *
1693  * @return array Array of user choices (value = ID, text = display name)
1694  */
1695  private static function get_created_by_choices( $view ) {
1696 
1697  /**
1698  * filter gravityview/get_users/search_widget
1699  * @see \GVCommon::get_users
1700  */
1701  $users = GVCommon::get_users( 'search_widget', array( 'fields' => array( 'ID', 'display_name' ) ) );
1702 
1703  $choices = array();
1704  foreach ( $users as $user ) {
1705  /**
1706  * @filter `gravityview/search/created_by/text` Filter the display text in created by search choices
1707  * @since develop
1708  * @param string[in,out] The text. Default: $user->display_name
1709  * @param \WP_User $user The user.
1710  * @param \GV\View $view The view.
1711  */
1712  $text = apply_filters( 'gravityview/search/created_by/text', $user->display_name, $user, $view );
1713  $choices[] = array(
1714  'value' => $user->ID,
1715  'text' => $text,
1716  );
1717  }
1718 
1719  return $choices;
1720  }
1721 
1722  /**
1723  * Calculate the search checkbox choices for approval status
1724  *
1725  * @since develop
1726  *
1727  * @return array Array of approval status choices (value = status, text = display name)
1728  */
1729  private static function get_is_approved_choices() {
1730 
1731  $choices = array();
1732  foreach ( GravityView_Entry_Approval_Status::get_all() as $status ) {
1733  $choices[] = array(
1734  'value' => $status['value'],
1735  'text' => $status['label'],
1736  );
1737  }
1738 
1739  return $choices;
1740  }
1741 
1742  /**
1743  * Output the Clear Search Results button
1744  * @since 1.5.4
1745  */
1746  public static function the_clear_search_button() {
1748 
1749  if ( $gravityview_view->search_clear ) {
1750 
1751  $url = strtok( add_query_arg( array() ), '?' );
1752 
1753  echo gravityview_get_link( $url, esc_html__( 'Clear', 'gravityview' ), 'class=button gv-search-clear' );
1754 
1755  }
1756  }
1757 
1758  /**
1759  * Based on the search method, fetch the value for a specific key
1760  *
1761  * @since 1.16.4
1762  *
1763  * @param string $name Name of the request key to fetch the value for
1764  *
1765  * @return mixed|string Value of request at $name key. Empty string if empty.
1766  */
1767  private function rgget_or_rgpost( $name ) {
1769 
1770  $value = stripslashes_deep( $value );
1771 
1772  $value = gv_map_deep( $value, 'rawurldecode' );
1773 
1774  $value = gv_map_deep( $value, '_wp_specialchars' );
1775 
1776  return $value;
1777  }
1778 
1779 
1780  /**
1781  * Require the datepicker script for the frontend GV script
1782  * @param array $js_dependencies Array of existing required scripts for the fe-views.js script
1783  * @return array Array required scripts, with `jquery-ui-datepicker` added
1784  */
1785  public function add_datepicker_js_dependency( $js_dependencies ) {
1786 
1787  $js_dependencies[] = 'jquery-ui-datepicker';
1788 
1789  return $js_dependencies;
1790  }
1791 
1792  /**
1793  * Modify the array passed to wp_localize_script()
1794  *
1795  * @param array $js_localization The data padded to the Javascript file
1796  * @param array $view_data View data array with View settings
1797  *
1798  * @return array
1799  */
1800  public function add_datepicker_localization( $localizations = array(), $view_data = array() ) {
1801  global $wp_locale;
1802 
1803  /**
1804  * @filter `gravityview_datepicker_settings` Modify the datepicker settings
1805  * @see http://api.jqueryui.com/datepicker/ Learn what settings are available
1806  * @see http://www.renegadetechconsulting.com/tutorials/jquery-datepicker-and-wordpress-i18n Thanks for the helpful information on $wp_locale
1807  * @param array $js_localization The data padded to the Javascript file
1808  * @param array $view_data View data array with View settings
1809  */
1810  $datepicker_settings = apply_filters( 'gravityview_datepicker_settings', array(
1811  'yearRange' => '-5:+5',
1812  'changeMonth' => true,
1813  'changeYear' => true,
1814  'closeText' => esc_attr_x( 'Close', 'Close calendar', 'gravityview' ),
1815  'prevText' => esc_attr_x( 'Prev', 'Previous month in calendar', 'gravityview' ),
1816  'nextText' => esc_attr_x( 'Next', 'Next month in calendar', 'gravityview' ),
1817  'currentText' => esc_attr_x( 'Today', 'Today in calendar', 'gravityview' ),
1818  'weekHeader' => esc_attr_x( 'Week', 'Week in calendar', 'gravityview' ),
1819  'monthStatus' => __( 'Show a different month', 'gravityview' ),
1820  'monthNames' => array_values( $wp_locale->month ),
1821  'monthNamesShort' => array_values( $wp_locale->month_abbrev ),
1822  'dayNames' => array_values( $wp_locale->weekday ),
1823  'dayNamesShort' => array_values( $wp_locale->weekday_abbrev ),
1824  'dayNamesMin' => array_values( $wp_locale->weekday_initial ),
1825  // get the start of week from WP general setting
1826  'firstDay' => get_option( 'start_of_week' ),
1827  // is Right to left language? default is false
1828  'isRTL' => is_rtl(),
1829  ), $view_data );
1830 
1831  $localizations['datepicker'] = $datepicker_settings;
1832 
1833  return $localizations;
1834 
1835  }
1836 
1837  /**
1838  * Register search widget scripts, including Flexibility
1839  *
1840  * @see https://github.com/10up/flexibility
1841  *
1842  * @since 1.17
1843  *
1844  * @return void
1845  */
1846  public function register_scripts() {
1847  wp_register_script( 'gv-flexibility', plugins_url( 'assets/lib/flexibility/flexibility.js', GRAVITYVIEW_FILE ), array(), \GV\Plugin::$version, true );
1848  }
1849 
1850  /**
1851  * If the current visitor is running IE 8 or 9, enqueue Flexibility
1852  *
1853  * @since 1.17
1854  *
1855  * @return void
1856  */
1857  private function maybe_enqueue_flexibility() {
1858  if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && preg_match( '/MSIE [8-9]/', $_SERVER['HTTP_USER_AGENT'] ) ) {
1859  wp_enqueue_script( 'gv-flexibility' );
1860  }
1861  }
1862 
1863  /**
1864  * Enqueue the datepicker script
1865  *
1866  * It sets the $gravityview->datepicker_class parameter
1867  *
1868  * @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.
1869  * @return void
1870  */
1871  public function enqueue_datepicker() {
1873 
1874  wp_enqueue_script( 'jquery-ui-datepicker' );
1875 
1876  add_filter( 'gravityview_js_dependencies', array( $this, 'add_datepicker_js_dependency' ) );
1877  add_filter( 'gravityview_js_localization', array( $this, 'add_datepicker_localization' ), 10, 2 );
1878 
1879  $scheme = is_ssl() ? 'https://' : 'http://';
1880  wp_enqueue_style( 'jquery-ui-datepicker', $scheme.'ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/smoothness/jquery-ui.css' );
1881 
1882  /**
1883  * @filter `gravityview_search_datepicker_class`
1884  * 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.
1885  * @param string $css_class CSS class to use. Default: `gv-datepicker datepicker mdy` \n
1886  * Options are:
1887  * - `mdy` (mm/dd/yyyy)
1888  * - `dmy` (dd/mm/yyyy)
1889  * - `dmy_dash` (dd-mm-yyyy)
1890  * - `dmy_dot` (dd.mm.yyyy)
1891  * - `ymd_slash` (yyyy/mm/dd)
1892  * - `ymd_dash` (yyyy-mm-dd)
1893  * - `ymd_dot` (yyyy.mm.dd)
1894  */
1895  $datepicker_class = apply_filters( 'gravityview_search_datepicker_class', "gv-datepicker datepicker " . $this->get_datepicker_format() );
1896 
1897  $gravityview_view->datepicker_class = $datepicker_class;
1898  }
1899 
1900  /**
1901  * Retrieve the datepicker format.
1902  *
1903  * @param bool $date_format Whether to return the PHP date format or the datpicker class name. Default: false.
1904  *
1905  * @see https://docs.gravityview.co/article/115-changing-the-format-of-the-search-widgets-date-picker
1906  *
1907  * @return string The datepicker format placeholder, or the PHP date format.
1908  */
1909  private function get_datepicker_format( $date_format = false ) {
1910 
1911  $default_format = 'mdy';
1912 
1913  /**
1914  * @filter `gravityview/widgets/search/datepicker/format`
1915  * @since 2.1.1
1916  * @param string $format Default: mdy
1917  * Options are:
1918  * - `mdy` (mm/dd/yyyy)
1919  * - `dmy` (dd/mm/yyyy)
1920  * - `dmy_dash` (dd-mm-yyyy)
1921  * - `dmy_dot` (dd.mm.yyyy)
1922  * - `ymd_slash` (yyyy/mm/dd)
1923  * - `ymd_dash` (yyyy-mm-dd)
1924  * - `ymd_dot` (yyyy.mm.dd)
1925  */
1926  $format = apply_filters( 'gravityview/widgets/search/datepicker/format', $default_format );
1927 
1928  $gf_date_formats = array(
1929  'mdy' => 'm/d/Y',
1930 
1931  'dmy_dash' => 'd-m-Y',
1932  'dmy_dot' => 'd.m.Y',
1933  'dmy' => 'd/m/Y',
1934 
1935  'ymd_slash' => 'Y/m/d',
1936  'ymd_dash' => 'Y-m-d',
1937  'ymd_dot' => 'Y.m.d',
1938  );
1939 
1940  if ( ! $date_format ) {
1941  // If the format key isn't valid, return default format key
1942  return isset( $gf_date_formats[ $format ] ) ? $format : $default_format;
1943  }
1944 
1945  // If the format key isn't valid, return default format value
1946  return \GV\Utils::get( $gf_date_formats, $format, $gf_date_formats[ $default_format ] );
1947  }
1948 
1949  /**
1950  * If previewing a View or page with embedded Views, make the search work properly by adding hidden fields with query vars
1951  *
1952  * @since 2.2.1
1953  *
1954  * @return void
1955  */
1956  public function add_preview_inputs() {
1957  global $wp;
1958 
1959  if ( ! is_preview() || ! current_user_can( 'publish_gravityviews') ) {
1960  return;
1961  }
1962 
1963  // Outputs `preview` and `post_id` variables
1964  foreach ( $wp->query_vars as $key => $value ) {
1965  printf( '<input type="hidden" name="%s" value="%s" />', esc_attr( $key ), esc_attr( $value ) );
1966  }
1967 
1968  }
1969 
1970  /**
1971  * Get an operator URL override.
1972  *
1973  * @param array $get Where to look for the operator.
1974  * @param string $key The filter key to look for.
1975  * @param array $allowed The allowed operators (whitelist).
1976  * @param string $default The default operator.
1977  *
1978  * @return string The operator.
1979  */
1980  private function get_operator( $get, $key, $allowed, $default ) {
1981  $operator = \GV\Utils::get( $get, "$key|op", $default );
1982 
1983  /**
1984  * @filter `gravityview/search/operator_whitelist` An array of allowed operators for a field.
1985  * @param[in,out] string[] A whitelist of allowed operators.
1986  * @param string The filter name.
1987  */
1988  $allowed = apply_filters( 'gravityview/search/operator_whitelist', $allowed, $key );
1989 
1990  if ( ! in_array( $operator, $allowed, true ) ) {
1991  $operator = $default;
1992  }
1993 
1994  return $operator;
1995  }
1996 
1997 
1998 } // end class
1999 
2001 
2002 if ( ! gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
2003  return;
2004 }
2005 
2006 /**
2007  * A GF_Query condition that allows user data searches.
2008  */
2009 class GravityView_Widget_Search_Author_GF_Query_Condition extends \GF_Query_Condition {
2010  public function __construct( $filter, $view ) {
2011  $this->value = $filter['value'];
2012  $this->view = $view;
2013  }
2014 
2015  public function sql( $query ) {
2016  global $wpdb;
2017 
2018  $user_meta_fields = array(
2019  'nickname', 'first_name', 'last_name',
2020  );
2021 
2022  /**
2023  * @filter `gravityview/widgets/search/created_by/user_meta_fields` Filter the user meta fields to search.
2024  * @param[in,out] array The user meta fields.
2025  * @param \GV\View $view The view.
2026  */
2027  $user_meta_fields = apply_filters( 'gravityview/widgets/search/created_by/user_meta_fields', $user_meta_fields, $this->view );
2028 
2029  $user_fields = array(
2030  'user_nicename', 'user_login', 'display_name', 'user_email',
2031  );
2032 
2033  /**
2034  * @filter `gravityview/widgets/search/created_by/user_fields` Filter the user fields to search.
2035  * @param[in,out] array The user fields.
2036  * @param \GV\View $view The view.
2037  */
2038  $user_fields = apply_filters( 'gravityview/widgets/search/created_by/user_fields', $user_fields, $this->view );
2039 
2040  $conditions = array();
2041 
2042  foreach ( $user_fields as $user_field ) {
2043  $conditions[] = $wpdb->prepare( "`u`.`$user_field` LIKE %s", '%' . $wpdb->esc_like( $this->value ) . '%' );
2044  }
2045 
2046  foreach ( $user_meta_fields as $meta_field ) {
2047  $conditions[] = $wpdb->prepare( "(`um`.`meta_key` = %s AND `um`.`meta_value` LIKE %s)", $meta_field, '%' . $wpdb->esc_like( $this->value ) . '%' );
2048  }
2049 
2050  $conditions = '(' . implode( ' OR ', $conditions ) . ')';
2051 
2052  $alias = $query->_alias( null );
2053 
2054  return "(EXISTS (SELECT 1 FROM $wpdb->users u LEFT JOIN $wpdb->usermeta um ON u.ID = um.user_id WHERE (u.ID = `$alias`.`created_by` AND $conditions)))";
2055  }
2056 }
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
sieve_filter_choices( $filter, $context)
Sieve filter choices to only ones that are used.
static get_field_label( $field, $form_field=array())
Get the label for a search form field.
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.
$forms
Definition: data-source.php:19
add_datepicker_localization( $localizations=array(), $view_data=array())
Modify the array passed to wp_localize_script()
static getInstance( $passed_post=NULL)
set_search_method()
Sets the search method to GET (default) or POST.
get_search_method()
Returns the search method.
static get_search_input_labels()
Get labels for different types of search bar inputs.
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_created_by_choices( $view)
Calculate the search choices for the users.
static get_formatted_date( $value='', $format='Y-m-d', $value_format='Y-m-d')
Format a date value.
add_preview_inputs()
If previewing a View or page with embedded Views, make the search work properly by adding hidden fiel...
get_search_filter_details( $field, $context)
Prepare search fields to frontend render with other details (label, field type, searched values) ...
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.
gravityview()
Definition: _stubs.php:26
get( $key, $default=null)
Retrieve a setting.
if(gravityview() ->plugin->is_GF_25()) $form
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.
render_frontend( $widget_args, $content='', $context='')
Renders the Search Widget.
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:317
$search_method
whether search method is GET or POST ( default: GET )
register_scripts()
Register search widget scripts, including Flexibility.
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.
const UNAPPROVED
get_datepicker_format( $date_format=false)
Retrieve the datepicker format.
prepare_field_filter( $filter_key, $value, $view, $searchable_fields, $get=array())
Prepare the field filters to GFAPI.
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 —
get_operator( $get, $key, $allowed, $default)
Get an operator URL override.
static get( $array, $key, $default=null)
Grab a value from an array or an object or default.
A GF_Query condition that allows user data searches.
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?
static get_all()
Return array of status options.
static get_is_approved_choices()
Calculate the search checkbox choices for approval status.
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 get_input_types_by_field_type()
Get the input types available for different field types.
static getInstance()
Get the one true instantiated self.