Completed
Push — develop ( 230664...40034c )
by Gennady
10:46
created

GravityView_Widget_Search   D

Complexity

Total Complexity 161

Size/Duplication

Total Lines 1249
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 5

Test Coverage

Coverage 18.1%

Importance

Changes 0
Metric Value
dl 0
loc 1249
ccs 84
cts 464
cp 0.181
rs 4.4102
c 0
b 0
f 0
wmc 161
lcom 3
cbo 5

32 Methods

Rating   Name   Duplication   Size   Complexity  
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 67 6
D get_search_input_types() 0 29 10
B add_no_permalink_fields() 0 33 5
A get_search_method() 0 3 1
A getInstance() 0 6 2
A __construct() 0 68 1
A set_search_method() 0 13 2
F filter_entries() 0 141 27
D prepare_field_filter() 0 151 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 90 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 2
A add_datepicker_js_dependency() 0 6 1
B add_datepicker_localization() 0 36 1
A register_scripts() 0 5 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 GravityView_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 1
	public function __construct() {
31
32 1
		$this->widget_description = esc_html__( 'Search form for searching entries.', 'gravityview' );
33
34 1
		self::$instance = &$this;
35
36 1
		self::$file = plugin_dir_path( __FILE__ );
37
38 1
		$default_values = array( 'header' => 0, 'footer' => 0 );
39
40
		$settings = array(
41 1
			'search_layout' => array(
42 1
				'type' => 'radio',
43
				'full_width' => true,
44 1
				'label' => esc_html__( 'Search Layout', 'gravityview' ),
45 1
				'value' => 'horizontal',
46
				'options' => array(
47 1
					'horizontal' => esc_html__( 'Horizontal', 'gravityview' ),
48 1
					'vertical' => esc_html__( 'Vertical', 'gravityview' ),
49
				),
50
			),
51
			'search_clear' => array(
52 1
				'type' => 'checkbox',
53 1
				'label' => __( 'Show Clear button', 'gravityview' ),
54
				'value' => false,
55
			),
56
			'search_fields' => array(
57
				'type' => 'hidden',
58
				'label' => '',
59
				'class' => 'gv-search-fields-value',
60
				'value' => '[{"field":"search_all","input":"input_text"}]', // Default: Search Everything text box
61
			),
62
			'search_mode' => array(
63 1
				'type' => 'radio',
64
				'full_width' => true,
65 1
				'label' => esc_html__( 'Search Mode', 'gravityview' ),
66 1
				'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...
67 1
				'value' => 'any',
68 1
				'class' => 'hide-if-js',
69
				'options' => array(
70 1
					'any' => esc_html__( 'Match Any Fields', 'gravityview' ),
71 1
					'all' => esc_html__( 'Match All Fields', 'gravityview' ),
72
				),
73
			),
74
		);
75 1
		parent::__construct( esc_html__( 'Search Bar', 'gravityview' ), 'search_bar', $default_values, $settings );
76
77
		// frontend - filter entries
78 1
		add_filter( 'gravityview_fe_search_criteria', array( $this, 'filter_entries' ), 10, 1 );
79
80
		// frontend - add template path
81 1
		add_filter( 'gravityview_template_paths', array( $this, 'add_template_path' ) );
82
83
		// Add hidden fields for "Default" permalink structure
84 1
		add_filter( 'gravityview_widget_search_filters', array( $this, 'add_no_permalink_fields' ), 10, 3 );
85
86
		// admin - add scripts - run at 1100 to make sure GravityView_Admin_Views::add_scripts_and_styles() runs first at 999
87 1
		add_action( 'admin_enqueue_scripts', array( $this, 'add_scripts_and_styles' ), 1100 );
88 1
		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...
89 1
		add_filter( 'gravityview_noconflict_scripts', array( $this, 'register_no_conflict' ) );
90
91
		// ajax - get the searchable fields
92 1
		add_action( 'wp_ajax_gv_searchable_fields', array( 'GravityView_Widget_Search', 'get_searchable_fields' ) );
93
94
		// calculate the search method (POST / GET)
95 1
		$this->set_search_method();
96
97 1
	}
98
99
	/**
100
	 * @return GravityView_Widget_Search
101
	 */
102
	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...
103
		if ( empty( self::$instance ) ) {
104
			self::$instance = new GravityView_Widget_Search;
105
		}
106
		return self::$instance;
107
	}
108
109
	/**
110
	 * Sets the search method to GET (default) or POST
111
	 * @since 1.16.4
112
	 */
113 1
	private function set_search_method() {
114
		/**
115
		 * @filter `gravityview/search/method` Modify the search form method (GET / POST)
116
		 * @since 1.16.4
117
		 * @param string $search_method Assign an input type according to the form field type. Defaults: `boolean`, `multi`, `select`, `date`, `text`
118
		 * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
119
		 */
120 1
		$method = apply_filters( 'gravityview/search/method', $this->search_method );
121
122 1
		$method = strtolower( $method );
123
124 1
		$this->search_method = in_array( $method, array( 'get', 'post' ) ) ? $method : 'get';
125 1
	}
126
127
	/**
128
	 * Returns the search method
129
	 * @since 1.16.4
130
	 * @return string
131
	 */
132
	public function get_search_method() {
133
		return $this->search_method;
134
	}
135
136
	/**
137
	 * Get the input types available for different field types
138
	 *
139
	 * @since 1.17.5
140
	 *
141
	 * @return array [field type name] => (array|string) search bar input types
142
	 */
143
	public static function get_input_types_by_field_type() {
144
		/**
145
		 * Input Type groups
146
		 * @see admin-search-widget.js (getSelectInput)
147
		 * @var array
148
		 */
149
		$input_types = array(
150
			'text' => array( 'input_text' ),
151
			'address' => array( 'input_text' ),
152
			'number' => array( 'input_text' ),
153
			'date' => array( 'date', 'date_range' ),
154
			'boolean' => array( 'single_checkbox' ),
155
			'select' => array( 'select', 'radio', 'link' ),
156
			'multi' => array( 'select', 'multiselect', 'radio', 'checkbox', 'link' ),
157
		);
158
159
		/**
160
		 * @filter `gravityview/search/input_types` Change the types of search fields available to a field type
161
		 * @see GravityView_Widget_Search::get_search_input_labels() for the available input types
162
		 * @param array $input_types Associative array: key is field `name`, value is array of GravityView input types (note: use `input_text` for `text`)
163
		 */
164
		$input_types = apply_filters( 'gravityview/search/input_types', $input_types );
165
166
		return $input_types;
167
	}
168
169
	/**
170
	 * Get labels for different types of search bar inputs
171
	 *
172
	 * @since 1.17.5
173
	 *
174
	 * @return array [input type] => input type label
175
	 */
176
	public static function get_search_input_labels() {
177
		/**
178
		 * Input Type labels l10n
179
		 * @see admin-search-widget.js (getSelectInput)
180
		 * @var array
181
		 */
182
		$input_labels = array(
183
			'input_text' => esc_html__( 'Text', 'gravityview' ),
184
			'date' => esc_html__( 'Date', 'gravityview' ),
185
			'select' => esc_html__( 'Select', 'gravityview' ),
186
			'multiselect' => esc_html__( 'Select (multiple values)', 'gravityview' ),
187
			'radio' => esc_html__( 'Radio', 'gravityview' ),
188
			'checkbox' => esc_html__( 'Checkbox', 'gravityview' ),
189
			'single_checkbox' => esc_html__( 'Checkbox', 'gravityview' ),
190
			'link' => esc_html__( 'Links', 'gravityview' ),
191
			'date_range' => esc_html__( 'Date range', 'gravityview' ),
192
		);
193
194
		/**
195
		 * @filter `gravityview/search/input_types` Change the label of search field input types
196
		 * @param array $input_types Associative array: key is input type name, value is label
197
		 */
198
		$input_labels = apply_filters( 'gravityview/search/input_labels', $input_labels );
199
200
		return $input_labels;
201
	}
202
203
	public static function get_search_input_label( $input_type ) {
204
		$labels = self::get_search_input_labels();
205
206
		return rgar( $labels, $input_type, false );
207
	}
208
209
	/**
210
	 * Add script to Views edit screen (admin)
211
	 * @param  mixed $hook
212
	 */
213
	public function add_scripts_and_styles( $hook ) {
214
		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...
215
216
		// Don't process any scripts below here if it's not a GravityView page or the widgets screen
217
		if ( ! gravityview_is_admin_page( $hook ) && ( 'widgets.php' !== $pagenow ) ) {
218
			return;
219
		}
220
221
		$script_min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
222
		$script_source = empty( $script_min ) ? '/source' : '';
223
224
		wp_enqueue_script( 'gravityview_searchwidget_admin', plugins_url( 'assets/js'.$script_source.'/admin-search-widget'.$script_min.'.js', __FILE__ ), array( 'jquery', 'gravityview_views_scripts' ), GravityView_Plugin::version );
225
226
		wp_localize_script( 'gravityview_searchwidget_admin', 'gvSearchVar', array(
227
			'nonce' => wp_create_nonce( 'gravityview_ajaxsearchwidget' ),
228
			'label_nofields' => esc_html__( 'No search fields configured yet.', 'gravityview' ),
229
			'label_addfield' => esc_html__( 'Add Search Field', 'gravityview' ),
230
			'label_label' => esc_html__( 'Label', 'gravityview' ),
231
			'label_searchfield' => esc_html__( 'Search Field', 'gravityview' ),
232
			'label_inputtype' => esc_html__( 'Input Type', 'gravityview' ),
233
			'label_ajaxerror' => esc_html__( 'There was an error loading searchable fields. Save the View or refresh the page to fix this issue.', 'gravityview' ),
234
			'input_labels' => json_encode( self::get_search_input_labels() ),
235
			'input_types' => json_encode( self::get_input_types_by_field_type() ),
236
		) );
237
238
	}
239
240
	/**
241
	 * Add admin script to the no-conflict scripts whitelist
242
	 * @param array $allowed Scripts allowed in no-conflict mode
243
	 * @return array Scripts allowed in no-conflict mode, plus the search widget script
244
	 */
245
	public function register_no_conflict( $allowed ) {
246
		$allowed[] = 'gravityview_searchwidget_admin';
247
		return $allowed;
248
	}
249
250
	/**
251
	 * Ajax
252
	 * Returns the form fields ( only the searchable ones )
253
	 *
254
	 * @access public
255
	 * @return void
256
	 */
257
	public static function get_searchable_fields() {
258
259
		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...
260
			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...
261
		}
262
263
		$form = '';
264
265
		// Fetch the form for the current View
266
		if ( ! empty( $_POST['view_id'] ) ) {
267
268
			$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...
269
270
		} elseif ( ! empty( $_POST['formid'] ) ) {
271
272
			$form = (int) $_POST['formid'];
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
273
274
		} elseif ( ! empty( $_POST['template_id'] ) && class_exists( 'GravityView_Ajax' ) ) {
275
276
			$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...
277
278
		}
279
280
		// fetch form id assigned to the view
281
		$response = self::render_searchable_fields( $form );
282
283
		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...
284
	}
285
286
	/**
287
	 * Generates html for the available Search Fields dropdown
288
	 * @param  int $form_id
289
	 * @param  string $current (for future use)
290
	 * @return string
291
	 */
292
	public static function render_searchable_fields( $form_id = null, $current = '' ) {
293
294
		if ( is_null( $form_id ) ) {
295
			return '';
296
		}
297
298
		// start building output
299
300
		$output = '<select class="gv-search-fields">';
301
302
		$custom_fields = array(
303
			'search_all' => array(
304
				'text' => esc_html__( 'Search Everything', 'gravityview' ),
305
				'type' => 'text',
306
			),
307
			'entry_date' => array(
308
				'text' => esc_html__( 'Entry Date', 'gravityview' ),
309
				'type' => 'date',
310
			),
311
			'entry_id' => array(
312
				'text' => esc_html__( 'Entry ID', 'gravityview' ),
313
				'type' => 'text',
314
			),
315
			'created_by' => array(
316
				'text' => esc_html__( 'Entry Creator', 'gravityview' ),
317
				'type' => 'select',
318
			)
319
		);
320
321
		foreach( $custom_fields as $custom_field_key => $custom_field ) {
322
			$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...
323
		}
324
325
		// Get fields with sub-inputs and no parent
326
		$fields = gravityview_get_form_fields( $form_id, true, true );
327
328
		/**
329
		 * @filter `gravityview/search/searchable_fields` Modify the fields that are displayed as searchable in the Search Bar dropdown\n
330
		 * @since 1.17
331
		 * @see gravityview_get_form_fields() Used to fetch the fields
332
		 * @see GravityView_Widget_Search::get_search_input_types See this method to modify the type of input types allowed for a field
333
		 * @param array $fields Array of searchable fields, as fetched by gravityview_get_form_fields()
334
		 * @param  int $form_id
335
		 */
336
		$fields = apply_filters( 'gravityview/search/searchable_fields', $fields, $form_id );
337
338
		if ( ! empty( $fields ) ) {
339
340
			$blacklist_field_types = apply_filters( 'gravityview_blacklist_field_types', array( 'fileupload', 'post_image', 'post_id', 'section' ), null );
341
342
			foreach ( $fields as $id => $field ) {
343
344
				if ( in_array( $field['type'], $blacklist_field_types ) ) {
345
					continue;
346
				}
347
348
				$types = self::get_search_input_types( $id, $field['type'] );
349
350
				$output .= '<option value="'. $id .'" '. selected( $id, $current, false ).'data-inputtypes="'. esc_attr( $types ) .'">'. esc_html( $field['label'] ) .'</option>';
351
			}
352
		}
353
354
		$output .= '</select>';
355
356
		return $output;
357
358
	}
359
360
	/**
361
	 * Assign an input type according to the form field type
362
	 *
363
	 * @see admin-search-widget.js
364
	 *
365
	 * @param string|int|float $field_id Gravity Forms field ID
366
	 * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
367
	 *
368
	 * @return string GV field search input type ('multi', 'boolean', 'select', 'date', 'text')
369
	 */
370
	public static function get_search_input_types( $field_id = '', $field_type = null ) {
371
372
		// @todo - This needs to be improved - many fields have . including products and addresses
373
		if ( false !== strpos( (string) $field_id, '.' ) && in_array( $field_type, array( 'checkbox' ) ) || in_array( $field_id, array( 'is_fulfilled' ) ) ) {
374
			$input_type = 'boolean'; // on/off checkbox
375
		} elseif ( in_array( $field_type, array( 'checkbox', 'post_category', 'multiselect' ) ) ) {
376
			$input_type = 'multi'; //multiselect
377
		} elseif ( in_array( $field_type, array( 'select', 'radio' ) ) ) {
378
			$input_type = 'select';
379
		} elseif ( in_array( $field_type, array( 'date' ) ) || in_array( $field_id, array( 'payment_date' ) ) ) {
380
			$input_type = 'date';
381
		} elseif ( in_array( $field_type, array( 'number' ) ) || in_array( $field_id, array( 'payment_amount' ) ) ) {
382
			$input_type = 'number';
383
		} else {
384
			$input_type = 'text';
385
		}
386
387
		/**
388
		 * @filter `gravityview/extension/search/input_type` Modify the search form input type based on field type
389
		 * @since 1.2
390
		 * @since 1.19.2 Added $field_id parameter
391
		 * @param string $input_type Assign an input type according to the form field type. Defaults: `boolean`, `multi`, `select`, `date`, `text`
392
		 * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
393
		 * @param string|int|float $field_id ID of the field being processed
394
		 */
395
		$input_type = apply_filters( 'gravityview/extension/search/input_type', $input_type, $field_type, $field_id );
396
397
		return $input_type;
398
	}
399
400
	/**
401
	 * Display hidden fields to add support for sites using Default permalink structure
402
	 *
403
	 * @since 1.8
404
	 * @return array Search fields, modified if not using permalinks
405
	 */
406
	public function add_no_permalink_fields( $search_fields, $object, $widget_args = array() ) {
407
		/** @global WP_Rewrite $wp_rewrite */
408
		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...
409
410
		// Support default permalink structure
411
		if ( false === $wp_rewrite->using_permalinks() ) {
412
413
			// By default, use current post.
414
			$post_id = 0;
415
416
			// We're in the WordPress Widget context, and an overriding post ID has been set.
417
			if ( ! empty( $widget_args['post_id'] ) ) {
418
				$post_id = absint( $widget_args['post_id'] );
419
			}
420
			// We're in the WordPress Widget context, and the base View ID should be used
421
			else if ( ! empty( $widget_args['view_id'] ) ) {
422
				$post_id = absint( $widget_args['view_id'] );
423
			}
424
425
			$args = gravityview_get_permalink_query_args( $post_id );
426
427
			// Add hidden fields to the search form
428
			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...
429
				$search_fields[] = array(
430
					'name'  => $key,
431
					'input' => 'hidden',
432
					'value' => $value,
433
				);
434
			}
435
		}
436
437
		return $search_fields;
438
	}
439
440
441
	/** --- Frontend --- */
442
443
	/**
444
	 * Calculate the search criteria to filter entries
445
	 * @param  array $search_criteria
446
	 * @return array
447
	 */
448 2
	public function filter_entries( $search_criteria ) {
449
450 2
		if( 'post' === $this->search_method ) {
451
			$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...
452
		} else {
453 2
			$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...
454
		}
455
456 2
		do_action( 'gravityview_log_debug', sprintf( '%s[filter_entries] Requested $_%s: ', get_class( $this ), $this->search_method ), $get );
457
458 2
		if ( empty( $get ) || ! is_array( $get ) ) {
459 1
			return $search_criteria;
460
		}
461
462 2
		$get = stripslashes_deep( $get );
463
464 2
		$get = gv_map_deep( $get, 'rawurldecode' );
465
466
		// Make sure array key is set up
467 2
		$search_criteria['field_filters'] = rgar( $search_criteria, 'field_filters', array() );
468
469
		// add free search
470 2
		if ( ! empty( $get['gv_search'] ) ) {
471
472 1
			$search_all_value = trim( $get['gv_search'] );
473
474
			/**
475
			 * @filter `gravityview/search-all-split-words` Search for each word separately or the whole phrase?
476
			 * @since 1.20.2
477
			 * @param bool $split_words True: split a phrase into words; False: search whole word only [Default: true]
478
			 */
479 1
			$split_words = apply_filters( 'gravityview/search-all-split-words', true );
480
481 1
			if( $split_words ) {
482
483
				// Search for a piece
484 1
				$words = explode( ' ', $search_all_value );
485
486 1
				$words = array_filter( $words );
487
488
			} else {
489
490
				// Replace multiple spaces with one space
491 1
				$search_all_value = preg_replace( '/\s+/ism', ' ', $search_all_value );
492
493 1
				$words = array( $search_all_value );
494
			}
495
496 1
			foreach ( $words as $word ) {
497 1
				$search_criteria['field_filters'][] = array(
498 1
					'key' => null, // The field ID to search
499 1
					'value' => $word, // The value to search
500 1
					'operator' => 'contains', // What to search in. Options: `is` or `contains`
501
				);
502
			}
503
		}
504
505
		//start date & end date
506 2
		$curr_start = !empty( $get['gv_start'] ) ? $get['gv_start'] : '';
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
507 2
		$curr_end = !empty( $get['gv_start'] ) ? $get['gv_end'] : '';
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
508
509
		/**
510
		 * @filter `gravityview_date_created_adjust_timezone` Whether to adjust the timezone for entries. \n
511
		 * date_created is stored in UTC format. Convert search date into UTC (also used on templates/fields/date_created.php)
512
		 * @since 1.12
513
		 * @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
514
		 * @param[in] string $context Where the filter is being called from. `search` in this case.
515
		 */
516 2
		$adjust_tz = apply_filters( 'gravityview_date_created_adjust_timezone', true, 'search' );
517
518
		/**
519
		 * Don't set $search_criteria['start_date'] if start_date is empty as it may lead to bad query results (GFAPI::get_entries)
520
		 */
521 2
		if ( ! empty( $curr_start ) ) {
522 2
			$curr_start = date( 'Y-m-d H:i:s', strtotime( $curr_start ) );
523 2
			$search_criteria['start_date'] = $adjust_tz ? get_gmt_from_date( $curr_start ) : $curr_start;
524
		}
525
526 2
		if ( ! empty( $curr_end ) ) {
527
			// Fast-forward 24 hour on the end time
528 2
			$curr_end = date( 'Y-m-d H:i:s', strtotime( $curr_end ) + DAY_IN_SECONDS );
529 2
			$search_criteria['end_date'] = $adjust_tz ? get_gmt_from_date( $curr_end ) : $curr_end;
530
		}
531
532
		// search for a specific entry ID
533 2
		if ( ! empty( $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...
534 1
			$search_criteria['field_filters'][] = array(
535 1
				'key' => 'id',
536 1
				'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...
537 1
				'operator' => '=',
538
			);
539
		}
540
541
		// search for a specific Created_by ID
542 2
		if ( ! empty( $get[ 'gv_by' ] ) ) {
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...
543 1
			$search_criteria['field_filters'][] = array(
544 1
				'key' => 'created_by',
545 1
				'value' => absint( $get['gv_by'] ),
546 1
				'operator' => '=',
547
			);
548
		}
549
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
550
551
		// Get search mode passed in URL
552 2
		$mode = isset( $get['mode'] ) && in_array( $get['mode'], array( 'any', 'all' ) ) ?  $get['mode'] : 'any';
553
554
		// get the other search filters
555 2
		foreach ( $get as $key => $value ) {
556
557 2
			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...
558 2
				continue;
559
			}
560
561
			// could return simple filter or multiple filters
562
			$filter = $this->prepare_field_filter( $key, $value );
563
564
			if ( isset( $filter[0]['value'] ) ) {
565
				$search_criteria['field_filters'] = array_merge( $search_criteria['field_filters'], $filter );
566
567
				// if date range type, set search mode to ALL
568
				if ( ! empty( $filter[0]['operator'] ) && in_array( $filter[0]['operator'], array( '>=', '<=', '>', '<' ) ) ) {
569
					$mode = 'all';
570
				}
571
			} elseif( !empty( $filter ) ) {
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
572
				$search_criteria['field_filters'][] = $filter;
573
			}
574
		}
575
576
		/**
577
		 * @filter `gravityview/search/mode` Set the Search Mode (`all` or `any`)
578
		 * @since 1.5.1
579
		 * @param[out,in] string $mode Search mode (`any` vs `all`)
580
		 */
581 2
		$search_criteria['field_filters']['mode'] = apply_filters( 'gravityview/search/mode', $mode );
582
583 2
		do_action( 'gravityview_log_debug', sprintf( '%s[filter_entries] Returned Search Criteria: ', get_class( $this ) ), $search_criteria );
584
585 2
		unset( $get );
586
587 2
		return $search_criteria;
588
	}
589
590
	/**
591
	 * Prepare the field filters to GFAPI
592
	 *
593
	 * 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.
594
	 *
595
	 * Format searched values
596
	 * @param  string $key   $_GET/$_POST search key
597
	 * @param  string $value $_GET/$_POST search value
598
	 * @return array        1 or 2 deph levels
599
	 */
600
	public function prepare_field_filter( $key, $value ) {
601
602
		$gravityview_view = GravityView_View::getInstance();
603
604
		$field_id = str_replace( 'filter_', '', $key );
605
606
		// calculates field_id, removing 'filter_' and for '_' for advanced fields ( like name or checkbox )
607
		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...
608
			$field_id = str_replace( '_', '.', $field_id );
609
		}
610
611
		// get form field array
612
		$form = $gravityview_view->getForm();
613
		$form_field = gravityview_get_field( $form, $field_id );
614
615
		// default filter array
616
		$filter = array(
617
			'key' => $field_id,
618
			'value' => $value,
619
		);
620
621
		switch ( $form_field['type'] ) {
622
623
			case 'select':
624
			case 'radio':
625
				$filter['operator'] = 'is';
626
				break;
627
628
			case 'post_category':
629
630
				if ( ! is_array( $value ) ) {
631
					$value = array( $value );
632
				}
633
634
				// Reset filter variable
635
				$filter = array();
636
637
				foreach ( $value as $val ) {
638
					$cat = get_term( $val, 'category' );
639
					$filter[] = array(
640
						'key' => $field_id,
641
						'value' => esc_attr( $cat->name ) . ':' . $val,
642
						'operator' => 'is',
643
					);
644
				}
645
646
				break;
647
648
			case 'multiselect':
649
650
				if ( ! is_array( $value ) ) {
651
					break;
652
				}
653
654
				// Reset filter variable
655
				$filter = array();
656
657
				foreach ( $value as $val ) {
658
					$filter[] = array( 'key' => $field_id, 'value' => $val );
659
				}
660
661
				break;
662
663
			case 'checkbox':
664
				// convert checkbox on/off into the correct search filter
665
				if ( false !== strpos( $field_id, '.' ) && ! empty( $form_field['inputs'] ) && ! empty( $form_field['choices'] ) ) {
666
					foreach ( $form_field['inputs'] as $k => $input ) {
667
						if ( $input['id'] == $field_id ) {
668
							$filter['value'] = $form_field['choices'][ $k ]['value'];
669
							$filter['operator'] = 'is';
670
							break;
671
						}
672
					}
673
				} elseif ( is_array( $value ) ) {
674
675
					// Reset filter variable
676
					$filter = array();
677
678
					foreach ( $value as $val ) {
679
						$filter[] = array(
680
							'key'   => $field_id,
681
							'value' => $val,
682
							'operator' => 'is',
683
						);
684
					}
685
				}
686
687
				break;
688
689
			case 'name':
690
			case 'address':
691
692
				if ( false === strpos( $field_id, '.' ) ) {
693
694
					$words = explode( ' ', $value );
695
696
					$filters = array();
697
					foreach ( $words as $word ) {
698
						if ( ! empty( $word ) && strlen( $word ) > 1 ) {
699
							// Keep the same key for each filter
700
							$filter['value'] = $word;
701
							// Add a search for the value
702
							$filters[] = $filter;
703
						}
704
					}
705
706
					$filter = $filters;
707
				}
708
709
				break;
710
711
			case 'date':
712
713
				if ( is_array( $value ) ) {
714
715
					// Reset filter variable
716
					$filter = array();
717
718
					foreach ( $value as $k => $date ) {
719
						if ( empty( $date ) ) {
720
							continue;
721
						}
722
						$operator = 'start' === $k ? '>=' : '<=';
723
724
						/**
725
						 * @hack
726
						 * @since 1.16.3
727
						 * Safeguard until GF implements '<=' operator
728
						 */
729
						if( !GFFormsModel::is_valid_operator( $operator ) && $operator === '<=' ) {
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
730
							$operator = '<';
731
							$date = date( 'Y-m-d', strtotime( $date . ' +1 day' ) );
732
						}
733
734
						$filter[] = array(
735
							'key' => $field_id,
736
							'value' => self::get_formatted_date( $date, 'Y-m-d' ),
737
							'operator' => $operator,
738
						);
739
					}
740
				} else {
741
					$filter['value'] = self::get_formatted_date( $value, 'Y-m-d' );
742
				}
743
744
				break;
745
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
746
747
		} // switch field type
748
749
		return $filter;
750
	}
751
752
	/**
753
	 * Get the Field Format form GravityForms
754
	 *
755
	 * @param GF_Field_Date $field The field object
756
	 * @since 1.10
757
	 *
758
	 * @return string Format of the date in the database
759
	 */
760
	public static function get_date_field_format( GF_Field_Date $field ) {
761
		$format = 'm/d/Y';
762
		$datepicker = array(
763
			'mdy' => 'm/d/Y',
764
			'dmy' => 'd/m/Y',
765
			'dmy_dash' => 'd-m-Y',
766
			'dmy_dot' => 'm.d.Y',
767
			'ymd_slash' => 'Y/m/d',
768
			'ymd_dash' => 'Y-m-d',
769
			'ymd_dot' => 'Y.m.d',
770
		);
771
772
		if ( ! empty( $field->dateFormat ) && isset( $datepicker[ $field->dateFormat ] ) ){
773
			$format = $datepicker[ $field->dateFormat ];
774
		}
775
776
		return $format;
777
	}
778
779
	/**
780
	 * Format a date value
781
	 *
782
	 * @param string $value Date value input
783
	 * @param string $format Wanted formatted date
784
	 * @return string
785
	 */
786
	public static function get_formatted_date( $value = '', $format = 'Y-m-d' ) {
787
		$date = date_create( $value );
788
		if ( empty( $date ) ) {
789
			do_action( 'gravityview_log_debug', sprintf( '%s[get_formatted_date] Date format not valid: ', get_class( self::$instance ) ), $value );
790
			return '';
791
		}
792
		return $date->format( $format );
793
	}
794
795
796
	/**
797
	 * Include this extension templates path
798
	 * @param array $file_paths List of template paths ordered
799
	 */
800
	public function add_template_path( $file_paths ) {
801
802
		// Index 100 is the default GravityView template path.
803
		$file_paths[102] = self::$file . 'templates/';
804
805
		return $file_paths;
806
	}
807
808
	/**
809
	 * Check whether the configured search fields have a date field
810
	 *
811
	 * @since 1.17.5
812
	 *
813
	 * @param array $search_fields
814
	 *
815
	 * @return bool True: has a `date` or `date_range` field
816
	 */
817
	private function has_date_field( $search_fields ) {
818
819
		$has_date = false;
820
821
		foreach ( $search_fields as $k => $field ) {
822
			if ( in_array( $field['input'], array( 'date', 'date_range', 'entry_date' ) ) ) {
823
				$has_date = true;
824
				break;
825
			}
826
		}
827
828
		return $has_date;
829
	}
830
831
	/**
832
	 * Renders the Search Widget
833
	 * @param array $widget_args
834
	 * @param string $content
835
	 * @param string $context
836
	 *
837
	 * @return void
838
	 */
839
	public function render_frontend( $widget_args, $content = '', $context = '' ) {
840
		/** @var GravityView_View $gravityview_view */
841
		$gravityview_view = GravityView_View::getInstance();
842
843
		if ( empty( $gravityview_view ) ) {
844
			do_action( 'gravityview_log_debug', sprintf( '%s[render_frontend]: $gravityview_view not instantiated yet.', get_class( $this ) ) );
845
			return;
846
		}
847
848
		// get configured search fields
849
		$search_fields = ! empty( $widget_args['search_fields'] ) ? json_decode( $widget_args['search_fields'], true ) : '';
850
851
		if ( empty( $search_fields ) || ! is_array( $search_fields ) ) {
852
			do_action( 'gravityview_log_debug', sprintf( '%s[render_frontend] No search fields configured for widget:', get_class( $this ) ), $widget_args );
853
			return;
854
		}
855
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
856
857
		// prepare fields
858
		foreach ( $search_fields as $k => $field ) {
859
860
			$updated_field = $field;
861
862
			$updated_field = $this->get_search_filter_details( $updated_field );
863
864
			switch ( $field['field'] ) {
865
866
				case 'search_all':
867
					$updated_field['key'] = 'search_all';
868
					$updated_field['input'] = 'search_all';
869
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_search' );
870
					break;
871
872
				case 'entry_date':
873
					$updated_field['key'] = 'entry_date';
874
					$updated_field['input'] = 'entry_date';
875
					$updated_field['value'] = array(
876
						'start' => $this->rgget_or_rgpost( 'gv_start' ),
877
						'end' => $this->rgget_or_rgpost( 'gv_end' ),
878
					);
879
					break;
880
881
				case 'entry_id':
882
					$updated_field['key'] = 'entry_id';
883
					$updated_field['input'] = 'entry_id';
884
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_id' );
885
					break;
886
887
				case 'created_by':
888
					$updated_field['key'] = 'created_by';
889
					$updated_field['name'] = 'gv_by';
890
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_by' );
891
					$updated_field['choices'] = self::get_created_by_choices();
892
					break;
893
			}
894
895
			$search_fields[ $k ] = $updated_field;
896
		}
897
898
		do_action( 'gravityview_log_debug', sprintf( '%s[render_frontend] Calculated Search Fields: ', get_class( $this ) ), $search_fields );
899
900
		/**
901
		 * @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.
902
		 * @param array $search_fields Array of search filters with `key`, `label`, `value`, `type`, `choices` keys
903
		 * @param GravityView_Widget_Search $this Current widget object
904
		 * @param array $widget_args Args passed to this method. {@since 1.8}
905
		 * @var array
906
		 */
907
		$gravityview_view->search_fields = apply_filters( 'gravityview_widget_search_filters', $search_fields, $this, $widget_args );
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...
908
909
		$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...
910
911
		/** @since 1.14 */
912
		$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...
913
914
		$custom_class = ! empty( $widget_args['custom_class'] ) ? $widget_args['custom_class'] : '';
915
916
		$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...
917
918
		$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...
919
920
		if ( $this->has_date_field( $search_fields ) ) {
921
			// enqueue datepicker stuff only if needed!
922
			$this->enqueue_datepicker();
923
		}
924
925
		$this->maybe_enqueue_flexibility();
926
927
		$gravityview_view->render( 'widget', 'search', false );
928
	}
929
930
	/**
931
	 * Get the search class for a search form
932
	 *
933
	 * @since 1.5.4
934
	 *
935
	 * @return string Sanitized CSS class for the search form
936
	 */
937
	public static function get_search_class( $custom_class = '' ) {
938
		$gravityview_view = GravityView_View::getInstance();
939
940
		$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...
941
942
		if ( ! empty( $custom_class )  ) {
943
			$search_class .= ' '.$custom_class;
944
		}
945
946
		/**
947
		 * @filter `gravityview_search_class` Modify the CSS class for the search form
948
		 * @param string $search_class The CSS class for the search form
949
		 */
950
		$search_class = apply_filters( 'gravityview_search_class', $search_class );
951
952
		// Is there an active search being performed? Used by fe-views.js
953
		$search_class .= GravityView_frontend::getInstance()->isSearch() ? ' gv-is-search' : '';
954
955
		return gravityview_sanitize_html_class( $search_class );
956
	}
957
958
959
	/**
960
	 * Calculate the search form action
961
	 * @since 1.6
962
	 *
963
	 * @return string
964
	 */
965
	public static function get_search_form_action() {
966
		$gravityview_view = GravityView_View::getInstance();
967
968
		$post_id = $gravityview_view->getPostId() ? $gravityview_view->getPostId() : $gravityview_view->getViewId();
969
970
		$url = add_query_arg( array(), get_permalink( $post_id ) );
971
972
		return esc_url( $url );
973
	}
974
975
	/**
976
	 * Get the label for a search form field
977
	 * @param  array $field      Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
978
	 * @param  array $form_field Form field data, as fetched by `gravityview_get_field()`
979
	 * @return string             Label for the search form
980
	 */
981
	private static function get_field_label( $field, $form_field = array() ) {
982
983
		$label = rgget( 'label', $field );
984
985
		if( '' === $label ) {
986
987
			$label = isset( $form_field['label'] ) ? $form_field['label'] : '';
988
989
			switch( $field['field'] ) {
990
				case 'search_all':
991
					$label = __( 'Search Entries:', 'gravityview' );
992
					break;
993
				case 'entry_date':
994
					$label = __( 'Filter by date:', 'gravityview' );
995
					break;
996
				case 'entry_id':
997
					$label = __( 'Entry ID:', 'gravityview' );
998
					break;
999
				default:
1000
					// If this is a field input, not a field
1001
					if ( strpos( $field['field'], '.' ) > 0 && ! empty( $form_field['inputs'] ) ) {
1002
1003
						// Get the label for the field in question, which returns an array
1004
						$items = wp_list_filter( $form_field['inputs'], array( 'id' => $field['field'] ) );
1005
1006
						// Get the item with the `label` key
1007
						$values = wp_list_pluck( $items, 'label' );
1008
1009
						// There will only one item in the array, but this is easier
1010
						foreach ( $values as $value ) {
1011
							$label = $value;
1012
							break;
1013
						}
1014
					}
1015
			}
1016
		}
1017
1018
		/**
1019
		 * @filter `gravityview_search_field_label` Modify the label for a search field. Supports returning HTML
1020
		 * @since 1.17.3 Added $field parameter
1021
		 * @param[in,out] string $label Existing label text, sanitized.
1022
		 * @param[in] array $form_field Gravity Forms field array, as returned by `GFFormsModel::get_field()`
1023
		 * @param[in] array $field Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1024
		 */
1025
		$label = apply_filters( 'gravityview_search_field_label', esc_attr( $label ), $form_field, $field );
1026
1027
		return $label;
1028
	}
1029
1030
	/**
1031
	 * Prepare search fields to frontend render with other details (label, field type, searched values)
1032
	 *
1033
	 * @param array $field
1034
	 * @return array
1035
	 */
1036
	private function get_search_filter_details( $field ) {
1037
1038
		$gravityview_view = GravityView_View::getInstance();
1039
1040
		$form = $gravityview_view->getForm();
1041
1042
		// for advanced field ids (eg, first name / last name )
1043
		$name = 'filter_' . str_replace( '.', '_', $field['field'] );
1044
1045
		// get searched value from $_GET/$_POST (string or array)
1046
		$value = $this->rgget_or_rgpost( $name );
1047
1048
		// get form field details
1049
		$form_field = gravityview_get_field( $form, $field['field'] );
1050
1051
		$filter = array(
1052
			'key' => $field['field'],
1053
			'name' => $name,
1054
			'label' => self::get_field_label( $field, $form_field ),
1055
			'input' => $field['input'],
1056
			'value' => $value,
1057
			'type' => $form_field['type'],
1058
		);
1059
1060
		// collect choices
1061
		if ( 'post_category' === $form_field['type'] && ! empty( $form_field['displayAllCategories'] ) && empty( $form_field['choices'] ) ) {
1062
			$filter['choices'] = gravityview_get_terms_choices();
1063
		} elseif ( ! empty( $form_field['choices'] ) ) {
1064
			$filter['choices'] = $form_field['choices'];
1065
		}
1066
1067
		if ( 'date_range' === $field['input'] && empty( $value ) ) {
1068
			$filter['value'] = array( 'start' => '', 'end' => '' );
1069
		}
1070
1071
		return $filter;
1072
1073
	}
1074
1075
	/**
1076
	 * Calculate the search choices for the users
1077
	 *
1078
	 * @since 1.8
1079
	 *
1080
	 * @return array Array of user choices (value = ID, text = display name)
1081
	 */
1082
	private static function get_created_by_choices() {
1083
1084
		/**
1085
		 * filter gravityview/get_users/search_widget
1086
		 * @see \GVCommon::get_users
1087
		 */
1088
		$users = GVCommon::get_users( 'search_widget', array( 'fields' => array( 'ID', 'display_name' ) ) );
1089
1090
		$choices = array();
1091
		foreach ( $users as $user ) {
1092
			$choices[] = array(
1093
				'value' => $user->ID,
1094
				'text' => $user->display_name,
1095
			);
1096
		}
1097
1098
		return $choices;
1099
	}
1100
1101
1102
	/**
1103
	 * Output the Clear Search Results button
1104
	 * @since 1.5.4
1105
	 */
1106
	public static function the_clear_search_button() {
1107
		$gravityview_view = GravityView_View::getInstance();
1108
1109
		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...
1110
1111
			$url = strtok( add_query_arg( array() ), '?' );
1112
1113
			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...
1114
1115
		}
1116
	}
1117
1118
	/**
1119
	 * Based on the search method, fetch the value for a specific key
1120
	 *
1121
	 * @since 1.16.4
1122
	 *
1123
	 * @param string $name Name of the request key to fetch the value for
1124
	 *
1125
	 * @return mixed|string Value of request at $name key. Empty string if empty.
1126
	 */
1127
	private function rgget_or_rgpost( $name ) {
1128
		$value = 'get' === $this->search_method ? rgget( $name ) : rgpost( $name );
1129
1130
		$value = stripslashes_deep( $value );
1131
1132
		$value = gv_map_deep( $value, 'rawurldecode' );
1133
1134
		$value = gv_map_deep( $value, '_wp_specialchars' );
1135
1136
		return $value;
1137
	}
1138
1139
1140
	/**
1141
	 * Require the datepicker script for the frontend GV script
1142
	 * @param array $js_dependencies Array of existing required scripts for the fe-views.js script
1143
	 * @return array Array required scripts, with `jquery-ui-datepicker` added
1144
	 */
1145
	public function add_datepicker_js_dependency( $js_dependencies ) {
1146
1147
		$js_dependencies[] = 'jquery-ui-datepicker';
1148
1149
		return $js_dependencies;
1150
	}
1151
1152
	/**
1153
	 * Modify the array passed to wp_localize_script()
1154
	 *
1155
	 * @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...
1156
	 * @param array $view_data View data array with View settings
1157
	 *
1158
	 * @return array
1159
	 */
1160
	public function add_datepicker_localization( $localizations = array(), $view_data = array() ) {
1161
		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...
1162
1163
		/**
1164
		 * @filter `gravityview_datepicker_settings` Modify the datepicker settings
1165
		 * @see http://api.jqueryui.com/datepicker/ Learn what settings are available
1166
		 * @see http://www.renegadetechconsulting.com/tutorials/jquery-datepicker-and-wordpress-i18n Thanks for the helpful information on $wp_locale
1167
		 * @param array $js_localization The data padded to the Javascript file
1168
		 * @param array $view_data View data array with View settings
1169
		 */
1170
		$datepicker_settings = apply_filters( 'gravityview_datepicker_settings', array(
1171
			'yearRange' => '-5:+5',
1172
			'changeMonth' => true,
1173
			'changeYear' => true,
1174
			'closeText' => esc_attr_x( 'Close', 'Close calendar', 'gravityview' ),
1175
			'prevText' => esc_attr_x( 'Prev', 'Previous month in calendar', 'gravityview' ),
1176
			'nextText' => esc_attr_x( 'Next', 'Next month in calendar', 'gravityview' ),
1177
			'currentText' => esc_attr_x( 'Today', 'Today in calendar', 'gravityview' ),
1178
			'weekHeader' => esc_attr_x( 'Week', 'Week in calendar', 'gravityview' ),
1179
			'monthStatus'       => __( 'Show a different month', 'gravityview' ),
1180
			'monthNames'        => array_values( $wp_locale->month ),
1181
			'monthNamesShort'   => array_values( $wp_locale->month_abbrev ),
1182
			'dayNames'          => array_values( $wp_locale->weekday ),
1183
			'dayNamesShort'     => array_values( $wp_locale->weekday_abbrev ),
1184
			'dayNamesMin'       => array_values( $wp_locale->weekday_initial ),
1185
			// get the start of week from WP general setting
1186
			'firstDay'          => get_option( 'start_of_week' ),
1187
			// is Right to left language? default is false
1188
			'isRTL'             => is_rtl(),
1189
		), $view_data );
1190
1191
		$localizations['datepicker'] = $datepicker_settings;
1192
1193
		return $localizations;
1194
1195
	}
1196
1197
	/**
1198
	 * Register search widget scripts, including Flexibility
1199
	 *
1200
	 * @see https://github.com/10up/flexibility
1201
	 *
1202
	 * @since 1.17
1203
	 *
1204
	 * @return void
1205
	 */
1206
	public function register_scripts() {
1207
1208
		wp_register_script( 'gv-flexibility', plugins_url( 'assets/lib/flexibility/flexibility.js', GRAVITYVIEW_FILE ), array(), GravityView_Plugin::version, true );
1209
1210
	}
1211
1212
	/**
1213
	 * If the current visitor is running IE 8 or 9, enqueue Flexibility
1214
	 *
1215
	 * @since 1.17
1216
	 *
1217
	 * @return void
1218
	 */
1219
	private function maybe_enqueue_flexibility() {
1220
		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...
1221
			wp_enqueue_script( 'gv-flexibility' );
1222
		}
1223
	}
1224
1225
	/**
1226
	 * Enqueue the datepicker script
1227
	 *
1228
	 * It sets the $gravityview->datepicker_class parameter
1229
	 *
1230
	 * @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.
1231
	 * @return void
1232
	 */
1233
	public function enqueue_datepicker() {
1234
		$gravityview_view = GravityView_View::getInstance();
1235
1236
		wp_enqueue_script( 'jquery-ui-datepicker' );
1237
1238
		add_filter( 'gravityview_js_dependencies', array( $this, 'add_datepicker_js_dependency' ) );
1239
		add_filter( 'gravityview_js_localization', array( $this, 'add_datepicker_localization' ), 10, 2 );
1240
1241
		$scheme = is_ssl() ? 'https://' : 'http://';
1242
		wp_enqueue_style( 'jquery-ui-datepicker', $scheme.'ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/smoothness/jquery-ui.css' );
1243
1244
		/**
1245
		 * @filter `gravityview_search_datepicker_class`
1246
		 * 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.
1247
		 * @param string $css_class CSS class to use. Default: `gv-datepicker datepicker mdy` \n
1248
		 * Options are:
1249
		 * - `mdy` (mm/dd/yyyy)
1250
		 * - `dmy` (dd/mm/yyyy)
1251
		 * - `dmy_dash` (dd-mm-yyyy)
1252
		 * - `dmy_dot` (dd.mm.yyyy)
1253
		 * - `ymp_slash` (yyyy/mm/dd)
1254
		 * - `ymd_dash` (yyyy-mm-dd)
1255
		 * - `ymp_dot` (yyyy.mm.dd)
1256
		 */
1257
		$datepicker_class = apply_filters( 'gravityview_search_datepicker_class', 'gv-datepicker datepicker mdy' );
1258
1259
		$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...
1260
1261
	}
1262
1263
1264
} // end class
1265
1266
new GravityView_Widget_Search;