Completed
Push — develop ( 7b2f6e...f3f7c0 )
by Gennady
18:39
created

class-search-widget.php$0 ➔ sql()   A

Complexity

Conditions 3

Size

Total Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
dl 0
loc 43
ccs 11
cts 11
cp 1
crap 3
rs 9.232
c 0
b 0
f 0
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();
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 27
	public function __construct() {
31
32 27
		$this->widget_id = 'search_bar';
33 27
		$this->widget_description = esc_html__( 'Search form for searching entries.', 'gravityview' );
34
35 27
		self::$instance = &$this;
36
37 27
		self::$file = plugin_dir_path( __FILE__ );
38
39 27
		$default_values = array( 'header' => 0, 'footer' => 0 );
40
41
		$settings = array(
42 27
			'search_layout' => array(
43 27
				'type' => 'radio',
44
				'full_width' => true,
45 27
				'label' => esc_html__( 'Search Layout', 'gravityview' ),
46 27
				'value' => 'horizontal',
47
				'options' => array(
48 27
					'horizontal' => esc_html__( 'Horizontal', 'gravityview' ),
49 27
					'vertical' => esc_html__( 'Vertical', 'gravityview' ),
50
				),
51
			),
52
			'search_clear' => array(
53 27
				'type' => 'checkbox',
54 27
				'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 27
				'type' => 'radio',
65
				'full_width' => true,
66 27
				'label' => esc_html__( 'Search Mode', 'gravityview' ),
67 27
				'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 27
				'value' => 'any',
69 27
				'class' => 'hide-if-js',
70
				'options' => array(
71 27
					'any' => esc_html__( 'Match Any Fields', 'gravityview' ),
72 27
					'all' => esc_html__( 'Match All Fields', 'gravityview' ),
73
				),
74
			),
75
		);
76
77 27
		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 27
		parent::__construct( esc_html__( 'Search Bar', 'gravityview' ), null, $default_values, $settings );
97
98
		// calculate the search method (POST / GET)
99 27
		$this->set_search_method();
100 27
	}
101
102
	/**
103
	 * @return GravityView_Widget_Search
104
	 */
105 5
	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 5
		if ( empty( self::$instance ) ) {
107
			self::$instance = new GravityView_Widget_Search;
108
		}
109 5
		return self::$instance;
110
	}
111
112
	/**
113
	 * Sets the search method to GET (default) or POST
114
	 * @since 1.16.4
115
	 */
116 27
	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 27
		$method = apply_filters( 'gravityview/search/method', $this->search_method );
124
125 27
		$method = strtolower( $method );
126
127 27
		$this->search_method = in_array( $method, array( 'get', 'post' ) ) ? $method : 'get';
128 27
	}
129
130
	/**
131
	 * Returns the search method
132
	 * @since 1.16.4
133
	 * @return string
134
	 */
135 5
	public function get_search_method() {
136 5
		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
			// hybrids
162
			'created_by' => array( 'select', 'radio', 'checkbox', 'multiselect', 'link', 'input_text' ),
163
		);
164
165
		/**
166
		 * @filter `gravityview/search/input_types` Change the types of search fields available to a field type
167
		 * @see GravityView_Widget_Search::get_search_input_labels() for the available input types
168
		 * @param array $input_types Associative array: key is field `name`, value is array of GravityView input types (note: use `input_text` for `text`)
169
		 */
170
		$input_types = apply_filters( 'gravityview/search/input_types', $input_types );
171
172
		return $input_types;
173
	}
174
175
	/**
176
	 * Get labels for different types of search bar inputs
177
	 *
178
	 * @since 1.17.5
179
	 *
180
	 * @return array [input type] => input type label
181
	 */
182
	public static function get_search_input_labels() {
183
		/**
184
		 * Input Type labels l10n
185
		 * @see admin-search-widget.js (getSelectInput)
186
		 * @var array
187
		 */
188
		$input_labels = array(
189
			'input_text' => esc_html__( 'Text', 'gravityview' ),
190
			'date' => esc_html__( 'Date', 'gravityview' ),
191
			'select' => esc_html__( 'Select', 'gravityview' ),
192
			'multiselect' => esc_html__( 'Select (multiple values)', 'gravityview' ),
193
			'radio' => esc_html__( 'Radio', 'gravityview' ),
194
			'checkbox' => esc_html__( 'Checkbox', 'gravityview' ),
195
			'single_checkbox' => esc_html__( 'Checkbox', 'gravityview' ),
196
			'link' => esc_html__( 'Links', 'gravityview' ),
197
			'date_range' => esc_html__( 'Date range', 'gravityview' ),
198
		);
199
200
		/**
201
		 * @filter `gravityview/search/input_types` Change the label of search field input types
202
		 * @param array $input_types Associative array: key is input type name, value is label
203
		 */
204
		$input_labels = apply_filters( 'gravityview/search/input_labels', $input_labels );
205
206
		return $input_labels;
207
	}
208
209
	public static function get_search_input_label( $input_type ) {
210
		$labels = self::get_search_input_labels();
211
212
		return \GV\Utils::get( $labels, $input_type, false );
213
	}
214
215
	/**
216
	 * Add script to Views edit screen (admin)
217
	 * @param  mixed $hook
218
	 */
219
	public function add_scripts_and_styles( $hook ) {
220
		global $pagenow;
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...
221
222
		// Don't process any scripts below here if it's not a GravityView page or the widgets screen
223
		if ( ! gravityview()->request->is_admin( $hook, 'single' ) && ( 'widgets.php' !== $pagenow ) ) {
0 ignored issues
show
Unused Code introduced by
The call to Request::is_admin() has too many arguments starting with $hook.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
224
			return;
225
		}
226
227
		$script_min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
228
		$script_source = empty( $script_min ) ? '/source' : '';
229
230
		wp_enqueue_script( 'gravityview_searchwidget_admin', plugins_url( 'assets/js'.$script_source.'/admin-search-widget'.$script_min.'.js', __FILE__ ), array( 'jquery', 'gravityview_views_scripts' ), \GV\Plugin::$version );
231
232
		wp_localize_script( 'gravityview_searchwidget_admin', 'gvSearchVar', array(
233
			'nonce' => wp_create_nonce( 'gravityview_ajaxsearchwidget' ),
234
			'label_nofields' => esc_html__( 'No search fields configured yet.', 'gravityview' ),
235
			'label_addfield' => esc_html__( 'Add Search Field', 'gravityview' ),
236
			'label_label' => esc_html__( 'Label', 'gravityview' ),
237
			'label_searchfield' => esc_html__( 'Search Field', 'gravityview' ),
238
			'label_inputtype' => esc_html__( 'Input Type', 'gravityview' ),
239
			'label_ajaxerror' => esc_html__( 'There was an error loading searchable fields. Save the View or refresh the page to fix this issue.', 'gravityview' ),
240
			'input_labels' => json_encode( self::get_search_input_labels() ),
241
			'input_types' => json_encode( self::get_input_types_by_field_type() ),
242
		) );
243
244
	}
245
246
	/**
247
	 * Add admin script to the no-conflict scripts whitelist
248
	 * @param array $allowed Scripts allowed in no-conflict mode
249
	 * @return array Scripts allowed in no-conflict mode, plus the search widget script
250
	 */
251
	public function register_no_conflict( $allowed ) {
252
		$allowed[] = 'gravityview_searchwidget_admin';
253
		return $allowed;
254
	}
255
256
	/**
257
	 * Ajax
258
	 * Returns the form fields ( only the searchable ones )
259
	 *
260
	 * @access public
261
	 * @return void
262
	 */
263
	public static function get_searchable_fields() {
264
265
		if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'gravityview_ajaxsearchwidget' ) ) {
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...
266
			exit( '0' );
267
		}
268
269
		$form = '';
270
271
		// Fetch the form for the current View
272
		if ( ! empty( $_POST['view_id'] ) ) {
273
274
			$form = gravityview_get_form_id( $_POST['view_id'] );
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...
275
276
		} elseif ( ! empty( $_POST['formid'] ) ) {
277
278
			$form = (int) $_POST['formid'];
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
279
280
		} elseif ( ! empty( $_POST['template_id'] ) && class_exists( 'GravityView_Ajax' ) ) {
281
282
			$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...
283
284
		}
285
286
		// fetch form id assigned to the view
287
		$response = self::render_searchable_fields( $form );
288
289
		exit( $response );
290
	}
291
292
	/**
293
	 * Generates html for the available Search Fields dropdown
294
	 * @param  int $form_id
295
	 * @param  string $current (for future use)
296
	 * @return string
297
	 */
298
	public static function render_searchable_fields( $form_id = null, $current = '' ) {
299
300
		if ( is_null( $form_id ) ) {
301
			return '';
302
		}
303
304
		// start building output
305
306
		$output = '<select class="gv-search-fields">';
307
308
		$custom_fields = array(
309
			'search_all' => array(
310
				'text' => esc_html__( 'Search Everything', 'gravityview' ),
311
				'type' => 'text',
312
			),
313
			'entry_date' => array(
314
				'text' => esc_html__( 'Entry Date', 'gravityview' ),
315
				'type' => 'date',
316
			),
317
			'entry_id' => array(
318
				'text' => esc_html__( 'Entry ID', 'gravityview' ),
319
				'type' => 'text',
320
			),
321
			'created_by' => array(
322
				'text' => esc_html__( 'Entry Creator', 'gravityview' ),
323
				'type' => 'created_by',
324
			),
325
			'is_starred' => array(
326
				'text' => esc_html__( 'Is Starred', 'gravityview' ),
327
				'type' => 'boolean',
328
			),
329
		);
330
331
		if ( gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
332
			$custom_fields['is_approved'] = array(
333
				'text' => esc_html__( 'Is Approved', 'gravityview' ),
334
				'type' => 'boolean',
335
			);
336
		}
337
338
		foreach( $custom_fields as $custom_field_key => $custom_field ) {
339
			$output .= sprintf( '<option value="%s" %s data-inputtypes="%s" data-placeholder="%s">%s</option>', $custom_field_key, selected( $custom_field_key, $current, false ), $custom_field['type'], self::get_field_label( array('field' => $custom_field_key ) ), $custom_field['text'] );
0 ignored issues
show
introduced by
No space after opening parenthesis of array is bad style
Loading history...
340
		}
341
342
		// Get fields with sub-inputs and no parent
343
		$fields = gravityview_get_form_fields( $form_id, true, true );
344
345
		/**
346
		 * @filter `gravityview/search/searchable_fields` Modify the fields that are displayed as searchable in the Search Bar dropdown\n
347
		 * @since 1.17
348
		 * @see gravityview_get_form_fields() Used to fetch the fields
349
		 * @see GravityView_Widget_Search::get_search_input_types See this method to modify the type of input types allowed for a field
350
		 * @param array $fields Array of searchable fields, as fetched by gravityview_get_form_fields()
351
		 * @param  int $form_id
352
		 */
353
		$fields = apply_filters( 'gravityview/search/searchable_fields', $fields, $form_id );
354
355
		if ( ! empty( $fields ) ) {
356
357
			$blacklist_field_types = apply_filters( 'gravityview_blacklist_field_types', array( 'fileupload', 'post_image', 'post_id', 'section' ), null );
358
359
			foreach ( $fields as $id => $field ) {
360
361
				if ( in_array( $field['type'], $blacklist_field_types ) ) {
362
					continue;
363
				}
364
365
				$types = self::get_search_input_types( $id, $field['type'] );
366
367
				$output .= '<option value="'. $id .'" '. selected( $id, $current, false ).'data-inputtypes="'. esc_attr( $types ) .'">'. esc_html( $field['label'] ) .'</option>';
368
			}
369
		}
370
371
		$output .= '</select>';
372
373
		return $output;
374
375
	}
376
377
	/**
378
	 * Assign an input type according to the form field type
379
	 *
380
	 * @see admin-search-widget.js
381
	 *
382
	 * @param string|int|float $field_id Gravity Forms field ID
383
	 * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
384
	 *
385
	 * @return string GV field search input type ('multi', 'boolean', 'select', 'date', 'text')
386
	 */
387
	public static function get_search_input_types( $field_id = '', $field_type = null ) {
388
389
		// @todo - This needs to be improved - many fields have . including products and addresses
390
		if ( false !== strpos( (string) $field_id, '.' ) && in_array( $field_type, array( 'checkbox' ) ) || in_array( $field_id, array( 'is_fulfilled' ) ) ) {
391
			$input_type = 'boolean'; // on/off checkbox
392
		} elseif ( in_array( $field_type, array( 'checkbox', 'post_category', 'multiselect' ) ) ) {
393
			$input_type = 'multi'; //multiselect
394
		} elseif ( in_array( $field_type, array( 'select', 'radio' ) ) ) {
395
			$input_type = 'select';
396
		} elseif ( in_array( $field_type, array( 'date' ) ) || in_array( $field_id, array( 'payment_date' ) ) ) {
397
			$input_type = 'date';
398
		} elseif ( in_array( $field_type, array( 'number' ) ) || in_array( $field_id, array( 'payment_amount' ) ) ) {
399
			$input_type = 'number';
400
		} else {
401
			$input_type = 'text';
402
		}
403
404
		/**
405
		 * @filter `gravityview/extension/search/input_type` Modify the search form input type based on field type
406
		 * @since 1.2
407
		 * @since 1.19.2 Added $field_id parameter
408
		 * @param string $input_type Assign an input type according to the form field type. Defaults: `boolean`, `multi`, `select`, `date`, `text`
409
		 * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
410
		 * @param string|int|float $field_id ID of the field being processed
411
		 */
412
		$input_type = apply_filters( 'gravityview/extension/search/input_type', $input_type, $field_type, $field_id );
413
414
		return $input_type;
415
	}
416
417
	/**
418
	 * Display hidden fields to add support for sites using Default permalink structure
419
	 *
420 4
	 * @since 1.8
421
	 * @return array Search fields, modified if not using permalinks
422 4
	 */
423
	public function add_no_permalink_fields( $search_fields, $object, $widget_args = array() ) {
424
		/** @global WP_Rewrite $wp_rewrite */
425 4
		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...
426
427
		// Support default permalink structure
428 4
		if ( false === $wp_rewrite->using_permalinks() ) {
429
430
			// By default, use current post.
431 4
			$post_id = 0;
432
433
			// We're in the WordPress Widget context, and an overriding post ID has been set.
434
			if ( ! empty( $widget_args['post_id'] ) ) {
435 4
				$post_id = absint( $widget_args['post_id'] );
436
			}
437
			// We're in the WordPress Widget context, and the base View ID should be used
438
			else if ( ! empty( $widget_args['view_id'] ) ) {
439 4
				$post_id = absint( $widget_args['view_id'] );
440
			}
441
442 4
			$args = gravityview_get_permalink_query_args( $post_id );
443 4
444 4
			// Add hidden fields to the search form
445 4
			foreach ( $args as $key => $value ) {
0 ignored issues
show
Bug introduced by
The expression $args of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
446 4
				$search_fields[] = array(
447
					'name'  => $key,
448
					'input' => 'hidden',
449
					'value' => $value,
450
				);
451 4
			}
452
		}
453
454
		return $search_fields;
455
	}
456
457
	/**
458
	 * Get the fields that are searchable for a View
459
	 *
460
	 * @since 2.0
461
	 * @since 2.0.9 Added $with_full_field parameter
462
	 *
463
	 * @param \GV\View|null $view
464
	 * @param bool $with_full_field Return full field array, or just field ID? Default: false (just field ID)
465
	 *
466
	 * TODO: Move to \GV\View, perhaps? And return a Field_Collection
467
	 * TODO: Use in gravityview()->request->is_search() to calculate whether a valid search
468 22
	 *
469
	 * @return array If no View, returns empty array. Otherwise, returns array of fields configured in widgets and Search Bar for a View
470
	 */
471
	private function get_view_searchable_fields( $view, $with_full_field = false ) {
472
473 22
		/**
474
		 * Find all search widgets on the view and get the searchable fields settings.
475 22
		 */
476
		$searchable_fields = array();
477
478
		if ( ! $view ) {
479
			return $searchable_fields;
480
		}
481
482 22
		/**
483
		 * Include the sidebar Widgets.
484 22
		 */
485 22
		$widgets = (array) get_option( 'widget_gravityview_search', array() );
486
487
		foreach ( $widgets as $widget ) {
488 22
			if ( ! empty( $widget['view_id'] ) && $widget['view_id'] == $view->ID ) {
489
				if( $_fields = json_decode( $widget['search_fields'], true ) ) {
490
					foreach ( $_fields as $field ) {
491
						$searchable_fields [] = $with_full_field ? $field : $field['field'];
492
					}
493
				}
494 22
			}
495 22
		}
496 22
497 22
		foreach ( $view->widgets->by_id( $this->get_widget_id() )->all() as $widget ) {
498
			if( $_fields = json_decode( $widget->configuration->get( 'search_fields' ), true ) ) {
499
				foreach ( $_fields as $field ) {
500
					$searchable_fields [] = $with_full_field ? $field : $field['field'];
501
				}
502 22
			}
503
		}
504
505
		return $searchable_fields;
506
	}
507
508
	/** --- Frontend --- */
509
510
	/**
511
	 * Calculate the search criteria to filter entries
512
	 * @param array $search_criteria The search criteria
513
	 * @param int $form_id The form ID
514
	 * @param array $args Some args
515
	 *
516
	 * @param bool $force_search_criteria Whether to suppress GF_Query filter, internally used in self::gf_query_filter
517 51
	 *
518 51
	 * @return array
519
	 */
520
	public function filter_entries( $search_criteria, $form_id = null, $args = array(), $force_search_criteria = false ) {
521
		if ( ! $force_search_criteria && gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
522
			/**
523 32
			 * If GF_Query is available, we can construct custom conditions with nested
524 32
			 * booleans on the query, giving up the old ways of flat search_criteria field_filters.
525
			 */
526
			add_action( 'gravityview/view/query', array( $this, 'gf_query_filter' ), 10, 3 );
527 50
			return $search_criteria; // Return the original criteria, GF_Query modification kicks in later
528
		}
529
530 50
		if( 'post' === $this->search_method ) {
531
			$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...
532
		} else {
533 50
			$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...
534
		}
535 50
536
		$view = \GV\View::by_id( \GV\Utils::get( $args, 'id' ) );
537 50
538 28
		gravityview()->log->debug( 'Requested $_{method}: ', array( 'method' => $this->search_method, 'data' => $get ) );
539
540
		if ( empty( $get ) || ! is_array( $get ) ) {
541 23
			return $search_criteria;
542
		}
543 23
544
		$get = stripslashes_deep( $get );
545
546 23
		$get = gv_map_deep( $get, 'rawurldecode' );
547
548 23
		// Make sure array key is set up
549
		$search_criteria['field_filters'] = \GV\Utils::get( $search_criteria, 'field_filters', array() );
550
551 23
		$searchable_fields = $this->get_view_searchable_fields( $view );
552
553 1
		// add free search
554
		if ( isset( $get['gv_search'] ) && '' !== $get['gv_search'] && in_array( 'search_all', $searchable_fields ) ) {
555
556
			$search_all_value = trim( $get['gv_search'] );
557
558
			/**
559
			 * @filter `gravityview/search-all-split-words` Search for each word separately or the whole phrase?
560 1
			 * @since 1.20.2
561
			 * @param bool $split_words True: split a phrase into words; False: search whole word only [Default: true]
562 1
			 */
563
			$split_words = apply_filters( 'gravityview/search-all-split-words', true );
564
565 1
			if ( $split_words ) {
566
567 1
				// Search for a piece
568
				$words = explode( ' ', $search_all_value );
569
570
				$words = array_filter( $words );
571
572 1
			} else {
573
574 1
				// Replace multiple spaces with one space
575
				$search_all_value = preg_replace( '/\s+/ism', ' ', $search_all_value );
576
577 1
				$words = array( $search_all_value );
578 1
			}
579 1
580 1
			foreach ( $words as $word ) {
581 1
				$search_criteria['field_filters'][] = array(
582
					'key' => null, // The field ID to search
583
					'value' => $word, // The value to search
584
					'operator' => 'contains', // What to search in. Options: `is` or `contains`
585
				);
586
			}
587 23
		}
588
589
		// start date & end date
590
		if ( in_array( 'entry_date', $searchable_fields ) ) {
591 11
			/**
592 11
			 * Get and normalize the dates according to the input format.
593 11
			 */
594
			if ( $curr_start = ! empty( $get['gv_start'] ) ? $get['gv_start'] : '' ) {
595
				if( $curr_start_date = date_create_from_format( $this->get_datepicker_format( true ), $curr_start ) ) {
596
					$curr_start = $curr_start_date->format( 'Y-m-d' );
597 11
				}
598 11
			}
599 11
600
			if ( $curr_end = ! empty( $get['gv_start'] ) ? ( ! empty( $get['gv_end'] ) ? $get['gv_end'] : '' ) : '' ) {
601
				if( $curr_end_date = date_create_from_format( $this->get_datepicker_format( true ), $curr_end ) ) {
602
					$curr_end = $curr_end_date->format( 'Y-m-d' );
603 11
				}
604
			}
605
606
			if ( $view ) {
607 11
				/**
608 1
				 * Override start and end dates if View is limited to some already.
609 1
				 */
610
				if ( $start_date = $view->settings->get( 'start_date' ) ) {
611
					if ( $start_timestamp = strtotime( $curr_start ) ) {
612 11
						$curr_start = $start_timestamp < strtotime( $start_date ) ? $start_date : $curr_start;
613
					}
614
				}
615
				if ( $end_date = $view->settings->get( 'end_date' ) ) {
616
					if ( $end_timestamp = strtotime( $curr_end ) ) {
617
						$curr_end = $end_timestamp > strtotime( $end_date ) ? $end_date : $curr_end;
618
					}
619
				}
620
			}
621
622
			/**
623
			 * @filter `gravityview_date_created_adjust_timezone` Whether to adjust the timezone for entries. \n
624
			 * date_created is stored in UTC format. Convert search date into UTC (also used on templates/fields/date_created.php)
625
			 * @since 1.12
626 11
			 * @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
627
			 * @param[in] string $context Where the filter is being called from. `search` in this case.
628
			 */
629
			$adjust_tz = apply_filters( 'gravityview_date_created_adjust_timezone', true, 'search' );
630
631 11
			/**
632 11
			 * Don't set $search_criteria['start_date'] if start_date is empty as it may lead to bad query results (GFAPI::get_entries)
633 11
			 */
634
			if ( ! empty( $curr_start ) ) {
635
				$curr_start = date( 'Y-m-d H:i:s', strtotime( $curr_start ) );
636 11
				$search_criteria['start_date'] = $adjust_tz ? get_gmt_from_date( $curr_start ) : $curr_start;
637
			}
638 11
639 11
			if ( ! empty( $curr_end ) ) {
640 11
				// Fast-forward 24 hour on the end time
641 11
				$curr_end = date( 'Y-m-d H:i:s', strtotime( $curr_end ) + DAY_IN_SECONDS );
642
				$search_criteria['end_date'] = $adjust_tz ? get_gmt_from_date( $curr_end ) : $curr_end;
643
				if ( strpos( $search_criteria['end_date'], '00:00:00' ) ) { // See https://github.com/gravityview/GravityView/issues/1056
644
					$search_criteria['end_date'] = date( 'Y-m-d H:i:s', strtotime( $search_criteria['end_date'] ) - 1 );
645
				}
646
			}
647 23
		}
648 2
649 2
		// search for a specific entry ID
650 2
		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...
651 2
			$search_criteria['field_filters'][] = array(
652
				'key' => 'id',
653
				'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...
654
				'operator' => '=',
655
			);
656 23
		}
657 2
658 2
		// search for a specific Created_by ID
659 2
		if ( ! empty( $get[ 'gv_by' ] ) && in_array( 'created_by', $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...
660 2
			$search_criteria['field_filters'][] = array(
661
				'key' => 'created_by',
662
				'value' => absint( $get['gv_by'] ),
663
				'operator' => '=',
664
			);
665
		}
666 23
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
667
668
		// Get search mode passed in URL
669 23
		$mode = isset( $get['mode'] ) && in_array( $get['mode'], array( 'any', 'all' ) ) ?  $get['mode'] : 'any';
670
671 23
		// get the other search filters
672 13
		foreach ( $get as $key => $value ) {
673
674
			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...
675 13
				continue;
676
			}
677
678 13
			$filter_key = $this->convert_request_key_to_filter_key( $key );
679 1
680
			// could return simple filter or multiple filters
681
			if ( ! in_array( 'search_all', $searchable_fields ) && ! in_array( $filter_key , $searchable_fields ) ) {
682 12
				continue;
683
			}
684 12
685
			$filter = $this->prepare_field_filter( $filter_key, $value, $view );
686
687
			if ( isset( $filter[0]['value'] ) ) {
688
				$search_criteria['field_filters'] = array_merge( $search_criteria['field_filters'], $filter );
689
690
				// if date range type, set search mode to ALL
691 12
				if ( ! empty( $filter[0]['operator'] ) && in_array( $filter[0]['operator'], array( '>=', '<=', '>', '<' ) ) ) {
692 12
					$mode = 'all';
693
				}
694
			} elseif( !empty( $filter ) ) {
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
695
				$search_criteria['field_filters'][] = $filter;
696
			}
697
		}
698
699
		/**
700
		 * @filter `gravityview/search/mode` Set the Search Mode (`all` or `any`)
701 23
		 * @since 1.5.1
702
		 * @param[out,in] string $mode Search mode (`any` vs `all`)
703 23
		 */
704
		$search_criteria['field_filters']['mode'] = apply_filters( 'gravityview/search/mode', $mode );
705 23
706
		gravityview()->log->debug( 'Returned Search Criteria: ', array( 'data' => $search_criteria ) );
707 23
708
		unset( $get );
709
710
		return $search_criteria;
711
	}
712
713
	/**
714
	 * Filters the \GF_Query with advanced logic.
715
	 *
716
	 * Dropin for the legacy flat filters when \GF_Query is available.
717
	 *
718
	 * @param \GF_Query $query The current query object reference
719 31
	 * @param \GV\View $this The current view object
0 ignored issues
show
Bug introduced by
There is no parameter named $this. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
720
	 * @param \GV\Request $request The request object
721
	 */
722
	public function gf_query_filter( &$query, $view, $request ) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
723
		/**
724 31
		 * This is a shortcut to get all the needed search criteria.
725
		 * We feed these into an new GF_Query and tack them onto the current object.
726 31
		 */
727 27
		$search_criteria = $this->filter_entries( array(), null, array( 'id' => $view->ID ), true /** force search_criteria */ );
728
729
		if ( empty( $search_criteria['field_filters'] ) ) {
730 4
			return;
731
		}
732 4
733 4
		$widgets = $view->widgets->by_id( $this->widget_id );
734 4
		if ( $widgets->count() ) {
735
			$widget = $widgets->all()[0];
736
			foreach ( $search_fields = json_decode( $widget->configuration->get( 'search_fields' ), true ) as $search_field ) {
737
				if ( 'created_by' === $search_field['field'] && 'input_text' === $search_field['input'] ) {
738 4
					$created_by_text_mode = true;
739 2
				}
740 2
			}
741
		}
742 2
743 2
		$extra_conditions = array();
744 2
745
		foreach ( $search_criteria['field_filters'] as &$filter ) {
746
			if ( ! is_array( $filter ) ) {
747
				continue;
748
			}
749
750
			// Construct a manual query for unapproved statuses
751 2
			if ( 'is_approved' === $filter['key'] && in_array( \GravityView_Entry_Approval_Status::UNAPPROVED, $filter['value'] ) ) {
752
				$_tmp_query       = new GF_Query( $view->form->ID, array(
753
					'field_filters' => array(
754 2
						array(
755
							'operator' => 'in',
756 2
							'key'      => 'is_approved',
757
							'value'    => $filter['value'],
758 2
						),
759
						array(
760 2
							'operator' => 'is',
761
							'key'      => 'is_approved',
762
							'value'    => '',
763
						),
764 4
						'mode' => 'any'
0 ignored issues
show
introduced by
Key specified for array entry; first entry has no key
Loading history...
765
					),
766
				) );
767 4
				$_tmp_query_parts = $_tmp_query->_introspect();
768 2
769
				$extra_conditions[] = $_tmp_query_parts['where'];
770
771
				$filter = false;
772
				continue;
773
			}
774
775
			// Construct manual query for text mode creator search
776 4
			if ( 'created_by' === $filter['key'] && ! empty( $created_by_text_mode ) ) {
777
				$extra_conditions[] = new class( $filter, $view ) extends \GF_Query_Condition {
778
					public function __construct( $filter, $view ) {
779 4
						$this->value = $filter['value'];
780
						$this->view = $view;
781
					}
782
783
					public function sql( $query ) {
784
						$user_meta_fields = array(
785
							'nickname', 'first_name', 'last_name', 
786 4
						);
787 4
788
						global $wpdb;
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...
789
790
						/**
791
						 * @filter `gravityview/widgets/search/created_by/user_meta_fields` Filter the user meta fields to search by.
792 4
						 * @param[in,out] array The user meta fields.
793
						 * @param \GV\View $view The view.
794
						 */
795
						$user_meta_fields = apply_filters( 'gravityview/widgets/search/created_by/user_meta_fields', $user_meta_fields, $this->view );
796
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
797 4
798 4
						$user_fields = array(
799 4
							'user_nicename', 'user_login', 'display_name', 'user_email', 
800
						);
801
						/**
802
						 * @filter `gravityview/widgets/search/created_by/user_fields` Filter the user meta fields to search by.
803
						 * @param[in,out] array The user fields.
804
						 * @param \GV\View $view The view.
805
						 */
806
						$user_fields = apply_filters( 'gravityview/widgets/search/created_by/user_fields', $user_fields, $this->view );
807
808
						$column = sprintf( '`%s`.`created_by`', $query->_alias( null ) );
809
810
						$conditions = array();
811
812
						foreach ( $user_fields as $user_field ) {
813
							$conditions[] = $wpdb->prepare( "`u`.`$user_field` LIKE %s", '%' . $wpdb->esc_like( $this->value ) .  '%' );
814
						}
815 13
816
						foreach ( $user_meta_fields as $meta_field ) {
817 13
							$conditions[] = $wpdb->prepare( "(`um`.`meta_key` = %s AND `um`.`meta_value` LIKE %s)", $meta_field, '%' . $wpdb->esc_like( $this->value ) .  '%' );
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal (`um`.`meta_key` = %s AN...`.`meta_value` LIKE %s) does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
818
						}
819
820 13
						$conditions = '(' . implode( ' OR ', $conditions ) . ')';
821 11
822
						$alias = $query->_alias( null );
823
824 13
						return "(EXISTS (SELECT 1 FROM $wpdb->users u LEFT JOIN $wpdb->usermeta um ON u.ID = um.user_id WHERE (u.ID = `$alias`.`created_by` AND $conditions)))";
0 ignored issues
show
introduced by
Usage of users/usermeta tables is highly discouraged in VIP context, For storing user additional user metadata, you should look at User Attributes.
Loading history...
825
					}
826
				};
827
828
				$filter = false;
829
				continue;
830
			}
831
832
			// By default, we want searches to be wildcard for each field.
833
			$filter['operator'] = empty( $filter['operator'] ) ? 'contains' : $filter['operator'];
834
835
			// For multichoice, let's have an in (OR) search.
836
			if ( is_array( $filter['value'] ) ) {
837
				$filter['operator'] = 'in'; // @todo what about in contains (OR LIKE chains)?
838
			}
839
840 12
			/**
841
			 * @filter `gravityview_search_operator` Modify the search operator for the field (contains, is, isnot, etc)
842
			 * @param string $operator Existing search operator
843 12
			 * @param array $filter array with `key`, `value`, `operator`, `type` keys
844
			 */
845
			$filter['operator'] = apply_filters( 'gravityview_search_operator', $filter['operator'], $filter );
846
		}
847 12
848 12
		$search_criteria['field_filters'] = array_filter( $search_criteria['field_filters'] );
849
850
		/**
851 12
		 * Parse the filter criteria to generate the needed
852
		 * WHERE clauses. This is a trick to not write our own generation
853 12
		 * code by reusing what's inside GF_Query already.
854 12
		 */
855 1
		$_tmp_query       = new GF_Query( $view->form->ID, $search_criteria );
856 1
		$_tmp_query_parts = $_tmp_query->_introspect();
857
858 11
		/**
859
		 * Grab the current clauses. We'll be combining them shortly.
860
		 */
861
		$query_parts      = $query->_introspect();
862
863
		/**
864
		 * Combine the parts as a new WHERE clause.
865
		 */
866
		$where = call_user_func_array( '\GF_Query_Condition::_and', array_merge( array( $query_parts['where'], $_tmp_query_parts['where'] ), $extra_conditions ) );
867
		$query->where( $where );
868
	}
869
870
	/**
871
	 * Convert $_GET/$_POST key to the field/meta ID
872
	 *
873
	 * Examples:
874
	 * - `filter_is_starred` => `is_starred`
875
	 * - `filter_1_2` => `1.2`
876
	 * - `filter_5` => `5`
877
	 *
878 11
	 * @since 2.0
879
	 *
880
	 * @param string $key $_GET/_$_POST search key
881
	 *
882
	 * @return string
883
	 */
884
	private function convert_request_key_to_filter_key( $key ) {
885
886
		$field_id = str_replace( 'filter_', '', $key );
887
888
		// calculates field_id, removing 'filter_' and for '_' for advanced fields ( like name or checkbox )
889
		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...
890
			$field_id = str_replace( '_', '.', $field_id );
891
		}
892
893 11
		return $field_id;
894
	}
895
896
	/**
897
	 * Prepare the field filters to GFAPI
898
	 *
899
	 * 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.
900
	 *
901
	 * Format searched values
902
	 *
903
	 * @param  string $filter_key ID of the field, or entry meta key
904
	 * @param  string $value $_GET/$_POST search value
905
	 * @param  \GV\View $view The view we're looking at
906
	 *
907
	 * @return array        1 or 2 deph levels
908
	 */
909
	public function prepare_field_filter( $filter_key, $value, $view ) {
910
911
		// get form field array
912
		$form_field = is_numeric( $filter_key ) ? \GV\GF_Field::by_id( $view->form, $filter_key ) : \GV\Internal_Field::by_id( $filter_key );
913
914
		// default filter array
915
		$filter = array(
916
			'key'   => $filter_key,
917
			'value' => $value,
918
		);
919 11
920 11
		switch ( $form_field->type ) {
921
922
			case 'select':
923
			case 'radio':
924
				$filter['operator'] = 'is';
925
				break;
926
927
			case 'post_category':
928
929
				if ( ! is_array( $value ) ) {
930
					$value = array( $value );
931
				}
932
933
				// Reset filter variable
934
				$filter = array();
935
936
				foreach ( $value as $val ) {
937
					$cat = get_term( $val, 'category' );
938
					$filter[] = array(
939
						'key'      => $filter_key,
940
						'value'    => esc_attr( $cat->name ) . ':' . $val,
941
						'operator' => 'is',
942
					);
943
				}
944
945
				break;
946
947
			case 'multiselect':
948
949
				if ( ! is_array( $value ) ) {
950
					break;
951
				}
952
953
				// Reset filter variable
954
				$filter = array();
955
956
				foreach ( $value as $val ) {
957
					$filter[] = array( 'key' => $filter_key, 'value' => $val );
958
				}
959
960
				break;
961
962
			case 'checkbox':
963
				// convert checkbox on/off into the correct search filter
964
				if ( false !== strpos( $filter_key, '.' ) && ! empty( $form_field->inputs ) && ! empty( $form_field->choices ) ) {
965 11
					foreach ( $form_field->inputs as $k => $input ) {
966
						if ( $input['id'] == $filter_key ) {
967 8
							$filter['value'] = $form_field->choices[ $k ]['value'];
968
							$filter['operator'] = 'is';
969 8
							break;
970
						}
971
					}
972
				} elseif ( is_array( $value ) ) {
973
974
					// Reset filter variable
975
					$filter = array();
976
977
					foreach ( $value as $val ) {
978
						$filter[] = array(
979
							'key'      => $filter_key,
980
							'value'    => $val,
981
							'operator' => 'is',
982
						);
983
					}
984
				}
985
986
				break;
987
988
			case 'name':
989
			case 'address':
990
991
				if ( false === strpos( $filter_key, '.' ) ) {
992
993
					$words = explode( ' ', $value );
994
995
					$filters = array();
996
					foreach ( $words as $word ) {
997 8
						if ( ! empty( $word ) && strlen( $word ) > 1 ) {
998 8
							// Keep the same key for each filter
999
							$filter['value'] = $word;
1000
							// Add a search for the value
1001 8
							$filters[] = $filter;
1002
						}
1003
					}
1004
1005
					$filter = $filters;
1006 12
				}
1007
1008
				// State/Province should be exact matches
1009
				if ( 'address' === $form_field->field->type ) {
1010
1011
					$searchable_fields = $this->get_view_searchable_fields( $view, true );
1012
1013
					foreach ( $searchable_fields as $searchable_field ) {
1014
1015
						if( $form_field->ID !== $searchable_field['field'] ) {
1016
							continue;
1017
						}
1018
1019
						// Only exact-match dropdowns, not text search
1020
						if( in_array( $searchable_field['input'], array( 'text', 'search' ), true ) ) {
1021
							continue;
1022
						}
1023
1024
						$input_id = gravityview_get_input_id_from_id( $form_field->ID );
1025
1026
						if ( 4 === $input_id ) {
1027
							$filter['operator'] = 'is';
1028
						};
1029
					}
1030
				}
1031
1032
				break;
1033
1034
			case 'date':
1035
1036
				$date_format = $this->get_datepicker_format( true );
1037
1038
				if ( is_array( $value ) ) {
1039
1040
					// Reset filter variable
1041
					$filter = array();
1042
1043
					foreach ( $value as $k => $date ) {
1044
						if ( empty( $date ) ) {
1045
							continue;
1046
						}
1047 8
						$operator = 'start' === $k ? '>=' : '<=';
1048
1049 8
						/**
1050
						 * @hack
1051 8
						 * @since 1.16.3
1052
						 * Safeguard until GF implements '<=' operator
1053
						 */
1054
						if( !GFFormsModel::is_valid_operator( $operator ) && $operator === '<=' ) {
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
1055 8
							$operator = '<';
1056
							$date = date( 'Y-m-d', strtotime( self::get_formatted_date( $date, 'Y-m-d', $date_format ) . ' +1 day' ) );
1057
						}
1058
1059
						$filter[] = array(
1060
							'key'      => $filter_key,
1061
							'value'    => self::get_formatted_date( $date, 'Y-m-d', $date_format ),
1062
							'operator' => $operator,
1063 1
						);
1064
					}
1065
				} else {
1066 1
					$date = $value;
1067
					$filter['value'] = self::get_formatted_date( $date, 'Y-m-d', $date_format );
1068 1
				}
1069
1070
				break;
1071
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
1072
1073
		} // switch field type
1074
1075
		return $filter;
1076
	}
1077
1078
	/**
1079
	 * Get the Field Format form GravityForms
1080 4
	 *
1081
	 * @param GF_Field_Date $field The field object
1082 4
	 * @since 1.10
1083
	 *
1084 4
	 * @return string Format of the date in the database
1085 4
	 */
1086
	public static function get_date_field_format( GF_Field_Date $field ) {
1087 4
		$format = 'm/d/Y';
1088
		$datepicker = array(
1089
			'mdy' => 'm/d/Y',
1090
			'dmy' => 'd/m/Y',
1091 4
			'dmy_dash' => 'd-m-Y',
1092
			'dmy_dot' => 'd.m.Y',
1093
			'ymd_slash' => 'Y/m/d',
1094
			'ymd_dash' => 'Y-m-d',
1095
			'ymd_dot' => 'Y.m.d',
1096
		);
1097
1098
		if ( ! empty( $field->dateFormat ) && isset( $datepicker[ $field->dateFormat ] ) ){
1099
			$format = $datepicker[ $field->dateFormat ];
1100
		}
1101
1102 4
		return $format;
1103
	}
1104 4
1105
	/**
1106 4
	 * Format a date value
1107
	 *
1108
	 * @param string $value Date value input
1109
	 * @param string $format Wanted formatted date
1110
	 *
1111
	 * @since 2.1.2
1112 4
	 * @param string $value_format The value format. Default: Y-m-d
1113
	 *
1114 4
	 * @return string
1115
	 */
1116
	public static function get_formatted_date( $value = '', $format = 'Y-m-d', $value_format = 'Y-m-d' ) {
1117
1118
		$date = date_create_from_format( $value_format, $value );
1119
1120
		if ( empty( $date ) ) {
1121 4
			gravityview()->log->debug( 'Date format not valid: {value}', array( 'value' => $value ) );
1122
			return '';
1123 4
		}
1124
		return $date->format( $format );
1125 4
	}
1126
1127 4
1128
	/**
1129 4
	 * Include this extension templates path
1130 4
	 * @param array $file_paths List of template paths ordered
1131 4
	 */
1132 4
	public function add_template_path( $file_paths ) {
1133 4
1134
		// Index 100 is the default GravityView template path.
1135
		$file_paths[102] = self::$file . 'templates/';
1136
1137
		return $file_paths;
1138
	}
1139
1140
	/**
1141
	 * Check whether the configured search fields have a date field
1142
	 *
1143
	 * @since 1.17.5
1144
	 *
1145
	 * @param array $search_fields
1146
	 *
1147
	 * @return bool True: has a `date` or `date_range` field
1148
	 */
1149
	private function has_date_field( $search_fields ) {
1150
1151
		$has_date = false;
1152
1153
		foreach ( $search_fields as $k => $field ) {
1154
			if ( in_array( $field['input'], array( 'date', 'date_range', 'entry_date' ) ) ) {
1155
				$has_date = true;
1156
				break;
1157
			}
1158
		}
1159
1160
		return $has_date;
1161
	}
1162
1163
	/**
1164
	 * Renders the Search Widget
1165 4
	 * @param array $widget_args
1166
	 * @param string $content
1167
	 * @param string $context
1168 4
	 *
1169
	 * @return void
1170
	 */
1171
	public function render_frontend( $widget_args, $content = '', $context = '' ) {
1172
		/** @var GravityView_View $gravityview_view */
1173
		$gravityview_view = GravityView_View::getInstance();
1174
1175
		if ( empty( $gravityview_view ) ) {
1176
			gravityview()->log->debug( '$gravityview_view not instantiated yet.' );
1177
			return;
1178 4
		}
1179
1180 4
		// get configured search fields
1181
		$search_fields = ! empty( $widget_args['search_fields'] ) ? json_decode( $widget_args['search_fields'], true ) : '';
1182
1183 4
		if ( empty( $search_fields ) || ! is_array( $search_fields ) ) {
1184
			gravityview()->log->debug( 'No search fields configured for widget:', array( 'data' => $widget_args ) );
1185 4
			return;
1186
		}
1187 4
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
1188
1189 4
		// prepare fields
1190
		foreach ( $search_fields as $k => $field ) {
1191 4
1192
			$updated_field = $field;
1193
1194
			$updated_field = $this->get_search_filter_details( $updated_field );
1195
1196 4
			switch ( $field['field'] ) {
1197
1198 4
				case 'search_all':
1199 4
					$updated_field['key'] = 'search_all';
1200
					$updated_field['input'] = 'search_all';
1201
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_search' );
1202
					break;
1203
1204
				case 'entry_date':
1205
					$updated_field['key'] = 'entry_date';
1206
					$updated_field['input'] = 'entry_date';
1207
					$updated_field['value'] = array(
1208 4
						'start' => $this->rgget_or_rgpost( 'gv_start' ),
1209 4
						'end' => $this->rgget_or_rgpost( 'gv_end' ),
1210
					);
1211 4
					break;
1212
1213 4
				case 'entry_id':
1214
					$updated_field['key'] = 'entry_id';
1215
					$updated_field['input'] = 'entry_id';
1216
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_id' );
1217
					break;
1218
1219
				case 'created_by':
1220
					$updated_field['key'] = 'created_by';
1221 4
					$updated_field['name'] = 'gv_by';
1222
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_by' );
1223
					$updated_field['choices'] = self::get_created_by_choices();
1224 4
					break;
1225
				
1226 4
				case 'is_approved':
1227
					$updated_field['key'] = 'is_approved';
1228
					$updated_field['input'] = 'checkbox';
1229
					$updated_field['value'] = $this->rgget_or_rgpost( 'filter_is_approved' );
1230
					$updated_field['choices'] = self::get_is_approved_choices();
1231
					break;
1232
			}
1233
1234
			$search_fields[ $k ] = $updated_field;
1235
		}
1236 4
1237 4
		gravityview()->log->debug( 'Calculated Search Fields: ', array( 'data' => $search_fields ) );
1238
1239 4
		/**
1240
		 * @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.
1241 4
		 * @param array $search_fields Array of search filters with `key`, `label`, `value`, `type`, `choices` keys
1242
		 * @param GravityView_Widget_Search $this Current widget object
1243 4
		 * @param array $widget_args Args passed to this method. {@since 1.8}
1244
		 * @param \GV\Template_Context $context {@since 2.0}
1245
		 * @var array
1246
		 */
1247
		$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...
1248
1249
		$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...
1250
1251
		/** @since 1.14 */
1252 4
		$gravityview_view->search_mode = ! empty( $widget_args['search_mode'] ) ? $widget_args['search_mode'] : 'any';
0 ignored issues
show
Bug introduced by
The property search_mode does not seem to exist in GravityView_View.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1253
1254 4
		$custom_class = ! empty( $widget_args['custom_class'] ) ? $widget_args['custom_class'] : '';
1255
1256 4
		$gravityview_view->search_class = self::get_search_class( $custom_class );
0 ignored issues
show
Bug introduced by
The property search_class does not seem to exist in GravityView_View.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1257
1258 4
		$gravityview_view->search_clear = ! empty( $widget_args['search_clear'] ) ? $widget_args['search_clear'] : false;
0 ignored issues
show
Bug introduced by
The property search_clear does not seem to exist in GravityView_View.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1259
1260 4
		if ( $this->has_date_field( $search_fields ) ) {
1261 4
			// enqueue datepicker stuff only if needed!
1262 4
			$this->enqueue_datepicker();
1263 4
		}
1264
1265
		$this->maybe_enqueue_flexibility();
1266
1267
		$gravityview_view->render( 'widget', 'search', false );
1268
	}
1269
1270
	/**
1271
	 * Get the search class for a search form
1272
	 *
1273
	 * @since 1.5.4
1274
	 *
1275
	 * @return string Sanitized CSS class for the search form
1276
	 */
1277
	public static function get_search_class( $custom_class = '' ) {
1278
		$gravityview_view = GravityView_View::getInstance();
1279
1280
		$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...
1281
1282
		if ( ! empty( $custom_class )  ) {
1283
			$search_class .= ' '.$custom_class;
1284
		}
1285
1286
		/**
1287
		 * @filter `gravityview_search_class` Modify the CSS class for the search form
1288
		 * @param string $search_class The CSS class for the search form
1289
		 */
1290
		$search_class = apply_filters( 'gravityview_search_class', $search_class );
1291
1292
		// Is there an active search being performed? Used by fe-views.js
1293
		$search_class .= GravityView_frontend::getInstance()->isSearch() ? ' gv-is-search' : '';
1294
1295
		return gravityview_sanitize_html_class( $search_class );
1296 4
	}
1297
1298 4
1299
	/**
1300
	 * Calculate the search form action
1301
	 * @since 1.6
1302
	 *
1303
	 * @return string
1304
	 */
1305
	public static function get_search_form_action() {
1306
		$gravityview_view = GravityView_View::getInstance();
1307 4
1308
		$post_id = $gravityview_view->getPostId() ? $gravityview_view->getPostId() : $gravityview_view->getViewId();
1309 4
1310
		$url = add_query_arg( array(), get_permalink( $post_id ) );
1311 4
1312
		return esc_url( $url );
1313
	}
1314 4
1315
	/**
1316
	 * Get the label for a search form field
1317 4
	 * @param  array $field      Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1318
	 * @param  array $form_field Form field data, as fetched by `gravityview_get_field()`
1319
	 * @return string             Label for the search form
1320 4
	 */
1321
	private static function get_field_label( $field, $form_field = array() ) {
1322
1323 4
		$label = \GV\Utils::_GET( 'label', \GV\Utils::get( $field, 'label' ) );
1324 4
1325 4
		if ( ! $label ) {
1326 4
1327 4
			$label = isset( $form_field['label'] ) ? $form_field['label'] : '';
1328 4
1329
			switch( $field['field'] ) {
1330
				case 'search_all':
1331
					$label = __( 'Search Entries:', 'gravityview' );
1332 4
					break;
1333
				case 'entry_date':
1334 4
					$label = __( 'Filter by date:', 'gravityview' );
1335
					break;
1336
				case 'entry_id':
1337
					$label = __( 'Entry ID:', 'gravityview' );
1338 4
					break;
1339
				default:
1340
					// If this is a field input, not a field
1341
					if ( strpos( $field['field'], '.' ) > 0 && ! empty( $form_field['inputs'] ) ) {
1342 4
1343
						// Get the label for the field in question, which returns an array
1344
						$items = wp_list_filter( $form_field['inputs'], array( 'id' => $field['field'] ) );
1345
1346
						// Get the item with the `label` key
1347
						$values = wp_list_pluck( $items, 'label' );
1348
1349
						// There will only one item in the array, but this is easier
1350
						foreach ( $values as $value ) {
1351
							$label = $value;
1352
							break;
1353
						}
1354
					}
1355
			}
1356
		}
1357
1358
		/**
1359
		 * @filter `gravityview_search_field_label` Modify the label for a search field. Supports returning HTML
1360
		 * @since 1.17.3 Added $field parameter
1361
		 * @param[in,out] string $label Existing label text, sanitized.
1362
		 * @param[in] array $form_field Gravity Forms field array, as returned by `GFFormsModel::get_field()`
1363
		 * @param[in] array $field Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1364
		 */
1365
		$label = apply_filters( 'gravityview_search_field_label', esc_attr( $label ), $form_field, $field );
1366
1367
		return $label;
1368
	}
1369
1370
	/**
1371
	 * Prepare search fields to frontend render with other details (label, field type, searched values)
1372
	 *
1373
	 * @param array $field
1374
	 * @return array
1375
	 */
1376
	private function get_search_filter_details( $field ) {
1377
1378
		$gravityview_view = GravityView_View::getInstance();
1379
1380
		$form = $gravityview_view->getForm();
1381
1382
		// for advanced field ids (eg, first name / last name )
1383
		$name = 'filter_' . str_replace( '.', '_', $field['field'] );
1384
1385
		// get searched value from $_GET/$_POST (string or array)
1386
		$value = $this->rgget_or_rgpost( $name );
1387
1388
		// get form field details
1389
		$form_field = gravityview_get_field( $form, $field['field'] );
1390
1391
		$filter = array(
1392
			'key' => $field['field'],
1393
			'name' => $name,
1394
			'label' => self::get_field_label( $field, $form_field ),
1395
			'input' => $field['input'],
1396 4
			'value' => $value,
1397 4
			'type' => $form_field['type'],
1398
		);
1399 4
1400
		// collect choices
1401
		if ( 'post_category' === $form_field['type'] && ! empty( $form_field['displayAllCategories'] ) && empty( $form_field['choices'] ) ) {
1402
			$filter['choices'] = gravityview_get_terms_choices();
1403
		} elseif ( ! empty( $form_field['choices'] ) ) {
1404
			$filter['choices'] = $form_field['choices'];
1405
		}
1406 4
1407
		if ( 'date_range' === $field['input'] && empty( $value ) ) {
1408
			$filter['value'] = array( 'start' => '', 'end' => '' );
1409
		}
1410
1411
		return $filter;
1412
1413
	}
1414
1415
	/**
1416
	 * Calculate the search choices for the users
1417 4
	 *
1418 4
	 * @since 1.8
1419
	 *
1420 4
	 * @return array Array of user choices (value = ID, text = display name)
1421
	 */
1422 4
	private static function get_created_by_choices() {
1423
1424 4
		/**
1425
		 * filter gravityview/get_users/search_widget
1426 4
		 * @see \GVCommon::get_users
1427
		 */
1428
		$users = GVCommon::get_users( 'search_widget', array( 'fields' => array( 'ID', 'display_name' ) ) );
1429
1430
		$choices = array();
1431
		foreach ( $users as $user ) {
1432
			$choices[] = array(
1433
				'value' => $user->ID,
1434
				'text' => $user->display_name,
1435
			);
1436
		}
1437
1438
		return $choices;
1439
	}
1440
1441
	/**
1442
	 * Calculate the search checkbox choices for approval status
1443
	 *
1444
	 * @since develop
1445
	 *
1446
	 * @return array Array of approval status choices (value = status, text = display name)
1447
	 */
1448
	private static function get_is_approved_choices() {
1449
1450
		$choices = array();
1451
		foreach ( GravityView_Entry_Approval_Status::get_all() as $status ) {
1452
			$choices[] = array(
1453
				'value' => $status['value'],
1454
				'text' => $status['label'],
1455
			);
1456
		}
1457
1458
		return $choices;
1459
	}
1460
1461
	/**
1462
	 * Output the Clear Search Results button
1463
	 * @since 1.5.4
1464
	 */
1465
	public static function the_clear_search_button() {
1466
		$gravityview_view = GravityView_View::getInstance();
1467
1468
		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...
1469
1470
			$url = strtok( add_query_arg( array() ), '?' );
1471
1472
			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...
1473
1474
		}
1475
	}
1476
1477
	/**
1478
	 * Based on the search method, fetch the value for a specific key
1479
	 *
1480
	 * @since 1.16.4
1481
	 *
1482
	 * @param string $name Name of the request key to fetch the value for
1483
	 *
1484
	 * @return mixed|string Value of request at $name key. Empty string if empty.
1485
	 */
1486
	private function rgget_or_rgpost( $name ) {
1487
		$value = \GV\Utils::_REQUEST( $name );
1488
1489
		$value = stripslashes_deep( $value );
1490
1491
		$value = gv_map_deep( $value, 'rawurldecode' );
1492
1493
		$value = gv_map_deep( $value, '_wp_specialchars' );
1494
1495
		return $value;
1496
	}
1497
1498
1499
	/**
1500
	 * Require the datepicker script for the frontend GV script
1501
	 * @param array $js_dependencies Array of existing required scripts for the fe-views.js script
1502
	 * @return array Array required scripts, with `jquery-ui-datepicker` added
1503
	 */
1504
	public function add_datepicker_js_dependency( $js_dependencies ) {
1505
1506
		$js_dependencies[] = 'jquery-ui-datepicker';
1507 4
1508 4
		return $js_dependencies;
1509
	}
1510
1511 4
	/**
1512
	 * Modify the array passed to wp_localize_script()
1513
	 *
1514
	 * @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...
1515
	 * @param array $view_data View data array with View settings
1516
	 *
1517
	 * @return array
1518
	 */
1519
	public function add_datepicker_localization( $localizations = array(), $view_data = array() ) {
1520
		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...
1521
1522
		/**
1523
		 * @filter `gravityview_datepicker_settings` Modify the datepicker settings
1524
		 * @see http://api.jqueryui.com/datepicker/ Learn what settings are available
1525
		 * @see http://www.renegadetechconsulting.com/tutorials/jquery-datepicker-and-wordpress-i18n Thanks for the helpful information on $wp_locale
1526
		 * @param array $js_localization The data padded to the Javascript file
1527
		 * @param array $view_data View data array with View settings
1528
		 */
1529
		$datepicker_settings = apply_filters( 'gravityview_datepicker_settings', array(
1530
			'yearRange' => '-5:+5',
1531
			'changeMonth' => true,
1532
			'changeYear' => true,
1533
			'closeText' => esc_attr_x( 'Close', 'Close calendar', 'gravityview' ),
1534
			'prevText' => esc_attr_x( 'Prev', 'Previous month in calendar', 'gravityview' ),
1535
			'nextText' => esc_attr_x( 'Next', 'Next month in calendar', 'gravityview' ),
1536
			'currentText' => esc_attr_x( 'Today', 'Today in calendar', 'gravityview' ),
1537
			'weekHeader' => esc_attr_x( 'Week', 'Week in calendar', 'gravityview' ),
1538
			'monthStatus'       => __( 'Show a different month', 'gravityview' ),
1539
			'monthNames'        => array_values( $wp_locale->month ),
1540
			'monthNamesShort'   => array_values( $wp_locale->month_abbrev ),
1541
			'dayNames'          => array_values( $wp_locale->weekday ),
1542
			'dayNamesShort'     => array_values( $wp_locale->weekday_abbrev ),
1543
			'dayNamesMin'       => array_values( $wp_locale->weekday_initial ),
1544
			// get the start of week from WP general setting
1545
			'firstDay'          => get_option( 'start_of_week' ),
1546
			// is Right to left language? default is false
1547
			'isRTL'             => is_rtl(),
1548
		), $view_data );
1549
1550
		$localizations['datepicker'] = $datepicker_settings;
1551
1552
		return $localizations;
1553
1554
	}
1555
1556
	/**
1557
	 * Register search widget scripts, including Flexibility
1558
	 *
1559 18
	 * @see https://github.com/10up/flexibility
1560
	 *
1561 18
	 * @since 1.17
1562
	 *
1563
	 * @return void
1564
	 */
1565
	public function register_scripts() {
1566
		wp_register_script( 'gv-flexibility', plugins_url( 'assets/lib/flexibility/flexibility.js', GRAVITYVIEW_FILE ), array(), \GV\Plugin::$version, true );
1567
	}
1568
1569
	/**
1570
	 * If the current visitor is running IE 8 or 9, enqueue Flexibility
1571
	 *
1572
	 * @since 1.17
1573
	 *
1574
	 * @return void
1575
	 */
1576 18
	private function maybe_enqueue_flexibility() {
1577
		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...
1578
			wp_enqueue_script( 'gv-flexibility' );
1579 18
		}
1580
	}
1581
1582
	/**
1583
	 * Enqueue the datepicker script
1584
	 *
1585
	 * It sets the $gravityview->datepicker_class parameter
1586
	 *
1587
	 * @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.
1588
	 * @return void
1589
	 */
1590 18
	public function enqueue_datepicker() {
1591
		$gravityview_view = GravityView_View::getInstance();
1592
1593
		wp_enqueue_script( 'jquery-ui-datepicker' );
1594
1595
		add_filter( 'gravityview_js_dependencies', array( $this, 'add_datepicker_js_dependency' ) );
1596 18
		add_filter( 'gravityview_js_localization', array( $this, 'add_datepicker_localization' ), 10, 2 );
1597
1598
		$scheme = is_ssl() ? 'https://' : 'http://';
1599
		wp_enqueue_style( 'jquery-ui-datepicker', $scheme.'ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/smoothness/jquery-ui.css' );
1600
1601
		/**
1602
		 * @filter `gravityview_search_datepicker_class`
1603
		 * 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.
1604
		 * @param string $css_class CSS class to use. Default: `gv-datepicker datepicker mdy` \n
1605
		 * Options are:
1606
		 * - `mdy` (mm/dd/yyyy)
1607
		 * - `dmy` (dd/mm/yyyy)
1608
		 * - `dmy_dash` (dd-mm-yyyy)
1609
		 * - `dmy_dot` (dd.mm.yyyy)
1610
		 * - `ymd_slash` (yyyy/mm/dd)
1611
		 * - `ymd_dash` (yyyy-mm-dd)
1612
		 * - `ymd_dot` (yyyy.mm.dd)
1613
		 */
1614
		$datepicker_class = apply_filters( 'gravityview_search_datepicker_class', "gv-datepicker datepicker " . $this->get_datepicker_format() );
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal gv-datepicker datepicker does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
1615
1616
		$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...
1617
	}
1618
1619
	/**
1620
	 * Retrieve the datepicker format.
1621
	 *
1622
	 * @param bool $date_format Whether to return the PHP date format or the datpicker class name. Default: false.
1623
	 *
1624
	 * @see https://docs.gravityview.co/article/115-changing-the-format-of-the-search-widgets-date-picker
1625
	 *
1626
	 * @return string The datepicker format placeholder, or the PHP date format.
1627
	 */
1628
	private function get_datepicker_format( $date_format = false ) {
1629
1630
		$default_format = 'mdy';
1631
1632
		/**
1633
		 * @filter `gravityview/widgets/search/datepicker/format`
1634
		 * @since 2.1.1
1635
		 * @param string           $format Default: mdy
1636
		 * Options are:
1637
		 * - `mdy` (mm/dd/yyyy)
1638
		 * - `dmy` (dd/mm/yyyy)
1639
		 * - `dmy_dash` (dd-mm-yyyy)
1640
		 * - `dmy_dot` (dd.mm.yyyy)
1641
		 * - `ymd_slash` (yyyy/mm/dd)
1642
		 * - `ymd_dash` (yyyy-mm-dd)
1643
		 * - `ymd_dot` (yyyy.mm.dd)
1644
		 */
1645
		$format = apply_filters( 'gravityview/widgets/search/datepicker/format', $default_format );
1646
1647
		$gf_date_formats = array(
1648
			'mdy' => 'm/d/Y',
1649
1650
			'dmy_dash' => 'd-m-Y',
1651
			'dmy_dot' => 'd.m.Y',
1652
			'dmy' => 'd/m/Y',
1653
1654
			'ymd_slash' => 'Y/m/d',
1655
			'ymd_dash' => 'Y-m-d',
1656
			'ymd_dot' => 'Y.m.d',
1657
		);
1658
1659
		if ( ! $date_format ) {
1660
			// If the format key isn't valid, return default format key
1661
			return isset( $gf_date_formats[ $format ] ) ? $format : $default_format;
1662
		}
1663
1664
		// If the format key isn't valid, return default format value
1665
		return \GV\Utils::get( $gf_date_formats, $format, $gf_date_formats[ $default_format ] );
1666
	}
1667
1668
1669
} // end class
1670
1671
new GravityView_Widget_Search;
1672