Completed
Push — develop ( 918969...60c622 )
by Zack
06:57
created

add_datepicker_js_dependency()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 6
ccs 0
cts 3
cp 0
crap 2
rs 10
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 91
	public function filter_entries( $search_criteria, $form_id = null, $args = array(), $force_search_criteria = false ) {
537 91
		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 71
			add_action( 'gravityview/view/query', array( $this, 'gf_query_filter' ), 10, 3 );
543 71
			return $search_criteria; // Return the original criteria, GF_Query modification kicks in later
544
		}
545
546 90
		if( 'post' === $this->search_method ) {
547
			$get = $_POST;
548
		} else {
549 90
			$get = $_GET;
550
		}
551
552 90
		$view = \GV\View::by_id( \GV\Utils::get( $args, 'id' ) );
553
554 90
		gravityview()->log->debug( 'Requested $_{method}: ', array( 'method' => $this->search_method, 'data' => $get ) );
555
556 90
		if ( empty( $get ) || ! is_array( $get ) ) {
557 63
			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 70
	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 70
		$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 70
		remove_filter( 'gravityview_fe_search_criteria', array( $this, 'filter_entries' ), 10, 3 );
754 70
		$search_criteria = apply_filters( 'gravityview_fe_search_criteria', $search_criteria, $view->form->ID, $view->settings->as_atts() );
755 70
		add_filter( 'gravityview_fe_search_criteria', array( $this, 'filter_entries' ), 10, 3 );
756
757 70
		$query_class = $view->get_query_class();
758
759 70
		if ( empty( $search_criteria['field_filters'] ) ) {
760 62
			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|\GV\Template_Context $context
1291
	 *
1292
	 * @return void
1293
	 */
1294 4
	public function render_frontend( $widget_args, $content = '', $context = '' ) {
1295
1296
		/** @var GravityView_View $gravityview_view */
1297 4
		$gravityview_view = GravityView_View::getInstance();
1298
1299 4
		if ( empty( $gravityview_view ) ) {
1300
			gravityview()->log->debug( '$gravityview_view not instantiated yet.' );
1301
			return;
1302
		}
1303
1304 4
		$view = \GV\View::by_id( $gravityview_view->view_id );
1305
1306
		// get configured search fields
1307 4
		$search_fields = ! empty( $widget_args['search_fields'] ) ? json_decode( $widget_args['search_fields'], true ) : '';
1308
1309 4
		if ( empty( $search_fields ) || ! is_array( $search_fields ) ) {
1310
			gravityview()->log->debug( 'No search fields configured for widget:', array( 'data' => $widget_args ) );
1311
			return;
1312
		}
1313
1314
		// prepare fields
1315 4
		foreach ( $search_fields as $k => $field ) {
1316
1317 4
			$updated_field = $field;
1318
1319 4
			$updated_field = $this->get_search_filter_details( $updated_field, $context );
1320
1321 4
			switch ( $field['field'] ) {
1322
1323 4
				case 'search_all':
1324 4
					$updated_field['key'] = 'search_all';
1325 4
					$updated_field['input'] = 'search_all';
1326 4
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_search' );
1327 4
					break;
1328
1329
				case 'entry_date':
1330
					$updated_field['key'] = 'entry_date';
1331
					$updated_field['input'] = 'entry_date';
1332
					$updated_field['value'] = array(
1333
						'start' => $this->rgget_or_rgpost( 'gv_start' ),
1334
						'end' => $this->rgget_or_rgpost( 'gv_end' ),
1335
					);
1336
					break;
1337
1338
				case 'entry_id':
1339
					$updated_field['key'] = 'entry_id';
1340
					$updated_field['input'] = 'entry_id';
1341
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_id' );
1342
					break;
1343
1344
				case 'created_by':
1345
					$updated_field['key'] = 'created_by';
1346
					$updated_field['name'] = 'gv_by';
1347
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_by' );
1348
					$updated_field['choices'] = self::get_created_by_choices( $view );
1349
					break;
1350
1351
				case 'is_approved':
1352
					$updated_field['key'] = 'is_approved';
1353
					$updated_field['value'] = $this->rgget_or_rgpost( 'filter_is_approved' );
1354
					$updated_field['choices'] = self::get_is_approved_choices();
1355
					break;
1356
			}
1357
1358 4
			$search_fields[ $k ] = $updated_field;
1359
		}
1360
1361 4
		gravityview()->log->debug( 'Calculated Search Fields: ', array( 'data' => $search_fields ) );
1362
1363
		/**
1364
		 * @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.
1365
		 * @param array $search_fields Array of search filters with `key`, `label`, `value`, `type`, `choices` keys
1366
		 * @param GravityView_Widget_Search $this Current widget object
1367
		 * @param array $widget_args Args passed to this method. {@since 1.8}
1368
		 * @param \GV\Template_Context $context {@since 2.0}
1369
		 * @var array
1370
		 */
1371 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...
1372
1373 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...
1374
1375 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...
1376
1377
		/** @since 1.14 */
1378 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...
1379
1380 4
		$custom_class = ! empty( $widget_args['custom_class'] ) ? $widget_args['custom_class'] : '';
1381
1382 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...
1383
1384 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...
1385
1386 4
		if ( $this->has_date_field( $search_fields ) ) {
1387
			// enqueue datepicker stuff only if needed!
1388
			$this->enqueue_datepicker();
1389
		}
1390
1391 4
		$this->maybe_enqueue_flexibility();
1392
1393 4
		$gravityview_view->render( 'widget', 'search', false );
1394 4
	}
1395
1396
	/**
1397
	 * Get the search class for a search form
1398
	 *
1399
	 * @since 1.5.4
1400
	 *
1401
	 * @return string Sanitized CSS class for the search form
1402
	 */
1403 4
	public static function get_search_class( $custom_class = '' ) {
1404 4
		$gravityview_view = GravityView_View::getInstance();
1405
1406 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...
1407
1408 4
		if ( ! empty( $custom_class )  ) {
1409
			$search_class .= ' '.$custom_class;
1410
		}
1411
1412
		/**
1413
		 * @filter `gravityview_search_class` Modify the CSS class for the search form
1414
		 * @param string $search_class The CSS class for the search form
1415
		 */
1416 4
		$search_class = apply_filters( 'gravityview_search_class', $search_class );
1417
1418
		// Is there an active search being performed? Used by fe-views.js
1419 4
		$search_class .= GravityView_frontend::getInstance()->isSearch() ? ' gv-is-search' : '';
1420
1421 4
		return gravityview_sanitize_html_class( $search_class );
1422
	}
1423
1424
1425
	/**
1426
	 * Calculate the search form action
1427
	 * @since 1.6
1428
	 *
1429
	 * @return string
1430
	 */
1431 4
	public static function get_search_form_action() {
1432 4
		$gravityview_view = GravityView_View::getInstance();
1433
1434 4
		$post_id = $gravityview_view->getPostId() ? $gravityview_view->getPostId() : $gravityview_view->getViewId();
1435
1436 4
		$url = add_query_arg( array(), get_permalink( $post_id ) );
1437
1438
		/**
1439
		 * @filter `gravityview/widget/search/form/action` Override the search URL.
1440
		 * @param[in,out] string $action Where the form submits to.
1441
		 *
1442
		 * Further parameters will be added once adhoc context is added.
1443
		 * Use gravityview()->request until then.
1444
		 */
1445 4
		return apply_filters( 'gravityview/widget/search/form/action', $url );
1446
	}
1447
1448
	/**
1449
	 * Get the label for a search form field
1450
	 * @param  array $field      Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1451
	 * @param  array $form_field Form field data, as fetched by `gravityview_get_field()`
1452
	 * @return string             Label for the search form
1453
	 */
1454 4
	private static function get_field_label( $field, $form_field = array() ) {
1455
1456 4
		$label = \GV\Utils::_GET( 'label', \GV\Utils::get( $field, 'label' ) );
1457
1458 4
		if ( ! $label ) {
1459
1460 4
			$label = isset( $form_field['label'] ) ? $form_field['label'] : '';
1461
1462 4
			switch( $field['field'] ) {
1463 4
				case 'search_all':
1464 4
					$label = __( 'Search Entries:', 'gravityview' );
1465 4
					break;
1466
				case 'entry_date':
1467
					$label = __( 'Filter by date:', 'gravityview' );
1468
					break;
1469
				case 'entry_id':
1470
					$label = __( 'Entry ID:', 'gravityview' );
1471
					break;
1472
				default:
1473
					// If this is a field input, not a field
1474
					if ( strpos( $field['field'], '.' ) > 0 && ! empty( $form_field['inputs'] ) ) {
1475
1476
						// Get the label for the field in question, which returns an array
1477
						$items = wp_list_filter( $form_field['inputs'], array( 'id' => $field['field'] ) );
1478
1479
						// Get the item with the `label` key
1480
						$values = wp_list_pluck( $items, 'label' );
1481
1482
						// There will only one item in the array, but this is easier
1483
						foreach ( $values as $value ) {
1484
							$label = $value;
1485
							break;
1486
						}
1487
					}
1488
			}
1489
		}
1490
1491
		/**
1492
		 * @filter `gravityview_search_field_label` Modify the label for a search field. Supports returning HTML
1493
		 * @since 1.17.3 Added $field parameter
1494
		 * @param[in,out] string $label Existing label text, sanitized.
1495
		 * @param[in] array $form_field Gravity Forms field array, as returned by `GFFormsModel::get_field()`
1496
		 * @param[in] array $field Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1497
		 */
1498 4
		$label = apply_filters( 'gravityview_search_field_label', esc_attr( $label ), $form_field, $field );
1499
1500 4
		return $label;
1501
	}
1502
1503
	/**
1504
	 * Prepare search fields to frontend render with other details (label, field type, searched values)
1505
	 *
1506
	 * @param array $field
1507
	 * @param \GV\Context $context
1508
	 *
1509
	 * @return array
1510
	 */
1511 4
	private function get_search_filter_details( $field, $context ) {
1512
1513 4
		$gravityview_view = GravityView_View::getInstance();
1514
1515 4
		$form = $gravityview_view->getForm();
1516
1517
		// for advanced field ids (eg, first name / last name )
1518 4
		$name = 'filter_' . str_replace( '.', '_', $field['field'] );
1519
1520
		// get searched value from $_GET/$_POST (string or array)
1521 4
		$value = $this->rgget_or_rgpost( $name );
1522
1523
		// get form field details
1524 4
		$form_field = gravityview_get_field( $form, $field['field'] );
1525
1526 4
		$form_field_type = \GV\Utils::get( $form_field, 'type' );
1527
1528
		$filter = array(
1529 4
			'key' => \GV\Utils::get( $field, 'field' ),
1530 4
			'name' => $name,
1531 4
			'label' => self::get_field_label( $field, $form_field ),
1532 4
			'input' => \GV\Utils::get( $field, 'input' ),
1533 4
			'value' => $value,
1534 4
			'type' => $form_field_type,
1535
		);
1536
1537
		// collect choices
1538 4
		if ( 'post_category' === $form_field_type && ! empty( $form_field['displayAllCategories'] ) && empty( $form_field['choices'] ) ) {
1539
			$filter['choices'] = gravityview_get_terms_choices();
1540 4
		} elseif ( ! empty( $form_field['choices'] ) ) {
1541
			$filter['choices'] = $form_field['choices'];
1542
		}
1543
1544 4
		if ( 'date_range' === $field['input'] && empty( $value ) ) {
1545
			$filter['value'] = array( 'start' => '', 'end' => '' );
1546
		}
1547
1548 4
		if ( ! empty( $filter['choices'] ) ) {
1549
			/**
1550
			 * @filter `gravityview/search/sieve_choices` Only output used choices for this field.
1551
			 * @param[in,out] bool Yes or no.
1552
			 * @param array $field The field configuration.
1553
			 * @param \GV\Context The context.
1554
			 */
1555
			if ( apply_filters( 'gravityview/search/sieve_choices', false, $field, $context ) ) {
1556
				$filter['choices'] = $this->sieve_filter_choices( $filter, $context );
1557
			}
1558
		}
1559
1560
		/**
1561
		 * @filter `gravityview/search/filter_details` Filter the output filter details for the Search widget.
1562
		 * @param[in,out] array $filter The filter details
1563
		 * @param array $field The search field configuration
1564
		 * @param \GV\Context The context
1565
		 * @since develop
1566
		 */
1567 4
		$filter = apply_filters( 'gravityview/search/filter_details', $filter, $field, $context );
1568
1569 4
		return $filter;
1570
1571
	}
1572
1573
	/**
1574
	 * Sieve filter choices to only ones that are used.
1575
	 *
1576
	 * @param array $filter The filter configuration.
1577
	 * @param \GV\Context $context The context
1578
	 *
1579
	 * @since develop
1580
	 * @internal
1581
	 *
1582
	 * @return array The filter choices.
1583
	 */
1584
	private function sieve_filter_choices( $filter, $context ) {
1585
		if ( empty( $filter['key'] ) || empty( $filter['choices'] ) ) {
1586
			return $filter; // @todo Populate plugins might give us empty choices
1587
		}
1588
1589
		if ( ! is_numeric( $filter['key'] ) ) {
1590
			return $filter;
1591
		}
1592
1593
		$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...
1594
1595
		global $wpdb;
1596
1597
		$table = GFFormsModel::get_entry_meta_table_name();
1598
1599
		$key_like = $wpdb->esc_like( $filter['key'] ) . '.%';
1600
1601
		switch ( \GV\Utils::get( $filter, 'type' ) ):
1602
			case 'post_category':
1603
				$choices = $wpdb->get_col( $wpdb->prepare(
1604
					"SELECT DISTINCT SUBSTRING_INDEX(meta_value, ':', 1) FROM $table WHERE (meta_key LIKE %s OR meta_key = %d) AND form_id = %d",
1605
					$key_like, $filter['key'], $form_id
1606
				) );
1607
				break;
1608
			default:
1609
				$choices = $wpdb->get_col( $wpdb->prepare(
1610
					"SELECT DISTINCT meta_value FROM $table WHERE (meta_key LIKE %s OR meta_key = %d) AND form_id = %d",
1611
					$key_like, $filter['key'], $form_id
1612
				) );
1613
1614
				if ( ( $field = gravityview_get_field( $form_id, $filter['key'] ) ) && 'json' === $field->storageType ) {
1615
					$choices = array_map( 'json_decode', $choices );
1616
					$_choices_array = array();
1617
					foreach ( $choices as $choice ) {
1618
						if ( is_array( $choice ) ) {
1619
							$_choices_array = array_merge( $_choices_array, $choice );
1620
						} else {
1621
							$_choices_array []= $choice;
1622
						}
1623
					}
1624
					$choices = array_unique( $_choices_array );
1625
				}
1626
1627
				break;
1628
		endswitch;
1629
1630
		$filter_choices = array();
1631
		foreach ( $filter['choices'] as $choice ) {
1632
			if ( in_array( $choice['text'], $choices, true ) || in_array( $choice['value'], $choices, true ) ) {
1633
				$filter_choices[] = $choice;
1634
			}
1635
		}
1636
1637
		return $filter_choices;
1638
	}
1639
1640
	/**
1641
	 * Calculate the search choices for the users
1642
	 *
1643
	 * @param \GV\View $view The view
1644
	 * @since develop
1645
	 *
1646
	 * @since 1.8
1647
	 *
1648
	 * @return array Array of user choices (value = ID, text = display name)
1649
	 */
1650
	private static function get_created_by_choices( $view ) {
1651
1652
		/**
1653
		 * filter gravityview/get_users/search_widget
1654
		 * @see \GVCommon::get_users
1655
		 */
1656
		$users = GVCommon::get_users( 'search_widget', array( 'fields' => array( 'ID', 'display_name' ) ) );
1657
1658
		$choices = array();
1659
		foreach ( $users as $user ) {
1660
			/**
1661
			 * @filter `gravityview/search/created_by/text` Filter the display text in created by search choices
1662
			 * @since develop
1663
			 * @param string[in,out] The text. Default: $user->display_name
1664
			 * @param \WP_User $user The user.
1665
			 * @param \GV\View $view The view.
1666
			 */
1667
			$text = apply_filters( 'gravityview/search/created_by/text', $user->display_name, $user, $view );
1668
			$choices[] = array(
1669
				'value' => $user->ID,
1670
				'text' => $text,
1671
			);
1672
		}
1673
1674
		return $choices;
1675
	}
1676
1677
	/**
1678
	 * Calculate the search checkbox choices for approval status
1679
	 *
1680
	 * @since develop
1681
	 *
1682
	 * @return array Array of approval status choices (value = status, text = display name)
1683
	 */
1684
	private static function get_is_approved_choices() {
1685
1686
		$choices = array();
1687
		foreach ( GravityView_Entry_Approval_Status::get_all() as $status ) {
1688
			$choices[] = array(
1689
				'value' => $status['value'],
1690
				'text' => $status['label'],
1691
			);
1692
		}
1693
1694
		return $choices;
1695
	}
1696
1697
	/**
1698
	 * Output the Clear Search Results button
1699
	 * @since 1.5.4
1700
	 */
1701 4
	public static function the_clear_search_button() {
1702 4
		$gravityview_view = GravityView_View::getInstance();
1703
1704 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...
1705
1706
			$url = strtok( add_query_arg( array() ), '?' );
1707
1708
			echo gravityview_get_link( $url, esc_html__( 'Clear', 'gravityview' ), 'class=button gv-search-clear' );
1709
1710
		}
1711 4
	}
1712
1713
	/**
1714
	 * Based on the search method, fetch the value for a specific key
1715
	 *
1716
	 * @since 1.16.4
1717
	 *
1718
	 * @param string $name Name of the request key to fetch the value for
1719
	 *
1720
	 * @return mixed|string Value of request at $name key. Empty string if empty.
1721
	 */
1722 4
	private function rgget_or_rgpost( $name ) {
1723 4
		$value = \GV\Utils::_REQUEST( $name );
1724
1725 4
		$value = stripslashes_deep( $value );
1726
1727 4
		$value = gv_map_deep( $value, 'rawurldecode' );
1728
1729 4
		$value = gv_map_deep( $value, '_wp_specialchars' );
1730
1731 4
		return $value;
1732
	}
1733
1734
1735
	/**
1736
	 * Require the datepicker script for the frontend GV script
1737
	 * @param array $js_dependencies Array of existing required scripts for the fe-views.js script
1738
	 * @return array Array required scripts, with `jquery-ui-datepicker` added
1739
	 */
1740
	public function add_datepicker_js_dependency( $js_dependencies ) {
1741
1742
		$js_dependencies[] = 'jquery-ui-datepicker';
1743
1744
		return $js_dependencies;
1745
	}
1746
1747
	/**
1748
	 * Modify the array passed to wp_localize_script()
1749
	 *
1750
	 * @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...
1751
	 * @param array $view_data View data array with View settings
1752
	 *
1753
	 * @return array
1754
	 */
1755
	public function add_datepicker_localization( $localizations = array(), $view_data = array() ) {
1756
		global $wp_locale;
1757
1758
		/**
1759
		 * @filter `gravityview_datepicker_settings` Modify the datepicker settings
1760
		 * @see http://api.jqueryui.com/datepicker/ Learn what settings are available
1761
		 * @see http://www.renegadetechconsulting.com/tutorials/jquery-datepicker-and-wordpress-i18n Thanks for the helpful information on $wp_locale
1762
		 * @param array $js_localization The data padded to the Javascript file
1763
		 * @param array $view_data View data array with View settings
1764
		 */
1765
		$datepicker_settings = apply_filters( 'gravityview_datepicker_settings', array(
1766
			'yearRange' => '-5:+5',
1767
			'changeMonth' => true,
1768
			'changeYear' => true,
1769
			'closeText' => esc_attr_x( 'Close', 'Close calendar', 'gravityview' ),
1770
			'prevText' => esc_attr_x( 'Prev', 'Previous month in calendar', 'gravityview' ),
1771
			'nextText' => esc_attr_x( 'Next', 'Next month in calendar', 'gravityview' ),
1772
			'currentText' => esc_attr_x( 'Today', 'Today in calendar', 'gravityview' ),
1773
			'weekHeader' => esc_attr_x( 'Week', 'Week in calendar', 'gravityview' ),
1774
			'monthStatus'       => __( 'Show a different month', 'gravityview' ),
1775
			'monthNames'        => array_values( $wp_locale->month ),
1776
			'monthNamesShort'   => array_values( $wp_locale->month_abbrev ),
1777
			'dayNames'          => array_values( $wp_locale->weekday ),
1778
			'dayNamesShort'     => array_values( $wp_locale->weekday_abbrev ),
1779
			'dayNamesMin'       => array_values( $wp_locale->weekday_initial ),
1780
			// get the start of week from WP general setting
1781
			'firstDay'          => get_option( 'start_of_week' ),
1782
			// is Right to left language? default is false
1783
			'isRTL'             => is_rtl(),
1784
		), $view_data );
1785
1786
		$localizations['datepicker'] = $datepicker_settings;
1787
1788
		return $localizations;
1789
1790
	}
1791
1792
	/**
1793
	 * Register search widget scripts, including Flexibility
1794
	 *
1795
	 * @see https://github.com/10up/flexibility
1796
	 *
1797
	 * @since 1.17
1798
	 *
1799
	 * @return void
1800
	 */
1801
	public function register_scripts() {
1802
		wp_register_script( 'gv-flexibility', plugins_url( 'assets/lib/flexibility/flexibility.js', GRAVITYVIEW_FILE ), array(), \GV\Plugin::$version, true );
1803
	}
1804
1805
	/**
1806
	 * If the current visitor is running IE 8 or 9, enqueue Flexibility
1807
	 *
1808
	 * @since 1.17
1809
	 *
1810
	 * @return void
1811
	 */
1812 4
	private function maybe_enqueue_flexibility() {
1813 4
		if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && preg_match( '/MSIE [8-9]/', $_SERVER['HTTP_USER_AGENT'] ) ) {
1814
			wp_enqueue_script( 'gv-flexibility' );
1815
		}
1816 4
	}
1817
1818
	/**
1819
	 * Enqueue the datepicker script
1820
	 *
1821
	 * It sets the $gravityview->datepicker_class parameter
1822
	 *
1823
	 * @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.
1824
	 * @return void
1825
	 */
1826
	public function enqueue_datepicker() {
1827
		$gravityview_view = GravityView_View::getInstance();
1828
1829
		wp_enqueue_script( 'jquery-ui-datepicker' );
1830
1831
		add_filter( 'gravityview_js_dependencies', array( $this, 'add_datepicker_js_dependency' ) );
1832
		add_filter( 'gravityview_js_localization', array( $this, 'add_datepicker_localization' ), 10, 2 );
1833
1834
		$scheme = is_ssl() ? 'https://' : 'http://';
1835
		wp_enqueue_style( 'jquery-ui-datepicker', $scheme.'ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/smoothness/jquery-ui.css' );
1836
1837
		/**
1838
		 * @filter `gravityview_search_datepicker_class`
1839
		 * 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.
1840
		 * @param string $css_class CSS class to use. Default: `gv-datepicker datepicker mdy` \n
1841
		 * Options are:
1842
		 * - `mdy` (mm/dd/yyyy)
1843
		 * - `dmy` (dd/mm/yyyy)
1844
		 * - `dmy_dash` (dd-mm-yyyy)
1845
		 * - `dmy_dot` (dd.mm.yyyy)
1846
		 * - `ymd_slash` (yyyy/mm/dd)
1847
		 * - `ymd_dash` (yyyy-mm-dd)
1848
		 * - `ymd_dot` (yyyy.mm.dd)
1849
		 */
1850
		$datepicker_class = apply_filters( 'gravityview_search_datepicker_class', "gv-datepicker datepicker " . $this->get_datepicker_format() );
1851
1852
		$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...
1853
	}
1854
1855
	/**
1856
	 * Retrieve the datepicker format.
1857
	 *
1858
	 * @param bool $date_format Whether to return the PHP date format or the datpicker class name. Default: false.
1859
	 *
1860
	 * @see https://docs.gravityview.co/article/115-changing-the-format-of-the-search-widgets-date-picker
1861
	 *
1862
	 * @return string The datepicker format placeholder, or the PHP date format.
1863
	 */
1864 19
	private function get_datepicker_format( $date_format = false ) {
1865
1866 19
		$default_format = 'mdy';
1867
1868
		/**
1869
		 * @filter `gravityview/widgets/search/datepicker/format`
1870
		 * @since 2.1.1
1871
		 * @param string           $format Default: mdy
1872
		 * Options are:
1873
		 * - `mdy` (mm/dd/yyyy)
1874
		 * - `dmy` (dd/mm/yyyy)
1875
		 * - `dmy_dash` (dd-mm-yyyy)
1876
		 * - `dmy_dot` (dd.mm.yyyy)
1877
		 * - `ymd_slash` (yyyy/mm/dd)
1878
		 * - `ymd_dash` (yyyy-mm-dd)
1879
		 * - `ymd_dot` (yyyy.mm.dd)
1880
		 */
1881 19
		$format = apply_filters( 'gravityview/widgets/search/datepicker/format', $default_format );
1882
1883
		$gf_date_formats = array(
1884 19
			'mdy' => 'm/d/Y',
1885
1886
			'dmy_dash' => 'd-m-Y',
1887
			'dmy_dot' => 'd.m.Y',
1888
			'dmy' => 'd/m/Y',
1889
1890
			'ymd_slash' => 'Y/m/d',
1891
			'ymd_dash' => 'Y-m-d',
1892
			'ymd_dot' => 'Y.m.d',
1893
		);
1894
1895 19
		if ( ! $date_format ) {
1896
			// If the format key isn't valid, return default format key
1897
			return isset( $gf_date_formats[ $format ] ) ? $format : $default_format;
1898
		}
1899
1900
		// If the format key isn't valid, return default format value
1901 19
		return \GV\Utils::get( $gf_date_formats, $format, $gf_date_formats[ $default_format ] );
1902
	}
1903
1904
	/**
1905
	 * If previewing a View or page with embedded Views, make the search work properly by adding hidden fields with query vars
1906
	 *
1907
	 * @since 2.2.1
1908
	 *
1909
	 * @return void
1910
	 */
1911 4
	public function add_preview_inputs() {
1912 4
		global $wp;
1913
1914 4
		if ( ! is_preview() || ! current_user_can( 'publish_gravityviews') ) {
1915 4
			return;
1916
		}
1917
1918
		// Outputs `preview` and `post_id` variables
1919
		foreach ( $wp->query_vars as $key => $value ) {
1920
			printf( '<input type="hidden" name="%s" value="%s" />', esc_attr( $key ), esc_attr( $value ) );
1921
		}
1922
1923
	}
1924
1925
	/**
1926
	 * Get an operator URL override.
1927
	 *
1928
	 * @param array  $get     Where to look for the operator.
1929
	 * @param string $key     The filter key to look for.
1930
	 * @param array  $allowed The allowed operators (whitelist).
1931
	 * @param string $default The default operator.
1932
	 *
1933
	 * @return string The operator.
1934
	 */
1935 19
	private function get_operator( $get, $key, $allowed, $default ) {
1936 19
		$operator = \GV\Utils::get( $get, "$key|op", $default );
1937
1938
		/**
1939
		 * @filter `gravityview/search/operator_whitelist` An array of allowed operators for a field.
1940
		 * @param[in,out] string[] A whitelist of allowed operators.
1941
		 * @param string The filter name.
1942
		 */
1943 19
		$allowed = apply_filters( 'gravityview/search/operator_whitelist', $allowed, $key );
1944
1945 19
		if ( ! in_array( $operator, $allowed, true ) ) {
1946 1
			$operator = $default;
1947
		}
1948
1949 19
		return $operator;
1950
	}
1951
1952
1953
} // end class
1954
1955
new GravityView_Widget_Search;
1956
1957
if ( ! gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
1958
	return;
1959
}
1960
1961
/**
1962
 * A GF_Query condition that allows user data searches.
1963
 */
1964
class GravityView_Widget_Search_Author_GF_Query_Condition extends \GF_Query_Condition {
1965 1
	public function __construct( $filter, $view ) {
1966 1
		$this->value = $filter['value'];
1967 1
		$this->view = $view;
1968 1
	}
1969
1970 1
	public function sql( $query ) {
1971 1
		global $wpdb;
1972
1973
		$user_meta_fields = array(
1974 1
			'nickname', 'first_name', 'last_name',
1975
		);
1976
1977
		/**
1978
		 * @filter `gravityview/widgets/search/created_by/user_meta_fields` Filter the user meta fields to search.
1979
		 * @param[in,out] array The user meta fields.
1980
		 * @param \GV\View $view The view.
1981
		 */
1982 1
		$user_meta_fields = apply_filters( 'gravityview/widgets/search/created_by/user_meta_fields', $user_meta_fields, $this->view );
1983
1984
		$user_fields = array(
1985 1
			'user_nicename', 'user_login', 'display_name', 'user_email',
1986
		);
1987
1988
		/**
1989
		 * @filter `gravityview/widgets/search/created_by/user_fields` Filter the user fields to search.
1990
		 * @param[in,out] array The user fields.
1991
		 * @param \GV\View $view The view.
1992
		 */
1993 1
		$user_fields = apply_filters( 'gravityview/widgets/search/created_by/user_fields', $user_fields, $this->view );
1994
1995 1
		$conditions = array();
1996
1997 1
		foreach ( $user_fields as $user_field ) {
1998 1
			$conditions[] = $wpdb->prepare( "`u`.`$user_field` LIKE %s", '%' . $wpdb->esc_like( $this->value ) .  '%' );
1999
		}
2000
2001 1
		foreach ( $user_meta_fields as $meta_field ) {
2002 1
			$conditions[] = $wpdb->prepare( "(`um`.`meta_key` = %s AND `um`.`meta_value` LIKE %s)", $meta_field, '%' . $wpdb->esc_like( $this->value ) .  '%' );
2003
		}
2004
2005 1
		$conditions = '(' . implode( ' OR ', $conditions ) . ')';
2006
2007 1
		$alias = $query->_alias( null );
2008
2009 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)))";
2010
	}
2011
}
2012