Completed
Push — master ( b01591...3ee7a1 )
by Zack
70:00 queued 53:54
created

GravityView_Widget_Search::__construct()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 73

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 2.0569

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 73
ccs 25
cts 33
cp 0.7576
crap 2.0569
rs 8.589
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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