Completed
Push — master ( 14e1a2...154914 )
by Zack
18:57 queued 01:08
created

GravityView_Widget_Search   F

Complexity

Total Complexity 188

Size/Duplication

Total Lines 1353
Duplicated Lines 0 %

Coupling/Cohesion

Components 5
Dependencies 10

Test Coverage

Coverage 45.58%

Importance

Changes 0
Metric Value
dl 0
loc 1353
ccs 227
cts 498
cp 0.4558
rs 0.6314
c 0
b 0
f 0
wmc 188
lcom 5
cbo 10

34 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 71 2
A getInstance() 0 6 2
A set_search_method() 0 13 2
A get_search_method() 0 3 1
B get_input_types_by_field_type() 0 25 1
B get_search_input_labels() 0 26 1
A get_search_input_label() 0 5 1
B add_scripts_and_styles() 0 26 6
A register_no_conflict() 0 4 1
C get_searchable_fields() 0 28 7
B render_searchable_fields() 0 71 6
D get_search_input_types() 0 29 10
B add_no_permalink_fields() 0 33 5
D get_view_searchable_fields() 0 36 10
F filter_entries() 0 172 42
A convert_request_key_to_filter_key() 0 11 2
D prepare_field_filter() 0 141 31
A get_date_field_format() 0 18 3
A get_formatted_date() 0 8 2
A add_template_path() 0 7 1
A has_date_field() 0 13 3
F render_frontend() 0 91 15
A get_search_class() 0 20 3
A get_search_form_action() 0 9 2
C get_field_label() 0 48 9
C get_search_filter_details() 0 38 7
A get_created_by_choices() 0 18 2
A the_clear_search_button() 0 11 2
A rgget_or_rgpost() 0 11 1
A add_datepicker_js_dependency() 0 6 1
B add_datepicker_localization() 0 36 1
A register_scripts() 0 3 1
A maybe_enqueue_flexibility() 0 5 3
B enqueue_datepicker() 0 29 2

How to fix   Complexity   

Complex Class

Complex classes like GravityView_Widget_Search often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use GravityView_Widget_Search, and based on these observations, apply Extract Interface, too.

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 16 and the first side effect is on line 13.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
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();
0 ignored issues
show
Unused Code introduced by
The property $search_filters is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
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 6
	public function __construct() {
31
32 6
		$this->widget_id = 'search_bar';
33 6
		$this->widget_description = esc_html__( 'Search form for searching entries.', 'gravityview' );
34
35 6
		self::$instance = &$this;
36
37 6
		self::$file = plugin_dir_path( __FILE__ );
38
39 6
		$default_values = array( 'header' => 0, 'footer' => 0 );
40
41
		$settings = array(
42 6
			'search_layout' => array(
43 6
				'type' => 'radio',
44
				'full_width' => true,
45 6
				'label' => esc_html__( 'Search Layout', 'gravityview' ),
46 6
				'value' => 'horizontal',
47
				'options' => array(
48 6
					'horizontal' => esc_html__( 'Horizontal', 'gravityview' ),
49 6
					'vertical' => esc_html__( 'Vertical', 'gravityview' ),
50
				),
51
			),
52
			'search_clear' => array(
53 6
				'type' => 'checkbox',
54 6
				'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 6
				'type' => 'radio',
65
				'full_width' => true,
66 6
				'label' => esc_html__( 'Search Mode', 'gravityview' ),
67 6
				'desc' => __('Should search results match all search fields, or any?', 'gravityview'),
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
68 6
				'value' => 'any',
69 6
				'class' => 'hide-if-js',
70
				'options' => array(
71 6
					'any' => esc_html__( 'Match Any Fields', 'gravityview' ),
72 6
					'all' => esc_html__( 'Match All Fields', 'gravityview' ),
73
				),
74
			),
75
		);
76
77 6
		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') );
0 ignored issues
show
introduced by
No space before closing parenthesis of array is bad style
Loading history...
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
96 6
		parent::__construct( esc_html__( 'Search Bar', 'gravityview' ), null, $default_values, $settings );
97
98
		// calculate the search method (POST / GET)
99 6
		$this->set_search_method();
100 6
	}
101
102
	/**
103
	 * @return GravityView_Widget_Search
104
	 */
105 2
	public static function getInstance() {
0 ignored issues
show
Coding Style introduced by
The function name getInstance is in camel caps, but expected get_instance instead as per the coding standard.
Loading history...
106 2
		if ( empty( self::$instance ) ) {
107
			self::$instance = new GravityView_Widget_Search;
108
		}
109 2
		return self::$instance;
110
	}
111
112
	/**
113
	 * Sets the search method to GET (default) or POST
114
	 * @since 1.16.4
115
	 */
116 6
	private function set_search_method() {
117
		/**
118
		 * @filter `gravityview/search/method` Modify the search form method (GET / POST)
119
		 * @since 1.16.4
120
		 * @param string $search_method Assign an input type according to the form field type. Defaults: `boolean`, `multi`, `select`, `date`, `text`
121
		 * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
122
		 */
123 6
		$method = apply_filters( 'gravityview/search/method', $this->search_method );
124
125 6
		$method = strtolower( $method );
126
127 6
		$this->search_method = in_array( $method, array( 'get', 'post' ) ) ? $method : 'get';
128 6
	}
129
130
	/**
131
	 * Returns the search method
132
	 * @since 1.16.4
133
	 * @return string
134
	 */
135 2
	public function get_search_method() {
136 2
		return $this->search_method;
137
	}
138
139
	/**
140
	 * Get the input types available for different field types
141
	 *
142
	 * @since 1.17.5
143
	 *
144
	 * @return array [field type name] => (array|string) search bar input types
145
	 */
146
	public static function get_input_types_by_field_type() {
147
		/**
148
		 * Input Type groups
149
		 * @see admin-search-widget.js (getSelectInput)
150
		 * @var array
151
		 */
152
		$input_types = array(
153
			'text' => array( 'input_text' ),
154
			'address' => array( 'input_text' ),
155
			'number' => array( 'input_text' ),
156
			'date' => array( 'date', 'date_range' ),
157
			'boolean' => array( 'single_checkbox' ),
158
			'select' => array( 'select', 'radio', 'link' ),
159
			'multi' => array( 'select', 'multiselect', 'radio', 'checkbox', 'link' ),
160
		);
161
162
		/**
163
		 * @filter `gravityview/search/input_types` Change the types of search fields available to a field type
164
		 * @see GravityView_Widget_Search::get_search_input_labels() for the available input types
165
		 * @param array $input_types Associative array: key is field `name`, value is array of GravityView input types (note: use `input_text` for `text`)
166
		 */
167
		$input_types = apply_filters( 'gravityview/search/input_types', $input_types );
168
169
		return $input_types;
170
	}
171
172
	/**
173
	 * Get labels for different types of search bar inputs
174
	 *
175
	 * @since 1.17.5
176
	 *
177
	 * @return array [input type] => input type label
178
	 */
179
	public static function get_search_input_labels() {
180
		/**
181
		 * Input Type labels l10n
182
		 * @see admin-search-widget.js (getSelectInput)
183
		 * @var array
184
		 */
185
		$input_labels = array(
186
			'input_text' => esc_html__( 'Text', 'gravityview' ),
187
			'date' => esc_html__( 'Date', 'gravityview' ),
188
			'select' => esc_html__( 'Select', 'gravityview' ),
189
			'multiselect' => esc_html__( 'Select (multiple values)', 'gravityview' ),
190
			'radio' => esc_html__( 'Radio', 'gravityview' ),
191
			'checkbox' => esc_html__( 'Checkbox', 'gravityview' ),
192
			'single_checkbox' => esc_html__( 'Checkbox', 'gravityview' ),
193
			'link' => esc_html__( 'Links', 'gravityview' ),
194
			'date_range' => esc_html__( 'Date range', 'gravityview' ),
195
		);
196
197
		/**
198
		 * @filter `gravityview/search/input_types` Change the label of search field input types
199
		 * @param array $input_types Associative array: key is input type name, value is label
200
		 */
201
		$input_labels = apply_filters( 'gravityview/search/input_labels', $input_labels );
202
203
		return $input_labels;
204
	}
205
206
	public static function get_search_input_label( $input_type ) {
207
		$labels = self::get_search_input_labels();
208
209
		return \GV\Utils::get( $labels, $input_type, false );
210
	}
211
212
	/**
213
	 * Add script to Views edit screen (admin)
214
	 * @param  mixed $hook
215
	 */
216
	public function add_scripts_and_styles( $hook ) {
217
		global $pagenow;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
218
219
		// Don't process any scripts below here if it's not a GravityView page or the widgets screen
220
		if ( ! gravityview_is_admin_page( $hook, 'single' ) && ( 'widgets.php' !== $pagenow ) ) {
0 ignored issues
show
Deprecated Code introduced by
The function gravityview_is_admin_page() has been deprecated with message: See `gravityview()->request->is_admin` or `\GV\Request::is_admin`

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
221
			return;
222
		}
223
224
		$script_min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
225
		$script_source = empty( $script_min ) ? '/source' : '';
226
227
		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 );
228
229
		wp_localize_script( 'gravityview_searchwidget_admin', 'gvSearchVar', array(
230
			'nonce' => wp_create_nonce( 'gravityview_ajaxsearchwidget' ),
231
			'label_nofields' => esc_html__( 'No search fields configured yet.', 'gravityview' ),
232
			'label_addfield' => esc_html__( 'Add Search Field', 'gravityview' ),
233
			'label_label' => esc_html__( 'Label', 'gravityview' ),
234
			'label_searchfield' => esc_html__( 'Search Field', 'gravityview' ),
235
			'label_inputtype' => esc_html__( 'Input Type', 'gravityview' ),
236
			'label_ajaxerror' => esc_html__( 'There was an error loading searchable fields. Save the View or refresh the page to fix this issue.', 'gravityview' ),
237
			'input_labels' => json_encode( self::get_search_input_labels() ),
238
			'input_types' => json_encode( self::get_input_types_by_field_type() ),
239
		) );
240
241
	}
242
243
	/**
244
	 * Add admin script to the no-conflict scripts whitelist
245
	 * @param array $allowed Scripts allowed in no-conflict mode
246
	 * @return array Scripts allowed in no-conflict mode, plus the search widget script
247
	 */
248
	public function register_no_conflict( $allowed ) {
249
		$allowed[] = 'gravityview_searchwidget_admin';
250
		return $allowed;
251
	}
252
253
	/**
254
	 * Ajax
255
	 * Returns the form fields ( only the searchable ones )
256
	 *
257
	 * @access public
258
	 * @return void
259
	 */
260
	public static function get_searchable_fields() {
261
262
		if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'gravityview_ajaxsearchwidget' ) ) {
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
263
			exit( '0' );
0 ignored issues
show
Coding Style Compatibility introduced by
The method get_searchable_fields() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
264
		}
265
266
		$form = '';
267
268
		// Fetch the form for the current View
269
		if ( ! empty( $_POST['view_id'] ) ) {
270
271
			$form = gravityview_get_form_id( $_POST['view_id'] );
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
272
273
		} elseif ( ! empty( $_POST['formid'] ) ) {
274
275
			$form = (int) $_POST['formid'];
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
276
277
		} elseif ( ! empty( $_POST['template_id'] ) && class_exists( 'GravityView_Ajax' ) ) {
278
279
			$form = GravityView_Ajax::pre_get_form_fields( $_POST['template_id'] );
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
280
281
		}
282
283
		// fetch form id assigned to the view
284
		$response = self::render_searchable_fields( $form );
285
286
		exit( $response );
0 ignored issues
show
Coding Style Compatibility introduced by
The method get_searchable_fields() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
287
	}
288
289
	/**
290
	 * Generates html for the available Search Fields dropdown
291
	 * @param  int $form_id
292
	 * @param  string $current (for future use)
293
	 * @return string
294
	 */
295
	public static function render_searchable_fields( $form_id = null, $current = '' ) {
296
297
		if ( is_null( $form_id ) ) {
298
			return '';
299
		}
300
301
		// start building output
302
303
		$output = '<select class="gv-search-fields">';
304
305
		$custom_fields = array(
306
			'search_all' => array(
307
				'text' => esc_html__( 'Search Everything', 'gravityview' ),
308
				'type' => 'text',
309
			),
310
			'entry_date' => array(
311
				'text' => esc_html__( 'Entry Date', 'gravityview' ),
312
				'type' => 'date',
313
			),
314
			'entry_id' => array(
315
				'text' => esc_html__( 'Entry ID', 'gravityview' ),
316
				'type' => 'text',
317
			),
318
			'created_by' => array(
319
				'text' => esc_html__( 'Entry Creator', 'gravityview' ),
320
				'type' => 'select',
321
			),
322
			'is_starred' => array(
323
				'text' => esc_html__( 'Is Starred', 'gravityview' ),
324
				'type' => 'boolean',
325
			),
326
		);
327
328
		foreach( $custom_fields as $custom_field_key => $custom_field ) {
329
			$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'] );
0 ignored issues
show
introduced by
No space after opening parenthesis of array is bad style
Loading history...
330
		}
331
332
		// Get fields with sub-inputs and no parent
333
		$fields = gravityview_get_form_fields( $form_id, true, true );
334
335
		/**
336
		 * @filter `gravityview/search/searchable_fields` Modify the fields that are displayed as searchable in the Search Bar dropdown\n
337
		 * @since 1.17
338
		 * @see gravityview_get_form_fields() Used to fetch the fields
339
		 * @see GravityView_Widget_Search::get_search_input_types See this method to modify the type of input types allowed for a field
340
		 * @param array $fields Array of searchable fields, as fetched by gravityview_get_form_fields()
341
		 * @param  int $form_id
342
		 */
343
		$fields = apply_filters( 'gravityview/search/searchable_fields', $fields, $form_id );
344
345
		if ( ! empty( $fields ) ) {
346
347
			$blacklist_field_types = apply_filters( 'gravityview_blacklist_field_types', array( 'fileupload', 'post_image', 'post_id', 'section' ), null );
348
349
			foreach ( $fields as $id => $field ) {
350
351
				if ( in_array( $field['type'], $blacklist_field_types ) ) {
352
					continue;
353
				}
354
355
				$types = self::get_search_input_types( $id, $field['type'] );
356
357
				$output .= '<option value="'. $id .'" '. selected( $id, $current, false ).'data-inputtypes="'. esc_attr( $types ) .'">'. esc_html( $field['label'] ) .'</option>';
358
			}
359
		}
360
361
		$output .= '</select>';
362
363
		return $output;
364
365
	}
366
367
	/**
368
	 * Assign an input type according to the form field type
369
	 *
370
	 * @see admin-search-widget.js
371
	 *
372
	 * @param string|int|float $field_id Gravity Forms field ID
373
	 * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
374
	 *
375
	 * @return string GV field search input type ('multi', 'boolean', 'select', 'date', 'text')
376
	 */
377
	public static function get_search_input_types( $field_id = '', $field_type = null ) {
378
379
		// @todo - This needs to be improved - many fields have . including products and addresses
380
		if ( false !== strpos( (string) $field_id, '.' ) && in_array( $field_type, array( 'checkbox' ) ) || in_array( $field_id, array( 'is_fulfilled' ) ) ) {
381
			$input_type = 'boolean'; // on/off checkbox
382
		} elseif ( in_array( $field_type, array( 'checkbox', 'post_category', 'multiselect' ) ) ) {
383
			$input_type = 'multi'; //multiselect
384
		} elseif ( in_array( $field_type, array( 'select', 'radio' ) ) ) {
385
			$input_type = 'select';
386
		} elseif ( in_array( $field_type, array( 'date' ) ) || in_array( $field_id, array( 'payment_date' ) ) ) {
387
			$input_type = 'date';
388
		} elseif ( in_array( $field_type, array( 'number' ) ) || in_array( $field_id, array( 'payment_amount' ) ) ) {
389
			$input_type = 'number';
390
		} else {
391
			$input_type = 'text';
392
		}
393
394
		/**
395
		 * @filter `gravityview/extension/search/input_type` Modify the search form input type based on field type
396
		 * @since 1.2
397
		 * @since 1.19.2 Added $field_id parameter
398
		 * @param string $input_type Assign an input type according to the form field type. Defaults: `boolean`, `multi`, `select`, `date`, `text`
399
		 * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
400
		 * @param string|int|float $field_id ID of the field being processed
401
		 */
402
		$input_type = apply_filters( 'gravityview/extension/search/input_type', $input_type, $field_type, $field_id );
403
404
		return $input_type;
405
	}
406
407
	/**
408
	 * Display hidden fields to add support for sites using Default permalink structure
409
	 *
410
	 * @since 1.8
411
	 * @return array Search fields, modified if not using permalinks
412
	 */
413 2
	public function add_no_permalink_fields( $search_fields, $object, $widget_args = array() ) {
414
		/** @global WP_Rewrite $wp_rewrite */
415 2
		global $wp_rewrite;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
416
417
		// Support default permalink structure
418 2
		if ( false === $wp_rewrite->using_permalinks() ) {
419
420
			// By default, use current post.
421 2
			$post_id = 0;
422
423
			// We're in the WordPress Widget context, and an overriding post ID has been set.
424 2
			if ( ! empty( $widget_args['post_id'] ) ) {
425
				$post_id = absint( $widget_args['post_id'] );
426
			}
427
			// We're in the WordPress Widget context, and the base View ID should be used
428 2
			else if ( ! empty( $widget_args['view_id'] ) ) {
429
				$post_id = absint( $widget_args['view_id'] );
430
			}
431
432 2
			$args = gravityview_get_permalink_query_args( $post_id );
433
434
			// Add hidden fields to the search form
435 2
			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...
436 2
				$search_fields[] = array(
437 2
					'name'  => $key,
438 2
					'input' => 'hidden',
439 2
					'value' => $value,
440
				);
441
			}
442
		}
443
444 2
		return $search_fields;
445
	}
446
447
	/**
448
	 * Get the fields that are searchable for a View
449
	 *
450
	 * @since 2.0
451
	 *
452
	 * @param \GV\View|null $view
453
	 *
454
	 * TODO: Move to \GV\View, perhaps? And return a Field_Collection
455
	 * TODO: Use in gravityview()->request->is_search() to calculate whether a valid search
456
	 *
457
	 * @return array If no View, returns empty array. Otherwise, returns array of fields configured in widgets and Search Bar for a View
458
	 */
459 3
	private function get_view_searchable_fields( $view ) {
460
461
		/**
462
		 * Find all search widgets on the view and get the searchable fields settings.
463
		 */
464 3
		$searchable_fields = array();
465
466 3
		if ( ! $view ) {
467
			return $searchable_fields;
468
		}
469
470
		/**
471
		 * Include the sidebar Widgets.
472
		 */
473 3
		$widgets = (array) get_option( 'widget_gravityview_search', array() );
474
475 3
		foreach ( $widgets as $widget ) {
476 3
			if ( ! empty( $widget['view_id'] ) && $widget['view_id'] == $view->ID ) {
477
				if( $_fields = json_decode( $widget['search_fields'], true ) ) {
478
					foreach ( $_fields as $field ) {
479 3
						$searchable_fields [] = $field['field'];
480
					}
481
				}
482
			}
483
		}
484
485 3
		foreach ( $view->widgets->by_id( $this->get_widget_id() )->all() as $widget ) {
486 3
			if( $_fields = json_decode( $widget->configuration->get( 'search_fields' ), true ) ) {
487 3
				foreach ( $_fields as $field ) {
488 3
					$searchable_fields [] = $field['field'];
489
				}
490
			}
491
		}
492
493 3
		return $searchable_fields;
494
	}
495
496
	/** --- Frontend --- */
497
498
	/**
499
	 * Calculate the search criteria to filter entries
500
	 * @param  array $search_criteria
501
	 * @return array
502
	 */
503 26
	public function filter_entries( $search_criteria, $form_id = null, $args = array() ) {
504
505 26
		if( 'post' === $this->search_method ) {
506
			$get = $_POST;
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
507
		} else {
508 26
			$get = $_GET;
0 ignored issues
show
introduced by
Detected access of super global var $_GET, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
509
		}
510
511 26
		$view = \GV\View::by_id( \GV\Utils::get( $args, 'id' ) );
512
513 26
		gravityview()->log->debug( 'Requested $_{method}: ', array( 'method' => $this->search_method, 'data' => $get ) );
514
515 26
		if ( empty( $get ) || ! is_array( $get ) ) {
516 23
			return $search_criteria;
517
		}
518
519 4
		$get = stripslashes_deep( $get );
520
521 4
		$get = gv_map_deep( $get, 'rawurldecode' );
522
523
		// Make sure array key is set up
524 4
		$search_criteria['field_filters'] = \GV\Utils::get( $search_criteria, 'field_filters', array() );
525
526 4
		$searchable_fields = $this->get_view_searchable_fields( $view );
527
528
		// add free search
529 4
		if ( isset( $get['gv_search'] ) && '' !== $get['gv_search'] && in_array( 'search_all', $searchable_fields ) ) {
530
531 1
			$search_all_value = trim( $get['gv_search'] );
532
533
			/**
534
			 * @filter `gravityview/search-all-split-words` Search for each word separately or the whole phrase?
535
			 * @since 1.20.2
536
			 * @param bool $split_words True: split a phrase into words; False: search whole word only [Default: true]
537
			 */
538 1
			$split_words = apply_filters( 'gravityview/search-all-split-words', true );
539
540 1
			if ( $split_words ) {
541
542
				// Search for a piece
543 1
				$words = explode( ' ', $search_all_value );
544
545 1
				$words = array_filter( $words );
546
547
			} else {
548
549
				// Replace multiple spaces with one space
550 1
				$search_all_value = preg_replace( '/\s+/ism', ' ', $search_all_value );
551
552 1
				$words = array( $search_all_value );
553
			}
554
555 1
			foreach ( $words as $word ) {
556 1
				$search_criteria['field_filters'][] = array(
557 1
					'key' => null, // The field ID to search
558 1
					'value' => $word, // The value to search
559 1
					'operator' => 'contains', // What to search in. Options: `is` or `contains`
560
				);
561
			}
562
		}
563
564
		// start date & end date
565 4
		if ( in_array( 'entry_date', $searchable_fields ) ) {
566 3
			$curr_start = !empty( $get['gv_start'] ) ? $get['gv_start'] : '';
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
567 3
			$curr_end = !empty( $get['gv_start'] ) ? $get['gv_end'] : '';
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
568
569 3
			if ( $view ) {
570
				/**
571
				 * Override start and end dates if View is limited to some already.
572
				 */
573 3
				if ( $start_date =$view->settings->get( 'start_date' ) ) {
0 ignored issues
show
introduced by
Expected 1 space after "="; 0 found
Loading history...
574 1
					if ( $start_timestamp = strtotime( $curr_start ) ) {
575 1
						$curr_start = $start_timestamp < strtotime( $start_date ) ? $start_date : $curr_start;
576
					}
577
				}
578 3
				if ( $end_date =$view->settings->get( 'end_date' ) ) {
0 ignored issues
show
introduced by
Expected 1 space after "="; 0 found
Loading history...
579
					if ( $end_timestamp = strtotime( $curr_end ) ) {
580
						$curr_end = $end_timestamp > strtotime( $end_date ) ? $end_date : $curr_end;
581
					}
582
				}
583
			}
584
585
			/**
586
			 * @filter `gravityview_date_created_adjust_timezone` Whether to adjust the timezone for entries. \n
587
			 * date_created is stored in UTC format. Convert search date into UTC (also used on templates/fields/date_created.php)
588
			 * @since 1.12
589
			 * @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
590
			 * @param[in] string $context Where the filter is being called from. `search` in this case.
591
			 */
592 3
			$adjust_tz = apply_filters( 'gravityview_date_created_adjust_timezone', true, 'search' );
593
594
			/**
595
			 * Don't set $search_criteria['start_date'] if start_date is empty as it may lead to bad query results (GFAPI::get_entries)
596
			 */
597 3
			if ( ! empty( $curr_start ) ) {
598 3
				$curr_start = date( 'Y-m-d H:i:s', strtotime( $curr_start ) );
599 3
				$search_criteria['start_date'] = $adjust_tz ? get_gmt_from_date( $curr_start ) : $curr_start;
600
			}
601
602 3
			if ( ! empty( $curr_end ) ) {
603
				// Fast-forward 24 hour on the end time
604 3
				$curr_end = date( 'Y-m-d H:i:s', strtotime( $curr_end ) + DAY_IN_SECONDS );
605 3
				$search_criteria['end_date'] = $adjust_tz ? get_gmt_from_date( $curr_end ) : $curr_end;
606 3
				if ( strpos( $search_criteria['end_date'], '00:00:00' ) ) { // See https://github.com/gravityview/GravityView/issues/1056
607 3
					$search_criteria['end_date'] = date( 'Y-m-d H:i:s', strtotime( $search_criteria['end_date'] ) - 1 );
608
				}
609
			}
610
		}
611
612
		// search for a specific entry ID
613 4
		if ( ! empty( $get[ 'gv_id' ] ) && in_array( 'entry_id', $searchable_fields ) ) {
0 ignored issues
show
introduced by
Array keys should NOT be surrounded by spaces if they only contain a string or an integer.
Loading history...
614 2
			$search_criteria['field_filters'][] = array(
615 2
				'key' => 'id',
616 2
				'value' => absint( $get[ 'gv_id' ] ),
0 ignored issues
show
introduced by
Array keys should NOT be surrounded by spaces if they only contain a string or an integer.
Loading history...
617 2
				'operator' => '=',
618
			);
619
		}
620
621
		// search for a specific Created_by ID
622 4
		if ( ! empty( $get[ 'gv_by' ] ) && in_array( 'entry_creator', $searchable_fields ) ) {
0 ignored issues
show
introduced by
Array keys should NOT be surrounded by spaces if they only contain a string or an integer.
Loading history...
623 1
			$search_criteria['field_filters'][] = array(
624 1
				'key' => 'created_by',
625 1
				'value' => absint( $get['gv_by'] ),
626 1
				'operator' => '=',
627
			);
628
		}
629
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
630
631
		// Get search mode passed in URL
632 4
		$mode = isset( $get['mode'] ) && in_array( $get['mode'], array( 'any', 'all' ) ) ?  $get['mode'] : 'any';
633
634
		// get the other search filters
635 4
		foreach ( $get as $key => $value ) {
636
637 4
			if ( 0 !== strpos( $key, 'filter_' ) || gv_empty( $value, false, false ) || ( is_array( $value ) && count( $value ) === 1 && gv_empty( $value[0], false, false ) ) ) {
0 ignored issues
show
introduced by
Found "=== 1". Use Yoda Condition checks, you must
Loading history...
638 3
				continue;
639
			}
640
641 2
			$filter_key = $this->convert_request_key_to_filter_key( $key );
642
643
			// could return simple filter or multiple filters
644 2
			if ( ! in_array( 'search_all', $searchable_fields ) && ! in_array( $filter_key , $searchable_fields ) ) {
645 1
				continue;
646
			}
647
648 1
			$filter = $this->prepare_field_filter( $filter_key, $value, $view );
649
650 1
			if ( isset( $filter[0]['value'] ) ) {
651
				$search_criteria['field_filters'] = array_merge( $search_criteria['field_filters'], $filter );
652
653
				// if date range type, set search mode to ALL
654
				if ( ! empty( $filter[0]['operator'] ) && in_array( $filter[0]['operator'], array( '>=', '<=', '>', '<' ) ) ) {
655
					$mode = 'all';
656
				}
657 1
			} elseif( !empty( $filter ) ) {
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
658 1
				$search_criteria['field_filters'][] = $filter;
659
			}
660
		}
661
662
		/**
663
		 * @filter `gravityview/search/mode` Set the Search Mode (`all` or `any`)
664
		 * @since 1.5.1
665
		 * @param[out,in] string $mode Search mode (`any` vs `all`)
666
		 */
667 4
		$search_criteria['field_filters']['mode'] = apply_filters( 'gravityview/search/mode', $mode );
668
669 4
		gravityview()->log->debug( 'Returned Search Criteria: ', array( 'data' => $search_criteria ) );
670
671 4
		unset( $get );
672
673 4
		return $search_criteria;
674
	}
675
676
	/**
677
	 * Convert $_GET/$_POST key to the field/meta ID
678
	 *
679
	 * Examples:
680
	 * - `filter_is_starred` => `is_starred`
681
	 * - `filter_1_2` => `1.2`
682
	 * - `filter_5` => `5`
683
	 *
684
	 * @since 2.0
685
	 *
686
	 * @param string $key $_GET/_$_POST search key
687
	 *
688
	 * @return string
689
	 */
690 2
	private function convert_request_key_to_filter_key( $key ) {
691
692 2
		$field_id = str_replace( 'filter_', '', $key );
693
694
		// calculates field_id, removing 'filter_' and for '_' for advanced fields ( like name or checkbox )
695 2
		if ( preg_match('/^[0-9_]+$/ism', $field_id ) ) {
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
696 2
			$field_id = str_replace( '_', '.', $field_id );
697
		}
698
699 2
		return $field_id;
700
	}
701
702
	/**
703
	 * Prepare the field filters to GFAPI
704
	 *
705
	 * 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.
706
	 *
707
	 * Format searched values
708
	 *
709
	 * @param  string $filter_key ID of the field, or entry meta key
710
	 * @param  string $value $_GET/$_POST search value
711
	 * @param  \GV\View $view The view we're looking at
712
	 *
713
	 * @return array        1 or 2 deph levels
714
	 */
715 1
	public function prepare_field_filter( $filter_key, $value, $view ) {
716
717
		// get form field array
718 1
		$form_field = is_numeric( $filter_key ) ? \GV\GF_Field::by_id( $view->form, $filter_key ) : \GV\Internal_Field::by_id( $filter_key );
719
720
		// default filter array
721
		$filter = array(
722 1
			'key'   => $filter_key,
723 1
			'value' => $value,
724
		);
725
726 1
		switch ( $form_field->type ) {
727
728 1
			case 'select':
729 1
			case 'radio':
730 1
				$filter['operator'] = 'is';
731 1
				break;
732
733
			case 'post_category':
734
735
				if ( ! is_array( $value ) ) {
736
					$value = array( $value );
737
				}
738
739
				// Reset filter variable
740
				$filter = array();
741
742
				foreach ( $value as $val ) {
743
					$cat = get_term( $val, 'category' );
744
					$filter[] = array(
745
						'key'      => $filter_key,
746
						'value'    => esc_attr( $cat->name ) . ':' . $val,
747
						'operator' => 'is',
748
					);
749
				}
750
751
				break;
752
753
			case 'multiselect':
754
755
				if ( ! is_array( $value ) ) {
756
					break;
757
				}
758
759
				// Reset filter variable
760
				$filter = array();
761
762
				foreach ( $value as $val ) {
763
					$filter[] = array( 'key' => $filter_key, 'value' => $val );
764
				}
765
766
				break;
767
768
			case 'checkbox':
769
				// convert checkbox on/off into the correct search filter
770
				if ( false !== strpos( $filter_key, '.' ) && ! empty( $form_field->inputs ) && ! empty( $form_field->choices ) ) {
771
					foreach ( $form_field->inputs as $k => $input ) {
772
						if ( $input['id'] == $filter_key ) {
773
							$filter['value'] = $form_field->choices[ $k ]['value'];
774
							$filter['operator'] = 'is';
775
							break;
776
						}
777
					}
778
				} elseif ( is_array( $value ) ) {
779
780
					// Reset filter variable
781
					$filter = array();
782
783
					foreach ( $value as $val ) {
784
						$filter[] = array(
785
							'key'      => $filter_key,
786
							'value'    => $val,
787
							'operator' => 'is',
788
						);
789
					}
790
				}
791
792
				break;
793
794
			case 'name':
795
			case 'address':
796
797
				if ( false === strpos( $filter_key, '.' ) ) {
798
799
					$words = explode( ' ', $value );
800
801
					$filters = array();
802
					foreach ( $words as $word ) {
803
						if ( ! empty( $word ) && strlen( $word ) > 1 ) {
804
							// Keep the same key for each filter
805
							$filter['value'] = $word;
806
							// Add a search for the value
807
							$filters[] = $filter;
808
						}
809
					}
810
811
					$filter = $filters;
812
				}
813
814
				break;
815
816
			case 'date':
817
818
				if ( is_array( $value ) ) {
819
820
					// Reset filter variable
821
					$filter = array();
822
823
					foreach ( $value as $k => $date ) {
824
						if ( empty( $date ) ) {
825
							continue;
826
						}
827
						$operator = 'start' === $k ? '>=' : '<=';
828
829
						/**
830
						 * @hack
831
						 * @since 1.16.3
832
						 * Safeguard until GF implements '<=' operator
833
						 */
834
						if( !GFFormsModel::is_valid_operator( $operator ) && $operator === '<=' ) {
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
835
							$operator = '<';
836
							$date = date( 'Y-m-d', strtotime( $date . ' +1 day' ) );
837
						}
838
839
						$filter[] = array(
840
							'key'      => $filter_key,
841
							'value'    => self::get_formatted_date( $date, 'Y-m-d' ),
842
							'operator' => $operator,
843
						);
844
					}
845
				} else {
846
					$filter['value'] = self::get_formatted_date( $value, 'Y-m-d' );
847
				}
848
849
				break;
850
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
851
852
		} // switch field type
853
854 1
		return $filter;
855
	}
856
857
	/**
858
	 * Get the Field Format form GravityForms
859
	 *
860
	 * @param GF_Field_Date $field The field object
861
	 * @since 1.10
862
	 *
863
	 * @return string Format of the date in the database
864
	 */
865
	public static function get_date_field_format( GF_Field_Date $field ) {
866
		$format = 'm/d/Y';
867
		$datepicker = array(
868
			'mdy' => 'm/d/Y',
869
			'dmy' => 'd/m/Y',
870
			'dmy_dash' => 'd-m-Y',
871
			'dmy_dot' => 'm.d.Y',
872
			'ymd_slash' => 'Y/m/d',
873
			'ymd_dash' => 'Y-m-d',
874
			'ymd_dot' => 'Y.m.d',
875
		);
876
877
		if ( ! empty( $field->dateFormat ) && isset( $datepicker[ $field->dateFormat ] ) ){
878
			$format = $datepicker[ $field->dateFormat ];
879
		}
880
881
		return $format;
882
	}
883
884
	/**
885
	 * Format a date value
886
	 *
887
	 * @param string $value Date value input
888
	 * @param string $format Wanted formatted date
889
	 * @return string
890
	 */
891
	public static function get_formatted_date( $value = '', $format = 'Y-m-d' ) {
892
		$date = date_create( $value );
893
		if ( empty( $date ) ) {
894
			gravityview()->log->debug( 'Date format not valid: {value}', array( 'value' => $value ) );
895
			return '';
896
		}
897
		return $date->format( $format );
898
	}
899
900
901
	/**
902
	 * Include this extension templates path
903
	 * @param array $file_paths List of template paths ordered
904
	 */
905 24
	public function add_template_path( $file_paths ) {
906
907
		// Index 100 is the default GravityView template path.
908 24
		$file_paths[102] = self::$file . 'templates/';
909
910 24
		return $file_paths;
911
	}
912
913
	/**
914
	 * Check whether the configured search fields have a date field
915
	 *
916
	 * @since 1.17.5
917
	 *
918
	 * @param array $search_fields
919
	 *
920
	 * @return bool True: has a `date` or `date_range` field
921
	 */
922 2
	private function has_date_field( $search_fields ) {
923
924 2
		$has_date = false;
925
926 2
		foreach ( $search_fields as $k => $field ) {
927 2
			if ( in_array( $field['input'], array( 'date', 'date_range', 'entry_date' ) ) ) {
928
				$has_date = true;
929 2
				break;
930
			}
931
		}
932
933 2
		return $has_date;
934
	}
935
936
	/**
937
	 * Renders the Search Widget
938
	 * @param array $widget_args
939
	 * @param string $content
940
	 * @param string $context
941
	 *
942
	 * @return void
943
	 */
944 2
	public function render_frontend( $widget_args, $content = '', $context = '' ) {
945
		/** @var GravityView_View $gravityview_view */
946 2
		$gravityview_view = GravityView_View::getInstance();
947
948 2
		if ( empty( $gravityview_view ) ) {
949
			gravityview()->log->debug( '$gravityview_view not instantiated yet.' );
950
			return;
951
		}
952
953
		// get configured search fields
954 2
		$search_fields = ! empty( $widget_args['search_fields'] ) ? json_decode( $widget_args['search_fields'], true ) : '';
955
956 2
		if ( empty( $search_fields ) || ! is_array( $search_fields ) ) {
957
			gravityview()->log->debug( 'No search fields configured for widget:', array( 'data' => $widget_args ) );
958
			return;
959
		}
960
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
961
962
		// prepare fields
963 2
		foreach ( $search_fields as $k => $field ) {
964
965 2
			$updated_field = $field;
966
967 2
			$updated_field = $this->get_search_filter_details( $updated_field );
968
969 2
			switch ( $field['field'] ) {
970
971 2
				case 'search_all':
972 2
					$updated_field['key'] = 'search_all';
973 2
					$updated_field['input'] = 'search_all';
974 2
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_search' );
975 2
					break;
976
977
				case 'entry_date':
978
					$updated_field['key'] = 'entry_date';
979
					$updated_field['input'] = 'entry_date';
980
					$updated_field['value'] = array(
981
						'start' => $this->rgget_or_rgpost( 'gv_start' ),
982
						'end' => $this->rgget_or_rgpost( 'gv_end' ),
983
					);
984
					break;
985
986
				case 'entry_id':
987
					$updated_field['key'] = 'entry_id';
988
					$updated_field['input'] = 'entry_id';
989
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_id' );
990
					break;
991
992
				case 'created_by':
993
					$updated_field['key'] = 'created_by';
994
					$updated_field['name'] = 'gv_by';
995
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_by' );
996
					$updated_field['choices'] = self::get_created_by_choices();
997
					break;
998
			}
999
1000 2
			$search_fields[ $k ] = $updated_field;
1001
		}
1002
1003 2
		gravityview()->log->debug( 'Calculated Search Fields: ', array( 'data' => $search_fields ) );
1004
1005
		/**
1006
		 * @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.
1007
		 * @param array $search_fields Array of search filters with `key`, `label`, `value`, `type`, `choices` keys
1008
		 * @param GravityView_Widget_Search $this Current widget object
1009
		 * @param array $widget_args Args passed to this method. {@since 1.8}
1010
		 * @param \GV\Template_Context $context {@since 2.0}
1011
		 * @var array
1012
		 */
1013 2
		$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...
1014
1015 2
		$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...
1016
1017
		/** @since 1.14 */
1018 2
		$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...
1019
1020 2
		$custom_class = ! empty( $widget_args['custom_class'] ) ? $widget_args['custom_class'] : '';
1021
1022 2
		$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...
1023
1024 2
		$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...
1025
1026 2
		if ( $this->has_date_field( $search_fields ) ) {
1027
			// enqueue datepicker stuff only if needed!
1028
			$this->enqueue_datepicker();
1029
		}
1030
1031 2
		$this->maybe_enqueue_flexibility();
1032
1033 2
		$gravityview_view->render( 'widget', 'search', false );
1034 2
	}
1035
1036
	/**
1037
	 * Get the search class for a search form
1038
	 *
1039
	 * @since 1.5.4
1040
	 *
1041
	 * @return string Sanitized CSS class for the search form
1042
	 */
1043 2
	public static function get_search_class( $custom_class = '' ) {
1044 2
		$gravityview_view = GravityView_View::getInstance();
1045
1046 2
		$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...
1047
1048 2
		if ( ! empty( $custom_class )  ) {
1049
			$search_class .= ' '.$custom_class;
1050
		}
1051
1052
		/**
1053
		 * @filter `gravityview_search_class` Modify the CSS class for the search form
1054
		 * @param string $search_class The CSS class for the search form
1055
		 */
1056 2
		$search_class = apply_filters( 'gravityview_search_class', $search_class );
1057
1058
		// Is there an active search being performed? Used by fe-views.js
1059 2
		$search_class .= GravityView_frontend::getInstance()->isSearch() ? ' gv-is-search' : '';
1060
1061 2
		return gravityview_sanitize_html_class( $search_class );
1062
	}
1063
1064
1065
	/**
1066
	 * Calculate the search form action
1067
	 * @since 1.6
1068
	 *
1069
	 * @return string
1070
	 */
1071 2
	public static function get_search_form_action() {
1072 2
		$gravityview_view = GravityView_View::getInstance();
1073
1074 2
		$post_id = $gravityview_view->getPostId() ? $gravityview_view->getPostId() : $gravityview_view->getViewId();
1075
1076 2
		$url = add_query_arg( array(), get_permalink( $post_id ) );
1077
1078 2
		return esc_url( $url );
1079
	}
1080
1081
	/**
1082
	 * Get the label for a search form field
1083
	 * @param  array $field      Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1084
	 * @param  array $form_field Form field data, as fetched by `gravityview_get_field()`
1085
	 * @return string             Label for the search form
1086
	 */
1087 2
	private static function get_field_label( $field, $form_field = array() ) {
1088
1089 2
		$label = \GV\Utils::_GET( 'label', \GV\Utils::get( $field, 'label' ) );
1090
1091 2
		if ( ! $label ) {
1092
1093 2
			$label = isset( $form_field['label'] ) ? $form_field['label'] : '';
1094
1095 2
			switch( $field['field'] ) {
1096 2
				case 'search_all':
1097 2
					$label = __( 'Search Entries:', 'gravityview' );
1098 2
					break;
1099
				case 'entry_date':
1100
					$label = __( 'Filter by date:', 'gravityview' );
1101
					break;
1102
				case 'entry_id':
1103
					$label = __( 'Entry ID:', 'gravityview' );
1104
					break;
1105
				default:
1106
					// If this is a field input, not a field
1107
					if ( strpos( $field['field'], '.' ) > 0 && ! empty( $form_field['inputs'] ) ) {
1108
1109
						// Get the label for the field in question, which returns an array
1110
						$items = wp_list_filter( $form_field['inputs'], array( 'id' => $field['field'] ) );
1111
1112
						// Get the item with the `label` key
1113
						$values = wp_list_pluck( $items, 'label' );
1114
1115
						// There will only one item in the array, but this is easier
1116
						foreach ( $values as $value ) {
1117
							$label = $value;
1118
							break;
1119
						}
1120
					}
1121
			}
1122
		}
1123
1124
		/**
1125
		 * @filter `gravityview_search_field_label` Modify the label for a search field. Supports returning HTML
1126
		 * @since 1.17.3 Added $field parameter
1127
		 * @param[in,out] string $label Existing label text, sanitized.
1128
		 * @param[in] array $form_field Gravity Forms field array, as returned by `GFFormsModel::get_field()`
1129
		 * @param[in] array $field Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1130
		 */
1131 2
		$label = apply_filters( 'gravityview_search_field_label', esc_attr( $label ), $form_field, $field );
1132
1133 2
		return $label;
1134
	}
1135
1136
	/**
1137
	 * Prepare search fields to frontend render with other details (label, field type, searched values)
1138
	 *
1139
	 * @param array $field
1140
	 * @return array
1141
	 */
1142 2
	private function get_search_filter_details( $field ) {
1143
1144 2
		$gravityview_view = GravityView_View::getInstance();
1145
1146 2
		$form = $gravityview_view->getForm();
1147
1148
		// for advanced field ids (eg, first name / last name )
1149 2
		$name = 'filter_' . str_replace( '.', '_', $field['field'] );
1150
1151
		// get searched value from $_GET/$_POST (string or array)
1152 2
		$value = $this->rgget_or_rgpost( $name );
1153
1154
		// get form field details
1155 2
		$form_field = gravityview_get_field( $form, $field['field'] );
1156
1157
		$filter = array(
1158 2
			'key' => $field['field'],
1159 2
			'name' => $name,
1160 2
			'label' => self::get_field_label( $field, $form_field ),
1161 2
			'input' => $field['input'],
1162 2
			'value' => $value,
1163 2
			'type' => $form_field['type'],
1164
		);
1165
1166
		// collect choices
1167 2
		if ( 'post_category' === $form_field['type'] && ! empty( $form_field['displayAllCategories'] ) && empty( $form_field['choices'] ) ) {
1168
			$filter['choices'] = gravityview_get_terms_choices();
1169 2
		} elseif ( ! empty( $form_field['choices'] ) ) {
1170
			$filter['choices'] = $form_field['choices'];
1171
		}
1172
1173 2
		if ( 'date_range' === $field['input'] && empty( $value ) ) {
1174
			$filter['value'] = array( 'start' => '', 'end' => '' );
1175
		}
1176
1177 2
		return $filter;
1178
1179
	}
1180
1181
	/**
1182
	 * Calculate the search choices for the users
1183
	 *
1184
	 * @since 1.8
1185
	 *
1186
	 * @return array Array of user choices (value = ID, text = display name)
1187
	 */
1188
	private static function get_created_by_choices() {
1189
1190
		/**
1191
		 * filter gravityview/get_users/search_widget
1192
		 * @see \GVCommon::get_users
1193
		 */
1194
		$users = GVCommon::get_users( 'search_widget', array( 'fields' => array( 'ID', 'display_name' ) ) );
1195
1196
		$choices = array();
1197
		foreach ( $users as $user ) {
1198
			$choices[] = array(
1199
				'value' => $user->ID,
1200
				'text' => $user->display_name,
1201
			);
1202
		}
1203
1204
		return $choices;
1205
	}
1206
1207
1208
	/**
1209
	 * Output the Clear Search Results button
1210
	 * @since 1.5.4
1211
	 */
1212 2
	public static function the_clear_search_button() {
1213 2
		$gravityview_view = GravityView_View::getInstance();
1214
1215 2
		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...
1216
1217
			$url = strtok( add_query_arg( array() ), '?' );
1218
1219
			echo gravityview_get_link( $url, esc_html__( 'Clear', 'gravityview' ), 'class=button gv-search-clear' );
0 ignored issues
show
introduced by
Expected a sanitizing function (see Codex for 'Data Validation'), but instead saw 'gravityview_get_link'
Loading history...
1220
1221
		}
1222 2
	}
1223
1224
	/**
1225
	 * Based on the search method, fetch the value for a specific key
1226
	 *
1227
	 * @since 1.16.4
1228
	 *
1229
	 * @param string $name Name of the request key to fetch the value for
1230
	 *
1231
	 * @return mixed|string Value of request at $name key. Empty string if empty.
1232
	 */
1233 2
	private function rgget_or_rgpost( $name ) {
1234 2
		$value = \GV\Utils::_REQUEST( $name );
1235
1236 2
		$value = stripslashes_deep( $value );
1237
1238 2
		$value = gv_map_deep( $value, 'rawurldecode' );
1239
1240 2
		$value = gv_map_deep( $value, '_wp_specialchars' );
1241
1242 2
		return $value;
1243
	}
1244
1245
1246
	/**
1247
	 * Require the datepicker script for the frontend GV script
1248
	 * @param array $js_dependencies Array of existing required scripts for the fe-views.js script
1249
	 * @return array Array required scripts, with `jquery-ui-datepicker` added
1250
	 */
1251
	public function add_datepicker_js_dependency( $js_dependencies ) {
1252
1253
		$js_dependencies[] = 'jquery-ui-datepicker';
1254
1255
		return $js_dependencies;
1256
	}
1257
1258
	/**
1259
	 * Modify the array passed to wp_localize_script()
1260
	 *
1261
	 * @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...
1262
	 * @param array $view_data View data array with View settings
1263
	 *
1264
	 * @return array
1265
	 */
1266
	public function add_datepicker_localization( $localizations = array(), $view_data = array() ) {
1267
		global $wp_locale;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1268
1269
		/**
1270
		 * @filter `gravityview_datepicker_settings` Modify the datepicker settings
1271
		 * @see http://api.jqueryui.com/datepicker/ Learn what settings are available
1272
		 * @see http://www.renegadetechconsulting.com/tutorials/jquery-datepicker-and-wordpress-i18n Thanks for the helpful information on $wp_locale
1273
		 * @param array $js_localization The data padded to the Javascript file
1274
		 * @param array $view_data View data array with View settings
1275
		 */
1276
		$datepicker_settings = apply_filters( 'gravityview_datepicker_settings', array(
1277
			'yearRange' => '-5:+5',
1278
			'changeMonth' => true,
1279
			'changeYear' => true,
1280
			'closeText' => esc_attr_x( 'Close', 'Close calendar', 'gravityview' ),
1281
			'prevText' => esc_attr_x( 'Prev', 'Previous month in calendar', 'gravityview' ),
1282
			'nextText' => esc_attr_x( 'Next', 'Next month in calendar', 'gravityview' ),
1283
			'currentText' => esc_attr_x( 'Today', 'Today in calendar', 'gravityview' ),
1284
			'weekHeader' => esc_attr_x( 'Week', 'Week in calendar', 'gravityview' ),
1285
			'monthStatus'       => __( 'Show a different month', 'gravityview' ),
1286
			'monthNames'        => array_values( $wp_locale->month ),
1287
			'monthNamesShort'   => array_values( $wp_locale->month_abbrev ),
1288
			'dayNames'          => array_values( $wp_locale->weekday ),
1289
			'dayNamesShort'     => array_values( $wp_locale->weekday_abbrev ),
1290
			'dayNamesMin'       => array_values( $wp_locale->weekday_initial ),
1291
			// get the start of week from WP general setting
1292
			'firstDay'          => get_option( 'start_of_week' ),
1293
			// is Right to left language? default is false
1294
			'isRTL'             => is_rtl(),
1295
		), $view_data );
1296
1297
		$localizations['datepicker'] = $datepicker_settings;
1298
1299
		return $localizations;
1300
1301
	}
1302
1303
	/**
1304
	 * Register search widget scripts, including Flexibility
1305
	 *
1306
	 * @see https://github.com/10up/flexibility
1307
	 *
1308
	 * @since 1.17
1309
	 *
1310
	 * @return void
1311
	 */
1312
	public function register_scripts() {
1313
		wp_register_script( 'gv-flexibility', plugins_url( 'assets/lib/flexibility/flexibility.js', GRAVITYVIEW_FILE ), array(), \GV\Plugin::$version, true );
1314
	}
1315
1316
	/**
1317
	 * If the current visitor is running IE 8 or 9, enqueue Flexibility
1318
	 *
1319
	 * @since 1.17
1320
	 *
1321
	 * @return void
1322
	 */
1323 2
	private function maybe_enqueue_flexibility() {
1324 2
		if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && preg_match( '/MSIE [8-9]/', $_SERVER['HTTP_USER_AGENT'] ) ) {
0 ignored issues
show
introduced by
Due to using Batcache, server side based client related logic will not work, use JS instead.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_SERVER
Loading history...
1325
			wp_enqueue_script( 'gv-flexibility' );
1326
		}
1327 2
	}
1328
1329
	/**
1330
	 * Enqueue the datepicker script
1331
	 *
1332
	 * It sets the $gravityview->datepicker_class parameter
1333
	 *
1334
	 * @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.
1335
	 * @return void
1336
	 */
1337
	public function enqueue_datepicker() {
1338
		$gravityview_view = GravityView_View::getInstance();
1339
1340
		wp_enqueue_script( 'jquery-ui-datepicker' );
1341
1342
		add_filter( 'gravityview_js_dependencies', array( $this, 'add_datepicker_js_dependency' ) );
1343
		add_filter( 'gravityview_js_localization', array( $this, 'add_datepicker_localization' ), 10, 2 );
1344
1345
		$scheme = is_ssl() ? 'https://' : 'http://';
1346
		wp_enqueue_style( 'jquery-ui-datepicker', $scheme.'ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/smoothness/jquery-ui.css' );
1347
1348
		/**
1349
		 * @filter `gravityview_search_datepicker_class`
1350
		 * 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.
1351
		 * @param string $css_class CSS class to use. Default: `gv-datepicker datepicker mdy` \n
1352
		 * Options are:
1353
		 * - `mdy` (mm/dd/yyyy)
1354
		 * - `dmy` (dd/mm/yyyy)
1355
		 * - `dmy_dash` (dd-mm-yyyy)
1356
		 * - `dmy_dot` (dd.mm.yyyy)
1357
		 * - `ymp_slash` (yyyy/mm/dd)
1358
		 * - `ymd_dash` (yyyy-mm-dd)
1359
		 * - `ymp_dot` (yyyy.mm.dd)
1360
		 */
1361
		$datepicker_class = apply_filters( 'gravityview_search_datepicker_class', 'gv-datepicker datepicker mdy' );
1362
1363
		$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...
1364
1365
	}
1366
1367
1368
} // end class
1369
1370
new GravityView_Widget_Search;
1371