Completed
Push — develop ( 0382bd...a61784 )
by Zack
21:58 queued 06:07
created

get_is_approved_choices()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 12
ccs 0
cts 7
cp 0
crap 6
rs 9.8666
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 38
	public function __construct() {
31
32 38
		$this->widget_id = 'search_bar';
33 38
		$this->widget_description = esc_html__( 'Search form for searching entries.', 'gravityview' );
34
35 38
		self::$instance = &$this;
36
37 38
		self::$file = plugin_dir_path( __FILE__ );
38
39 38
		$default_values = array( 'header' => 0, 'footer' => 0 );
40
41
		$settings = array(
42 38
			'search_layout' => array(
43 38
				'type' => 'radio',
44
				'full_width' => true,
45 38
				'label' => esc_html__( 'Search Layout', 'gravityview' ),
46 38
				'value' => 'horizontal',
47
				'options' => array(
48 38
					'horizontal' => esc_html__( 'Horizontal', 'gravityview' ),
49 38
					'vertical' => esc_html__( 'Vertical', 'gravityview' ),
50
				),
51
			),
52
			'search_clear' => array(
53 38
				'type' => 'checkbox',
54 38
				'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 38
				'type' => 'radio',
65
				'full_width' => true,
66 38
				'label' => esc_html__( 'Search Mode', 'gravityview' ),
67 38
				'desc' => __('Should search results match all search fields, or any?', 'gravityview'),
68 38
				'value' => 'any',
69 38
				'class' => 'hide-if-js',
70
				'options' => array(
71 38
					'any' => esc_html__( 'Match Any Fields', 'gravityview' ),
72 38
					'all' => esc_html__( 'Match All Fields', 'gravityview' ),
73
				),
74
			),
75
		);
76
77 38
		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 38
		parent::__construct( esc_html__( 'Search Bar', 'gravityview' ), null, $default_values, $settings );
96
97
		// calculate the search method (POST / GET)
98 38
		$this->set_search_method();
99 38
	}
100
101
	/**
102
	 * @return GravityView_Widget_Search
103
	 */
104 5
	public static function getInstance() {
105 5
		if ( empty( self::$instance ) ) {
106
			self::$instance = new GravityView_Widget_Search;
107
		}
108 5
		return self::$instance;
109
	}
110
111
	/**
112
	 * Sets the search method to GET (default) or POST
113
	 * @since 1.16.4
114
	 */
115 38
	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 38
		$method = apply_filters( 'gravityview/search/method', $this->search_method );
123
124 38
		$method = strtolower( $method );
125
126 38
		$this->search_method = in_array( $method, array( 'get', 'post' ) ) ? $method : 'get';
127 38
	}
128
129
	/**
130
	 * Returns the search method
131
	 * @since 1.16.4
132
	 * @return string
133
	 */
134 5
	public function get_search_method() {
135 5
		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 37
	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 37
		$searchable_fields = array();
479
480 37
		if ( ! $view ) {
481
			return $searchable_fields;
482
		}
483
484
		/**
485
		 * Include the sidebar Widgets.
486
		 */
487 37
		$widgets = (array) get_option( 'widget_gravityview_search', array() );
488
489 37
		foreach ( $widgets as $widget ) {
490 37
			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 37
		foreach ( $view->widgets->by_id( $this->get_widget_id() )->all() as $widget ) {
503 32
			if( $_fields = json_decode( $widget->configuration->get( 'search_fields' ), true ) ) {
504 32
				foreach ( $_fields as $field ) {
505 32
					if ( empty( $field['form_id'] ) ) {
506 32
						$field['form_id'] = $view->form ? $view->form->ID : 0;
507
					}
508 32
					$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 37
		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 84
	public function filter_entries( $search_criteria, $form_id = null, $args = array(), $force_search_criteria = false ) {
537 84
		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 64
			add_action( 'gravityview/view/query', array( $this, 'gf_query_filter' ), 10, 3 );
543 64
			return $search_criteria; // Return the original criteria, GF_Query modification kicks in later
544
		}
545
546 83
		if( 'post' === $this->search_method ) {
547
			$get = $_POST;
548
		} else {
549 83
			$get = $_GET;
550
		}
551
552 83
		$view = \GV\View::by_id( \GV\Utils::get( $args, 'id' ) );
553
554 83
		gravityview()->log->debug( 'Requested $_{method}: ', array( 'method' => $this->search_method, 'data' => $get ) );
555
556 83
		if ( empty( $get ) || ! is_array( $get ) ) {
557 57
			return $search_criteria;
558
		}
559
560 38
		$get = stripslashes_deep( $get );
561
562 38
		$get = gv_map_deep( $get, 'rawurldecode' );
563
564
		// Make sure array key is set up
565 38
		$search_criteria['field_filters'] = \GV\Utils::get( $search_criteria, 'field_filters', array() );
566
567 38
		$searchable_fields = $this->get_view_searchable_fields( $view );
568 38
		$searchable_field_objects = $this->get_view_searchable_fields( $view, true );
569
570
		// add free search
571 38
		if ( isset( $get['gv_search'] ) && '' !== $get['gv_search'] && in_array( 'search_all', $searchable_fields ) ) {
572
573 4
			$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 4
			$split_words = apply_filters( 'gravityview/search-all-split-words', true );
581
582 4
			if ( $split_words ) {
583
584
				// Search for a piece
585 4
				$words = explode( ' ', $search_all_value );
586
587 4
				$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 4
			foreach ( $words as $word ) {
598 4
				$search_criteria['field_filters'][] = array(
599 4
					'key' => null, // The field ID to search
600 4
					'value' => $word, // The value to search
601 4
					'operator' => 'contains', // What to search in. Options: `is` or `contains`
602
				);
603
			}
604
		}
605
606
		// start date & end date
607 38
		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 38
		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 38
		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 38
		$mode = isset( $get['mode'] ) && in_array( $get['mode'], array( 'any', 'all' ) ) ?  $get['mode'] : 'any';
686
687
		// get the other search filters
688 38
		foreach ( $get as $key => $value ) {
689
690 38
			if ( 0 !== strpos( $key, 'filter_' ) || gv_empty( $value, false, false ) || ( is_array( $value ) && count( $value ) === 1 && gv_empty( $value[0], false, false ) ) ) {
691 25
				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 38
		$search_criteria['field_filters']['mode'] = apply_filters( 'gravityview/search/mode', $mode );
726
727 38
		gravityview()->log->debug( 'Returned Search Criteria: ', array( 'data' => $search_criteria ) );
728
729 38
		unset( $get );
730
731 38
		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 63
	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 63
		$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 63
		remove_filter( 'gravityview_fe_search_criteria', array( $this, 'filter_entries' ), 10, 3 );
754 63
		$search_criteria = apply_filters( 'gravityview_fe_search_criteria', $search_criteria, $view->form->ID, $view->settings->as_atts() );
755 63
		add_filter( 'gravityview_fe_search_criteria', array( $this, 'filter_entries' ), 10, 3 );
756
757 63
		$query_class = $view->get_query_class();
758
759 63
		if ( empty( $search_criteria['field_filters'] ) ) {
760 56
			return;
761
		}
762
763 18
		$widgets = $view->widgets->by_id( $this->widget_id );
764 18
		if ( $widgets->count() ) {
765 13
			$widgets = $widgets->all();
766 13
			$widget  = $widgets[0];
767
768 13
			$search_fields = json_decode( $widget->configuration->get( 'search_fields' ), true );
769
770 13
			foreach ( (array) $search_fields as $search_field ) {
771 13
				if ( 'created_by' === $search_field['field'] && 'input_text' === $search_field['input'] ) {
772 1
					$created_by_text_mode = true;
773
				}
774
			}
775
		}
776
777 18
		$extra_conditions = array();
778 18
		$mode = 'any';
779
780 18
		foreach ( $search_criteria['field_filters'] as &$filter ) {
781 18
			if ( ! is_array( $filter ) ) {
782 18
				if ( in_array( strtolower( $filter ), array( 'any', 'all' ) ) ) {
783 18
					$mode = $filter;
784
				}
785 18
				continue;
786
			}
787
788
			// Construct a manual query for unapproved statuses
789 12
			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 12
			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 11
			$filter['operator'] = empty( $filter['operator'] ) ? 'contains' : $filter['operator'];
822
823
			// For multichoice, let's have an in (OR) search.
824 11
			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 11
			if ( empty( $filter['form_id'] ) ) {
830 4
				$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 11
			$filter['operator'] = apply_filters( 'gravityview_search_operator', $filter['operator'], $filter, $view );
841
		}
842
843 18
		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 18
		$search_conditions = array();
860
861 18
		if ( $filters = array_filter( $search_criteria['field_filters'] ) ) {
862
863 18
			foreach ( $filters as $filter ) {
864 18
				if ( ! is_array( $filter ) ) {
865 10
					continue;
866
				}
867
868
				/**
869
				 * Parse the filter criteria to generate the needed
870
				 * WHERE condition. This is a trick to not write our own generation
871
				 * code by reusing what's inside GF_Query already as they
872
				 * take care of many small things like forcing numeric, etc.
873
				 */
874 11
				$_tmp_query       = new $query_class( $filter['form_id'], array( 'mode' => 'any', 'field_filters' => array( $filter ) ) );
875 11
				$_tmp_query_parts = $_tmp_query->_introspect();
876 11
				$search_condition = $_tmp_query_parts['where'];
877
878 11
				if ( empty( $filter['key'] ) && $search_condition->expressions ) {
879 1
					$search_conditions[] = $search_condition;
880
				} else {
881 10
					$left = $search_condition->left;
882 10
					$alias = $query->_alias( $left->field_id, $left->source, $left->is_entry_column() ? 't' : 'm' );
883
884 10
					if ( $view->joins && $left->field_id == GF_Query_Column::META ) {
885 1
						foreach ( $view->joins as $_join ) {
886 1
							$on = $_join->join_on;
887 1
							$join = $_join->join;
888
889
							// Join
890 1
							$search_conditions[] = new GF_Query_Condition(
891 1
								new GF_Query_Column( GF_Query_Column::META, $join->ID, $query->_alias( GF_Query_Column::META, $join->ID, 'm' ) ),
892 1
								$search_condition->operator,
893 1
								$search_condition->right
894
							);
895
896
							// On
897 1
							$search_conditions[] = new GF_Query_Condition(
898 1
								new GF_Query_Column( GF_Query_Column::META, $on->ID, $query->_alias( GF_Query_Column::META, $on->ID, 'm' ) ),
899 1
								$search_condition->operator,
900 1
								$search_condition->right
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 18
			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 11
				$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 18
		$query_parts = $query->_introspect();
922
923
		/**
924
		 * Combine the parts as a new WHERE clause.
925
		 */
926 18
		$where = call_user_func_array( '\GF_Query_Condition::_and', array_merge( array( $query_parts['where'] ), $search_conditions, $extra_conditions ) );
927 18
		$query->where( $where );
928 18
	}
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
		if ( ! $form_field = is_numeric( $field_id ) ? \GV\GF_Field::by_id( $form, $field_id ) : \GV\Internal_Field::by_id( $field_id ) ) {
1030
			return false;
1031
		}
1032
1033
		// default filter array
1034
		$filter = array(
1035 16
			'key'   => $field_id,
1036 16
			'value' => $value,
1037 16
			'form_id' => $form->ID,
1038
		);
1039
1040 16
		switch ( $form_field->type ) {
1041
1042 16
			case 'select':
1043 16
			case 'radio':
1044 1
				$filter['operator'] = $this->get_operator( $get, $key, array( 'is' ), 'is' );
1045 1
				break;
1046
1047 15
			case 'post_category':
1048
1049
				if ( ! is_array( $value ) ) {
1050
					$value = array( $value );
1051
				}
1052
1053
				// Reset filter variable
1054
				$filter = array();
1055
1056
				foreach ( $value as $val ) {
1057
					$cat = get_term( $val, 'category' );
1058
					$filter[] = array(
1059
						'key'      => $field_id,
1060
						'value'    => esc_attr( $cat->name ) . ':' . $val,
1061
						'operator' => $this->get_operator( $get, $key, array( 'is' ), 'is' ),
1062
					);
1063
				}
1064
1065
				break;
1066
1067 15
			case 'multiselect':
1068
1069
				if ( ! is_array( $value ) ) {
1070
					break;
1071
				}
1072
1073
				// Reset filter variable
1074
				$filter = array();
1075
1076
				foreach ( $value as $val ) {
1077
					$filter[] = array( 'key' => $field_id, 'value' => $val );
1078
				}
1079
1080
				break;
1081
1082 15
			case 'checkbox':
1083
				// convert checkbox on/off into the correct search filter
1084
				if ( false !== strpos( $field_id, '.' ) && ! empty( $form_field->inputs ) && ! empty( $form_field->choices ) ) {
1085
					foreach ( $form_field->inputs as $k => $input ) {
1086
						if ( $input['id'] == $field_id ) {
1087
							$filter['value'] = $form_field->choices[ $k ]['value'];
1088
							$filter['operator'] = $this->get_operator( $get, $key, array( 'is' ), 'is' );
1089
							break;
1090
						}
1091
					}
1092
				} elseif ( is_array( $value ) ) {
1093
1094
					// Reset filter variable
1095
					$filter = array();
1096
1097
					foreach ( $value as $val ) {
1098
						$filter[] = array(
1099
							'key'      => $field_id,
1100
							'value'    => $val,
1101
							'operator' => $this->get_operator( $get, $key, array( 'is' ), 'is' ),
1102
						);
1103
					}
1104
				}
1105
1106
				break;
1107
1108 15
			case 'name':
1109 15
			case 'address':
1110
1111 1
				if ( false === strpos( $field_id, '.' ) ) {
1112
1113
					$words = explode( ' ', $value );
1114
1115
					$filters = array();
1116
					foreach ( $words as $word ) {
1117
						if ( ! empty( $word ) && strlen( $word ) > 1 ) {
1118
							// Keep the same key for each filter
1119
							$filter['value'] = $word;
1120
							// Add a search for the value
1121
							$filters[] = $filter;
1122
						}
1123
					}
1124
1125
					$filter = $filters;
1126
				}
1127
1128
				// State/Province should be exact matches
1129 1
				if ( 'address' === $form_field->field->type ) {
1130
1131 1
					$searchable_fields = $this->get_view_searchable_fields( $view, true );
1132
1133 1
					foreach ( $searchable_fields as $searchable_field ) {
1134
1135 1
						if( $form_field->ID !== $searchable_field['field'] ) {
1136
							continue;
1137
						}
1138
1139
						// Only exact-match dropdowns, not text search
1140 1
						if( in_array( $searchable_field['input'], array( 'text', 'search' ), true ) ) {
1141 1
							continue;
1142
						}
1143
1144
						$input_id = gravityview_get_input_id_from_id( $form_field->ID );
1145
1146
						if ( 4 === $input_id ) {
1147
							$filter['operator'] = $this->get_operator( $get, $key, array( 'is' ), 'is' );
1148
						};
1149
					}
1150
				}
1151
1152 1
				break;
1153
1154 15
			case 'date':
1155
1156 8
				$date_format = $this->get_datepicker_format( true );
1157
1158 8
				if ( is_array( $value ) ) {
1159
1160
					// Reset filter variable
1161
					$filter = array();
1162
1163
					foreach ( $value as $k => $date ) {
1164
						if ( empty( $date ) ) {
1165
							continue;
1166
						}
1167
						$operator = 'start' === $k ? '>=' : '<=';
1168
1169
						/**
1170
						 * @hack
1171
						 * @since 1.16.3
1172
						 * Safeguard until GF implements '<=' operator
1173
						 */
1174
						if( !GFFormsModel::is_valid_operator( $operator ) && $operator === '<=' ) {
1175
							$operator = '<';
1176
							$date = date( 'Y-m-d', strtotime( self::get_formatted_date( $date, 'Y-m-d', $date_format ) . ' +1 day' ) );
1177
						}
1178
1179
						$filter[] = array(
1180
							'key'      => $field_id,
1181
							'value'    => self::get_formatted_date( $date, 'Y-m-d', $date_format ),
1182
							'operator' => $this->get_operator( $get, $key, array( $operator ), $operator ),
1183
						);
1184
					}
1185
				} else {
1186 8
					$date = $value;
1187 8
					$filter['value'] = self::get_formatted_date( $date, 'Y-m-d', $date_format );
1188 8
					$filter['operator'] = $this->get_operator( $get, $key, array( 'is' ), 'is' );
1189
				}
1190
1191 8
				break;
1192
1193
1194
		} // switch field type
1195
1196 16
		return $filter;
1197
	}
1198
1199
	/**
1200
	 * Get the Field Format form GravityForms
1201
	 *
1202
	 * @param GF_Field_Date $field The field object
1203
	 * @since 1.10
1204
	 *
1205
	 * @return string Format of the date in the database
1206
	 */
1207
	public static function get_date_field_format( GF_Field_Date $field ) {
1208
		$format = 'm/d/Y';
1209
		$datepicker = array(
1210
			'mdy' => 'm/d/Y',
1211
			'dmy' => 'd/m/Y',
1212
			'dmy_dash' => 'd-m-Y',
1213
			'dmy_dot' => 'd.m.Y',
1214
			'ymd_slash' => 'Y/m/d',
1215
			'ymd_dash' => 'Y-m-d',
1216
			'ymd_dot' => 'Y.m.d',
1217
		);
1218
1219
		if ( ! empty( $field->dateFormat ) && isset( $datepicker[ $field->dateFormat ] ) ){
1220
			$format = $datepicker[ $field->dateFormat ];
1221
		}
1222
1223
		return $format;
1224
	}
1225
1226
	/**
1227
	 * Format a date value
1228
	 *
1229
	 * @param string $value Date value input
1230
	 * @param string $format Wanted formatted date
1231
	 *
1232
	 * @since 2.1.2
1233
	 * @param string $value_format The value format. Default: Y-m-d
1234
	 *
1235
	 * @return string
1236
	 */
1237 8
	public static function get_formatted_date( $value = '', $format = 'Y-m-d', $value_format = 'Y-m-d' ) {
1238
1239 8
		$date = date_create_from_format( $value_format, $value );
1240
1241 8
		if ( empty( $date ) ) {
1242
			gravityview()->log->debug( 'Date format not valid: {value}', array( 'value' => $value ) );
1243
			return '';
1244
		}
1245 8
		return $date->format( $format );
1246
	}
1247
1248
1249
	/**
1250
	 * Include this extension templates path
1251
	 * @param array $file_paths List of template paths ordered
1252
	 */
1253 1
	public function add_template_path( $file_paths ) {
1254
1255
		// Index 100 is the default GravityView template path.
1256 1
		$file_paths[102] = self::$file . 'templates/';
1257
1258 1
		return $file_paths;
1259
	}
1260
1261
	/**
1262
	 * Check whether the configured search fields have a date field
1263
	 *
1264
	 * @since 1.17.5
1265
	 *
1266
	 * @param array $search_fields
1267
	 *
1268
	 * @return bool True: has a `date` or `date_range` field
1269
	 */
1270 4
	private function has_date_field( $search_fields ) {
1271
1272 4
		$has_date = false;
1273
1274 4
		foreach ( $search_fields as $k => $field ) {
1275 4
			if ( in_array( $field['input'], array( 'date', 'date_range', 'entry_date' ) ) ) {
1276
				$has_date = true;
1277
				break;
1278
			}
1279
		}
1280
1281 4
		return $has_date;
1282
	}
1283
1284
	/**
1285
	 * Renders the Search Widget
1286
	 * @param array $widget_args
1287
	 * @param string $content
1288
	 * @param string $context
1289
	 *
1290
	 * @return void
1291
	 */
1292 4
	public function render_frontend( $widget_args, $content = '', $context = '' ) {
1293
		/** @var GravityView_View $gravityview_view */
1294 4
		$gravityview_view = GravityView_View::getInstance();
1295
1296 4
		if ( empty( $gravityview_view ) ) {
1297
			gravityview()->log->debug( '$gravityview_view not instantiated yet.' );
1298
			return;
1299
		}
1300
1301
		// get configured search fields
1302 4
		$search_fields = ! empty( $widget_args['search_fields'] ) ? json_decode( $widget_args['search_fields'], true ) : '';
1303
1304 4
		if ( empty( $search_fields ) || ! is_array( $search_fields ) ) {
1305
			gravityview()->log->debug( 'No search fields configured for widget:', array( 'data' => $widget_args ) );
1306
			return;
1307
		}
1308
1309 4
		$view = \GV\View::by_id( $gravityview_view->view_id );
1310
1311
		// prepare fields
1312 4
		foreach ( $search_fields as $k => $field ) {
1313
1314 4
			$updated_field = $field;
1315
1316 4
			$updated_field = $this->get_search_filter_details( $updated_field, $context );
1317
1318 4
			switch ( $field['field'] ) {
1319
1320 4
				case 'search_all':
1321 4
					$updated_field['key'] = 'search_all';
1322 4
					$updated_field['input'] = 'search_all';
1323 4
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_search' );
1324 4
					break;
1325
1326
				case 'entry_date':
1327
					$updated_field['key'] = 'entry_date';
1328
					$updated_field['input'] = 'entry_date';
1329
					$updated_field['value'] = array(
1330
						'start' => $this->rgget_or_rgpost( 'gv_start' ),
1331
						'end' => $this->rgget_or_rgpost( 'gv_end' ),
1332
					);
1333
					break;
1334
1335
				case 'entry_id':
1336
					$updated_field['key'] = 'entry_id';
1337
					$updated_field['input'] = 'entry_id';
1338
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_id' );
1339
					break;
1340
1341
				case 'created_by':
1342
					$updated_field['key'] = 'created_by';
1343
					$updated_field['name'] = 'gv_by';
1344
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_by' );
1345
					$updated_field['choices'] = self::get_created_by_choices( $view );
1346
					break;
1347
				
1348
				case 'is_approved':
1349
					$updated_field['key'] = 'is_approved';
1350
					$updated_field['value'] = $this->rgget_or_rgpost( 'filter_is_approved' );
1351
					$updated_field['choices'] = self::get_is_approved_choices();
1352
					break;
1353
			}
1354
1355 4
			$search_fields[ $k ] = $updated_field;
1356
		}
1357
1358 4
		gravityview()->log->debug( 'Calculated Search Fields: ', array( 'data' => $search_fields ) );
1359
1360
		/**
1361
		 * @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.
1362
		 * @param array $search_fields Array of search filters with `key`, `label`, `value`, `type`, `choices` keys
1363
		 * @param GravityView_Widget_Search $this Current widget object
1364
		 * @param array $widget_args Args passed to this method. {@since 1.8}
1365
		 * @param \GV\Template_Context $context {@since 2.0}
1366
		 * @var array
1367
		 */
1368 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...
1369
1370 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...
1371
1372 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...
1373
1374
		/** @since 1.14 */
1375 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...
1376
1377 4
		$custom_class = ! empty( $widget_args['custom_class'] ) ? $widget_args['custom_class'] : '';
1378
1379 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...
1380
1381 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...
1382
1383 4
		if ( $this->has_date_field( $search_fields ) ) {
1384
			// enqueue datepicker stuff only if needed!
1385
			$this->enqueue_datepicker();
1386
		}
1387
1388 4
		$this->maybe_enqueue_flexibility();
1389
1390 4
		$gravityview_view->render( 'widget', 'search', false );
1391 4
	}
1392
1393
	/**
1394
	 * Get the search class for a search form
1395
	 *
1396
	 * @since 1.5.4
1397
	 *
1398
	 * @return string Sanitized CSS class for the search form
1399
	 */
1400 4
	public static function get_search_class( $custom_class = '' ) {
1401 4
		$gravityview_view = GravityView_View::getInstance();
1402
1403 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...
1404
1405 4
		if ( ! empty( $custom_class )  ) {
1406
			$search_class .= ' '.$custom_class;
1407
		}
1408
1409
		/**
1410
		 * @filter `gravityview_search_class` Modify the CSS class for the search form
1411
		 * @param string $search_class The CSS class for the search form
1412
		 */
1413 4
		$search_class = apply_filters( 'gravityview_search_class', $search_class );
1414
1415
		// Is there an active search being performed? Used by fe-views.js
1416 4
		$search_class .= GravityView_frontend::getInstance()->isSearch() ? ' gv-is-search' : '';
1417
1418 4
		return gravityview_sanitize_html_class( $search_class );
1419
	}
1420
1421
1422
	/**
1423
	 * Calculate the search form action
1424
	 * @since 1.6
1425
	 *
1426
	 * @return string
1427
	 */
1428 4
	public static function get_search_form_action() {
1429 4
		$gravityview_view = GravityView_View::getInstance();
1430
1431 4
		$post_id = $gravityview_view->getPostId() ? $gravityview_view->getPostId() : $gravityview_view->getViewId();
1432
1433 4
		$url = add_query_arg( array(), get_permalink( $post_id ) );
1434
1435 4
		return esc_url( $url );
1436
	}
1437
1438
	/**
1439
	 * Get the label for a search form field
1440
	 * @param  array $field      Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1441
	 * @param  array $form_field Form field data, as fetched by `gravityview_get_field()`
1442
	 * @return string             Label for the search form
1443
	 */
1444 4
	private static function get_field_label( $field, $form_field = array() ) {
1445
1446 4
		$label = \GV\Utils::_GET( 'label', \GV\Utils::get( $field, 'label' ) );
1447
1448 4
		if ( ! $label ) {
1449
1450 4
			$label = isset( $form_field['label'] ) ? $form_field['label'] : '';
1451
1452 4
			switch( $field['field'] ) {
1453 4
				case 'search_all':
1454 4
					$label = __( 'Search Entries:', 'gravityview' );
1455 4
					break;
1456
				case 'entry_date':
1457
					$label = __( 'Filter by date:', 'gravityview' );
1458
					break;
1459
				case 'entry_id':
1460
					$label = __( 'Entry ID:', 'gravityview' );
1461
					break;
1462
				default:
1463
					// If this is a field input, not a field
1464
					if ( strpos( $field['field'], '.' ) > 0 && ! empty( $form_field['inputs'] ) ) {
1465
1466
						// Get the label for the field in question, which returns an array
1467
						$items = wp_list_filter( $form_field['inputs'], array( 'id' => $field['field'] ) );
1468
1469
						// Get the item with the `label` key
1470
						$values = wp_list_pluck( $items, 'label' );
1471
1472
						// There will only one item in the array, but this is easier
1473
						foreach ( $values as $value ) {
1474
							$label = $value;
1475
							break;
1476
						}
1477
					}
1478
			}
1479
		}
1480
1481
		/**
1482
		 * @filter `gravityview_search_field_label` Modify the label for a search field. Supports returning HTML
1483
		 * @since 1.17.3 Added $field parameter
1484
		 * @param[in,out] string $label Existing label text, sanitized.
1485
		 * @param[in] array $form_field Gravity Forms field array, as returned by `GFFormsModel::get_field()`
1486
		 * @param[in] array $field Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1487
		 */
1488 4
		$label = apply_filters( 'gravityview_search_field_label', esc_attr( $label ), $form_field, $field );
1489
1490 4
		return $label;
1491
	}
1492
1493
	/**
1494
	 * Prepare search fields to frontend render with other details (label, field type, searched values)
1495
	 *
1496
	 * @param array $field
1497
	 * @param \GV\Context $context
1498
	 *
1499
	 * @return array
1500
	 */
1501 4
	private function get_search_filter_details( $field, $context ) {
1502
1503 4
		$gravityview_view = GravityView_View::getInstance();
1504
1505 4
		$form = $gravityview_view->getForm();
1506
1507
		// for advanced field ids (eg, first name / last name )
1508 4
		$name = 'filter_' . str_replace( '.', '_', $field['field'] );
1509
1510
		// get searched value from $_GET/$_POST (string or array)
1511 4
		$value = $this->rgget_or_rgpost( $name );
1512
1513
		// get form field details
1514 4
		$form_field = gravityview_get_field( $form, $field['field'] );
1515
1516
		$filter = array(
1517 4
			'key' => $field['field'],
1518 4
			'name' => $name,
1519 4
			'label' => self::get_field_label( $field, $form_field ),
1520 4
			'input' => $field['input'],
1521 4
			'value' => $value,
1522 4
			'type' => $form_field['type'],
1523
		);
1524
1525
		// collect choices
1526 4
		if ( 'post_category' === $form_field['type'] && ! empty( $form_field['displayAllCategories'] ) && empty( $form_field['choices'] ) ) {
1527
			$filter['choices'] = gravityview_get_terms_choices();
1528 4
		} elseif ( ! empty( $form_field['choices'] ) ) {
1529
			$filter['choices'] = $form_field['choices'];
1530
		}
1531
1532 4
		if ( 'date_range' === $field['input'] && empty( $value ) ) {
1533
			$filter['value'] = array( 'start' => '', 'end' => '' );
1534
		}
1535
1536 4
		if ( ! empty( $filter['choices'] ) ) {
1537
			/**
1538
			 * @filter `gravityview/search/sieve_choices` Only output used choices for this field.
1539
			 * @param[in,out] bool Yes or no.
1540
			 * @param array $field The field configuration.
1541
			 * @param \GV\Context The context.
1542
			 */
1543
			if ( apply_filters( 'gravityview/search/sieve_choices', false, $field, $context ) ) {
1544
				$filter['choices'] = $this->sieve_filter_choices( $filter, $context );
1545
			}
1546
		}
1547
1548
		/**
1549
		 * @filter `gravityview/search/filter_details` Filter the output filter details for the Search widget.
1550
		 * @param[in,out] array $filter The filter details
1551
		 * @param array $field The search field configuration
1552
		 * @param \GV\Context The context
1553
		 * @since develop
1554
		 */
1555 4
		$filter = apply_filters( 'gravityview/search/filter_details', $filter, $field, $context );
1556
1557 4
		return $filter;
1558
1559
	}
1560
1561
	/**
1562
	 * Sieve filter choices to only ones that are used.
1563
	 *
1564
	 * @param array $filter The filter configuration.
1565
	 * @param \GV\Context $context The context
1566
	 *
1567
	 * @since develop
1568
	 * @internal
1569
	 *
1570
	 * @return array The filter choices.
1571
	 */
1572
	private function sieve_filter_choices( $filter, $context ) {
1573
		if ( empty( $filter['key'] ) || empty( $filter['choices'] ) ) {
1574
			return $filter; // @todo Populate plugins might give us empty choices
1575
		}
1576
1577
		if ( ! is_numeric( $filter['key'] ) ) {
1578
			return $filter;
1579
		}
1580
1581
		$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...
1582
1583
		global $wpdb;
1584
1585
		$table = GFFormsModel::get_entry_meta_table_name();
1586
1587
		$key_like = $wpdb->esc_like( $filter['key'] ) . '.%';
1588
1589
		switch ( \GV\Utils::get( $filter, 'type' ) ):
1590
			case 'post_category':
1591
				$choices = $wpdb->get_col( $wpdb->prepare(
1592
					"SELECT DISTINCT SUBSTRING_INDEX(meta_value, ':', 1) FROM $table WHERE (meta_key LIKE %s OR meta_key = %d) AND form_id = %d",
1593
					$key_like, $filter['key'], $form_id
1594
				) );
1595
				break;
1596
			default:
1597
				$choices = $wpdb->get_col( $wpdb->prepare(
1598
					"SELECT DISTINCT meta_value FROM $table WHERE (meta_key LIKE %s OR meta_key = %d) AND form_id = %d",
1599
					$key_like, $filter['key'], $form_id
1600
				) );
1601
1602
				if ( ( $field = gravityview_get_field( $form_id, $filter['key'] ) ) && 'json' === $field->storageType ) {
1603
					$choices = array_map( 'json_decode', $choices );
1604
					$_choices_array = array();
1605
					foreach ( $choices as $choice ) {
1606
						if ( is_array( $choice ) ) {
1607
							$_choices_array = array_merge( $_choices_array, $choice );
1608
						} else {
1609
							$_choices_array []= $choice;
1610
						}
1611
					}
1612
					$choices = array_unique( $_choices_array );
1613
				}
1614
1615
				break;
1616
		endswitch;
1617
1618
		$filter_choices = array();
1619
		foreach ( $filter['choices'] as $choice ) {
1620
			if ( in_array( $choice['text'], $choices, true ) || in_array( $choice['value'], $choices, true ) ) {
1621
				$filter_choices[] = $choice;
1622
			}
1623
		}
1624
1625
		return $filter_choices;
1626
	}
1627
1628
	/**
1629
	 * Calculate the search choices for the users
1630
	 *
1631
	 * @param \GV\View $view The view
1632
	 * @since develop
1633
	 *
1634
	 * @since 1.8
1635
	 *
1636
	 * @return array Array of user choices (value = ID, text = display name)
1637
	 */
1638
	private static function get_created_by_choices( $view ) {
1639
1640
		/**
1641
		 * filter gravityview/get_users/search_widget
1642
		 * @see \GVCommon::get_users
1643
		 */
1644
		$users = GVCommon::get_users( 'search_widget', array( 'fields' => array( 'ID', 'display_name' ) ) );
1645
1646
		$choices = array();
1647
		foreach ( $users as $user ) {
1648
			/**
1649
			 * @filter `gravityview/search/created_by/text` Filter the display text in created by search choices
1650
			 * @since develop
1651
			 * @param string[in,out] The text. Default: $user->display_name
1652
			 * @param \WP_User $user The user.
1653
			 * @param \GV\View $view The view.
1654
			 */
1655
			$text = apply_filters( 'gravityview/search/created_by/text', $user->display_name, $user, $view );
1656
			$choices[] = array(
1657
				'value' => $user->ID,
1658
				'text' => $text,
1659
			);
1660
		}
1661
1662
		return $choices;
1663
	}
1664
1665
	/**
1666
	 * Calculate the search checkbox choices for approval status
1667
	 *
1668
	 * @since develop
1669
	 *
1670
	 * @return array Array of approval status choices (value = status, text = display name)
1671
	 */
1672
	private static function get_is_approved_choices() {
1673
1674
		$choices = array();
1675
		foreach ( GravityView_Entry_Approval_Status::get_all() as $status ) {
1676
			$choices[] = array(
1677
				'value' => $status['value'],
1678
				'text' => $status['label'],
1679
			);
1680
		}
1681
1682
		return $choices;
1683
	}
1684
1685
	/**
1686
	 * Output the Clear Search Results button
1687
	 * @since 1.5.4
1688
	 */
1689 4
	public static function the_clear_search_button() {
1690 4
		$gravityview_view = GravityView_View::getInstance();
1691
1692 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...
1693
1694
			$url = strtok( add_query_arg( array() ), '?' );
1695
1696
			echo gravityview_get_link( $url, esc_html__( 'Clear', 'gravityview' ), 'class=button gv-search-clear' );
1697
1698
		}
1699 4
	}
1700
1701
	/**
1702
	 * Based on the search method, fetch the value for a specific key
1703
	 *
1704
	 * @since 1.16.4
1705
	 *
1706
	 * @param string $name Name of the request key to fetch the value for
1707
	 *
1708
	 * @return mixed|string Value of request at $name key. Empty string if empty.
1709
	 */
1710 4
	private function rgget_or_rgpost( $name ) {
1711 4
		$value = \GV\Utils::_REQUEST( $name );
1712
1713 4
		$value = stripslashes_deep( $value );
1714
1715 4
		$value = gv_map_deep( $value, 'rawurldecode' );
1716
1717 4
		$value = gv_map_deep( $value, '_wp_specialchars' );
1718
1719 4
		return $value;
1720
	}
1721
1722
1723
	/**
1724
	 * Require the datepicker script for the frontend GV script
1725
	 * @param array $js_dependencies Array of existing required scripts for the fe-views.js script
1726
	 * @return array Array required scripts, with `jquery-ui-datepicker` added
1727
	 */
1728
	public function add_datepicker_js_dependency( $js_dependencies ) {
1729
1730
		$js_dependencies[] = 'jquery-ui-datepicker';
1731
1732
		return $js_dependencies;
1733
	}
1734
1735
	/**
1736
	 * Modify the array passed to wp_localize_script()
1737
	 *
1738
	 * @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...
1739
	 * @param array $view_data View data array with View settings
1740
	 *
1741
	 * @return array
1742
	 */
1743
	public function add_datepicker_localization( $localizations = array(), $view_data = array() ) {
1744
		global $wp_locale;
1745
1746
		/**
1747
		 * @filter `gravityview_datepicker_settings` Modify the datepicker settings
1748
		 * @see http://api.jqueryui.com/datepicker/ Learn what settings are available
1749
		 * @see http://www.renegadetechconsulting.com/tutorials/jquery-datepicker-and-wordpress-i18n Thanks for the helpful information on $wp_locale
1750
		 * @param array $js_localization The data padded to the Javascript file
1751
		 * @param array $view_data View data array with View settings
1752
		 */
1753
		$datepicker_settings = apply_filters( 'gravityview_datepicker_settings', array(
1754
			'yearRange' => '-5:+5',
1755
			'changeMonth' => true,
1756
			'changeYear' => true,
1757
			'closeText' => esc_attr_x( 'Close', 'Close calendar', 'gravityview' ),
1758
			'prevText' => esc_attr_x( 'Prev', 'Previous month in calendar', 'gravityview' ),
1759
			'nextText' => esc_attr_x( 'Next', 'Next month in calendar', 'gravityview' ),
1760
			'currentText' => esc_attr_x( 'Today', 'Today in calendar', 'gravityview' ),
1761
			'weekHeader' => esc_attr_x( 'Week', 'Week in calendar', 'gravityview' ),
1762
			'monthStatus'       => __( 'Show a different month', 'gravityview' ),
1763
			'monthNames'        => array_values( $wp_locale->month ),
1764
			'monthNamesShort'   => array_values( $wp_locale->month_abbrev ),
1765
			'dayNames'          => array_values( $wp_locale->weekday ),
1766
			'dayNamesShort'     => array_values( $wp_locale->weekday_abbrev ),
1767
			'dayNamesMin'       => array_values( $wp_locale->weekday_initial ),
1768
			// get the start of week from WP general setting
1769
			'firstDay'          => get_option( 'start_of_week' ),
1770
			// is Right to left language? default is false
1771
			'isRTL'             => is_rtl(),
1772
		), $view_data );
1773
1774
		$localizations['datepicker'] = $datepicker_settings;
1775
1776
		return $localizations;
1777
1778
	}
1779
1780
	/**
1781
	 * Register search widget scripts, including Flexibility
1782
	 *
1783
	 * @see https://github.com/10up/flexibility
1784
	 *
1785
	 * @since 1.17
1786
	 *
1787
	 * @return void
1788
	 */
1789
	public function register_scripts() {
1790
		wp_register_script( 'gv-flexibility', plugins_url( 'assets/lib/flexibility/flexibility.js', GRAVITYVIEW_FILE ), array(), \GV\Plugin::$version, true );
1791
	}
1792
1793
	/**
1794
	 * If the current visitor is running IE 8 or 9, enqueue Flexibility
1795
	 *
1796
	 * @since 1.17
1797
	 *
1798
	 * @return void
1799
	 */
1800 4
	private function maybe_enqueue_flexibility() {
1801 4
		if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && preg_match( '/MSIE [8-9]/', $_SERVER['HTTP_USER_AGENT'] ) ) {
1802
			wp_enqueue_script( 'gv-flexibility' );
1803
		}
1804 4
	}
1805
1806
	/**
1807
	 * Enqueue the datepicker script
1808
	 *
1809
	 * It sets the $gravityview->datepicker_class parameter
1810
	 *
1811
	 * @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.
1812
	 * @return void
1813
	 */
1814
	public function enqueue_datepicker() {
1815
		$gravityview_view = GravityView_View::getInstance();
1816
1817
		wp_enqueue_script( 'jquery-ui-datepicker' );
1818
1819
		add_filter( 'gravityview_js_dependencies', array( $this, 'add_datepicker_js_dependency' ) );
1820
		add_filter( 'gravityview_js_localization', array( $this, 'add_datepicker_localization' ), 10, 2 );
1821
1822
		$scheme = is_ssl() ? 'https://' : 'http://';
1823
		wp_enqueue_style( 'jquery-ui-datepicker', $scheme.'ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/smoothness/jquery-ui.css' );
1824
1825
		/**
1826
		 * @filter `gravityview_search_datepicker_class`
1827
		 * 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.
1828
		 * @param string $css_class CSS class to use. Default: `gv-datepicker datepicker mdy` \n
1829
		 * Options are:
1830
		 * - `mdy` (mm/dd/yyyy)
1831
		 * - `dmy` (dd/mm/yyyy)
1832
		 * - `dmy_dash` (dd-mm-yyyy)
1833
		 * - `dmy_dot` (dd.mm.yyyy)
1834
		 * - `ymd_slash` (yyyy/mm/dd)
1835
		 * - `ymd_dash` (yyyy-mm-dd)
1836
		 * - `ymd_dot` (yyyy.mm.dd)
1837
		 */
1838
		$datepicker_class = apply_filters( 'gravityview_search_datepicker_class', "gv-datepicker datepicker " . $this->get_datepicker_format() );
1839
1840
		$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...
1841
	}
1842
1843
	/**
1844
	 * Retrieve the datepicker format.
1845
	 *
1846
	 * @param bool $date_format Whether to return the PHP date format or the datpicker class name. Default: false.
1847
	 *
1848
	 * @see https://docs.gravityview.co/article/115-changing-the-format-of-the-search-widgets-date-picker
1849
	 *
1850
	 * @return string The datepicker format placeholder, or the PHP date format.
1851
	 */
1852 19
	private function get_datepicker_format( $date_format = false ) {
1853
1854 19
		$default_format = 'mdy';
1855
1856
		/**
1857
		 * @filter `gravityview/widgets/search/datepicker/format`
1858
		 * @since 2.1.1
1859
		 * @param string           $format Default: mdy
1860
		 * Options are:
1861
		 * - `mdy` (mm/dd/yyyy)
1862
		 * - `dmy` (dd/mm/yyyy)
1863
		 * - `dmy_dash` (dd-mm-yyyy)
1864
		 * - `dmy_dot` (dd.mm.yyyy)
1865
		 * - `ymd_slash` (yyyy/mm/dd)
1866
		 * - `ymd_dash` (yyyy-mm-dd)
1867
		 * - `ymd_dot` (yyyy.mm.dd)
1868
		 */
1869 19
		$format = apply_filters( 'gravityview/widgets/search/datepicker/format', $default_format );
1870
1871
		$gf_date_formats = array(
1872 19
			'mdy' => 'm/d/Y',
1873
1874
			'dmy_dash' => 'd-m-Y',
1875
			'dmy_dot' => 'd.m.Y',
1876
			'dmy' => 'd/m/Y',
1877
1878
			'ymd_slash' => 'Y/m/d',
1879
			'ymd_dash' => 'Y-m-d',
1880
			'ymd_dot' => 'Y.m.d',
1881
		);
1882
1883 19
		if ( ! $date_format ) {
1884
			// If the format key isn't valid, return default format key
1885
			return isset( $gf_date_formats[ $format ] ) ? $format : $default_format;
1886
		}
1887
1888
		// If the format key isn't valid, return default format value
1889 19
		return \GV\Utils::get( $gf_date_formats, $format, $gf_date_formats[ $default_format ] );
1890
	}
1891
1892
	/**
1893
	 * If previewing a View or page with embedded Views, make the search work properly by adding hidden fields with query vars
1894
	 *
1895
	 * @since 2.2.1
1896
	 *
1897
	 * @return void
1898
	 */
1899 4
	public function add_preview_inputs() {
1900 4
		global $wp;
1901
1902 4
		if ( ! is_preview() || ! current_user_can( 'publish_gravityviews') ) {
1903 4
			return;
1904
		}
1905
1906
		// Outputs `preview` and `post_id` variables
1907
		foreach ( $wp->query_vars as $key => $value ) {
1908
			printf( '<input type="hidden" name="%s" value="%s" />', esc_attr( $key ), esc_attr( $value ) );
1909
		}
1910
1911
	}
1912
1913
	/**
1914
	 * Get an operator URL override.
1915
	 *
1916
	 * @param array  $get     Where to look for the operator.
1917
	 * @param string $key     The filter key to look for.
1918
	 * @param array  $allowed The allowed operators (whitelist).
1919
	 * @param string $default The default operator.
1920
	 *
1921
	 * @return string The operator.
1922
	 */
1923 19
	private function get_operator( $get, $key, $allowed, $default ) {
1924 19
		$operator = \GV\Utils::get( $get, "$key|op", $default );
1925
1926
		/**
1927
		 * @filter `gravityview/search/operator_whitelist` An array of allowed operators for a field.
1928
		 * @param[in,out] string[] A whitelist of allowed operators.
1929
		 * @param string The filter name.
1930
		 */
1931 19
		$allowed = apply_filters( 'gravityview/search/operator_whitelist', $allowed, $key );
1932
1933 19
		if ( ! in_array( $operator, $allowed, true ) ) {
1934 1
			$operator = $default;
1935
		}
1936
1937 19
		return $operator;
1938
	}
1939
1940
1941
} // end class
1942
1943
new GravityView_Widget_Search;
1944
1945
if ( ! gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
1946
	return;
1947
}
1948
1949
/**
1950
 * A GF_Query condition that allows user data searches.
1951
 */
1952
class GravityView_Widget_Search_Author_GF_Query_Condition extends \GF_Query_Condition {
1953 1
	public function __construct( $filter, $view ) {
1954 1
		$this->value = $filter['value'];
1955 1
		$this->view = $view;
1956 1
	}
1957
1958 1
	public function sql( $query ) {
1959 1
		global $wpdb;
1960
1961
		$user_meta_fields = array(
1962 1
			'nickname', 'first_name', 'last_name',
1963
		);
1964
1965
		/**
1966
		 * @filter `gravityview/widgets/search/created_by/user_meta_fields` Filter the user meta fields to search.
1967
		 * @param[in,out] array The user meta fields.
1968
		 * @param \GV\View $view The view.
1969
		 */
1970 1
		$user_meta_fields = apply_filters( 'gravityview/widgets/search/created_by/user_meta_fields', $user_meta_fields, $this->view );
1971
1972
		$user_fields = array(
1973 1
			'user_nicename', 'user_login', 'display_name', 'user_email', 
1974
		);
1975
1976
		/**
1977
		 * @filter `gravityview/widgets/search/created_by/user_fields` Filter the user fields to search.
1978
		 * @param[in,out] array The user fields.
1979
		 * @param \GV\View $view The view.
1980
		 */
1981 1
		$user_fields = apply_filters( 'gravityview/widgets/search/created_by/user_fields', $user_fields, $this->view );
1982
1983 1
		$conditions = array();
1984
1985 1
		foreach ( $user_fields as $user_field ) {
1986 1
			$conditions[] = $wpdb->prepare( "`u`.`$user_field` LIKE %s", '%' . $wpdb->esc_like( $this->value ) .  '%' );
1987
		}
1988
1989 1
		foreach ( $user_meta_fields as $meta_field ) {
1990 1
			$conditions[] = $wpdb->prepare( "(`um`.`meta_key` = %s AND `um`.`meta_value` LIKE %s)", $meta_field, '%' . $wpdb->esc_like( $this->value ) .  '%' );
1991
		}
1992
1993 1
		$conditions = '(' . implode( ' OR ', $conditions ) . ')';
1994
1995 1
		$alias = $query->_alias( null );
1996
1997 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)))";
1998
	}
1999
}
2000