Completed
Push — develop ( 4ab178...4b70e0 )
by Gennady
24:10 queued 09:04
created

GravityView_Widget_Search::rgget_or_rgpost()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 11
ccs 6
cts 6
cp 1
crap 1
rs 9.9
c 0
b 0
f 0
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
16
class GravityView_Widget_Search extends \GV\Widget {
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 39
	public function __construct() {
31
32 39
		$this->widget_id = 'search_bar';
33 39
		$this->widget_description = esc_html__( 'Search form for searching entries.', 'gravityview' );
34
35 39
		self::$instance = &$this;
36
37 39
		self::$file = plugin_dir_path( __FILE__ );
38
39 39
		$default_values = array( 'header' => 0, 'footer' => 0 );
40
41
		$settings = array(
42 39
			'search_layout' => array(
43 39
				'type' => 'radio',
44
				'full_width' => true,
45 39
				'label' => esc_html__( 'Search Layout', 'gravityview' ),
46 39
				'value' => 'horizontal',
47
				'options' => array(
48 39
					'horizontal' => esc_html__( 'Horizontal', 'gravityview' ),
49 39
					'vertical' => esc_html__( 'Vertical', 'gravityview' ),
50
				),
51
			),
52
			'search_clear' => array(
53 39
				'type' => 'checkbox',
54 39
				'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 39
				'type' => 'radio',
65
				'full_width' => true,
66 39
				'label' => esc_html__( 'Search Mode', 'gravityview' ),
67 39
				'desc' => __('Should search results match all search fields, or any?', 'gravityview'),
68 39
				'value' => 'any',
69 39
				'class' => 'hide-if-js',
70
				'options' => array(
71 39
					'any' => esc_html__( 'Match Any Fields', 'gravityview' ),
72 39
					'all' => esc_html__( 'Match All Fields', 'gravityview' ),
73
				),
74
			),
75
		);
76
77 39
		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
			// admin - add scripts - run at 1100 to make sure GravityView_Admin_Views::add_scripts_and_styles() runs first at 999
85
			add_action( 'admin_enqueue_scripts', array( $this, 'add_scripts_and_styles' ), 1100 );
86
			add_action( 'wp_enqueue_scripts', array( $this, 'register_scripts') );
87
			add_filter( 'gravityview_noconflict_scripts', array( $this, 'register_no_conflict' ) );
88
89
			// ajax - get the searchable fields
90
			add_action( 'wp_ajax_gv_searchable_fields', array( 'GravityView_Widget_Search', 'get_searchable_fields' ) );
91
92
			add_action( 'gravityview_search_widget_fields_after', array( $this, 'add_preview_inputs' ) );
93
		}
94
95 39
		parent::__construct( esc_html__( 'Search Bar', 'gravityview' ), null, $default_values, $settings );
96
97
		// calculate the search method (POST / GET)
98 39
		$this->set_search_method();
99 39
	}
100
101
	/**
102
	 * @return GravityView_Widget_Search
103
	 */
104 6
	public static function getInstance() {
105 6
		if ( empty( self::$instance ) ) {
106
			self::$instance = new GravityView_Widget_Search;
107
		}
108 6
		return self::$instance;
109
	}
110
111
	/**
112
	 * Sets the search method to GET (default) or POST
113
	 * @since 1.16.4
114
	 */
115 39
	private function set_search_method() {
116
		/**
117
		 * @filter `gravityview/search/method` Modify the search form method (GET / POST)
118
		 * @since 1.16.4
119
		 * @param string $search_method Assign an input type according to the form field type. Defaults: `boolean`, `multi`, `select`, `date`, `text`
120
		 * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
121
		 */
122 39
		$method = apply_filters( 'gravityview/search/method', $this->search_method );
123
124 39
		$method = strtolower( $method );
125
126 39
		$this->search_method = in_array( $method, array( 'get', 'post' ) ) ? $method : 'get';
127 39
	}
128
129
	/**
130
	 * Returns the search method
131
	 * @since 1.16.4
132
	 * @return string
133
	 */
134 6
	public function get_search_method() {
135 6
		return $this->search_method;
136
	}
137
138
	/**
139
	 * Get the input types available for different field types
140
	 *
141
	 * @since 1.17.5
142
	 *
143
	 * @return array [field type name] => (array|string) search bar input types
144
	 */
145
	public static function get_input_types_by_field_type() {
146
		/**
147
		 * Input Type groups
148
		 * @see admin-search-widget.js (getSelectInput)
149
		 * @var array
150
		 */
151
		$input_types = array(
152
			'text' => array( 'input_text' ),
153
			'address' => array( 'input_text' ),
154
			'number' => array( 'input_text' ),
155
			'date' => array( 'date', 'date_range' ),
156
			'boolean' => array( 'single_checkbox' ),
157
			'select' => array( 'select', 'radio', 'link' ),
158
			'multi' => array( 'select', 'multiselect', 'radio', 'checkbox', 'link' ),
159
160
			// hybrids
161
			'created_by' => array( 'select', 'radio', 'checkbox', 'multiselect', 'link', 'input_text' ),
162
			'product'   => array( 'select', 'radio', 'link', 'input_text' ),
163
		);
164
165
		/**
166
		 * @filter `gravityview/search/input_types` Change the types of search fields available to a field type
167
		 * @see GravityView_Widget_Search::get_search_input_labels() for the available input types
168
		 * @param array $input_types Associative array: key is field `name`, value is array of GravityView input types (note: use `input_text` for `text`)
169
		 */
170
		$input_types = apply_filters( 'gravityview/search/input_types', $input_types );
171
172
		return $input_types;
173
	}
174
175
	/**
176
	 * Get labels for different types of search bar inputs
177
	 *
178
	 * @since 1.17.5
179
	 *
180
	 * @return array [input type] => input type label
181
	 */
182
	public static function get_search_input_labels() {
183
		/**
184
		 * Input Type labels l10n
185
		 * @see admin-search-widget.js (getSelectInput)
186
		 * @var array
187
		 */
188
		$input_labels = array(
189
			'input_text' => esc_html__( 'Text', 'gravityview' ),
190
			'date' => esc_html__( 'Date', 'gravityview' ),
191
			'select' => esc_html__( 'Select', 'gravityview' ),
192
			'multiselect' => esc_html__( 'Select (multiple values)', 'gravityview' ),
193
			'radio' => esc_html__( 'Radio', 'gravityview' ),
194
			'checkbox' => esc_html__( 'Checkbox', 'gravityview' ),
195
			'single_checkbox' => esc_html__( 'Checkbox', 'gravityview' ),
196
			'link' => esc_html__( 'Links', 'gravityview' ),
197
			'date_range' => esc_html__( 'Date range', 'gravityview' ),
198
		);
199
200
		/**
201
		 * @filter `gravityview/search/input_types` Change the label of search field input types
202
		 * @param array $input_types Associative array: key is input type name, value is label
203
		 */
204
		$input_labels = apply_filters( 'gravityview/search/input_labels', $input_labels );
205
206
		return $input_labels;
207
	}
208
209
	public static function get_search_input_label( $input_type ) {
210
		$labels = self::get_search_input_labels();
211
212
		return \GV\Utils::get( $labels, $input_type, false );
213
	}
214
215
	/**
216
	 * Add script to Views edit screen (admin)
217
	 * @param  mixed $hook
218
	 */
219
	public function add_scripts_and_styles( $hook ) {
220
		global $pagenow;
221
222
		// Don't process any scripts below here if it's not a GravityView page or the widgets screen
223
		if ( ! gravityview()->request->is_admin( $hook, 'single' ) && ( 'widgets.php' !== $pagenow ) ) {
0 ignored issues
show
Unused Code introduced by
The call to Request::is_admin() has too many arguments starting with $hook.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
Unused Code introduced by
The call to Frontend_Request::is_admin() has too many arguments starting with $hook.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
224
			return;
225
		}
226
227
		$script_min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
228
		$script_source = empty( $script_min ) ? '/source' : '';
229
230
		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 );
231
232
		wp_localize_script( 'gravityview_searchwidget_admin', 'gvSearchVar', array(
233
			'nonce' => wp_create_nonce( 'gravityview_ajaxsearchwidget' ),
234
			'label_nofields' => esc_html__( 'No search fields configured yet.', 'gravityview' ),
235
			'label_addfield' => esc_html__( 'Add Search Field', 'gravityview' ),
236
			'label_label' => esc_html__( 'Label', 'gravityview' ),
237
			'label_searchfield' => esc_html__( 'Search Field', 'gravityview' ),
238
			'label_inputtype' => esc_html__( 'Input Type', 'gravityview' ),
239
			'label_ajaxerror' => esc_html__( 'There was an error loading searchable fields. Save the View or refresh the page to fix this issue.', 'gravityview' ),
240
			'input_labels' => json_encode( self::get_search_input_labels() ),
241
			'input_types' => json_encode( self::get_input_types_by_field_type() ),
242
		) );
243
244
	}
245
246
	/**
247
	 * Add admin script to the no-conflict scripts whitelist
248
	 * @param array $allowed Scripts allowed in no-conflict mode
249
	 * @return array Scripts allowed in no-conflict mode, plus the search widget script
250
	 */
251
	public function register_no_conflict( $allowed ) {
252
		$allowed[] = 'gravityview_searchwidget_admin';
253
		return $allowed;
254
	}
255
256
	/**
257
	 * Ajax
258
	 * Returns the form fields ( only the searchable ones )
259
	 *
260
	 * @access public
261
	 * @return void
262
	 */
263
	public static function get_searchable_fields() {
264
265
		if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'gravityview_ajaxsearchwidget' ) ) {
266
			exit( '0' );
267
		}
268
269
		$form = '';
270
271
		// Fetch the form for the current View
272
		if ( ! empty( $_POST['view_id'] ) ) {
273
274
			$form = gravityview_get_form_id( $_POST['view_id'] );
275
276
		} elseif ( ! empty( $_POST['formid'] ) ) {
277
278
			$form = (int) $_POST['formid'];
279
280
		} elseif ( ! empty( $_POST['template_id'] ) && class_exists( 'GravityView_Ajax' ) ) {
281
282
			$form = GravityView_Ajax::pre_get_form_fields( $_POST['template_id'] );
283
284
		}
285
286
		// fetch form id assigned to the view
287
		$response = self::render_searchable_fields( $form );
288
289
		exit( $response );
290
	}
291
292
	/**
293
	 * Generates html for the available Search Fields dropdown
294
	 * @param  int $form_id
295
	 * @param  string $current (for future use)
296
	 * @return string
297
	 */
298
	public static function render_searchable_fields( $form_id = null, $current = '' ) {
299
300
		if ( is_null( $form_id ) ) {
301
			return '';
302
		}
303
304
		// start building output
305
306
		$output = '<select class="gv-search-fields">';
307
308
		$custom_fields = array(
309
			'search_all' => array(
310
				'text' => esc_html__( 'Search Everything', 'gravityview' ),
311
				'type' => 'text',
312
			),
313
			'entry_date' => array(
314
				'text' => esc_html__( 'Entry Date', 'gravityview' ),
315
				'type' => 'date',
316
			),
317
			'entry_id' => array(
318
				'text' => esc_html__( 'Entry ID', 'gravityview' ),
319
				'type' => 'text',
320
			),
321
			'created_by' => array(
322
				'text' => esc_html__( 'Entry Creator', 'gravityview' ),
323
				'type' => 'created_by',
324
			),
325
			'is_starred' => array(
326
				'text' => esc_html__( 'Is Starred', 'gravityview' ),
327
				'type' => 'boolean',
328
			),
329
		);
330
331
		if ( gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
332
			$custom_fields['is_approved'] = array(
333
				'text' => esc_html__( 'Approval Status', 'gravityview' ),
334
				'type' => 'multi',
335
			);
336
		}
337
338
		foreach( $custom_fields as $custom_field_key => $custom_field ) {
339
			$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'] );
340
		}
341
342
		// Get fields with sub-inputs and no parent
343
		$fields = gravityview_get_form_fields( $form_id, true, true );
344
345
		/**
346
		 * @filter `gravityview/search/searchable_fields` Modify the fields that are displayed as searchable in the Search Bar dropdown\n
347
		 * @since 1.17
348
		 * @see gravityview_get_form_fields() Used to fetch the fields
349
		 * @see GravityView_Widget_Search::get_search_input_types See this method to modify the type of input types allowed for a field
350
		 * @param array $fields Array of searchable fields, as fetched by gravityview_get_form_fields()
351
		 * @param  int $form_id
352
		 */
353
		$fields = apply_filters( 'gravityview/search/searchable_fields', $fields, $form_id );
354
355
		if ( ! empty( $fields ) ) {
356
357
			$blacklist_field_types = apply_filters( 'gravityview_blacklist_field_types', array( 'fileupload', 'post_image', 'post_id', 'section' ), null );
358
359
			foreach ( $fields as $id => $field ) {
360
361
				if ( in_array( $field['type'], $blacklist_field_types ) ) {
362
					continue;
363
				}
364
365
				$types = self::get_search_input_types( $id, $field['type'] );
366
367
				$output .= '<option value="'. $id .'" '. selected( $id, $current, false ).'data-inputtypes="'. esc_attr( $types ) .'">'. esc_html( $field['label'] ) .'</option>';
368
			}
369
		}
370
371
		$output .= '</select>';
372
373
		return $output;
374
375
	}
376
377
	/**
378
	 * Assign an input type according to the form field type
379
	 *
380
	 * @see admin-search-widget.js
381
	 *
382
	 * @param string|int|float $field_id Gravity Forms field ID
383
	 * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
384
	 *
385
	 * @return string GV field search input type ('multi', 'boolean', 'select', 'date', 'text')
386
	 */
387
	public static function get_search_input_types( $field_id = '', $field_type = null ) {
388
389
		// @todo - This needs to be improved - many fields have . including products and addresses
390
		if ( false !== strpos( (string) $field_id, '.' ) && in_array( $field_type, array( 'checkbox' ) ) || in_array( $field_id, array( 'is_fulfilled' ) ) ) {
391
			$input_type = 'boolean'; // on/off checkbox
392
		} elseif ( in_array( $field_type, array( 'checkbox', 'post_category', 'multiselect' ) ) ) {
393
			$input_type = 'multi'; //multiselect
394
		} elseif ( in_array( $field_type, array( 'select', 'radio' ) ) ) {
395
			$input_type = 'select';
396
		} elseif ( in_array( $field_type, array( 'date' ) ) || in_array( $field_id, array( 'payment_date' ) ) ) {
397
			$input_type = 'date';
398
		} elseif ( in_array( $field_type, array( 'number' ) ) || in_array( $field_id, array( 'payment_amount' ) ) ) {
399
			$input_type = 'number';
400
		} elseif ( in_array( $field_type, array( 'product' ) ) ) {
401
			$input_type = 'product';
402
		} else {
403
			$input_type = 'text';
404
		}
405
406
		/**
407
		 * @filter `gravityview/extension/search/input_type` Modify the search form input type based on field type
408
		 * @since 1.2
409
		 * @since 1.19.2 Added $field_id parameter
410
		 * @param string $input_type Assign an input type according to the form field type. Defaults: `boolean`, `multi`, `select`, `date`, `text`
411
		 * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
412
		 * @param string|int|float $field_id ID of the field being processed
413
		 */
414
		$input_type = apply_filters( 'gravityview/extension/search/input_type', $input_type, $field_type, $field_id );
415
416
		return $input_type;
417
	}
418
419
	/**
420
	 * Display hidden fields to add support for sites using Default permalink structure
421
	 *
422
	 * @since 1.8
423
	 * @return array Search fields, modified if not using permalinks
424
	 */
425 4
	public function add_no_permalink_fields( $search_fields, $object, $widget_args = array() ) {
0 ignored issues
show
Unused Code introduced by
The parameter $object is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
426
		/** @global WP_Rewrite $wp_rewrite */
427 4
		global $wp_rewrite;
428
429
		// Support default permalink structure
430 4
		if ( false === $wp_rewrite->using_permalinks() ) {
431
432
			// By default, use current post.
433 4
			$post_id = 0;
434
435
			// We're in the WordPress Widget context, and an overriding post ID has been set.
436 4
			if ( ! empty( $widget_args['post_id'] ) ) {
437
				$post_id = absint( $widget_args['post_id'] );
438
			}
439
			// We're in the WordPress Widget context, and the base View ID should be used
440 4
			else if ( ! empty( $widget_args['view_id'] ) ) {
441
				$post_id = absint( $widget_args['view_id'] );
442
			}
443
444 4
			$args = gravityview_get_permalink_query_args( $post_id );
445
446
			// Add hidden fields to the search form
447 4
			foreach ( $args as $key => $value ) {
0 ignored issues
show
Bug introduced by
The expression $args of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
448 4
				$search_fields[] = array(
449 4
					'name'  => $key,
450 4
					'input' => 'hidden',
451 4
					'value' => $value,
452
				);
453
			}
454
		}
455
456 4
		return $search_fields;
457
	}
458
459
	/**
460
	 * Get the fields that are searchable for a View
461
	 *
462
	 * @since 2.0
463
	 * @since 2.0.9 Added $with_full_field parameter
464
	 *
465
	 * @param \GV\View|null $view
466
	 * @param bool $with_full_field Return full field array, or just field ID? Default: false (just field ID)
467
	 *
468
	 * TODO: Move to \GV\View, perhaps? And return a Field_Collection
469
	 * TODO: Use in gravityview()->request->is_search() to calculate whether a valid search
470
	 *
471
	 * @return array If no View, returns empty array. Otherwise, returns array of fields configured in widgets and Search Bar for a View
472
	 */
473 39
	private function get_view_searchable_fields( $view, $with_full_field = false ) {
474
475
		/**
476
		 * Find all search widgets on the view and get the searchable fields settings.
477
		 */
478 39
		$searchable_fields = array();
479
480 39
		if ( ! $view ) {
481
			return $searchable_fields;
482
		}
483
484
		/**
485
		 * Include the sidebar Widgets.
486
		 */
487 39
		$widgets = (array) get_option( 'widget_gravityview_search', array() );
488
489 39
		foreach ( $widgets as $widget ) {
490 39
			if ( ! empty( $widget['view_id'] ) && $widget['view_id'] == $view->ID ) {
491
				if( $_fields = json_decode( $widget['search_fields'], true ) ) {
492
					foreach ( $_fields as $field ) {
493
						if ( empty( $field['form_id'] ) ) {
494
							$field['form_id'] = $view->form ? $view->form->ID : 0;
495
						}
496
						$searchable_fields[] = $with_full_field ? $field : $field['field'];
497
					}
498
				}
499
			}
500
		}
501
502 39
		foreach ( $view->widgets->by_id( $this->get_widget_id() )->all() as $widget ) {
503 33
			if( $_fields = json_decode( $widget->configuration->get( 'search_fields' ), true ) ) {
504 33
				foreach ( $_fields as $field ) {
505 33
					if ( empty( $field['form_id'] ) ) {
506 33
						$field['form_id'] = $view->form ? $view->form->ID : 0;
507
					}
508 33
					$searchable_fields[] = $with_full_field ? $field : $field['field'];
509
				}
510
			}
511
		}
512
513
		/**
514
		 * @filter `gravityview/search/searchable_fields/whitelist` Modifies the fields able to be searched using the Search Bar
515
		 * @since 2.5.1
516
		 *
517
		 * @param array $searchable_fields Array of GravityView-formatted fields or only the field ID? Example: [ '1.2', 'created_by' ]
518
		 * @param \GV\View $view Object of View being searched.
519
		 * @param bool $with_full_field Does $searchable_fields contain the full field array or just field ID? Default: false (just field ID)
520
		 */
521 39
		return apply_filters( 'gravityview/search/searchable_fields/whitelist', $searchable_fields, $view, $with_full_field );
522
	}
523
524
	/** --- Frontend --- */
525
526
	/**
527
	 * Calculate the search criteria to filter entries
528
	 * @param array $search_criteria The search criteria
529
	 * @param int $form_id The form ID
530
	 * @param array $args Some args
531
	 *
532
	 * @param bool $force_search_criteria Whether to suppress GF_Query filter, internally used in self::gf_query_filter
533
	 *
534
	 * @return array
535
	 */
536 86
	public function filter_entries( $search_criteria, $form_id = null, $args = array(), $force_search_criteria = false ) {
537 86
		if ( ! $force_search_criteria && gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
538
			/**
539
			 * If GF_Query is available, we can construct custom conditions with nested
540
			 * booleans on the query, giving up the old ways of flat search_criteria field_filters.
541
			 */
542 66
			add_action( 'gravityview/view/query', array( $this, 'gf_query_filter' ), 10, 3 );
543 66
			return $search_criteria; // Return the original criteria, GF_Query modification kicks in later
544
		}
545
546 85
		if( 'post' === $this->search_method ) {
547
			$get = $_POST;
548
		} else {
549 85
			$get = $_GET;
550
		}
551
552 85
		$view = \GV\View::by_id( \GV\Utils::get( $args, 'id' ) );
553
554 85
		gravityview()->log->debug( 'Requested $_{method}: ', array( 'method' => $this->search_method, 'data' => $get ) );
555
556 85
		if ( empty( $get ) || ! is_array( $get ) ) {
557 58
			return $search_criteria;
558
		}
559
560 40
		$get = stripslashes_deep( $get );
561
562 40
		$get = gv_map_deep( $get, 'rawurldecode' );
563
564
		// Make sure array key is set up
565 40
		$search_criteria['field_filters'] = \GV\Utils::get( $search_criteria, 'field_filters', array() );
566
567 40
		$searchable_fields = $this->get_view_searchable_fields( $view );
568 40
		$searchable_field_objects = $this->get_view_searchable_fields( $view, true );
569
570
		// add free search
571 40
		if ( isset( $get['gv_search'] ) && '' !== $get['gv_search'] && in_array( 'search_all', $searchable_fields ) ) {
572
573 5
			$search_all_value = trim( $get['gv_search'] );
574
575
			/**
576
			 * @filter `gravityview/search-all-split-words` Search for each word separately or the whole phrase?
577
			 * @since 1.20.2
578
			 * @param bool $split_words True: split a phrase into words; False: search whole word only [Default: true]
579
			 */
580 5
			$split_words = apply_filters( 'gravityview/search-all-split-words', true );
581
582 5
			if ( $split_words ) {
583
584
				// Search for a piece
585 5
				$words = explode( ' ', $search_all_value );
586
587 5
				$words = array_filter( $words );
588
589
			} else {
590
591
				// Replace multiple spaces with one space
592 1
				$search_all_value = preg_replace( '/\s+/ism', ' ', $search_all_value );
593
594 1
				$words = array( $search_all_value );
595
			}
596
597 5
			foreach ( $words as $word ) {
598 5
				$search_criteria['field_filters'][] = array(
599 5
					'key' => null, // The field ID to search
600 5
					'value' => $word, // The value to search
601 5
					'operator' => 'contains', // What to search in. Options: `is` or `contains`
602
				);
603
			}
604
		}
605
606
		// start date & end date
607 40
		if ( in_array( 'entry_date', $searchable_fields ) ) {
608
			/**
609
			 * Get and normalize the dates according to the input format.
610
			 */
611 12
			if ( $curr_start = ! empty( $get['gv_start'] ) ? $get['gv_start'] : '' ) {
612 12
				if( $curr_start_date = date_create_from_format( $this->get_datepicker_format( true ), $curr_start ) ) {
613 12
					$curr_start = $curr_start_date->format( 'Y-m-d' );
614
				}
615
			}
616
617 12
			if ( $curr_end = ! empty( $get['gv_start'] ) ? ( ! empty( $get['gv_end'] ) ? $get['gv_end'] : '' ) : '' ) {
618 12
				if( $curr_end_date = date_create_from_format( $this->get_datepicker_format( true ), $curr_end ) ) {
619 12
					$curr_end = $curr_end_date->format( 'Y-m-d' );
620
				}
621
			}
622
623 12
			if ( $view ) {
624
				/**
625
				 * Override start and end dates if View is limited to some already.
626
				 */
627 12
				if ( $start_date = $view->settings->get( 'start_date' ) ) {
628 1
					if ( $start_timestamp = strtotime( $curr_start ) ) {
629 1
						$curr_start = $start_timestamp < strtotime( $start_date ) ? $start_date : $curr_start;
630
					}
631
				}
632 12
				if ( $end_date = $view->settings->get( 'end_date' ) ) {
633
					if ( $end_timestamp = strtotime( $curr_end ) ) {
634
						$curr_end = $end_timestamp > strtotime( $end_date ) ? $end_date : $curr_end;
635
					}
636
				}
637
			}
638
639
			/**
640
			 * @filter `gravityview_date_created_adjust_timezone` Whether to adjust the timezone for entries. \n
641
			 * date_created is stored in UTC format. Convert search date into UTC (also used on templates/fields/date_created.php)
642
			 * @since 1.12
643
			 * @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
644
			 * @param[in] string $context Where the filter is being called from. `search` in this case.
645
			 */
646 12
			$adjust_tz = apply_filters( 'gravityview_date_created_adjust_timezone', true, 'search' );
647
648
			/**
649
			 * Don't set $search_criteria['start_date'] if start_date is empty as it may lead to bad query results (GFAPI::get_entries)
650
			 */
651 12
			if ( ! empty( $curr_start ) ) {
652 12
				$curr_start = date( 'Y-m-d H:i:s', strtotime( $curr_start ) );
653 12
				$search_criteria['start_date'] = $adjust_tz ? get_gmt_from_date( $curr_start ) : $curr_start;
654
			}
655
656 12
			if ( ! empty( $curr_end ) ) {
657
				// Fast-forward 24 hour on the end time
658 12
				$curr_end = date( 'Y-m-d H:i:s', strtotime( $curr_end ) + DAY_IN_SECONDS );
659 12
				$search_criteria['end_date'] = $adjust_tz ? get_gmt_from_date( $curr_end ) : $curr_end;
660 12
				if ( strpos( $search_criteria['end_date'], '00:00:00' ) ) { // See https://github.com/gravityview/GravityView/issues/1056
661 12
					$search_criteria['end_date'] = date( 'Y-m-d H:i:s', strtotime( $search_criteria['end_date'] ) - 1 );
662
				}
663
			}
664
		}
665
666
		// search for a specific entry ID
667 40
		if ( ! empty( $get[ 'gv_id' ] ) && in_array( 'entry_id', $searchable_fields ) ) {
668 2
			$search_criteria['field_filters'][] = array(
669 2
				'key' => 'id',
670 2
				'value' => absint( $get[ 'gv_id' ] ),
671 2
				'operator' => $this->get_operator( $get, 'gv_id', array( '=' ), '=' ),
672
			);
673
		}
674
675
		// search for a specific Created_by ID
676 40
		if ( ! empty( $get[ 'gv_by' ] ) && in_array( 'created_by', $searchable_fields ) ) {
677 4
			$search_criteria['field_filters'][] = array(
678 4
				'key' => 'created_by',
679 4
				'value' => $get['gv_by'],
680 4
				'operator' => $this->get_operator( $get, 'gv_by', array( '=' ), '=' ),
681
			);
682
		}
683
684
		// Get search mode passed in URL
685 40
		$mode = isset( $get['mode'] ) && in_array( $get['mode'], array( 'any', 'all' ) ) ?  $get['mode'] : 'any';
686
687
		// get the other search filters
688 40
		foreach ( $get as $key => $value ) {
689
690 40
			if ( 0 !== strpos( $key, 'filter_' ) || gv_empty( $value, false, false ) || ( is_array( $value ) && count( $value ) === 1 && gv_empty( $value[0], false, false ) ) ) {
691 27
				continue; // Not a filter, or empty
692
			}
693
694 17
			if ( strpos( $key, '|op' ) !== false ) {
695 1
				continue; // This is an operator
696
			}
697
698 17
			$filter_key = $this->convert_request_key_to_filter_key( $key );
699
700 17
			if ( ! $filter = $this->prepare_field_filter( $filter_key, $value, $view, $searchable_field_objects, $get ) ) {
701 3
				continue;
702
			}
703
704 16
			if ( ! isset( $filter['operator'] ) ) {
705 7
				$filter['operator'] = $this->get_operator( $get, $key, array( 'contains' ), 'contains' );
706
			}
707
708 16
			if ( isset( $filter[0]['value'] ) ) {
709
				$search_criteria['field_filters'] = array_merge( $search_criteria['field_filters'], $filter );
710
711
				// if date range type, set search mode to ALL
712
				if ( ! empty( $filter[0]['operator'] ) && in_array( $filter[0]['operator'], array( '>=', '<=', '>', '<' ) ) ) {
713
					$mode = 'all';
714
				}
715 16
			} elseif( !empty( $filter ) ) {
716 16
				$search_criteria['field_filters'][] = $filter;
717
			}
718
		}
719
720
		/**
721
		 * @filter `gravityview/search/mode` Set the Search Mode (`all` or `any`)
722
		 * @since 1.5.1
723
		 * @param[out,in] string $mode Search mode (`any` vs `all`)
724
		 */
725 40
		$search_criteria['field_filters']['mode'] = apply_filters( 'gravityview/search/mode', $mode );
726
727 40
		gravityview()->log->debug( 'Returned Search Criteria: ', array( 'data' => $search_criteria ) );
728
729 40
		unset( $get );
730
731 40
		return $search_criteria;
732
	}
733
734
	/**
735
	 * Filters the \GF_Query with advanced logic.
736
	 *
737
	 * Dropin for the legacy flat filters when \GF_Query is available.
738
	 *
739
	 * @param \GF_Query $query The current query object reference
740
	 * @param \GV\View $this The current view object
0 ignored issues
show
Bug introduced by
There is no parameter named $this. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
741
	 * @param \GV\Request $request The request object
742
	 */
743 65
	public function gf_query_filter( &$query, $view, $request ) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
744
		/**
745
		 * This is a shortcut to get all the needed search criteria.
746
		 * We feed these into an new GF_Query and tack them onto the current object.
747
		 */
748 65
		$search_criteria = $this->filter_entries( array(), null, array( 'id' => $view->ID ), true /** force search_criteria */ );
749
750
		/**
751
		 * Call any userland filters that they might have.
752
		 */
753 65
		remove_filter( 'gravityview_fe_search_criteria', array( $this, 'filter_entries' ), 10, 3 );
754 65
		$search_criteria = apply_filters( 'gravityview_fe_search_criteria', $search_criteria, $view->form->ID, $view->settings->as_atts() );
755 65
		add_filter( 'gravityview_fe_search_criteria', array( $this, 'filter_entries' ), 10, 3 );
756
757 65
		$query_class = $view->get_query_class();
758
759 65
		if ( empty( $search_criteria['field_filters'] ) ) {
760 57
			return;
761
		}
762
763 20
		$widgets = $view->widgets->by_id( $this->widget_id );
764 20
		if ( $widgets->count() ) {
765 14
			$widgets = $widgets->all();
766 14
			$widget  = $widgets[0];
767
768 14
			$search_fields = json_decode( $widget->configuration->get( 'search_fields' ), true );
769
770 14
			foreach ( (array) $search_fields as $search_field ) {
771 14
				if ( 'created_by' === $search_field['field'] && 'input_text' === $search_field['input'] ) {
772 1
					$created_by_text_mode = true;
773
				}
774
			}
775
		}
776
777 20
		$extra_conditions = array();
778 20
		$mode = 'any';
779
780 20
		foreach ( $search_criteria['field_filters'] as &$filter ) {
781 20
			if ( ! is_array( $filter ) ) {
782 20
				if ( in_array( strtolower( $filter ), array( 'any', 'all' ) ) ) {
783 20
					$mode = $filter;
784
				}
785 20
				continue;
786
			}
787
788
			// Construct a manual query for unapproved statuses
789 13
			if ( 'is_approved' === $filter['key'] && in_array( \GravityView_Entry_Approval_Status::UNAPPROVED, (array) $filter['value'] ) ) {
790 2
				$_tmp_query       = new $query_class( $view->form->ID, array(
791 2
					'field_filters' => array(
792
						array(
793 2
							'operator' => 'in',
794 2
							'key'      => 'is_approved',
795 2
							'value'    => (array) $filter['value'],
796
						),
797
						array(
798
							'operator' => 'is',
799
							'key'      => 'is_approved',
800
							'value'    => '',
801
						),
802 2
						'mode' => 'any'
803
					),
804
				) );
805 2
				$_tmp_query_parts = $_tmp_query->_introspect();
806
807 2
				$extra_conditions[] = $_tmp_query_parts['where'];
808
809 2
				$filter = false;
810 2
				continue;
811
			}
812
813
			// Construct manual query for text mode creator search
814 13
			if ( 'created_by' === $filter['key'] && ! empty( $created_by_text_mode ) ) {
815 1
				$extra_conditions[] = new GravityView_Widget_Search_Author_GF_Query_Condition( $filter, $view );
816 1
				$filter = false;
817 1
				continue;
818
			}
819
820
			// By default, we want searches to be wildcard for each field.
821 12
			$filter['operator'] = empty( $filter['operator'] ) ? 'contains' : $filter['operator'];
822
823
			// For multichoice, let's have an in (OR) search.
824 12
			if ( is_array( $filter['value'] ) ) {
825 3
				$filter['operator'] = 'in'; // @todo what about in contains (OR LIKE chains)?
826
			}
827
828
			// Default form with joins functionality
829 12
			if ( empty( $filter['form_id'] ) ) {
830 5
				$filter['form_id'] = $view->form ? $view->form->ID : 0;
831
			}
832
833
			/**
834
			 * @filter `gravityview_search_operator` Modify the search operator for the field (contains, is, isnot, etc)
835
			 * @param string $operator Existing search operator
836
			 * @param array $filter array with `key`, `value`, `operator`, `type` keys
837
			 * @since develop
838
			 * @param \GV\View $view The View we're operating on.
839
			 */
840 12
			$filter['operator'] = apply_filters( 'gravityview_search_operator', $filter['operator'], $filter, $view );
841
		}
842
843 20
		if ( ! empty( $search_criteria['start_date'] ) || ! empty( $search_criteria['end_date'] ) ) {
844 1
			$date_criteria = array();
845
846 1
			if ( isset( $search_criteria['start_date'] ) ) {
847 1
				$date_criteria['start_date'] = $search_criteria['start_date'];
848
			}
849
850 1
			if ( isset( $search_criteria['end_date'] ) ) {
851 1
				$date_criteria['end_date'] = $search_criteria['end_date'];
852
			}
853
854 1
			$_tmp_query         = new $query_class( $view->form->ID, $date_criteria );
855 1
			$_tmp_query_parts   = $_tmp_query->_introspect();
856 1
			$extra_conditions[] = $_tmp_query_parts['where'];
857
		}
858
859 20
		$search_conditions = array();
860
861 20
		if ( $filters = array_filter( $search_criteria['field_filters'] ) ) {
862 20
			foreach ( $filters as &$filter ) {
863 20
				if ( ! is_array( $filter ) ) {
864 20
					continue;
865
				}
866
867
				/**
868
				 * Parse the filter criteria to generate the needed
869
				 * WHERE condition. This is a trick to not write our own generation
870
				 * code by reusing what's inside GF_Query already as they
871
				 * take care of many small things like forcing numeric, etc.
872
				 */
873 12
				$_tmp_query       = new $query_class( $filter['form_id'], array( 'mode' => 'any', 'field_filters' => array( $filter ) ) );
874 12
				$_tmp_query_parts = $_tmp_query->_introspect();
875 12
				$search_condition = $_tmp_query_parts['where'];
876
877 12
				if ( empty( $filter['key'] ) && $search_condition->expressions ) {
878 1
					$search_conditions[] = $search_condition;
879
				} else {
880 11
					$left = $search_condition->left;
881 11
					$alias = $query->_alias( $left->field_id, $left->source, $left->is_entry_column() ? 't' : 'm' );
882
883 11
					if ( $view->joins && $left->field_id == GF_Query_Column::META ) {
884 2
						foreach ( $view->joins as $_join ) {
885 2
							$on = $_join->join_on;
886 2
							$join = $_join->join;
887
888 2
							$search_conditions[] = GF_Query_Condition::_or(
889
								// Join
890 2
								new GF_Query_Condition(
891 2
									new GF_Query_Column( GF_Query_Column::META, $join->ID, $query->_alias( GF_Query_Column::META, $join->ID, 'm' ) ),
892 2
									$search_condition->operator,
893 2
									$search_condition->right
894
								),
895
								// On
896 2
								new GF_Query_Condition(
897 2
									new GF_Query_Column( GF_Query_Column::META, $on->ID, $query->_alias( GF_Query_Column::META, $on->ID, 'm' ) ),
898 2
									$search_condition->operator,
899 2
									$search_condition->right
900
								)
901
							);
902
						}
903
					} else {
904 9
						$search_conditions[] = new GF_Query_Condition(
905 9
							new GF_Query_Column( $left->field_id, $left->source, $alias ),
906 9
							$search_condition->operator,
907 9
							$search_condition->right
908
						);
909
					}
910
				}
911
			}
912
913 20
			if ( $search_conditions ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $search_conditions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
914 12
				$search_conditions = array( call_user_func_array( '\GF_Query_Condition::' . ( $mode == 'all' ? '_and' : '_or' ), $search_conditions ) );
915
			}
916
		}
917
918
		/**
919
		 * Grab the current clauses. We'll be combining them shortly.
920
		 */
921 20
		$query_parts = $query->_introspect();
922
923
		/**
924
		 * Combine the parts as a new WHERE clause.
925
		 */
926 20
		$where = call_user_func_array( '\GF_Query_Condition::_and', array_merge( array( $query_parts['where'] ), $search_conditions, $extra_conditions ) );
927 20
		$query->where( $where );
928 20
	}
929
930
	/**
931
	 * Convert $_GET/$_POST key to the field/meta ID
932
	 *
933
	 * Examples:
934
	 * - `filter_is_starred` => `is_starred`
935
	 * - `filter_1_2` => `1.2`
936
	 * - `filter_5` => `5`
937
	 *
938
	 * @since 2.0
939
	 *
940
	 * @param string $key $_GET/_$_POST search key
941
	 *
942
	 * @return string
943
	 */
944 17
	private function convert_request_key_to_filter_key( $key ) {
945
946 17
		$field_id = str_replace( 'filter_', '', $key );
947
948
		// calculates field_id, removing 'filter_' and for '_' for advanced fields ( like name or checkbox )
949 17
		if ( preg_match('/^[0-9_]+$/ism', $field_id ) ) {
950 15
			$field_id = str_replace( '_', '.', $field_id );
951
		}
952
953 17
		return $field_id;
954
	}
955
956
	/**
957
	 * Prepare the field filters to GFAPI
958
	 *
959
	 * 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.
960
	 *
961
	 * Format searched values
962
	 *
963
	 * @param  string $filter_key ID of the field, or entry meta key
964
	 * @param  string $value $_GET/$_POST search value
965
	 * @param  \GV\View $view The view we're looking at
966
	 * @param array[] $searchable_fields The searchable fields as configured by the widget.
967
	 * @param string[] $get The $_GET/$_POST array.
968
	 *
969
	 * @since develop Added 5th $get parameter for operator overrides.
970
	 * @todo Set function as private.
971
	 *
972
	 * @return array|false 1 or 2 deph levels, false if not allowed
973
	 */
974 17
	public function prepare_field_filter( $filter_key, $value, $view, $searchable_fields, $get = array() ) {
975 17
		$key = $filter_key;
976 17
		$filter_key = explode( ':', $filter_key ); // field_id, form_id
977
978 17
		$form = null;
979
980 17
		if ( count( $filter_key ) > 1 ) {
981
			// form is specified
982 1
			list( $field_id, $form_id ) = $filter_key;
983
984 1
			if ( $forms = \GV\View::get_joined_forms( $view->ID ) ) {
985 1
				if ( ! $form = \GV\GF_Form::by_id( $form_id ) ) {
986
					return false;
987
				}
988
			}
989
990
			// form is allowed
991 1
			$found = false;
992 1
			foreach ( $forms as $form ) {
993 1
				if ( $form->ID == $form_id ) {
994 1
					$found = true;
995 1
					break;
996
				}
997
			}
998
999 1
			if ( ! $found ) {
1000
				return false;
1001
			}
1002
1003
			// form is in searchable fields
1004 1
			$found = false;
1005 1
			foreach ( $searchable_fields as $field ) {
1006 1
				if ( $field_id == $field['field'] && $form->ID == $field['form_id'] ) {
1007 1
					$found = true;
1008 1
					break;
1009
				}
1010
			}
1011
1012 1
			if ( ! $found ) {
1013 1
				return false;
1014
			}
1015
		} else {
1016 17
			$field_id = reset( $filter_key );
1017 17
			$searchable_fields = wp_list_pluck( $searchable_fields, 'field' );
1018 17
			if ( ! in_array( 'search_all', $searchable_fields ) && ! in_array( $field_id, $searchable_fields ) ) {
1019 2
				return false;
1020
			}
1021
		}
1022
1023 16
		if ( ! $form ) {
1024
			// fallback
1025 16
			$form = $view->form;
1026
		}
1027
1028
		// get form field array
1029 16
		$form_field = is_numeric( $field_id ) ? \GV\GF_Field::by_id( $form, $field_id ) : \GV\Internal_Field::by_id( $field_id );
1030
1031 16
		if ( ! $form_field ) {
1032
			return false;
1033
		}
1034
1035
		// default filter array
1036
		$filter = array(
1037 16
			'key'   => $field_id,
1038 16
			'value' => $value,
1039 16
			'form_id' => $form->ID,
1040
		);
1041
1042 16
		switch ( $form_field->type ) {
1043
1044 16
			case 'select':
1045 16
			case 'radio':
1046 1
				$filter['operator'] = $this->get_operator( $get, $key, array( 'is' ), 'is' );
1047 1
				break;
1048
1049 15
			case 'post_category':
1050
1051
				if ( ! is_array( $value ) ) {
1052
					$value = array( $value );
1053
				}
1054
1055
				// Reset filter variable
1056
				$filter = array();
1057
1058
				foreach ( $value as $val ) {
1059
					$cat = get_term( $val, 'category' );
1060
					$filter[] = array(
1061
						'key'      => $field_id,
1062
						'value'    => esc_attr( $cat->name ) . ':' . $val,
1063
						'operator' => $this->get_operator( $get, $key, array( 'is' ), 'is' ),
1064
					);
1065
				}
1066
1067
				break;
1068
1069 15
			case 'multiselect':
1070
1071
				if ( ! is_array( $value ) ) {
1072
					break;
1073
				}
1074
1075
				// Reset filter variable
1076
				$filter = array();
1077
1078
				foreach ( $value as $val ) {
1079
					$filter[] = array( 'key' => $field_id, 'value' => $val );
1080
				}
1081
1082
				break;
1083
1084 15
			case 'checkbox':
1085
				// convert checkbox on/off into the correct search filter
1086
				if ( false !== strpos( $field_id, '.' ) && ! empty( $form_field->inputs ) && ! empty( $form_field->choices ) ) {
1087
					foreach ( $form_field->inputs as $k => $input ) {
1088
						if ( $input['id'] == $field_id ) {
1089
							$filter['value'] = $form_field->choices[ $k ]['value'];
1090
							$filter['operator'] = $this->get_operator( $get, $key, array( 'is' ), 'is' );
1091
							break;
1092
						}
1093
					}
1094
				} elseif ( is_array( $value ) ) {
1095
1096
					// Reset filter variable
1097
					$filter = array();
1098
1099
					foreach ( $value as $val ) {
1100
						$filter[] = array(
1101
							'key'      => $field_id,
1102
							'value'    => $val,
1103
							'operator' => $this->get_operator( $get, $key, array( 'is' ), 'is' ),
1104
						);
1105
					}
1106
				}
1107
1108
				break;
1109
1110 15
			case 'name':
1111 15
			case 'address':
1112
1113 1
				if ( false === strpos( $field_id, '.' ) ) {
1114
1115
					$words = explode( ' ', $value );
1116
1117
					$filters = array();
1118
					foreach ( $words as $word ) {
1119
						if ( ! empty( $word ) && strlen( $word ) > 1 ) {
1120
							// Keep the same key for each filter
1121
							$filter['value'] = $word;
1122
							// Add a search for the value
1123
							$filters[] = $filter;
1124
						}
1125
					}
1126
1127
					$filter = $filters;
1128
				}
1129
1130
				// State/Province should be exact matches
1131 1
				if ( 'address' === $form_field->field->type ) {
1132
1133 1
					$searchable_fields = $this->get_view_searchable_fields( $view, true );
1134
1135 1
					foreach ( $searchable_fields as $searchable_field ) {
1136
1137 1
						if( $form_field->ID !== $searchable_field['field'] ) {
1138
							continue;
1139
						}
1140
1141
						// Only exact-match dropdowns, not text search
1142 1
						if( in_array( $searchable_field['input'], array( 'text', 'search' ), true ) ) {
1143 1
							continue;
1144
						}
1145
1146
						$input_id = gravityview_get_input_id_from_id( $form_field->ID );
1147
1148
						if ( 4 === $input_id ) {
1149
							$filter['operator'] = $this->get_operator( $get, $key, array( 'is' ), 'is' );
1150
						};
1151
					}
1152
				}
1153
1154 1
				break;
1155
1156 15
			case 'date':
1157
1158 8
				$date_format = $this->get_datepicker_format( true );
1159
1160 8
				if ( is_array( $value ) ) {
1161
1162
					// Reset filter variable
1163
					$filter = array();
1164
1165
					foreach ( $value as $k => $date ) {
1166
						if ( empty( $date ) ) {
1167
							continue;
1168
						}
1169
						$operator = 'start' === $k ? '>=' : '<=';
1170
1171
						/**
1172
						 * @hack
1173
						 * @since 1.16.3
1174
						 * Safeguard until GF implements '<=' operator
1175
						 */
1176
						if( !GFFormsModel::is_valid_operator( $operator ) && $operator === '<=' ) {
1177
							$operator = '<';
1178
							$date = date( 'Y-m-d', strtotime( self::get_formatted_date( $date, 'Y-m-d', $date_format ) . ' +1 day' ) );
1179
						}
1180
1181
						$filter[] = array(
1182
							'key'      => $field_id,
1183
							'value'    => self::get_formatted_date( $date, 'Y-m-d', $date_format ),
1184
							'operator' => $this->get_operator( $get, $key, array( $operator ), $operator ),
1185
						);
1186
					}
1187
				} else {
1188 8
					$date = $value;
1189 8
					$filter['value'] = self::get_formatted_date( $date, 'Y-m-d', $date_format );
1190 8
					$filter['operator'] = $this->get_operator( $get, $key, array( 'is' ), 'is' );
1191
				}
1192
1193 8
				break;
1194
1195
1196
		} // switch field type
1197
1198 16
		return $filter;
1199
	}
1200
1201
	/**
1202
	 * Get the Field Format form GravityForms
1203
	 *
1204
	 * @param GF_Field_Date $field The field object
1205
	 * @since 1.10
1206
	 *
1207
	 * @return string Format of the date in the database
1208
	 */
1209
	public static function get_date_field_format( GF_Field_Date $field ) {
1210
		$format = 'm/d/Y';
1211
		$datepicker = array(
1212
			'mdy' => 'm/d/Y',
1213
			'dmy' => 'd/m/Y',
1214
			'dmy_dash' => 'd-m-Y',
1215
			'dmy_dot' => 'd.m.Y',
1216
			'ymd_slash' => 'Y/m/d',
1217
			'ymd_dash' => 'Y-m-d',
1218
			'ymd_dot' => 'Y.m.d',
1219
		);
1220
1221
		if ( ! empty( $field->dateFormat ) && isset( $datepicker[ $field->dateFormat ] ) ){
1222
			$format = $datepicker[ $field->dateFormat ];
1223
		}
1224
1225
		return $format;
1226
	}
1227
1228
	/**
1229
	 * Format a date value
1230
	 *
1231
	 * @param string $value Date value input
1232
	 * @param string $format Wanted formatted date
1233
	 *
1234
	 * @since 2.1.2
1235
	 * @param string $value_format The value format. Default: Y-m-d
1236
	 *
1237
	 * @return string
1238
	 */
1239 8
	public static function get_formatted_date( $value = '', $format = 'Y-m-d', $value_format = 'Y-m-d' ) {
1240
1241 8
		$date = date_create_from_format( $value_format, $value );
1242
1243 8
		if ( empty( $date ) ) {
1244
			gravityview()->log->debug( 'Date format not valid: {value}', array( 'value' => $value ) );
1245
			return '';
1246
		}
1247 8
		return $date->format( $format );
1248
	}
1249
1250
1251
	/**
1252
	 * Include this extension templates path
1253
	 * @param array $file_paths List of template paths ordered
1254
	 */
1255 1
	public function add_template_path( $file_paths ) {
1256
1257
		// Index 100 is the default GravityView template path.
1258 1
		$file_paths[102] = self::$file . 'templates/';
1259
1260 1
		return $file_paths;
1261
	}
1262
1263
	/**
1264
	 * Check whether the configured search fields have a date field
1265
	 *
1266
	 * @since 1.17.5
1267
	 *
1268
	 * @param array $search_fields
1269
	 *
1270
	 * @return bool True: has a `date` or `date_range` field
1271
	 */
1272 4
	private function has_date_field( $search_fields ) {
1273
1274 4
		$has_date = false;
1275
1276 4
		foreach ( $search_fields as $k => $field ) {
1277 4
			if ( in_array( $field['input'], array( 'date', 'date_range', 'entry_date' ) ) ) {
1278
				$has_date = true;
1279
				break;
1280
			}
1281
		}
1282
1283 4
		return $has_date;
1284
	}
1285
1286
	/**
1287
	 * Renders the Search Widget
1288
	 * @param array $widget_args
1289
	 * @param string $content
1290
	 * @param string $context
1291
	 *
1292
	 * @return void
1293
	 */
1294 4
	public function render_frontend( $widget_args, $content = '', $context = '' ) {
1295
		/** @var GravityView_View $gravityview_view */
1296 4
		$gravityview_view = GravityView_View::getInstance();
1297
1298 4
		if ( empty( $gravityview_view ) ) {
1299
			gravityview()->log->debug( '$gravityview_view not instantiated yet.' );
1300
			return;
1301
		}
1302
1303
		// get configured search fields
1304 4
		$search_fields = ! empty( $widget_args['search_fields'] ) ? json_decode( $widget_args['search_fields'], true ) : '';
1305
1306 4
		if ( empty( $search_fields ) || ! is_array( $search_fields ) ) {
1307
			gravityview()->log->debug( 'No search fields configured for widget:', array( 'data' => $widget_args ) );
1308
			return;
1309
		}
1310
1311 4
		$view = \GV\View::by_id( $gravityview_view->view_id );
1312
1313
		// prepare fields
1314 4
		foreach ( $search_fields as $k => $field ) {
1315
1316 4
			$updated_field = $field;
1317
1318 4
			$updated_field = $this->get_search_filter_details( $updated_field, $context );
1319
1320 4
			switch ( $field['field'] ) {
1321
1322 4
				case 'search_all':
1323 4
					$updated_field['key'] = 'search_all';
1324 4
					$updated_field['input'] = 'search_all';
1325 4
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_search' );
1326 4
					break;
1327
1328
				case 'entry_date':
1329
					$updated_field['key'] = 'entry_date';
1330
					$updated_field['input'] = 'entry_date';
1331
					$updated_field['value'] = array(
1332
						'start' => $this->rgget_or_rgpost( 'gv_start' ),
1333
						'end' => $this->rgget_or_rgpost( 'gv_end' ),
1334
					);
1335
					break;
1336
1337
				case 'entry_id':
1338
					$updated_field['key'] = 'entry_id';
1339
					$updated_field['input'] = 'entry_id';
1340
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_id' );
1341
					break;
1342
1343
				case 'created_by':
1344
					$updated_field['key'] = 'created_by';
1345
					$updated_field['name'] = 'gv_by';
1346
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_by' );
1347
					$updated_field['choices'] = self::get_created_by_choices( $view );
1348
					break;
1349
1350
				case 'is_approved':
1351
					$updated_field['key'] = 'is_approved';
1352
					$updated_field['value'] = $this->rgget_or_rgpost( 'filter_is_approved' );
1353
					$updated_field['choices'] = self::get_is_approved_choices();
1354
					break;
1355
			}
1356
1357 4
			$search_fields[ $k ] = $updated_field;
1358
		}
1359
1360 4
		gravityview()->log->debug( 'Calculated Search Fields: ', array( 'data' => $search_fields ) );
1361
1362
		/**
1363
		 * @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.
1364
		 * @param array $search_fields Array of search filters with `key`, `label`, `value`, `type`, `choices` keys
1365
		 * @param GravityView_Widget_Search $this Current widget object
1366
		 * @param array $widget_args Args passed to this method. {@since 1.8}
1367
		 * @param \GV\Template_Context $context {@since 2.0}
1368
		 * @var array
1369
		 */
1370 4
		$gravityview_view->search_fields = apply_filters( 'gravityview_widget_search_filters', $search_fields, $this, $widget_args, $context );
0 ignored issues
show
Bug introduced by
The property search_fields does not seem to exist. Did you mean fields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1371
1372 4
		$gravityview_view->permalink_fields = $this->add_no_permalink_fields( array(), $this, $widget_args );
0 ignored issues
show
Bug introduced by
The property permalink_fields does not seem to exist. Did you mean fields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1373
1374 4
		$gravityview_view->search_layout = ! empty( $widget_args['search_layout'] ) ? $widget_args['search_layout'] : 'horizontal';
0 ignored issues
show
Bug introduced by
The property search_layout does not seem to exist in GravityView_View.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1375
1376
		/** @since 1.14 */
1377 4
		$gravityview_view->search_mode = ! empty( $widget_args['search_mode'] ) ? $widget_args['search_mode'] : 'any';
0 ignored issues
show
Bug introduced by
The property search_mode does not seem to exist in GravityView_View.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1378
1379 4
		$custom_class = ! empty( $widget_args['custom_class'] ) ? $widget_args['custom_class'] : '';
1380
1381 4
		$gravityview_view->search_class = self::get_search_class( $custom_class );
0 ignored issues
show
Bug introduced by
The property search_class does not seem to exist in GravityView_View.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1382
1383 4
		$gravityview_view->search_clear = ! empty( $widget_args['search_clear'] ) ? $widget_args['search_clear'] : false;
0 ignored issues
show
Bug introduced by
The property search_clear does not seem to exist in GravityView_View.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1384
1385 4
		if ( $this->has_date_field( $search_fields ) ) {
1386
			// enqueue datepicker stuff only if needed!
1387
			$this->enqueue_datepicker();
1388
		}
1389
1390 4
		$this->maybe_enqueue_flexibility();
1391
1392 4
		$gravityview_view->render( 'widget', 'search', false );
1393 4
	}
1394
1395
	/**
1396
	 * Get the search class for a search form
1397
	 *
1398
	 * @since 1.5.4
1399
	 *
1400
	 * @return string Sanitized CSS class for the search form
1401
	 */
1402 4
	public static function get_search_class( $custom_class = '' ) {
1403 4
		$gravityview_view = GravityView_View::getInstance();
1404
1405 4
		$search_class = 'gv-search-'.$gravityview_view->search_layout;
0 ignored issues
show
Documentation introduced by
The property search_layout does not exist on object<GravityView_View>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1406
1407 4
		if ( ! empty( $custom_class )  ) {
1408
			$search_class .= ' '.$custom_class;
1409
		}
1410
1411
		/**
1412
		 * @filter `gravityview_search_class` Modify the CSS class for the search form
1413
		 * @param string $search_class The CSS class for the search form
1414
		 */
1415 4
		$search_class = apply_filters( 'gravityview_search_class', $search_class );
1416
1417
		// Is there an active search being performed? Used by fe-views.js
1418 4
		$search_class .= GravityView_frontend::getInstance()->isSearch() ? ' gv-is-search' : '';
1419
1420 4
		return gravityview_sanitize_html_class( $search_class );
1421
	}
1422
1423
1424
	/**
1425
	 * Calculate the search form action
1426
	 * @since 1.6
1427
	 *
1428
	 * @return string
1429
	 */
1430 4
	public static function get_search_form_action() {
1431 4
		$gravityview_view = GravityView_View::getInstance();
1432
1433 4
		$post_id = $gravityview_view->getPostId() ? $gravityview_view->getPostId() : $gravityview_view->getViewId();
1434
1435 4
		$url = add_query_arg( array(), get_permalink( $post_id ) );
1436
1437 4
		return esc_url( $url );
1438
	}
1439
1440
	/**
1441
	 * Get the label for a search form field
1442
	 * @param  array $field      Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1443
	 * @param  array $form_field Form field data, as fetched by `gravityview_get_field()`
1444
	 * @return string             Label for the search form
1445
	 */
1446 4
	private static function get_field_label( $field, $form_field = array() ) {
1447
1448 4
		$label = \GV\Utils::_GET( 'label', \GV\Utils::get( $field, 'label' ) );
1449
1450 4
		if ( ! $label ) {
1451
1452 4
			$label = isset( $form_field['label'] ) ? $form_field['label'] : '';
1453
1454 4
			switch( $field['field'] ) {
1455 4
				case 'search_all':
1456 4
					$label = __( 'Search Entries:', 'gravityview' );
1457 4
					break;
1458
				case 'entry_date':
1459
					$label = __( 'Filter by date:', 'gravityview' );
1460
					break;
1461
				case 'entry_id':
1462
					$label = __( 'Entry ID:', 'gravityview' );
1463
					break;
1464
				default:
1465
					// If this is a field input, not a field
1466
					if ( strpos( $field['field'], '.' ) > 0 && ! empty( $form_field['inputs'] ) ) {
1467
1468
						// Get the label for the field in question, which returns an array
1469
						$items = wp_list_filter( $form_field['inputs'], array( 'id' => $field['field'] ) );
1470
1471
						// Get the item with the `label` key
1472
						$values = wp_list_pluck( $items, 'label' );
1473
1474
						// There will only one item in the array, but this is easier
1475
						foreach ( $values as $value ) {
1476
							$label = $value;
1477
							break;
1478
						}
1479
					}
1480
			}
1481
		}
1482
1483
		/**
1484
		 * @filter `gravityview_search_field_label` Modify the label for a search field. Supports returning HTML
1485
		 * @since 1.17.3 Added $field parameter
1486
		 * @param[in,out] string $label Existing label text, sanitized.
1487
		 * @param[in] array $form_field Gravity Forms field array, as returned by `GFFormsModel::get_field()`
1488
		 * @param[in] array $field Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1489
		 */
1490 4
		$label = apply_filters( 'gravityview_search_field_label', esc_attr( $label ), $form_field, $field );
1491
1492 4
		return $label;
1493
	}
1494
1495
	/**
1496
	 * Prepare search fields to frontend render with other details (label, field type, searched values)
1497
	 *
1498
	 * @param array $field
1499
	 * @param \GV\Context $context
1500
	 *
1501
	 * @return array
1502
	 */
1503 4
	private function get_search_filter_details( $field, $context ) {
1504
1505 4
		$gravityview_view = GravityView_View::getInstance();
1506
1507 4
		$form = $gravityview_view->getForm();
1508
1509
		// for advanced field ids (eg, first name / last name )
1510 4
		$name = 'filter_' . str_replace( '.', '_', $field['field'] );
1511
1512
		// get searched value from $_GET/$_POST (string or array)
1513 4
		$value = $this->rgget_or_rgpost( $name );
1514
1515
		// get form field details
1516 4
		$form_field = gravityview_get_field( $form, $field['field'] );
1517
1518
		$filter = array(
1519 4
			'key' => $field['field'],
1520 4
			'name' => $name,
1521 4
			'label' => self::get_field_label( $field, $form_field ),
1522 4
			'input' => $field['input'],
1523 4
			'value' => $value,
1524 4
			'type' => $form_field['type'],
1525
		);
1526
1527
		// collect choices
1528 4
		if ( 'post_category' === $form_field['type'] && ! empty( $form_field['displayAllCategories'] ) && empty( $form_field['choices'] ) ) {
1529
			$filter['choices'] = gravityview_get_terms_choices();
1530 4
		} elseif ( ! empty( $form_field['choices'] ) ) {
1531
			$filter['choices'] = $form_field['choices'];
1532
		}
1533
1534 4
		if ( 'date_range' === $field['input'] && empty( $value ) ) {
1535
			$filter['value'] = array( 'start' => '', 'end' => '' );
1536
		}
1537
1538 4
		if ( ! empty( $filter['choices'] ) ) {
1539
			/**
1540
			 * @filter `gravityview/search/sieve_choices` Only output used choices for this field.
1541
			 * @param[in,out] bool Yes or no.
1542
			 * @param array $field The field configuration.
1543
			 * @param \GV\Context The context.
1544
			 */
1545
			if ( apply_filters( 'gravityview/search/sieve_choices', false, $field, $context ) ) {
1546
				$filter['choices'] = $this->sieve_filter_choices( $filter, $context );
1547
			}
1548
		}
1549
1550
		/**
1551
		 * @filter `gravityview/search/filter_details` Filter the output filter details for the Search widget.
1552
		 * @param[in,out] array $filter The filter details
1553
		 * @param array $field The search field configuration
1554
		 * @param \GV\Context The context
1555
		 * @since develop
1556
		 */
1557 4
		$filter = apply_filters( 'gravityview/search/filter_details', $filter, $field, $context );
1558
1559 4
		return $filter;
1560
1561
	}
1562
1563
	/**
1564
	 * Sieve filter choices to only ones that are used.
1565
	 *
1566
	 * @param array $filter The filter configuration.
1567
	 * @param \GV\Context $context The context
1568
	 *
1569
	 * @since develop
1570
	 * @internal
1571
	 *
1572
	 * @return array The filter choices.
1573
	 */
1574
	private function sieve_filter_choices( $filter, $context ) {
1575
		if ( empty( $filter['key'] ) || empty( $filter['choices'] ) ) {
1576
			return $filter; // @todo Populate plugins might give us empty choices
1577
		}
1578
1579
		if ( ! is_numeric( $filter['key'] ) ) {
1580
			return $filter;
1581
		}
1582
1583
		$form_id = $context->view->form->ID; // @todo Support multiple forms (joins)
0 ignored issues
show
Bug introduced by
The property view does not seem to exist in GV\Context.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1584
1585
		global $wpdb;
1586
1587
		$table = GFFormsModel::get_entry_meta_table_name();
1588
1589
		$key_like = $wpdb->esc_like( $filter['key'] ) . '.%';
1590
1591
		switch ( \GV\Utils::get( $filter, 'type' ) ):
1592
			case 'post_category':
1593
				$choices = $wpdb->get_col( $wpdb->prepare(
1594
					"SELECT DISTINCT SUBSTRING_INDEX(meta_value, ':', 1) FROM $table WHERE (meta_key LIKE %s OR meta_key = %d) AND form_id = %d",
1595
					$key_like, $filter['key'], $form_id
1596
				) );
1597
				break;
1598
			default:
1599
				$choices = $wpdb->get_col( $wpdb->prepare(
1600
					"SELECT DISTINCT meta_value FROM $table WHERE (meta_key LIKE %s OR meta_key = %d) AND form_id = %d",
1601
					$key_like, $filter['key'], $form_id
1602
				) );
1603
1604
				if ( ( $field = gravityview_get_field( $form_id, $filter['key'] ) ) && 'json' === $field->storageType ) {
1605
					$choices = array_map( 'json_decode', $choices );
1606
					$_choices_array = array();
1607
					foreach ( $choices as $choice ) {
1608
						if ( is_array( $choice ) ) {
1609
							$_choices_array = array_merge( $_choices_array, $choice );
1610
						} else {
1611
							$_choices_array []= $choice;
1612
						}
1613
					}
1614
					$choices = array_unique( $_choices_array );
1615
				}
1616
1617
				break;
1618
		endswitch;
1619
1620
		$filter_choices = array();
1621
		foreach ( $filter['choices'] as $choice ) {
1622
			if ( in_array( $choice['text'], $choices, true ) || in_array( $choice['value'], $choices, true ) ) {
1623
				$filter_choices[] = $choice;
1624
			}
1625
		}
1626
1627
		return $filter_choices;
1628
	}
1629
1630
	/**
1631
	 * Calculate the search choices for the users
1632
	 *
1633
	 * @param \GV\View $view The view
1634
	 * @since develop
1635
	 *
1636
	 * @since 1.8
1637
	 *
1638
	 * @return array Array of user choices (value = ID, text = display name)
1639
	 */
1640
	private static function get_created_by_choices( $view ) {
1641
1642
		/**
1643
		 * filter gravityview/get_users/search_widget
1644
		 * @see \GVCommon::get_users
1645
		 */
1646
		$users = GVCommon::get_users( 'search_widget', array( 'fields' => array( 'ID', 'display_name' ) ) );
1647
1648
		$choices = array();
1649
		foreach ( $users as $user ) {
1650
			/**
1651
			 * @filter `gravityview/search/created_by/text` Filter the display text in created by search choices
1652
			 * @since develop
1653
			 * @param string[in,out] The text. Default: $user->display_name
1654
			 * @param \WP_User $user The user.
1655
			 * @param \GV\View $view The view.
1656
			 */
1657
			$text = apply_filters( 'gravityview/search/created_by/text', $user->display_name, $user, $view );
1658
			$choices[] = array(
1659
				'value' => $user->ID,
1660
				'text' => $text,
1661
			);
1662
		}
1663
1664
		return $choices;
1665
	}
1666
1667
	/**
1668
	 * Calculate the search checkbox choices for approval status
1669
	 *
1670
	 * @since develop
1671
	 *
1672
	 * @return array Array of approval status choices (value = status, text = display name)
1673
	 */
1674
	private static function get_is_approved_choices() {
1675
1676
		$choices = array();
1677
		foreach ( GravityView_Entry_Approval_Status::get_all() as $status ) {
1678
			$choices[] = array(
1679
				'value' => $status['value'],
1680
				'text' => $status['label'],
1681
			);
1682
		}
1683
1684
		return $choices;
1685
	}
1686
1687
	/**
1688
	 * Output the Clear Search Results button
1689
	 * @since 1.5.4
1690
	 */
1691 4
	public static function the_clear_search_button() {
1692 4
		$gravityview_view = GravityView_View::getInstance();
1693
1694 4
		if ( $gravityview_view->search_clear ) {
0 ignored issues
show
Documentation introduced by
The property search_clear does not exist on object<GravityView_View>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1695
1696
			$url = strtok( add_query_arg( array() ), '?' );
1697
1698
			echo gravityview_get_link( $url, esc_html__( 'Clear', 'gravityview' ), 'class=button gv-search-clear' );
1699
1700
		}
1701 4
	}
1702
1703
	/**
1704
	 * Based on the search method, fetch the value for a specific key
1705
	 *
1706
	 * @since 1.16.4
1707
	 *
1708
	 * @param string $name Name of the request key to fetch the value for
1709
	 *
1710
	 * @return mixed|string Value of request at $name key. Empty string if empty.
1711
	 */
1712 4
	private function rgget_or_rgpost( $name ) {
1713 4
		$value = \GV\Utils::_REQUEST( $name );
1714
1715 4
		$value = stripslashes_deep( $value );
1716
1717 4
		$value = gv_map_deep( $value, 'rawurldecode' );
1718
1719 4
		$value = gv_map_deep( $value, '_wp_specialchars' );
1720
1721 4
		return $value;
1722
	}
1723
1724
1725
	/**
1726
	 * Require the datepicker script for the frontend GV script
1727
	 * @param array $js_dependencies Array of existing required scripts for the fe-views.js script
1728
	 * @return array Array required scripts, with `jquery-ui-datepicker` added
1729
	 */
1730
	public function add_datepicker_js_dependency( $js_dependencies ) {
1731
1732
		$js_dependencies[] = 'jquery-ui-datepicker';
1733
1734
		return $js_dependencies;
1735
	}
1736
1737
	/**
1738
	 * Modify the array passed to wp_localize_script()
1739
	 *
1740
	 * @param array $js_localization The data padded to the Javascript file
0 ignored issues
show
Bug introduced by
There is no parameter named $js_localization. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1741
	 * @param array $view_data View data array with View settings
1742
	 *
1743
	 * @return array
1744
	 */
1745
	public function add_datepicker_localization( $localizations = array(), $view_data = array() ) {
1746
		global $wp_locale;
1747
1748
		/**
1749
		 * @filter `gravityview_datepicker_settings` Modify the datepicker settings
1750
		 * @see http://api.jqueryui.com/datepicker/ Learn what settings are available
1751
		 * @see http://www.renegadetechconsulting.com/tutorials/jquery-datepicker-and-wordpress-i18n Thanks for the helpful information on $wp_locale
1752
		 * @param array $js_localization The data padded to the Javascript file
1753
		 * @param array $view_data View data array with View settings
1754
		 */
1755
		$datepicker_settings = apply_filters( 'gravityview_datepicker_settings', array(
1756
			'yearRange' => '-5:+5',
1757
			'changeMonth' => true,
1758
			'changeYear' => true,
1759
			'closeText' => esc_attr_x( 'Close', 'Close calendar', 'gravityview' ),
1760
			'prevText' => esc_attr_x( 'Prev', 'Previous month in calendar', 'gravityview' ),
1761
			'nextText' => esc_attr_x( 'Next', 'Next month in calendar', 'gravityview' ),
1762
			'currentText' => esc_attr_x( 'Today', 'Today in calendar', 'gravityview' ),
1763
			'weekHeader' => esc_attr_x( 'Week', 'Week in calendar', 'gravityview' ),
1764
			'monthStatus'       => __( 'Show a different month', 'gravityview' ),
1765
			'monthNames'        => array_values( $wp_locale->month ),
1766
			'monthNamesShort'   => array_values( $wp_locale->month_abbrev ),
1767
			'dayNames'          => array_values( $wp_locale->weekday ),
1768
			'dayNamesShort'     => array_values( $wp_locale->weekday_abbrev ),
1769
			'dayNamesMin'       => array_values( $wp_locale->weekday_initial ),
1770
			// get the start of week from WP general setting
1771
			'firstDay'          => get_option( 'start_of_week' ),
1772
			// is Right to left language? default is false
1773
			'isRTL'             => is_rtl(),
1774
		), $view_data );
1775
1776
		$localizations['datepicker'] = $datepicker_settings;
1777
1778
		return $localizations;
1779
1780
	}
1781
1782
	/**
1783
	 * Register search widget scripts, including Flexibility
1784
	 *
1785
	 * @see https://github.com/10up/flexibility
1786
	 *
1787
	 * @since 1.17
1788
	 *
1789
	 * @return void
1790
	 */
1791
	public function register_scripts() {
1792
		wp_register_script( 'gv-flexibility', plugins_url( 'assets/lib/flexibility/flexibility.js', GRAVITYVIEW_FILE ), array(), \GV\Plugin::$version, true );
1793
	}
1794
1795
	/**
1796
	 * If the current visitor is running IE 8 or 9, enqueue Flexibility
1797
	 *
1798
	 * @since 1.17
1799
	 *
1800
	 * @return void
1801
	 */
1802 4
	private function maybe_enqueue_flexibility() {
1803 4
		if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && preg_match( '/MSIE [8-9]/', $_SERVER['HTTP_USER_AGENT'] ) ) {
1804
			wp_enqueue_script( 'gv-flexibility' );
1805
		}
1806 4
	}
1807
1808
	/**
1809
	 * Enqueue the datepicker script
1810
	 *
1811
	 * It sets the $gravityview->datepicker_class parameter
1812
	 *
1813
	 * @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.
1814
	 * @return void
1815
	 */
1816
	public function enqueue_datepicker() {
1817
		$gravityview_view = GravityView_View::getInstance();
1818
1819
		wp_enqueue_script( 'jquery-ui-datepicker' );
1820
1821
		add_filter( 'gravityview_js_dependencies', array( $this, 'add_datepicker_js_dependency' ) );
1822
		add_filter( 'gravityview_js_localization', array( $this, 'add_datepicker_localization' ), 10, 2 );
1823
1824
		$scheme = is_ssl() ? 'https://' : 'http://';
1825
		wp_enqueue_style( 'jquery-ui-datepicker', $scheme.'ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/smoothness/jquery-ui.css' );
1826
1827
		/**
1828
		 * @filter `gravityview_search_datepicker_class`
1829
		 * 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.
1830
		 * @param string $css_class CSS class to use. Default: `gv-datepicker datepicker mdy` \n
1831
		 * Options are:
1832
		 * - `mdy` (mm/dd/yyyy)
1833
		 * - `dmy` (dd/mm/yyyy)
1834
		 * - `dmy_dash` (dd-mm-yyyy)
1835
		 * - `dmy_dot` (dd.mm.yyyy)
1836
		 * - `ymd_slash` (yyyy/mm/dd)
1837
		 * - `ymd_dash` (yyyy-mm-dd)
1838
		 * - `ymd_dot` (yyyy.mm.dd)
1839
		 */
1840
		$datepicker_class = apply_filters( 'gravityview_search_datepicker_class', "gv-datepicker datepicker " . $this->get_datepicker_format() );
1841
1842
		$gravityview_view->datepicker_class = $datepicker_class;
0 ignored issues
show
Bug introduced by
The property datepicker_class does not seem to exist in GravityView_View.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1843
	}
1844
1845
	/**
1846
	 * Retrieve the datepicker format.
1847
	 *
1848
	 * @param bool $date_format Whether to return the PHP date format or the datpicker class name. Default: false.
1849
	 *
1850
	 * @see https://docs.gravityview.co/article/115-changing-the-format-of-the-search-widgets-date-picker
1851
	 *
1852
	 * @return string The datepicker format placeholder, or the PHP date format.
1853
	 */
1854 19
	private function get_datepicker_format( $date_format = false ) {
1855
1856 19
		$default_format = 'mdy';
1857
1858
		/**
1859
		 * @filter `gravityview/widgets/search/datepicker/format`
1860
		 * @since 2.1.1
1861
		 * @param string           $format Default: mdy
1862
		 * Options are:
1863
		 * - `mdy` (mm/dd/yyyy)
1864
		 * - `dmy` (dd/mm/yyyy)
1865
		 * - `dmy_dash` (dd-mm-yyyy)
1866
		 * - `dmy_dot` (dd.mm.yyyy)
1867
		 * - `ymd_slash` (yyyy/mm/dd)
1868
		 * - `ymd_dash` (yyyy-mm-dd)
1869
		 * - `ymd_dot` (yyyy.mm.dd)
1870
		 */
1871 19
		$format = apply_filters( 'gravityview/widgets/search/datepicker/format', $default_format );
1872
1873
		$gf_date_formats = array(
1874 19
			'mdy' => 'm/d/Y',
1875
1876
			'dmy_dash' => 'd-m-Y',
1877
			'dmy_dot' => 'd.m.Y',
1878
			'dmy' => 'd/m/Y',
1879
1880
			'ymd_slash' => 'Y/m/d',
1881
			'ymd_dash' => 'Y-m-d',
1882
			'ymd_dot' => 'Y.m.d',
1883
		);
1884
1885 19
		if ( ! $date_format ) {
1886
			// If the format key isn't valid, return default format key
1887
			return isset( $gf_date_formats[ $format ] ) ? $format : $default_format;
1888
		}
1889
1890
		// If the format key isn't valid, return default format value
1891 19
		return \GV\Utils::get( $gf_date_formats, $format, $gf_date_formats[ $default_format ] );
1892
	}
1893
1894
	/**
1895
	 * If previewing a View or page with embedded Views, make the search work properly by adding hidden fields with query vars
1896
	 *
1897
	 * @since 2.2.1
1898
	 *
1899
	 * @return void
1900
	 */
1901 4
	public function add_preview_inputs() {
1902 4
		global $wp;
1903
1904 4
		if ( ! is_preview() || ! current_user_can( 'publish_gravityviews') ) {
1905 4
			return;
1906
		}
1907
1908
		// Outputs `preview` and `post_id` variables
1909
		foreach ( $wp->query_vars as $key => $value ) {
1910
			printf( '<input type="hidden" name="%s" value="%s" />', esc_attr( $key ), esc_attr( $value ) );
1911
		}
1912
1913
	}
1914
1915
	/**
1916
	 * Get an operator URL override.
1917
	 *
1918
	 * @param array  $get     Where to look for the operator.
1919
	 * @param string $key     The filter key to look for.
1920
	 * @param array  $allowed The allowed operators (whitelist).
1921
	 * @param string $default The default operator.
1922
	 *
1923
	 * @return string The operator.
1924
	 */
1925 19
	private function get_operator( $get, $key, $allowed, $default ) {
1926 19
		$operator = \GV\Utils::get( $get, "$key|op", $default );
1927
1928
		/**
1929
		 * @filter `gravityview/search/operator_whitelist` An array of allowed operators for a field.
1930
		 * @param[in,out] string[] A whitelist of allowed operators.
1931
		 * @param string The filter name.
1932
		 */
1933 19
		$allowed = apply_filters( 'gravityview/search/operator_whitelist', $allowed, $key );
1934
1935 19
		if ( ! in_array( $operator, $allowed, true ) ) {
1936 1
			$operator = $default;
1937
		}
1938
1939 19
		return $operator;
1940
	}
1941
1942
1943
} // end class
1944
1945
new GravityView_Widget_Search;
1946
1947
if ( ! gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
1948
	return;
1949
}
1950
1951
/**
1952
 * A GF_Query condition that allows user data searches.
1953
 */
1954
class GravityView_Widget_Search_Author_GF_Query_Condition extends \GF_Query_Condition {
1955 1
	public function __construct( $filter, $view ) {
1956 1
		$this->value = $filter['value'];
1957 1
		$this->view = $view;
1958 1
	}
1959
1960 1
	public function sql( $query ) {
1961 1
		global $wpdb;
1962
1963
		$user_meta_fields = array(
1964 1
			'nickname', 'first_name', 'last_name',
1965
		);
1966
1967
		/**
1968
		 * @filter `gravityview/widgets/search/created_by/user_meta_fields` Filter the user meta fields to search.
1969
		 * @param[in,out] array The user meta fields.
1970
		 * @param \GV\View $view The view.
1971
		 */
1972 1
		$user_meta_fields = apply_filters( 'gravityview/widgets/search/created_by/user_meta_fields', $user_meta_fields, $this->view );
1973
1974
		$user_fields = array(
1975 1
			'user_nicename', 'user_login', 'display_name', 'user_email', 
1976
		);
1977
1978
		/**
1979
		 * @filter `gravityview/widgets/search/created_by/user_fields` Filter the user fields to search.
1980
		 * @param[in,out] array The user fields.
1981
		 * @param \GV\View $view The view.
1982
		 */
1983 1
		$user_fields = apply_filters( 'gravityview/widgets/search/created_by/user_fields', $user_fields, $this->view );
1984
1985 1
		$conditions = array();
1986
1987 1
		foreach ( $user_fields as $user_field ) {
1988 1
			$conditions[] = $wpdb->prepare( "`u`.`$user_field` LIKE %s", '%' . $wpdb->esc_like( $this->value ) .  '%' );
1989
		}
1990
1991 1
		foreach ( $user_meta_fields as $meta_field ) {
1992 1
			$conditions[] = $wpdb->prepare( "(`um`.`meta_key` = %s AND `um`.`meta_value` LIKE %s)", $meta_field, '%' . $wpdb->esc_like( $this->value ) .  '%' );
1993
		}
1994
1995 1
		$conditions = '(' . implode( ' OR ', $conditions ) . ')';
1996
1997 1
		$alias = $query->_alias( null );
1998
1999 1
		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)))";
2000
	}
2001
}
2002