Completed
Push — develop ( 465a4d...2795b1 )
by Gennady
31:42 queued 14:10
created

GravityView_Widget_Search::get_searchable_fields()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
cc 7
nc 5
nop 0
dl 0
loc 28
ccs 0
cts 12
cp 0
crap 56
rs 8.5386
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 37
	public function __construct() {
31
32 37
		$this->widget_id = 'search_bar';
33 37
		$this->widget_description = esc_html__( 'Search form for searching entries.', 'gravityview' );
34
35 37
		self::$instance = &$this;
36
37 37
		self::$file = plugin_dir_path( __FILE__ );
38
39 37
		$default_values = array( 'header' => 0, 'footer' => 0 );
40
41
		$settings = array(
42 37
			'search_layout' => array(
43 37
				'type' => 'radio',
44
				'full_width' => true,
45 37
				'label' => esc_html__( 'Search Layout', 'gravityview' ),
46 37
				'value' => 'horizontal',
47
				'options' => array(
48 37
					'horizontal' => esc_html__( 'Horizontal', 'gravityview' ),
49 37
					'vertical' => esc_html__( 'Vertical', 'gravityview' ),
50
				),
51
			),
52
			'search_clear' => array(
53 37
				'type' => 'checkbox',
54 37
				'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 37
				'type' => 'radio',
65
				'full_width' => true,
66 37
				'label' => esc_html__( 'Search Mode', 'gravityview' ),
67 37
				'desc' => __('Should search results match all search fields, or any?', 'gravityview'),
68 37
				'value' => 'any',
69 37
				'class' => 'hide-if-js',
70
				'options' => array(
71 37
					'any' => esc_html__( 'Match Any Fields', 'gravityview' ),
72 37
					'all' => esc_html__( 'Match All Fields', 'gravityview' ),
73
				),
74
			),
75
		);
76
77 37
		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 37
		parent::__construct( esc_html__( 'Search Bar', 'gravityview' ), null, $default_values, $settings );
96
97
		// calculate the search method (POST / GET)
98 37
		$this->set_search_method();
99 37
	}
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 37
	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 37
		$method = apply_filters( 'gravityview/search/method', $this->search_method );
123
124 37
		$method = strtolower( $method );
125
126 37
		$this->search_method = in_array( $method, array( 'get', 'post' ) ) ? $method : 'get';
127 37
	}
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...
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 36
	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 36
		$searchable_fields = array();
479
480 36
		if ( ! $view ) {
481
			return $searchable_fields;
482
		}
483
484
		/**
485
		 * Include the sidebar Widgets.
486
		 */
487 36
		$widgets = (array) get_option( 'widget_gravityview_search', array() );
488
489 36
		foreach ( $widgets as $widget ) {
490 36
			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 36
		foreach ( $view->widgets->by_id( $this->get_widget_id() )->all() as $widget ) {
503 31
			if( $_fields = json_decode( $widget->configuration->get( 'search_fields' ), true ) ) {
504 31
				foreach ( $_fields as $field ) {
505 31
					if ( empty( $field['form_id'] ) ) {
506 31
						$field['form_id'] = $view->form ? $view->form->ID : 0;
507
					}
508 31
					$searchable_fields[] = $with_full_field ? $field : $field['field'];
509
				}
510
			}
511
		}
512
513 36
		return $searchable_fields;
514
	}
515
516
	/** --- Frontend --- */
517
518
	/**
519
	 * Calculate the search criteria to filter entries
520
	 * @param array $search_criteria The search criteria
521
	 * @param int $form_id The form ID
522
	 * @param array $args Some args
523
	 *
524
	 * @param bool $force_search_criteria Whether to suppress GF_Query filter, internally used in self::gf_query_filter
525
	 *
526
	 * @return array
527
	 */
528 83
	public function filter_entries( $search_criteria, $form_id = null, $args = array(), $force_search_criteria = false ) {
529 83
		if ( ! $force_search_criteria && gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
530
			/**
531
			 * If GF_Query is available, we can construct custom conditions with nested
532
			 * booleans on the query, giving up the old ways of flat search_criteria field_filters.
533
			 */
534 64
			add_action( 'gravityview/view/query', array( $this, 'gf_query_filter' ), 10, 3 );
535 64
			return $search_criteria; // Return the original criteria, GF_Query modification kicks in later
536
		}
537
538 82
		if( 'post' === $this->search_method ) {
539
			$get = $_POST;
540
		} else {
541 82
			$get = $_GET;
542
		}
543
544 82
		$view = \GV\View::by_id( \GV\Utils::get( $args, 'id' ) );
545
546 82
		gravityview()->log->debug( 'Requested $_{method}: ', array( 'method' => $this->search_method, 'data' => $get ) );
547
548 82
		if ( empty( $get ) || ! is_array( $get ) ) {
549 57
			return $search_criteria;
550
		}
551
552 37
		$get = stripslashes_deep( $get );
553
554 37
		$get = gv_map_deep( $get, 'rawurldecode' );
555
556
		// Make sure array key is set up
557 37
		$search_criteria['field_filters'] = \GV\Utils::get( $search_criteria, 'field_filters', array() );
558
559 37
		$searchable_fields = $this->get_view_searchable_fields( $view );
560 37
		$searchable_field_objects = $this->get_view_searchable_fields( $view, true );
561
562
		// add free search
563 37
		if ( isset( $get['gv_search'] ) && '' !== $get['gv_search'] && in_array( 'search_all', $searchable_fields ) ) {
564
565 4
			$search_all_value = trim( $get['gv_search'] );
566
567
			/**
568
			 * @filter `gravityview/search-all-split-words` Search for each word separately or the whole phrase?
569
			 * @since 1.20.2
570
			 * @param bool $split_words True: split a phrase into words; False: search whole word only [Default: true]
571
			 */
572 4
			$split_words = apply_filters( 'gravityview/search-all-split-words', true );
573
574 4
			if ( $split_words ) {
575
576
				// Search for a piece
577 4
				$words = explode( ' ', $search_all_value );
578
579 4
				$words = array_filter( $words );
580
581
			} else {
582
583
				// Replace multiple spaces with one space
584 1
				$search_all_value = preg_replace( '/\s+/ism', ' ', $search_all_value );
585
586 1
				$words = array( $search_all_value );
587
			}
588
589 4
			foreach ( $words as $word ) {
590 4
				$search_criteria['field_filters'][] = array(
591 4
					'key' => null, // The field ID to search
592 4
					'value' => $word, // The value to search
593 4
					'operator' => 'contains', // What to search in. Options: `is` or `contains`
594
				);
595
			}
596
		}
597
598
		// start date & end date
599 37
		if ( in_array( 'entry_date', $searchable_fields ) ) {
600
			/**
601
			 * Get and normalize the dates according to the input format.
602
			 */
603 12
			if ( $curr_start = ! empty( $get['gv_start'] ) ? $get['gv_start'] : '' ) {
604 12
				if( $curr_start_date = date_create_from_format( $this->get_datepicker_format( true ), $curr_start ) ) {
605 12
					$curr_start = $curr_start_date->format( 'Y-m-d' );
606
				}
607
			}
608
609 12
			if ( $curr_end = ! empty( $get['gv_start'] ) ? ( ! empty( $get['gv_end'] ) ? $get['gv_end'] : '' ) : '' ) {
610 12
				if( $curr_end_date = date_create_from_format( $this->get_datepicker_format( true ), $curr_end ) ) {
611 12
					$curr_end = $curr_end_date->format( 'Y-m-d' );
612
				}
613
			}
614
615 12
			if ( $view ) {
616
				/**
617
				 * Override start and end dates if View is limited to some already.
618
				 */
619 12
				if ( $start_date = $view->settings->get( 'start_date' ) ) {
620 1
					if ( $start_timestamp = strtotime( $curr_start ) ) {
621 1
						$curr_start = $start_timestamp < strtotime( $start_date ) ? $start_date : $curr_start;
622
					}
623
				}
624 12
				if ( $end_date = $view->settings->get( 'end_date' ) ) {
625
					if ( $end_timestamp = strtotime( $curr_end ) ) {
626
						$curr_end = $end_timestamp > strtotime( $end_date ) ? $end_date : $curr_end;
627
					}
628
				}
629
			}
630
631
			/**
632
			 * @filter `gravityview_date_created_adjust_timezone` Whether to adjust the timezone for entries. \n
633
			 * date_created is stored in UTC format. Convert search date into UTC (also used on templates/fields/date_created.php)
634
			 * @since 1.12
635
			 * @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
636
			 * @param[in] string $context Where the filter is being called from. `search` in this case.
637
			 */
638 12
			$adjust_tz = apply_filters( 'gravityview_date_created_adjust_timezone', true, 'search' );
639
640
			/**
641
			 * Don't set $search_criteria['start_date'] if start_date is empty as it may lead to bad query results (GFAPI::get_entries)
642
			 */
643 12
			if ( ! empty( $curr_start ) ) {
644 12
				$curr_start = date( 'Y-m-d H:i:s', strtotime( $curr_start ) );
645 12
				$search_criteria['start_date'] = $adjust_tz ? get_gmt_from_date( $curr_start ) : $curr_start;
646
			}
647
648 12
			if ( ! empty( $curr_end ) ) {
649
				// Fast-forward 24 hour on the end time
650 12
				$curr_end = date( 'Y-m-d H:i:s', strtotime( $curr_end ) + DAY_IN_SECONDS );
651 12
				$search_criteria['end_date'] = $adjust_tz ? get_gmt_from_date( $curr_end ) : $curr_end;
652 12
				if ( strpos( $search_criteria['end_date'], '00:00:00' ) ) { // See https://github.com/gravityview/GravityView/issues/1056
653 12
					$search_criteria['end_date'] = date( 'Y-m-d H:i:s', strtotime( $search_criteria['end_date'] ) - 1 );
654
				}
655
			}
656
		}
657
658
		// search for a specific entry ID
659 37
		if ( ! empty( $get[ 'gv_id' ] ) && in_array( 'entry_id', $searchable_fields ) ) {
660 2
			$search_criteria['field_filters'][] = array(
661 2
				'key' => 'id',
662 2
				'value' => absint( $get[ 'gv_id' ] ),
663 2
				'operator' => $this->get_operator( $get, 'gv_id', array( '=' ), '=' ),
664
			);
665
		}
666
667
		// search for a specific Created_by ID
668 37
		if ( ! empty( $get[ 'gv_by' ] ) && in_array( 'created_by', $searchable_fields ) ) {
669 4
			$search_criteria['field_filters'][] = array(
670 4
				'key' => 'created_by',
671 4
				'value' => $get['gv_by'],
672 4
				'operator' => $this->get_operator( $get, 'gv_by', array( '=' ), '=' ),
673
			);
674
		}
675
676
		// Get search mode passed in URL
677 37
		$mode = isset( $get['mode'] ) && in_array( $get['mode'], array( 'any', 'all' ) ) ?  $get['mode'] : 'any';
678
679
		// get the other search filters
680 37
		foreach ( $get as $key => $value ) {
681
682 37
			if ( 0 !== strpos( $key, 'filter_' ) || gv_empty( $value, false, false ) || ( is_array( $value ) && count( $value ) === 1 && gv_empty( $value[0], false, false ) ) ) {
683 24
				continue; // Not a filter, or empty
684
			}
685
686 16
			if ( strpos( $key, '|op' ) !== false ) {
687 1
				continue; // This is an operator
688
			}
689
690 16
			$filter_key = $this->convert_request_key_to_filter_key( $key );
691
692 16
			if ( ! $filter = $this->prepare_field_filter( $filter_key, $value, $view, $searchable_field_objects, $get ) ) {
693 2
				continue;
694
			}
695
696 15
			if ( ! isset( $filter['operator'] ) ) {
697 6
				$filter['operator'] = $this->get_operator( $get, $key, array( 'contains' ), 'contains' );
698
			}
699
700 15
			if ( isset( $filter[0]['value'] ) ) {
701
				$search_criteria['field_filters'] = array_merge( $search_criteria['field_filters'], $filter );
702
703
				// if date range type, set search mode to ALL
704
				if ( ! empty( $filter[0]['operator'] ) && in_array( $filter[0]['operator'], array( '>=', '<=', '>', '<' ) ) ) {
705
					$mode = 'all';
706
				}
707 15
			} elseif( !empty( $filter ) ) {
708 15
				$search_criteria['field_filters'][] = $filter;
709
			}
710
		}
711
712
		/**
713
		 * @filter `gravityview/search/mode` Set the Search Mode (`all` or `any`)
714
		 * @since 1.5.1
715
		 * @param[out,in] string $mode Search mode (`any` vs `all`)
716
		 */
717 37
		$search_criteria['field_filters']['mode'] = apply_filters( 'gravityview/search/mode', $mode );
718
719 37
		gravityview()->log->debug( 'Returned Search Criteria: ', array( 'data' => $search_criteria ) );
720
721 37
		unset( $get );
722
723 37
		return $search_criteria;
724
	}
725
726
	/**
727
	 * Filters the \GF_Query with advanced logic.
728
	 *
729
	 * Dropin for the legacy flat filters when \GF_Query is available.
730
	 *
731
	 * @param \GF_Query $query The current query object reference
732
	 * @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...
733
	 * @param \GV\Request $request The request object
734
	 */
735 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...
736
		/**
737
		 * This is a shortcut to get all the needed search criteria.
738
		 * We feed these into an new GF_Query and tack them onto the current object.
739
		 */
740 63
		$search_criteria = $this->filter_entries( array(), null, array( 'id' => $view->ID ), true /** force search_criteria */ );
741
742
		/**
743
		 * Call any userland filters that they might have.
744
		 */
745 63
		remove_filter( 'gravityview_fe_search_criteria', array( $this, 'filter_entries' ), 10, 3 );
746 63
		$search_criteria = apply_filters( 'gravityview_fe_search_criteria', $search_criteria, $view->form->ID, $view->settings->as_atts() );
747 63
		add_filter( 'gravityview_fe_search_criteria', array( $this, 'filter_entries' ), 10, 3 );
748
749 63
		$query_class = $view->get_query_class();
750
751 63
		if ( empty( $search_criteria['field_filters'] ) ) {
752 56
			return;
753
		}
754
755 18
		$widgets = $view->widgets->by_id( $this->widget_id );
756 18
		if ( $widgets->count() ) {
757 13
			$widgets = $widgets->all();
758 13
			$widget  = $widgets[0];
759
760 13
			$search_fields = json_decode( $widget->configuration->get( 'search_fields' ), true );
761
762 13
			foreach ( (array) $search_fields as $search_field ) {
763 13
				if ( 'created_by' === $search_field['field'] && 'input_text' === $search_field['input'] ) {
764 1
					$created_by_text_mode = true;
765
				}
766
			}
767
		}
768
769 18
		$extra_conditions = array();
770 18
		$mode = 'any';
771
772 18
		foreach ( $search_criteria['field_filters'] as &$filter ) {
773 18
			if ( ! is_array( $filter ) ) {
774 18
				if ( in_array( strtolower( $filter ), array( 'any', 'all' ) ) ) {
775 18
					$mode = $filter;
776
				}
777 18
				continue;
778
			}
779
780
			// Construct a manual query for unapproved statuses
781 12
			if ( 'is_approved' === $filter['key'] && in_array( \GravityView_Entry_Approval_Status::UNAPPROVED, (array) $filter['value'] ) ) {
782 2
				$_tmp_query       = new $query_class( $view->form->ID, array(
783 2
					'field_filters' => array(
784
						array(
785 2
							'operator' => 'in',
786 2
							'key'      => 'is_approved',
787 2
							'value'    => (array) $filter['value'],
788
						),
789
						array(
790
							'operator' => 'is',
791
							'key'      => 'is_approved',
792
							'value'    => '',
793
						),
794 2
						'mode' => 'any'
795
					),
796
				) );
797 2
				$_tmp_query_parts = $_tmp_query->_introspect();
798
799 2
				$extra_conditions[] = $_tmp_query_parts['where'];
800
801 2
				$filter = false;
802 2
				continue;
803
			}
804
805
			// Construct manual query for text mode creator search
806 12
			if ( 'created_by' === $filter['key'] && ! empty( $created_by_text_mode ) ) {
807 1
				$extra_conditions[] = new GravityView_Widget_Search_Author_GF_Query_Condition( $filter, $view );
808 1
				$filter = false;
809 1
				continue;
810
			}
811
812
			// By default, we want searches to be wildcard for each field.
813 11
			$filter['operator'] = empty( $filter['operator'] ) ? 'contains' : $filter['operator'];
814
815
			// For multichoice, let's have an in (OR) search.
816 11
			if ( is_array( $filter['value'] ) ) {
817 3
				$filter['operator'] = 'in'; // @todo what about in contains (OR LIKE chains)?
818
			}
819
820
			// Default form with joins functionality
821 11
			if ( empty( $filter['form_id'] ) ) {
822 4
				$filter['form_id'] = $view->form ? $view->form->ID : 0;
823
			}
824
825
			/**
826
			 * @filter `gravityview_search_operator` Modify the search operator for the field (contains, is, isnot, etc)
827
			 * @param string $operator Existing search operator
828
			 * @param array $filter array with `key`, `value`, `operator`, `type` keys
829
			 * @since develop
830
			 * @param \GV\View $view The View we're operating on.
831
			 */
832 11
			$filter['operator'] = apply_filters( 'gravityview_search_operator', $filter['operator'], $filter, $view );
833
		}
834
835 18
		if ( ! empty( $search_criteria['start_date'] ) || ! empty( $search_criteria['end_date'] ) ) {
836 1
			$date_criteria = array();
837
838 1
			if ( isset( $search_criteria['start_date'] ) ) {
839 1
				$date_criteria['start_date'] = $search_criteria['start_date'];
840
			}
841
842 1
			if ( isset( $search_criteria['end_date'] ) ) {
843 1
				$date_criteria['end_date'] = $search_criteria['end_date'];
844
			}
845
846 1
			$_tmp_query         = new $query_class( $view->form->ID, $date_criteria );
847 1
			$_tmp_query_parts   = $_tmp_query->_introspect();
848 1
			$extra_conditions[] = $_tmp_query_parts['where'];
849
		}
850
851 18
		$search_conditions = array();
852
853 18
		if ( $filters = array_filter( $search_criteria['field_filters'] ) ) {
854
855 18
			foreach ( $filters as $filter ) {
856 18
				if ( ! is_array( $filter ) ) {
857 10
					continue;
858
				}
859
860
				/**
861
				 * Parse the filter criteria to generate the needed
862
				 * WHERE condition. This is a trick to not write our own generation
863
				 * code by reusing what's inside GF_Query already as they
864
				 * take care of many small things like forcing numeric, etc.
865
				 */
866 11
				$_tmp_query       = new $query_class( $filter['form_id'], array( 'mode' => 'any', 'field_filters' => array( $filter ) ) );
867 11
				$_tmp_query_parts = $_tmp_query->_introspect();
868 11
				$search_condition = $_tmp_query_parts['where'];
869
870 11
				if ( empty( $filter['key'] ) && $search_condition->expressions ) {
871 1
					$search_conditions[] = $search_condition;
872
				} else {
873 10
					$left = $search_condition->left;
874 10
					$alias = $query->_alias( $left->field_id, $left->source, $left->is_entry_column() ? 't' : 'm' );
875
876 10
					if ( $view->joins && $left->field_id == GF_Query_Column::META ) {
877 1
						foreach ( $view->joins as $_join ) {
878 1
							$on = $_join->join_on;
879 1
							$join = $_join->join;
880
881
							// Join
882 1
							$search_conditions[] = new GF_Query_Condition(
883 1
								new GF_Query_Column( GF_Query_Column::META, $join->ID, $query->_alias( GF_Query_Column::META, $join->ID, 'm' ) ),
884 1
								$search_condition->operator,
885 1
								$search_condition->right
886
							);
887
888
							// On
889 1
							$search_conditions[] = new GF_Query_Condition(
890 1
								new GF_Query_Column( GF_Query_Column::META, $on->ID, $query->_alias( GF_Query_Column::META, $on->ID, 'm' ) ),
891 1
								$search_condition->operator,
892 1
								$search_condition->right
893
							);
894
						}
895
					} else {
896 9
						$search_conditions[] = new GF_Query_Condition(
897 9
							new GF_Query_Column( $left->field_id, $left->source, $alias ),
898 9
							$search_condition->operator,
899 9
							$search_condition->right
900
						);
901
					}
902
				}
903
			}
904
905 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...
906 11
				$search_conditions = array( call_user_func_array( '\GF_Query_Condition::' . ( $mode == 'all' ? '_and' : '_or' ), $search_conditions ) );
907
			}
908
		}
909
910
		/**
911
		 * Grab the current clauses. We'll be combining them shortly.
912
		 */
913 18
		$query_parts = $query->_introspect();
914
915
		/**
916
		 * Combine the parts as a new WHERE clause.
917
		 */
918 18
		$where = call_user_func_array( '\GF_Query_Condition::_and', array_merge( array( $query_parts['where'] ), $search_conditions, $extra_conditions ) );
919 18
		$query->where( $where );
920 18
	}
921
922
	/**
923
	 * Convert $_GET/$_POST key to the field/meta ID
924
	 *
925
	 * Examples:
926
	 * - `filter_is_starred` => `is_starred`
927
	 * - `filter_1_2` => `1.2`
928
	 * - `filter_5` => `5`
929
	 *
930
	 * @since 2.0
931
	 *
932
	 * @param string $key $_GET/_$_POST search key
933
	 *
934
	 * @return string
935
	 */
936 16
	private function convert_request_key_to_filter_key( $key ) {
937
938 16
		$field_id = str_replace( 'filter_', '', $key );
939
940
		// calculates field_id, removing 'filter_' and for '_' for advanced fields ( like name or checkbox )
941 16
		if ( preg_match('/^[0-9_]+$/ism', $field_id ) ) {
942 14
			$field_id = str_replace( '_', '.', $field_id );
943
		}
944
945 16
		return $field_id;
946
	}
947
948
	/**
949
	 * Prepare the field filters to GFAPI
950
	 *
951
	 * 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.
952
	 *
953
	 * Format searched values
954
	 *
955
	 * @param  string $filter_key ID of the field, or entry meta key
956
	 * @param  string $value $_GET/$_POST search value
957
	 * @param  \GV\View $view The view we're looking at
958
	 * @param array[] $searchable_fields The searchable fields as configured by the widget.
959
	 * @param string[] $get The $_GET/$_POST array.
960
	 *
961
	 * @since develop Added 5th $get parameter for operator overrides.
962
	 * @todo Set function as private.
963
	 *
964
	 * @return array|false 1 or 2 deph levels, false if not allowed
965
	 */
966 16
	public function prepare_field_filter( $filter_key, $value, $view, $searchable_fields, $get = array() ) {
967 16
		$key = $filter_key;
968 16
		$filter_key = explode( ':', $filter_key ); // field_id, form_id
969
970 16
		$form = null;
971
972 16
		if ( count( $filter_key ) > 1 ) {
973
			// form is specified
974 1
			list( $field_id, $form_id ) = $filter_key;
975
976 1
			if ( $forms = \GV\View::get_joined_forms( $view->ID ) ) {
977 1
				if ( ! $form = \GV\GF_Form::by_id( $form_id ) ) {
978
					return false;
979
				}
980
			}
981
982
			// form is allowed
983 1
			$found = false;
984 1
			foreach ( $forms as $form ) {
985 1
				if ( $form->ID == $form_id ) {
986 1
					$found = true;
987 1
					break;
988
				}
989
			}
990
991 1
			if ( ! $found ) {
992
				return false;
993
			}
994
995
			// form is in searchable fields
996 1
			$found = false;
997 1
			foreach ( $searchable_fields as $field ) {
998 1
				if ( $field_id == $field['field'] && $form->ID == $field['form_id'] ) {
999 1
					$found = true;
1000 1
					break;
1001
				}
1002
			}
1003
1004 1
			if ( ! $found ) {
1005 1
				return false;
1006
			}
1007
		} else {
1008 16
			$field_id = reset( $filter_key );
1009 16
			$searchable_fields = wp_list_pluck( $searchable_fields, 'field' );
1010 16
			if ( ! in_array( 'search_all', $searchable_fields ) && ! in_array( $field_id, $searchable_fields ) ) {
1011 1
				return false;
1012
			}
1013
		}
1014
		
1015 15
		if ( ! $form ) {
1016
			// fallback
1017 15
			$form = $view->form;
1018
		}
1019
1020
		// get form field array
1021 15
		$form_field = is_numeric( $field_id ) ? \GV\GF_Field::by_id( $form, $field_id ) : \GV\Internal_Field::by_id( $field_id );
1022
1023
		// default filter array
1024
		$filter = array(
1025 15
			'key'   => $field_id,
1026 15
			'value' => $value,
1027 15
			'form_id' => $form->ID,
1028
		);
1029
1030 15
		switch ( $form_field->type ) {
1031
1032 15
			case 'select':
1033 15
			case 'radio':
1034 1
				$filter['operator'] = $this->get_operator( $get, $key, array( 'is' ), 'is' );
1035 1
				break;
1036
1037 14
			case 'post_category':
1038
1039
				if ( ! is_array( $value ) ) {
1040
					$value = array( $value );
1041
				}
1042
1043
				// Reset filter variable
1044
				$filter = array();
1045
1046
				foreach ( $value as $val ) {
1047
					$cat = get_term( $val, 'category' );
1048
					$filter[] = array(
1049
						'key'      => $field_id,
1050
						'value'    => esc_attr( $cat->name ) . ':' . $val,
1051
						'operator' => $this->get_operator( $get, $key, array( 'is' ), 'is' ),
1052
					);
1053
				}
1054
1055
				break;
1056
1057 14
			case 'multiselect':
1058
1059
				if ( ! is_array( $value ) ) {
1060
					break;
1061
				}
1062
1063
				// Reset filter variable
1064
				$filter = array();
1065
1066
				foreach ( $value as $val ) {
1067
					$filter[] = array( 'key' => $field_id, 'value' => $val );
1068
				}
1069
1070
				break;
1071
1072 14
			case 'checkbox':
1073
				// convert checkbox on/off into the correct search filter
1074
				if ( false !== strpos( $field_id, '.' ) && ! empty( $form_field->inputs ) && ! empty( $form_field->choices ) ) {
1075
					foreach ( $form_field->inputs as $k => $input ) {
1076
						if ( $input['id'] == $field_id ) {
1077
							$filter['value'] = $form_field->choices[ $k ]['value'];
1078
							$filter['operator'] = $this->get_operator( $get, $key, array( 'is' ), 'is' );
1079
							break;
1080
						}
1081
					}
1082
				} elseif ( is_array( $value ) ) {
1083
1084
					// Reset filter variable
1085
					$filter = array();
1086
1087
					foreach ( $value as $val ) {
1088
						$filter[] = array(
1089
							'key'      => $field_id,
1090
							'value'    => $val,
1091
							'operator' => $this->get_operator( $get, $key, array( 'is' ), 'is' ),
1092
						);
1093
					}
1094
				}
1095
1096
				break;
1097
1098 14
			case 'name':
1099 14
			case 'address':
1100
1101
				if ( false === strpos( $field_id, '.' ) ) {
1102
1103
					$words = explode( ' ', $value );
1104
1105
					$filters = array();
1106
					foreach ( $words as $word ) {
1107
						if ( ! empty( $word ) && strlen( $word ) > 1 ) {
1108
							// Keep the same key for each filter
1109
							$filter['value'] = $word;
1110
							// Add a search for the value
1111
							$filters[] = $filter;
1112
						}
1113
					}
1114
1115
					$filter = $filters;
1116
				}
1117
1118
				// State/Province should be exact matches
1119
				if ( 'address' === $form_field->field->type ) {
1120
1121
					$searchable_fields = $this->get_view_searchable_fields( $view, true );
1122
1123
					foreach ( $searchable_fields as $searchable_field ) {
1124
1125
						if( $form_field->ID !== $searchable_field['field'] ) {
1126
							continue;
1127
						}
1128
1129
						// Only exact-match dropdowns, not text search
1130
						if( in_array( $searchable_field['input'], array( 'text', 'search' ), true ) ) {
1131
							continue;
1132
						}
1133
1134
						$input_id = gravityview_get_input_id_from_id( $form_field->ID );
1135
1136
						if ( 4 === $input_id ) {
1137
							$filter['operator'] = $this->get_operator( $get, $key, array( 'is' ), 'is' );
1138
						};
1139
					}
1140
				}
1141
1142
				break;
1143
1144 14
			case 'date':
1145
1146 8
				$date_format = $this->get_datepicker_format( true );
1147
1148 8
				if ( is_array( $value ) ) {
1149
1150
					// Reset filter variable
1151
					$filter = array();
1152
1153
					foreach ( $value as $k => $date ) {
1154
						if ( empty( $date ) ) {
1155
							continue;
1156
						}
1157
						$operator = 'start' === $k ? '>=' : '<=';
1158
1159
						/**
1160
						 * @hack
1161
						 * @since 1.16.3
1162
						 * Safeguard until GF implements '<=' operator
1163
						 */
1164
						if( !GFFormsModel::is_valid_operator( $operator ) && $operator === '<=' ) {
1165
							$operator = '<';
1166
							$date = date( 'Y-m-d', strtotime( self::get_formatted_date( $date, 'Y-m-d', $date_format ) . ' +1 day' ) );
1167
						}
1168
1169
						$filter[] = array(
1170
							'key'      => $field_id,
1171
							'value'    => self::get_formatted_date( $date, 'Y-m-d', $date_format ),
1172
							'operator' => $this->get_operator( $get, $key, array( $operator ), $operator ),
1173
						);
1174
					}
1175
				} else {
1176 8
					$date = $value;
1177 8
					$filter['value'] = self::get_formatted_date( $date, 'Y-m-d', $date_format );
1178 8
					$filter['operator'] = $this->get_operator( $get, $key, array( 'is' ), 'is' );
1179
				}
1180
1181 8
				break;
1182
1183
1184
		} // switch field type
1185
1186 15
		return $filter;
1187
	}
1188
1189
	/**
1190
	 * Get the Field Format form GravityForms
1191
	 *
1192
	 * @param GF_Field_Date $field The field object
1193
	 * @since 1.10
1194
	 *
1195
	 * @return string Format of the date in the database
1196
	 */
1197
	public static function get_date_field_format( GF_Field_Date $field ) {
1198
		$format = 'm/d/Y';
1199
		$datepicker = array(
1200
			'mdy' => 'm/d/Y',
1201
			'dmy' => 'd/m/Y',
1202
			'dmy_dash' => 'd-m-Y',
1203
			'dmy_dot' => 'd.m.Y',
1204
			'ymd_slash' => 'Y/m/d',
1205
			'ymd_dash' => 'Y-m-d',
1206
			'ymd_dot' => 'Y.m.d',
1207
		);
1208
1209
		if ( ! empty( $field->dateFormat ) && isset( $datepicker[ $field->dateFormat ] ) ){
1210
			$format = $datepicker[ $field->dateFormat ];
1211
		}
1212
1213
		return $format;
1214
	}
1215
1216
	/**
1217
	 * Format a date value
1218
	 *
1219
	 * @param string $value Date value input
1220
	 * @param string $format Wanted formatted date
1221
	 *
1222
	 * @since 2.1.2
1223
	 * @param string $value_format The value format. Default: Y-m-d
1224
	 *
1225
	 * @return string
1226
	 */
1227 8
	public static function get_formatted_date( $value = '', $format = 'Y-m-d', $value_format = 'Y-m-d' ) {
1228
1229 8
		$date = date_create_from_format( $value_format, $value );
1230
1231 8
		if ( empty( $date ) ) {
1232
			gravityview()->log->debug( 'Date format not valid: {value}', array( 'value' => $value ) );
1233
			return '';
1234
		}
1235 8
		return $date->format( $format );
1236
	}
1237
1238
1239
	/**
1240
	 * Include this extension templates path
1241
	 * @param array $file_paths List of template paths ordered
1242
	 */
1243 1
	public function add_template_path( $file_paths ) {
1244
1245
		// Index 100 is the default GravityView template path.
1246 1
		$file_paths[102] = self::$file . 'templates/';
1247
1248 1
		return $file_paths;
1249
	}
1250
1251
	/**
1252
	 * Check whether the configured search fields have a date field
1253
	 *
1254
	 * @since 1.17.5
1255
	 *
1256
	 * @param array $search_fields
1257
	 *
1258
	 * @return bool True: has a `date` or `date_range` field
1259
	 */
1260 4
	private function has_date_field( $search_fields ) {
1261
1262 4
		$has_date = false;
1263
1264 4
		foreach ( $search_fields as $k => $field ) {
1265 4
			if ( in_array( $field['input'], array( 'date', 'date_range', 'entry_date' ) ) ) {
1266
				$has_date = true;
1267
				break;
1268
			}
1269
		}
1270
1271 4
		return $has_date;
1272
	}
1273
1274
	/**
1275
	 * Renders the Search Widget
1276
	 * @param array $widget_args
1277
	 * @param string $content
1278
	 * @param string $context
1279
	 *
1280
	 * @return void
1281
	 */
1282 4
	public function render_frontend( $widget_args, $content = '', $context = '' ) {
1283
		/** @var GravityView_View $gravityview_view */
1284 4
		$gravityview_view = GravityView_View::getInstance();
1285
1286 4
		if ( empty( $gravityview_view ) ) {
1287
			gravityview()->log->debug( '$gravityview_view not instantiated yet.' );
1288
			return;
1289
		}
1290
1291
		// get configured search fields
1292 4
		$search_fields = ! empty( $widget_args['search_fields'] ) ? json_decode( $widget_args['search_fields'], true ) : '';
1293
1294 4
		if ( empty( $search_fields ) || ! is_array( $search_fields ) ) {
1295
			gravityview()->log->debug( 'No search fields configured for widget:', array( 'data' => $widget_args ) );
1296
			return;
1297
		}
1298
1299 4
		$view = \GV\View::by_id( $gravityview_view->view_id );
1300
1301
		// prepare fields
1302 4
		foreach ( $search_fields as $k => $field ) {
1303
1304 4
			$updated_field = $field;
1305
1306 4
			$updated_field = $this->get_search_filter_details( $updated_field, $context );
1307
1308 4
			switch ( $field['field'] ) {
1309
1310 4
				case 'search_all':
1311 4
					$updated_field['key'] = 'search_all';
1312 4
					$updated_field['input'] = 'search_all';
1313 4
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_search' );
1314 4
					break;
1315
1316
				case 'entry_date':
1317
					$updated_field['key'] = 'entry_date';
1318
					$updated_field['input'] = 'entry_date';
1319
					$updated_field['value'] = array(
1320
						'start' => $this->rgget_or_rgpost( 'gv_start' ),
1321
						'end' => $this->rgget_or_rgpost( 'gv_end' ),
1322
					);
1323
					break;
1324
1325
				case 'entry_id':
1326
					$updated_field['key'] = 'entry_id';
1327
					$updated_field['input'] = 'entry_id';
1328
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_id' );
1329
					break;
1330
1331
				case 'created_by':
1332
					$updated_field['key'] = 'created_by';
1333
					$updated_field['name'] = 'gv_by';
1334
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_by' );
1335
					$updated_field['choices'] = self::get_created_by_choices( $view );
1336
					break;
1337
				
1338
				case 'is_approved':
1339
					$updated_field['key'] = 'is_approved';
1340
					$updated_field['value'] = $this->rgget_or_rgpost( 'filter_is_approved' );
1341
					$updated_field['choices'] = self::get_is_approved_choices();
1342
					break;
1343
			}
1344
1345 4
			$search_fields[ $k ] = $updated_field;
1346
		}
1347
1348 4
		gravityview()->log->debug( 'Calculated Search Fields: ', array( 'data' => $search_fields ) );
1349
1350
		/**
1351
		 * @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.
1352
		 * @param array $search_fields Array of search filters with `key`, `label`, `value`, `type`, `choices` keys
1353
		 * @param GravityView_Widget_Search $this Current widget object
1354
		 * @param array $widget_args Args passed to this method. {@since 1.8}
1355
		 * @param \GV\Template_Context $context {@since 2.0}
1356
		 * @var array
1357
		 */
1358 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...
1359
1360 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...
1361
1362 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...
1363
1364
		/** @since 1.14 */
1365 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...
1366
1367 4
		$custom_class = ! empty( $widget_args['custom_class'] ) ? $widget_args['custom_class'] : '';
1368
1369 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...
1370
1371 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...
1372
1373 4
		if ( $this->has_date_field( $search_fields ) ) {
1374
			// enqueue datepicker stuff only if needed!
1375
			$this->enqueue_datepicker();
1376
		}
1377
1378 4
		$this->maybe_enqueue_flexibility();
1379
1380 4
		$gravityview_view->render( 'widget', 'search', false );
1381 4
	}
1382
1383
	/**
1384
	 * Get the search class for a search form
1385
	 *
1386
	 * @since 1.5.4
1387
	 *
1388
	 * @return string Sanitized CSS class for the search form
1389
	 */
1390 4
	public static function get_search_class( $custom_class = '' ) {
1391 4
		$gravityview_view = GravityView_View::getInstance();
1392
1393 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...
1394
1395 4
		if ( ! empty( $custom_class )  ) {
1396
			$search_class .= ' '.$custom_class;
1397
		}
1398
1399
		/**
1400
		 * @filter `gravityview_search_class` Modify the CSS class for the search form
1401
		 * @param string $search_class The CSS class for the search form
1402
		 */
1403 4
		$search_class = apply_filters( 'gravityview_search_class', $search_class );
1404
1405
		// Is there an active search being performed? Used by fe-views.js
1406 4
		$search_class .= GravityView_frontend::getInstance()->isSearch() ? ' gv-is-search' : '';
1407
1408 4
		return gravityview_sanitize_html_class( $search_class );
1409
	}
1410
1411
1412
	/**
1413
	 * Calculate the search form action
1414
	 * @since 1.6
1415
	 *
1416
	 * @return string
1417
	 */
1418 4
	public static function get_search_form_action() {
1419 4
		$gravityview_view = GravityView_View::getInstance();
1420
1421 4
		$post_id = $gravityview_view->getPostId() ? $gravityview_view->getPostId() : $gravityview_view->getViewId();
1422
1423 4
		$url = add_query_arg( array(), get_permalink( $post_id ) );
1424
1425 4
		return esc_url( $url );
1426
	}
1427
1428
	/**
1429
	 * Get the label for a search form field
1430
	 * @param  array $field      Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1431
	 * @param  array $form_field Form field data, as fetched by `gravityview_get_field()`
1432
	 * @return string             Label for the search form
1433
	 */
1434 4
	private static function get_field_label( $field, $form_field = array() ) {
1435
1436 4
		$label = \GV\Utils::_GET( 'label', \GV\Utils::get( $field, 'label' ) );
1437
1438 4
		if ( ! $label ) {
1439
1440 4
			$label = isset( $form_field['label'] ) ? $form_field['label'] : '';
1441
1442 4
			switch( $field['field'] ) {
1443 4
				case 'search_all':
1444 4
					$label = __( 'Search Entries:', 'gravityview' );
1445 4
					break;
1446
				case 'entry_date':
1447
					$label = __( 'Filter by date:', 'gravityview' );
1448
					break;
1449
				case 'entry_id':
1450
					$label = __( 'Entry ID:', 'gravityview' );
1451
					break;
1452
				default:
1453
					// If this is a field input, not a field
1454
					if ( strpos( $field['field'], '.' ) > 0 && ! empty( $form_field['inputs'] ) ) {
1455
1456
						// Get the label for the field in question, which returns an array
1457
						$items = wp_list_filter( $form_field['inputs'], array( 'id' => $field['field'] ) );
1458
1459
						// Get the item with the `label` key
1460
						$values = wp_list_pluck( $items, 'label' );
1461
1462
						// There will only one item in the array, but this is easier
1463
						foreach ( $values as $value ) {
1464
							$label = $value;
1465
							break;
1466
						}
1467
					}
1468
			}
1469
		}
1470
1471
		/**
1472
		 * @filter `gravityview_search_field_label` Modify the label for a search field. Supports returning HTML
1473
		 * @since 1.17.3 Added $field parameter
1474
		 * @param[in,out] string $label Existing label text, sanitized.
1475
		 * @param[in] array $form_field Gravity Forms field array, as returned by `GFFormsModel::get_field()`
1476
		 * @param[in] array $field Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1477
		 */
1478 4
		$label = apply_filters( 'gravityview_search_field_label', esc_attr( $label ), $form_field, $field );
1479
1480 4
		return $label;
1481
	}
1482
1483
	/**
1484
	 * Prepare search fields to frontend render with other details (label, field type, searched values)
1485
	 *
1486
	 * @param array $field
1487
	 * @param \GV\Context $context
1488
	 *
1489
	 * @return array
1490
	 */
1491 4
	private function get_search_filter_details( $field, $context ) {
1492
1493 4
		$gravityview_view = GravityView_View::getInstance();
1494
1495 4
		$form = $gravityview_view->getForm();
1496
1497
		// for advanced field ids (eg, first name / last name )
1498 4
		$name = 'filter_' . str_replace( '.', '_', $field['field'] );
1499
1500
		// get searched value from $_GET/$_POST (string or array)
1501 4
		$value = $this->rgget_or_rgpost( $name );
1502
1503
		// get form field details
1504 4
		$form_field = gravityview_get_field( $form, $field['field'] );
1505
1506
		$filter = array(
1507 4
			'key' => $field['field'],
1508 4
			'name' => $name,
1509 4
			'label' => self::get_field_label( $field, $form_field ),
1510 4
			'input' => $field['input'],
1511 4
			'value' => $value,
1512 4
			'type' => $form_field['type'],
1513
		);
1514
1515
		// collect choices
1516 4
		if ( 'post_category' === $form_field['type'] && ! empty( $form_field['displayAllCategories'] ) && empty( $form_field['choices'] ) ) {
1517
			$filter['choices'] = gravityview_get_terms_choices();
1518 4
		} elseif ( ! empty( $form_field['choices'] ) ) {
1519
			$filter['choices'] = $form_field['choices'];
1520
		}
1521
1522 4
		if ( 'date_range' === $field['input'] && empty( $value ) ) {
1523
			$filter['value'] = array( 'start' => '', 'end' => '' );
1524
		}
1525
1526 4
		if ( ! empty( $filter['choices'] ) ) {
1527
			/**
1528
			 * @filter `gravityview/search/sieve_choices` Only output used choices for this field.
1529
			 * @param[in,out] bool Yes or no.
1530
			 * @param array $field The field configuration.
1531
			 * @param \GV\Context The context.
1532
			 */
1533
			if ( apply_filters( 'gravityview/search/sieve_choices', false, $field, $context ) ) {
1534
				$filter['choices'] = $this->sieve_filter_choices( $filter, $context );
1535
			}
1536
		}
1537
1538
		/**
1539
		 * @filter `gravityview/search/filter_details` Filter the output filter details for the Search widget.
1540
		 * @param[in,out] array $filter The filter details
1541
		 * @param array $field The search field configuration
1542
		 * @param \GV\Context The context
1543
		 * @since develop
1544
		 */
1545 4
		$filter = apply_filters( 'gravityview/search/filter_details', $filter, $field, $context );
1546
1547 4
		return $filter;
1548
1549
	}
1550
1551
	/**
1552
	 * Sieve filter choices to only ones that are used.
1553
	 *
1554
	 * @param array $filter The filter configuration.
1555
	 * @param \GV\Context $context The context
1556
	 *
1557
	 * @since develop
1558
	 * @internal
1559
	 *
1560
	 * @return array The filter choices.
1561
	 */
1562
	private function sieve_filter_choices( $filter, $context ) {
1563
		if ( empty( $filter['key'] ) || empty( $filter['choices'] ) ) {
1564
			return $filter; // @todo Populate plugins might give us empty choices
1565
		}
1566
1567
		if ( ! is_numeric( $filter['key'] ) ) {
1568
			return $filter;
1569
		}
1570
1571
		$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...
1572
1573
		global $wpdb;
1574
1575
		$table = GFFormsModel::get_entry_meta_table_name();
1576
1577
		$key_like = $wpdb->esc_like( $filter['key'] ) . '.%';
1578
1579
		$choices = $wpdb->get_col( $wpdb->prepare(
1580
			"SELECT DISTINCT meta_value FROM $table WHERE (meta_key LIKE %s OR meta_key = %d) AND form_id = %d",
1581
			$key_like, $filter['key'], $form_id
1582
		) );
1583
1584
		$filter_choices = array();
1585
		foreach ( $filter['choices'] as $choice ) {
1586
			if ( in_array( $choice['text'], $choices, true ) || in_array( $choice['value'], $choices, true ) ) {
1587
				$filter_choices[] = $choice;
1588
			}
1589
		}
1590
1591
		return $filter_choices;
1592
	}
1593
1594
	/**
1595
	 * Calculate the search choices for the users
1596
	 *
1597
	 * @param \GV\View $view The view
1598
	 * @since develop
1599
	 *
1600
	 * @since 1.8
1601
	 *
1602
	 * @return array Array of user choices (value = ID, text = display name)
1603
	 */
1604
	private static function get_created_by_choices( $view ) {
1605
1606
		/**
1607
		 * filter gravityview/get_users/search_widget
1608
		 * @see \GVCommon::get_users
1609
		 */
1610
		$users = GVCommon::get_users( 'search_widget', array( 'fields' => array( 'ID', 'display_name' ) ) );
1611
1612
		$choices = array();
1613
		foreach ( $users as $user ) {
1614
			/**
1615
			 * @filter `gravityview/search/created_by/text` Filter the display text in created by search choices
1616
			 * @since develop
1617
			 * @param string[in,out] The text. Default: $user->display_name
1618
			 * @param \WP_User $user The user.
1619
			 * @param \GV\View $view The view.
1620
			 */
1621
			$text = apply_filters( 'gravityview/search/created_by/text', $user->display_name, $user, $view );
1622
			$choices[] = array(
1623
				'value' => $user->ID,
1624
				'text' => $text,
1625
			);
1626
		}
1627
1628
		return $choices;
1629
	}
1630
1631
	/**
1632
	 * Calculate the search checkbox choices for approval status
1633
	 *
1634
	 * @since develop
1635
	 *
1636
	 * @return array Array of approval status choices (value = status, text = display name)
1637
	 */
1638
	private static function get_is_approved_choices() {
1639
1640
		$choices = array();
1641
		foreach ( GravityView_Entry_Approval_Status::get_all() as $status ) {
1642
			$choices[] = array(
1643
				'value' => $status['value'],
1644
				'text' => $status['label'],
1645
			);
1646
		}
1647
1648
		return $choices;
1649
	}
1650
1651
	/**
1652
	 * Output the Clear Search Results button
1653
	 * @since 1.5.4
1654
	 */
1655 4
	public static function the_clear_search_button() {
1656 4
		$gravityview_view = GravityView_View::getInstance();
1657
1658 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...
1659
1660
			$url = strtok( add_query_arg( array() ), '?' );
1661
1662
			echo gravityview_get_link( $url, esc_html__( 'Clear', 'gravityview' ), 'class=button gv-search-clear' );
1663
1664
		}
1665 4
	}
1666
1667
	/**
1668
	 * Based on the search method, fetch the value for a specific key
1669
	 *
1670
	 * @since 1.16.4
1671
	 *
1672
	 * @param string $name Name of the request key to fetch the value for
1673
	 *
1674
	 * @return mixed|string Value of request at $name key. Empty string if empty.
1675
	 */
1676 4
	private function rgget_or_rgpost( $name ) {
1677 4
		$value = \GV\Utils::_REQUEST( $name );
1678
1679 4
		$value = stripslashes_deep( $value );
1680
1681 4
		$value = gv_map_deep( $value, 'rawurldecode' );
1682
1683 4
		$value = gv_map_deep( $value, '_wp_specialchars' );
1684
1685 4
		return $value;
1686
	}
1687
1688
1689
	/**
1690
	 * Require the datepicker script for the frontend GV script
1691
	 * @param array $js_dependencies Array of existing required scripts for the fe-views.js script
1692
	 * @return array Array required scripts, with `jquery-ui-datepicker` added
1693
	 */
1694
	public function add_datepicker_js_dependency( $js_dependencies ) {
1695
1696
		$js_dependencies[] = 'jquery-ui-datepicker';
1697
1698
		return $js_dependencies;
1699
	}
1700
1701
	/**
1702
	 * Modify the array passed to wp_localize_script()
1703
	 *
1704
	 * @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...
1705
	 * @param array $view_data View data array with View settings
1706
	 *
1707
	 * @return array
1708
	 */
1709
	public function add_datepicker_localization( $localizations = array(), $view_data = array() ) {
1710
		global $wp_locale;
1711
1712
		/**
1713
		 * @filter `gravityview_datepicker_settings` Modify the datepicker settings
1714
		 * @see http://api.jqueryui.com/datepicker/ Learn what settings are available
1715
		 * @see http://www.renegadetechconsulting.com/tutorials/jquery-datepicker-and-wordpress-i18n Thanks for the helpful information on $wp_locale
1716
		 * @param array $js_localization The data padded to the Javascript file
1717
		 * @param array $view_data View data array with View settings
1718
		 */
1719
		$datepicker_settings = apply_filters( 'gravityview_datepicker_settings', array(
1720
			'yearRange' => '-5:+5',
1721
			'changeMonth' => true,
1722
			'changeYear' => true,
1723
			'closeText' => esc_attr_x( 'Close', 'Close calendar', 'gravityview' ),
1724
			'prevText' => esc_attr_x( 'Prev', 'Previous month in calendar', 'gravityview' ),
1725
			'nextText' => esc_attr_x( 'Next', 'Next month in calendar', 'gravityview' ),
1726
			'currentText' => esc_attr_x( 'Today', 'Today in calendar', 'gravityview' ),
1727
			'weekHeader' => esc_attr_x( 'Week', 'Week in calendar', 'gravityview' ),
1728
			'monthStatus'       => __( 'Show a different month', 'gravityview' ),
1729
			'monthNames'        => array_values( $wp_locale->month ),
1730
			'monthNamesShort'   => array_values( $wp_locale->month_abbrev ),
1731
			'dayNames'          => array_values( $wp_locale->weekday ),
1732
			'dayNamesShort'     => array_values( $wp_locale->weekday_abbrev ),
1733
			'dayNamesMin'       => array_values( $wp_locale->weekday_initial ),
1734
			// get the start of week from WP general setting
1735
			'firstDay'          => get_option( 'start_of_week' ),
1736
			// is Right to left language? default is false
1737
			'isRTL'             => is_rtl(),
1738
		), $view_data );
1739
1740
		$localizations['datepicker'] = $datepicker_settings;
1741
1742
		return $localizations;
1743
1744
	}
1745
1746
	/**
1747
	 * Register search widget scripts, including Flexibility
1748
	 *
1749
	 * @see https://github.com/10up/flexibility
1750
	 *
1751
	 * @since 1.17
1752
	 *
1753
	 * @return void
1754
	 */
1755
	public function register_scripts() {
1756
		wp_register_script( 'gv-flexibility', plugins_url( 'assets/lib/flexibility/flexibility.js', GRAVITYVIEW_FILE ), array(), \GV\Plugin::$version, true );
1757
	}
1758
1759
	/**
1760
	 * If the current visitor is running IE 8 or 9, enqueue Flexibility
1761
	 *
1762
	 * @since 1.17
1763
	 *
1764
	 * @return void
1765
	 */
1766 4
	private function maybe_enqueue_flexibility() {
1767 4
		if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && preg_match( '/MSIE [8-9]/', $_SERVER['HTTP_USER_AGENT'] ) ) {
1768
			wp_enqueue_script( 'gv-flexibility' );
1769
		}
1770 4
	}
1771
1772
	/**
1773
	 * Enqueue the datepicker script
1774
	 *
1775
	 * It sets the $gravityview->datepicker_class parameter
1776
	 *
1777
	 * @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.
1778
	 * @return void
1779
	 */
1780
	public function enqueue_datepicker() {
1781
		$gravityview_view = GravityView_View::getInstance();
1782
1783
		wp_enqueue_script( 'jquery-ui-datepicker' );
1784
1785
		add_filter( 'gravityview_js_dependencies', array( $this, 'add_datepicker_js_dependency' ) );
1786
		add_filter( 'gravityview_js_localization', array( $this, 'add_datepicker_localization' ), 10, 2 );
1787
1788
		$scheme = is_ssl() ? 'https://' : 'http://';
1789
		wp_enqueue_style( 'jquery-ui-datepicker', $scheme.'ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/smoothness/jquery-ui.css' );
1790
1791
		/**
1792
		 * @filter `gravityview_search_datepicker_class`
1793
		 * 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.
1794
		 * @param string $css_class CSS class to use. Default: `gv-datepicker datepicker mdy` \n
1795
		 * Options are:
1796
		 * - `mdy` (mm/dd/yyyy)
1797
		 * - `dmy` (dd/mm/yyyy)
1798
		 * - `dmy_dash` (dd-mm-yyyy)
1799
		 * - `dmy_dot` (dd.mm.yyyy)
1800
		 * - `ymd_slash` (yyyy/mm/dd)
1801
		 * - `ymd_dash` (yyyy-mm-dd)
1802
		 * - `ymd_dot` (yyyy.mm.dd)
1803
		 */
1804
		$datepicker_class = apply_filters( 'gravityview_search_datepicker_class', "gv-datepicker datepicker " . $this->get_datepicker_format() );
1805
1806
		$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...
1807
	}
1808
1809
	/**
1810
	 * Retrieve the datepicker format.
1811
	 *
1812
	 * @param bool $date_format Whether to return the PHP date format or the datpicker class name. Default: false.
1813
	 *
1814
	 * @see https://docs.gravityview.co/article/115-changing-the-format-of-the-search-widgets-date-picker
1815
	 *
1816
	 * @return string The datepicker format placeholder, or the PHP date format.
1817
	 */
1818 19
	private function get_datepicker_format( $date_format = false ) {
1819
1820 19
		$default_format = 'mdy';
1821
1822
		/**
1823
		 * @filter `gravityview/widgets/search/datepicker/format`
1824
		 * @since 2.1.1
1825
		 * @param string           $format Default: mdy
1826
		 * Options are:
1827
		 * - `mdy` (mm/dd/yyyy)
1828
		 * - `dmy` (dd/mm/yyyy)
1829
		 * - `dmy_dash` (dd-mm-yyyy)
1830
		 * - `dmy_dot` (dd.mm.yyyy)
1831
		 * - `ymd_slash` (yyyy/mm/dd)
1832
		 * - `ymd_dash` (yyyy-mm-dd)
1833
		 * - `ymd_dot` (yyyy.mm.dd)
1834
		 */
1835 19
		$format = apply_filters( 'gravityview/widgets/search/datepicker/format', $default_format );
1836
1837
		$gf_date_formats = array(
1838 19
			'mdy' => 'm/d/Y',
1839
1840
			'dmy_dash' => 'd-m-Y',
1841
			'dmy_dot' => 'd.m.Y',
1842
			'dmy' => 'd/m/Y',
1843
1844
			'ymd_slash' => 'Y/m/d',
1845
			'ymd_dash' => 'Y-m-d',
1846
			'ymd_dot' => 'Y.m.d',
1847
		);
1848
1849 19
		if ( ! $date_format ) {
1850
			// If the format key isn't valid, return default format key
1851
			return isset( $gf_date_formats[ $format ] ) ? $format : $default_format;
1852
		}
1853
1854
		// If the format key isn't valid, return default format value
1855 19
		return \GV\Utils::get( $gf_date_formats, $format, $gf_date_formats[ $default_format ] );
1856
	}
1857
1858
	/**
1859
	 * If previewing a View or page with embedded Views, make the search work properly by adding hidden fields with query vars
1860
	 *
1861
	 * @since 2.2.1
1862
	 *
1863
	 * @return void
1864
	 */
1865 4
	public function add_preview_inputs() {
1866 4
		global $wp;
1867
1868 4
		if ( ! is_preview() || ! current_user_can( 'publish_gravityviews') ) {
1869 4
			return;
1870
		}
1871
1872
		// Outputs `preview` and `post_id` variables
1873
		foreach ( $wp->query_vars as $key => $value ) {
1874
			printf( '<input type="hidden" name="%s" value="%s" />', esc_attr( $key ), esc_attr( $value ) );
1875
		}
1876
1877
	}
1878
1879
	/**
1880
	 * Get an operator URL override.
1881
	 *
1882
	 * @param array  $get     Where to look for the operator.
1883
	 * @param string $key     The filter key to look for.
1884
	 * @param array  $allowed The allowed operators (whitelist).
1885
	 * @param string $default The default operator.
1886
	 *
1887
	 * @return string The operator.
1888
	 */
1889 18
	private function get_operator( $get, $key, $allowed, $default ) {
1890 18
		$operator = \GV\Utils::get( $get, "$key|op", $default );
1891
1892
		/**
1893
		 * @filter `gravityview/search/operator_whitelist` An array of allowed operators for a field.
1894
		 * @param[in,out] string[] A whitelist of allowed operators.
1895
		 * @param string The filter name.
1896
		 */
1897 18
		$allowed = apply_filters( 'gravityview/search/operator_whitelist', $allowed, $key );
1898
1899 18
		if ( ! in_array( $operator, $allowed, true ) ) {
1900 1
			$operator = $default;
1901
		}
1902
1903 18
		return $operator;
1904
	}
1905
1906
1907
} // end class
1908
1909
new GravityView_Widget_Search;
1910
1911
if ( ! gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
1912
	return;
1913
}
1914
1915
/**
1916
 * A GF_Query condition that allows user data searches.
1917
 */
1918
class GravityView_Widget_Search_Author_GF_Query_Condition extends \GF_Query_Condition {
1919 1
	public function __construct( $filter, $view ) {
1920 1
		$this->value = $filter['value'];
1921 1
		$this->view = $view;
1922 1
	}
1923
1924 1
	public function sql( $query ) {
1925 1
		global $wpdb;
1926
1927
		$user_meta_fields = array(
1928 1
			'nickname', 'first_name', 'last_name',
1929
		);
1930
1931
		/**
1932
		 * @filter `gravityview/widgets/search/created_by/user_meta_fields` Filter the user meta fields to search.
1933
		 * @param[in,out] array The user meta fields.
1934
		 * @param \GV\View $view The view.
1935
		 */
1936 1
		$user_meta_fields = apply_filters( 'gravityview/widgets/search/created_by/user_meta_fields', $user_meta_fields, $this->view );
1937
1938
		$user_fields = array(
1939 1
			'user_nicename', 'user_login', 'display_name', 'user_email', 
1940
		);
1941
1942
		/**
1943
		 * @filter `gravityview/widgets/search/created_by/user_fields` Filter the user fields to search.
1944
		 * @param[in,out] array The user fields.
1945
		 * @param \GV\View $view The view.
1946
		 */
1947 1
		$user_fields = apply_filters( 'gravityview/widgets/search/created_by/user_fields', $user_fields, $this->view );
1948
1949 1
		$conditions = array();
1950
1951 1
		foreach ( $user_fields as $user_field ) {
1952 1
			$conditions[] = $wpdb->prepare( "`u`.`$user_field` LIKE %s", '%' . $wpdb->esc_like( $this->value ) .  '%' );
1953
		}
1954
1955 1
		foreach ( $user_meta_fields as $meta_field ) {
1956 1
			$conditions[] = $wpdb->prepare( "(`um`.`meta_key` = %s AND `um`.`meta_value` LIKE %s)", $meta_field, '%' . $wpdb->esc_like( $this->value ) .  '%' );
1957
		}
1958
1959 1
		$conditions = '(' . implode( ' OR ', $conditions ) . ')';
1960
1961 1
		$alias = $query->_alias( null );
1962
1963 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)))";
1964
	}
1965
}
1966