Completed
Push — develop ( f3f7c0...a3eda8 )
by Gennady
18:09
created

get_created_by_choices()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 18
ccs 0
cts 8
cp 0
crap 6
rs 9.6666
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 29
	public function __construct() {
31
32 29
		$this->widget_id = 'search_bar';
33 29
		$this->widget_description = esc_html__( 'Search form for searching entries.', 'gravityview' );
34
35 29
		self::$instance = &$this;
36
37 29
		self::$file = plugin_dir_path( __FILE__ );
38
39 29
		$default_values = array( 'header' => 0, 'footer' => 0 );
40
41
		$settings = array(
42 29
			'search_layout' => array(
43 29
				'type' => 'radio',
44
				'full_width' => true,
45 29
				'label' => esc_html__( 'Search Layout', 'gravityview' ),
46 29
				'value' => 'horizontal',
47
				'options' => array(
48 29
					'horizontal' => esc_html__( 'Horizontal', 'gravityview' ),
49 29
					'vertical' => esc_html__( 'Vertical', 'gravityview' ),
50
				),
51
			),
52
			'search_clear' => array(
53 29
				'type' => 'checkbox',
54 29
				'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 29
				'type' => 'radio',
65
				'full_width' => true,
66 29
				'label' => esc_html__( 'Search Mode', 'gravityview' ),
67 29
				'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 29
				'value' => 'any',
69 29
				'class' => 'hide-if-js',
70
				'options' => array(
71 29
					'any' => esc_html__( 'Match Any Fields', 'gravityview' ),
72 29
					'all' => esc_html__( 'Match All Fields', 'gravityview' ),
73
				),
74
			),
75
		);
76
77 29
		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 29
		parent::__construct( esc_html__( 'Search Bar', 'gravityview' ), null, $default_values, $settings );
97
98
		// calculate the search method (POST / GET)
99 29
		$this->set_search_method();
100 29
	}
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 29
	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 29
		$method = apply_filters( 'gravityview/search/method', $this->search_method );
124
125 29
		$method = strtolower( $method );
126
127 29
		$this->search_method = in_array( $method, array( 'get', 'post' ) ) ? $method : 'get';
128 29
	}
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
	 * @since 1.8
421
	 * @return array Search fields, modified if not using permalinks
422
	 */
423 4
	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 4
			if ( ! empty( $widget_args['post_id'] ) ) {
435
				$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 4
			else if ( ! empty( $widget_args['view_id'] ) ) {
439
				$post_id = absint( $widget_args['view_id'] );
440
			}
441
442 4
			$args = gravityview_get_permalink_query_args( $post_id );
443
444
			// 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 4
					'name'  => $key,
448 4
					'input' => 'hidden',
449 4
					'value' => $value,
450
				);
451
			}
452
		}
453
454 4
		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
	 *
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 24
	private function get_view_searchable_fields( $view, $with_full_field = false ) {
472
473
		/**
474
		 * Find all search widgets on the view and get the searchable fields settings.
475
		 */
476 24
		$searchable_fields = array();
477
478 24
		if ( ! $view ) {
479
			return $searchable_fields;
480
		}
481
482
		/**
483
		 * Include the sidebar Widgets.
484
		 */
485 24
		$widgets = (array) get_option( 'widget_gravityview_search', array() );
486
487 24
		foreach ( $widgets as $widget ) {
488 24
			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 24
						$searchable_fields [] = $with_full_field ? $field : $field['field'];
492
					}
493
				}
494
			}
495
		}
496
497 24
		foreach ( $view->widgets->by_id( $this->get_widget_id() )->all() as $widget ) {
498 24
			if( $_fields = json_decode( $widget->configuration->get( 'search_fields' ), true ) ) {
499 24
				foreach ( $_fields as $field ) {
500 24
					$searchable_fields [] = $with_full_field ? $field : $field['field'];
501
				}
502
			}
503
		}
504
505 24
		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
	 *
518
	 * @return array
519
	 */
520 53
	public function filter_entries( $search_criteria, $form_id = null, $args = array(), $force_search_criteria = false ) {
521 53
		if ( ! $force_search_criteria && gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
522
			/**
523
			 * If GF_Query is available, we can construct custom conditions with nested
524
			 * booleans on the query, giving up the old ways of flat search_criteria field_filters.
525
			 */
526 34
			add_action( 'gravityview/view/query', array( $this, 'gf_query_filter' ), 10, 3 );
527 34
			return $search_criteria; // Return the original criteria, GF_Query modification kicks in later
528
		}
529
530 52
		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 52
			$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
536 52
		$view = \GV\View::by_id( \GV\Utils::get( $args, 'id' ) );
537
538 52
		gravityview()->log->debug( 'Requested $_{method}: ', array( 'method' => $this->search_method, 'data' => $get ) );
539
540 52
		if ( empty( $get ) || ! is_array( $get ) ) {
541 30
			return $search_criteria;
542
		}
543
544 25
		$get = stripslashes_deep( $get );
545
546 25
		$get = gv_map_deep( $get, 'rawurldecode' );
547
548
		// Make sure array key is set up
549 25
		$search_criteria['field_filters'] = \GV\Utils::get( $search_criteria, 'field_filters', array() );
550
551 25
		$searchable_fields = $this->get_view_searchable_fields( $view );
552
553
		// add free search
554 25
		if ( isset( $get['gv_search'] ) && '' !== $get['gv_search'] && in_array( 'search_all', $searchable_fields ) ) {
555
556 1
			$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
			 * @since 1.20.2
561
			 * @param bool $split_words True: split a phrase into words; False: search whole word only [Default: true]
562
			 */
563 1
			$split_words = apply_filters( 'gravityview/search-all-split-words', true );
564
565 1
			if ( $split_words ) {
566
567
				// Search for a piece
568 1
				$words = explode( ' ', $search_all_value );
569
570 1
				$words = array_filter( $words );
571
572
			} else {
573
574
				// Replace multiple spaces with one space
575 1
				$search_all_value = preg_replace( '/\s+/ism', ' ', $search_all_value );
576
577 1
				$words = array( $search_all_value );
578
			}
579
580 1
			foreach ( $words as $word ) {
581 1
				$search_criteria['field_filters'][] = array(
582 1
					'key' => null, // The field ID to search
583 1
					'value' => $word, // The value to search
584 1
					'operator' => 'contains', // What to search in. Options: `is` or `contains`
585
				);
586
			}
587
		}
588
589
		// start date & end date
590 25
		if ( in_array( 'entry_date', $searchable_fields ) ) {
591
			/**
592
			 * Get and normalize the dates according to the input format.
593
			 */
594 11
			if ( $curr_start = ! empty( $get['gv_start'] ) ? $get['gv_start'] : '' ) {
595 11
				if( $curr_start_date = date_create_from_format( $this->get_datepicker_format( true ), $curr_start ) ) {
596 11
					$curr_start = $curr_start_date->format( 'Y-m-d' );
597
				}
598
			}
599
600 11
			if ( $curr_end = ! empty( $get['gv_start'] ) ? ( ! empty( $get['gv_end'] ) ? $get['gv_end'] : '' ) : '' ) {
601 11
				if( $curr_end_date = date_create_from_format( $this->get_datepicker_format( true ), $curr_end ) ) {
602 11
					$curr_end = $curr_end_date->format( 'Y-m-d' );
603
				}
604
			}
605
606 11
			if ( $view ) {
607
				/**
608
				 * Override start and end dates if View is limited to some already.
609
				 */
610 11
				if ( $start_date = $view->settings->get( 'start_date' ) ) {
611 1
					if ( $start_timestamp = strtotime( $curr_start ) ) {
612 1
						$curr_start = $start_timestamp < strtotime( $start_date ) ? $start_date : $curr_start;
613
					}
614
				}
615 11
				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
			 * @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 11
			$adjust_tz = apply_filters( 'gravityview_date_created_adjust_timezone', true, 'search' );
630
631
			/**
632
			 * Don't set $search_criteria['start_date'] if start_date is empty as it may lead to bad query results (GFAPI::get_entries)
633
			 */
634 11
			if ( ! empty( $curr_start ) ) {
635 11
				$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
639 11
			if ( ! empty( $curr_end ) ) {
640
				// 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 11
				$search_criteria['end_date'] = $adjust_tz ? get_gmt_from_date( $curr_end ) : $curr_end;
643 11
				if ( strpos( $search_criteria['end_date'], '00:00:00' ) ) { // See https://github.com/gravityview/GravityView/issues/1056
644 11
					$search_criteria['end_date'] = date( 'Y-m-d H:i:s', strtotime( $search_criteria['end_date'] ) - 1 );
645
				}
646
			}
647
		}
648
649
		// search for a specific entry ID
650 25
		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 2
				'key' => 'id',
653 2
				'value' => absint( $get[ 'gv_id' ] ),
0 ignored issues
show
introduced by
Array keys should NOT be surrounded by spaces if they only contain a string or an integer.
Loading history...
654 2
				'operator' => '=',
655
			);
656
		}
657
658
		// search for a specific Created_by ID
659 25
		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 4
			$search_criteria['field_filters'][] = array(
661 4
				'key' => 'created_by',
662 4
				'value' => $get['gv_by'],
663 4
				'operator' => '=',
664
			);
665
		}
666
667
		// Get search mode passed in URL
668 25
		$mode = isset( $get['mode'] ) && in_array( $get['mode'], array( 'any', 'all' ) ) ?  $get['mode'] : 'any';
669
670
		// get the other search filters
671 25
		foreach ( $get as $key => $value ) {
672
673 25
			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...
674 15
				continue;
675
			}
676
677 13
			$filter_key = $this->convert_request_key_to_filter_key( $key );
678
679
			// could return simple filter or multiple filters
680 13
			if ( ! in_array( 'search_all', $searchable_fields ) && ! in_array( $filter_key , $searchable_fields ) ) {
681 1
				continue;
682
			}
683
684 12
			$filter = $this->prepare_field_filter( $filter_key, $value, $view );
685
686 12
			if ( isset( $filter[0]['value'] ) ) {
687
				$search_criteria['field_filters'] = array_merge( $search_criteria['field_filters'], $filter );
688
689
				// if date range type, set search mode to ALL
690
				if ( ! empty( $filter[0]['operator'] ) && in_array( $filter[0]['operator'], array( '>=', '<=', '>', '<' ) ) ) {
691
					$mode = 'all';
692
				}
693 12
			} elseif( !empty( $filter ) ) {
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
694 12
				$search_criteria['field_filters'][] = $filter;
695
			}
696
		}
697
698
		/**
699
		 * @filter `gravityview/search/mode` Set the Search Mode (`all` or `any`)
700
		 * @since 1.5.1
701
		 * @param[out,in] string $mode Search mode (`any` vs `all`)
702
		 */
703 25
		$search_criteria['field_filters']['mode'] = apply_filters( 'gravityview/search/mode', $mode );
704
705 25
		gravityview()->log->debug( 'Returned Search Criteria: ', array( 'data' => $search_criteria ) );
706
707 25
		unset( $get );
708
709 25
		return $search_criteria;
710
	}
711
712
	/**
713
	 * Filters the \GF_Query with advanced logic.
714
	 *
715
	 * Dropin for the legacy flat filters when \GF_Query is available.
716
	 *
717
	 * @param \GF_Query $query The current query object reference
718
	 * @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...
719
	 * @param \GV\Request $request The request object
720
	 */
721 33
	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...
722
		/**
723
		 * This is a shortcut to get all the needed search criteria.
724
		 * We feed these into an new GF_Query and tack them onto the current object.
725
		 */
726 33
		$search_criteria = $this->filter_entries( array(), null, array( 'id' => $view->ID ), true /** force search_criteria */ );
727
728 33
		if ( empty( $search_criteria['field_filters'] ) ) {
729 29
			return;
730
		}
731
732 6
		$widgets = $view->widgets->by_id( $this->widget_id );
733 6
		if ( $widgets->count() ) {
734 6
			$widget = $widgets->all()[0];
735 6
			foreach ( $search_fields = json_decode( $widget->configuration->get( 'search_fields' ), true ) as $search_field ) {
736 6
				if ( 'created_by' === $search_field['field'] && 'input_text' === $search_field['input'] ) {
737 6
					$created_by_text_mode = true;
738
				}
739
			}
740
		}
741
742 6
		$extra_conditions = array();
743
744 6
		foreach ( $search_criteria['field_filters'] as &$filter ) {
745 6
			if ( ! is_array( $filter ) ) {
746 6
				continue;
747
			}
748
749
			// Construct a manual query for unapproved statuses
750 6
			if ( 'is_approved' === $filter['key'] && in_array( \GravityView_Entry_Approval_Status::UNAPPROVED, $filter['value'] ) ) {
751 2
				$_tmp_query       = new GF_Query( $view->form->ID, array(
752 2
					'field_filters' => array(
753
						array(
754 2
							'operator' => 'in',
755 2
							'key'      => 'is_approved',
756 2
							'value'    => $filter['value'],
757
						),
758
						array(
759
							'operator' => 'is',
760
							'key'      => 'is_approved',
761
							'value'    => '',
762
						),
763 2
						'mode' => 'any'
0 ignored issues
show
introduced by
Key specified for array entry; first entry has no key
Loading history...
764
					),
765
				) );
766 2
				$_tmp_query_parts = $_tmp_query->_introspect();
767
768 2
				$extra_conditions[] = $_tmp_query_parts['where'];
769
770 2
				$filter = false;
771 2
				continue;
772
			}
773
774
			// Construct manual query for text mode creator search
775 6
			if ( 'created_by' === $filter['key'] && ! empty( $created_by_text_mode ) ) {
776
				$extra_conditions[] = new class( $filter, $view ) extends \GF_Query_Condition {
777 1
					public function __construct( $filter, $view ) {
778 1
						$this->value = $filter['value'];
779 1
						$this->view = $view;
780 1
					}
781
782 1
					public function sql( $query ) {
783
						$user_meta_fields = array(
784 1
							'nickname', 'first_name', 'last_name', 
785
						);
786
787 1
						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...
788
789
						/**
790
						 * @filter `gravityview/widgets/search/created_by/user_meta_fields` Filter the user meta fields to search by.
791
						 * @param[in,out] array The user meta fields.
792
						 * @param \GV\View $view The view.
793
						 */
794 1
						$user_meta_fields = apply_filters( 'gravityview/widgets/search/created_by/user_meta_fields', $user_meta_fields, $this->view );
795
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
796
797
						$user_fields = array(
798 1
							'user_nicename', 'user_login', 'display_name', 'user_email', 
799
						);
800
						/**
801
						 * @filter `gravityview/widgets/search/created_by/user_fields` Filter the user meta fields to search by.
802
						 * @param[in,out] array The user fields.
803
						 * @param \GV\View $view The view.
804
						 */
805 1
						$user_fields = apply_filters( 'gravityview/widgets/search/created_by/user_fields', $user_fields, $this->view );
806
807 1
						$column = sprintf( '`%s`.`created_by`', $query->_alias( null ) );
808
809 1
						$conditions = array();
810
811 1
						foreach ( $user_fields as $user_field ) {
812 1
							$conditions[] = $wpdb->prepare( "`u`.`$user_field` LIKE %s", '%' . $wpdb->esc_like( $this->value ) .  '%' );
813
						}
814
815 1
						foreach ( $user_meta_fields as $meta_field ) {
816 1
							$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...
817
						}
818
819 1
						$conditions = '(' . implode( ' OR ', $conditions ) . ')';
820
821 1
						$alias = $query->_alias( null );
822
823 1
						return "(EXISTS (SELECT 1 FROM $wpdb->users u LEFT JOIN $wpdb->usermeta um ON u.ID = um.user_id WHERE (u.ID = `$alias`.`created_by` AND $conditions)))";
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...
824
					}
825
				};
826
827 1
				$filter = false;
828 1
				continue;
829
			}
830
831
			// By default, we want searches to be wildcard for each field.
832 5
			$filter['operator'] = empty( $filter['operator'] ) ? 'contains' : $filter['operator'];
833
834
			// For multichoice, let's have an in (OR) search.
835 5
			if ( is_array( $filter['value'] ) ) {
836 3
				$filter['operator'] = 'in'; // @todo what about in contains (OR LIKE chains)?
837
			}
838
839
			/**
840
			 * @filter `gravityview_search_operator` Modify the search operator for the field (contains, is, isnot, etc)
841
			 * @param string $operator Existing search operator
842
			 * @param array $filter array with `key`, `value`, `operator`, `type` keys
843
			 */
844 5
			$filter['operator'] = apply_filters( 'gravityview_search_operator', $filter['operator'], $filter );
845
		}
846
847 6
		$search_criteria['field_filters'] = array_filter( $search_criteria['field_filters'] );
848
849
		/**
850
		 * Parse the filter criteria to generate the needed
851
		 * WHERE clauses. This is a trick to not write our own generation
852
		 * code by reusing what's inside GF_Query already.
853
		 */
854 6
		$_tmp_query       = new GF_Query( $view->form->ID, $search_criteria );
855 6
		$_tmp_query_parts = $_tmp_query->_introspect();
856
857
		/**
858
		 * Grab the current clauses. We'll be combining them shortly.
859
		 */
860 6
		$query_parts      = $query->_introspect();
861
862
		/**
863
		 * Combine the parts as a new WHERE clause.
864
		 */
865 6
		$where = call_user_func_array( '\GF_Query_Condition::_and', array_merge( array( $query_parts['where'], $_tmp_query_parts['where'] ), $extra_conditions ) );
866 6
		$query->where( $where );
867 6
	}
868
869
	/**
870
	 * Convert $_GET/$_POST key to the field/meta ID
871
	 *
872
	 * Examples:
873
	 * - `filter_is_starred` => `is_starred`
874
	 * - `filter_1_2` => `1.2`
875
	 * - `filter_5` => `5`
876
	 *
877
	 * @since 2.0
878
	 *
879
	 * @param string $key $_GET/_$_POST search key
880
	 *
881
	 * @return string
882
	 */
883 13
	private function convert_request_key_to_filter_key( $key ) {
884
885 13
		$field_id = str_replace( 'filter_', '', $key );
886
887
		// calculates field_id, removing 'filter_' and for '_' for advanced fields ( like name or checkbox )
888 13
		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...
889 11
			$field_id = str_replace( '_', '.', $field_id );
890
		}
891
892 13
		return $field_id;
893
	}
894
895
	/**
896
	 * Prepare the field filters to GFAPI
897
	 *
898
	 * 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.
899
	 *
900
	 * Format searched values
901
	 *
902
	 * @param  string $filter_key ID of the field, or entry meta key
903
	 * @param  string $value $_GET/$_POST search value
904
	 * @param  \GV\View $view The view we're looking at
905
	 *
906
	 * @return array        1 or 2 deph levels
907
	 */
908 12
	public function prepare_field_filter( $filter_key, $value, $view ) {
909
910
		// get form field array
911 12
		$form_field = is_numeric( $filter_key ) ? \GV\GF_Field::by_id( $view->form, $filter_key ) : \GV\Internal_Field::by_id( $filter_key );
912
913
		// default filter array
914
		$filter = array(
915 12
			'key'   => $filter_key,
916 12
			'value' => $value,
917
		);
918
919 12
		switch ( $form_field->type ) {
920
921 12
			case 'select':
922 12
			case 'radio':
923 1
				$filter['operator'] = 'is';
924 1
				break;
925
926 11
			case 'post_category':
927
928
				if ( ! is_array( $value ) ) {
929
					$value = array( $value );
930
				}
931
932
				// Reset filter variable
933
				$filter = array();
934
935
				foreach ( $value as $val ) {
936
					$cat = get_term( $val, 'category' );
937
					$filter[] = array(
938
						'key'      => $filter_key,
939
						'value'    => esc_attr( $cat->name ) . ':' . $val,
940
						'operator' => 'is',
941
					);
942
				}
943
944
				break;
945
946 11
			case 'multiselect':
947
948
				if ( ! is_array( $value ) ) {
949
					break;
950
				}
951
952
				// Reset filter variable
953
				$filter = array();
954
955
				foreach ( $value as $val ) {
956
					$filter[] = array( 'key' => $filter_key, 'value' => $val );
957
				}
958
959
				break;
960
961 11
			case 'checkbox':
962
				// convert checkbox on/off into the correct search filter
963
				if ( false !== strpos( $filter_key, '.' ) && ! empty( $form_field->inputs ) && ! empty( $form_field->choices ) ) {
964
					foreach ( $form_field->inputs as $k => $input ) {
965
						if ( $input['id'] == $filter_key ) {
966
							$filter['value'] = $form_field->choices[ $k ]['value'];
967
							$filter['operator'] = 'is';
968
							break;
969
						}
970
					}
971
				} elseif ( is_array( $value ) ) {
972
973
					// Reset filter variable
974
					$filter = array();
975
976
					foreach ( $value as $val ) {
977
						$filter[] = array(
978
							'key'      => $filter_key,
979
							'value'    => $val,
980
							'operator' => 'is',
981
						);
982
					}
983
				}
984
985
				break;
986
987 11
			case 'name':
988 11
			case 'address':
989
990
				if ( false === strpos( $filter_key, '.' ) ) {
991
992
					$words = explode( ' ', $value );
993
994
					$filters = array();
995
					foreach ( $words as $word ) {
996
						if ( ! empty( $word ) && strlen( $word ) > 1 ) {
997
							// Keep the same key for each filter
998
							$filter['value'] = $word;
999
							// Add a search for the value
1000
							$filters[] = $filter;
1001
						}
1002
					}
1003
1004
					$filter = $filters;
1005
				}
1006
1007
				// State/Province should be exact matches
1008
				if ( 'address' === $form_field->field->type ) {
1009
1010
					$searchable_fields = $this->get_view_searchable_fields( $view, true );
1011
1012
					foreach ( $searchable_fields as $searchable_field ) {
1013
1014
						if( $form_field->ID !== $searchable_field['field'] ) {
1015
							continue;
1016
						}
1017
1018
						// Only exact-match dropdowns, not text search
1019
						if( in_array( $searchable_field['input'], array( 'text', 'search' ), true ) ) {
1020
							continue;
1021
						}
1022
1023
						$input_id = gravityview_get_input_id_from_id( $form_field->ID );
1024
1025
						if ( 4 === $input_id ) {
1026
							$filter['operator'] = 'is';
1027
						};
1028
					}
1029
				}
1030
1031
				break;
1032
1033 11
			case 'date':
1034
1035 8
				$date_format = $this->get_datepicker_format( true );
1036
1037 8
				if ( is_array( $value ) ) {
1038
1039
					// Reset filter variable
1040
					$filter = array();
1041
1042
					foreach ( $value as $k => $date ) {
1043
						if ( empty( $date ) ) {
1044
							continue;
1045
						}
1046
						$operator = 'start' === $k ? '>=' : '<=';
1047
1048
						/**
1049
						 * @hack
1050
						 * @since 1.16.3
1051
						 * Safeguard until GF implements '<=' operator
1052
						 */
1053
						if( !GFFormsModel::is_valid_operator( $operator ) && $operator === '<=' ) {
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
1054
							$operator = '<';
1055
							$date = date( 'Y-m-d', strtotime( self::get_formatted_date( $date, 'Y-m-d', $date_format ) . ' +1 day' ) );
1056
						}
1057
1058
						$filter[] = array(
1059
							'key'      => $filter_key,
1060
							'value'    => self::get_formatted_date( $date, 'Y-m-d', $date_format ),
1061
							'operator' => $operator,
1062
						);
1063
					}
1064
				} else {
1065 8
					$date = $value;
1066 8
					$filter['value'] = self::get_formatted_date( $date, 'Y-m-d', $date_format );
1067
				}
1068
1069 8
				break;
1070
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
1071
1072
		} // switch field type
1073
1074 12
		return $filter;
1075
	}
1076
1077
	/**
1078
	 * Get the Field Format form GravityForms
1079
	 *
1080
	 * @param GF_Field_Date $field The field object
1081
	 * @since 1.10
1082
	 *
1083
	 * @return string Format of the date in the database
1084
	 */
1085
	public static function get_date_field_format( GF_Field_Date $field ) {
1086
		$format = 'm/d/Y';
1087
		$datepicker = array(
1088
			'mdy' => 'm/d/Y',
1089
			'dmy' => 'd/m/Y',
1090
			'dmy_dash' => 'd-m-Y',
1091
			'dmy_dot' => 'd.m.Y',
1092
			'ymd_slash' => 'Y/m/d',
1093
			'ymd_dash' => 'Y-m-d',
1094
			'ymd_dot' => 'Y.m.d',
1095
		);
1096
1097
		if ( ! empty( $field->dateFormat ) && isset( $datepicker[ $field->dateFormat ] ) ){
1098
			$format = $datepicker[ $field->dateFormat ];
1099
		}
1100
1101
		return $format;
1102
	}
1103
1104
	/**
1105
	 * Format a date value
1106
	 *
1107
	 * @param string $value Date value input
1108
	 * @param string $format Wanted formatted date
1109
	 *
1110
	 * @since 2.1.2
1111
	 * @param string $value_format The value format. Default: Y-m-d
1112
	 *
1113
	 * @return string
1114
	 */
1115 8
	public static function get_formatted_date( $value = '', $format = 'Y-m-d', $value_format = 'Y-m-d' ) {
1116
1117 8
		$date = date_create_from_format( $value_format, $value );
1118
1119 8
		if ( empty( $date ) ) {
1120
			gravityview()->log->debug( 'Date format not valid: {value}', array( 'value' => $value ) );
1121
			return '';
1122
		}
1123 8
		return $date->format( $format );
1124
	}
1125
1126
1127
	/**
1128
	 * Include this extension templates path
1129
	 * @param array $file_paths List of template paths ordered
1130
	 */
1131 1
	public function add_template_path( $file_paths ) {
1132
1133
		// Index 100 is the default GravityView template path.
1134 1
		$file_paths[102] = self::$file . 'templates/';
1135
1136 1
		return $file_paths;
1137
	}
1138
1139
	/**
1140
	 * Check whether the configured search fields have a date field
1141
	 *
1142
	 * @since 1.17.5
1143
	 *
1144
	 * @param array $search_fields
1145
	 *
1146
	 * @return bool True: has a `date` or `date_range` field
1147
	 */
1148 4
	private function has_date_field( $search_fields ) {
1149
1150 4
		$has_date = false;
1151
1152 4
		foreach ( $search_fields as $k => $field ) {
1153 4
			if ( in_array( $field['input'], array( 'date', 'date_range', 'entry_date' ) ) ) {
1154
				$has_date = true;
1155 4
				break;
1156
			}
1157
		}
1158
1159 4
		return $has_date;
1160
	}
1161
1162
	/**
1163
	 * Renders the Search Widget
1164
	 * @param array $widget_args
1165
	 * @param string $content
1166
	 * @param string $context
1167
	 *
1168
	 * @return void
1169
	 */
1170 4
	public function render_frontend( $widget_args, $content = '', $context = '' ) {
1171
		/** @var GravityView_View $gravityview_view */
1172 4
		$gravityview_view = GravityView_View::getInstance();
1173
1174 4
		if ( empty( $gravityview_view ) ) {
1175
			gravityview()->log->debug( '$gravityview_view not instantiated yet.' );
1176
			return;
1177
		}
1178
1179
		// get configured search fields
1180 4
		$search_fields = ! empty( $widget_args['search_fields'] ) ? json_decode( $widget_args['search_fields'], true ) : '';
1181
1182 4
		if ( empty( $search_fields ) || ! is_array( $search_fields ) ) {
1183
			gravityview()->log->debug( 'No search fields configured for widget:', array( 'data' => $widget_args ) );
1184
			return;
1185
		}
1186
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
1187
1188
		// prepare fields
1189 4
		foreach ( $search_fields as $k => $field ) {
1190
1191 4
			$updated_field = $field;
1192
1193 4
			$updated_field = $this->get_search_filter_details( $updated_field );
1194
1195 4
			switch ( $field['field'] ) {
1196
1197 4
				case 'search_all':
1198 4
					$updated_field['key'] = 'search_all';
1199 4
					$updated_field['input'] = 'search_all';
1200 4
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_search' );
1201 4
					break;
1202
1203
				case 'entry_date':
1204
					$updated_field['key'] = 'entry_date';
1205
					$updated_field['input'] = 'entry_date';
1206
					$updated_field['value'] = array(
1207
						'start' => $this->rgget_or_rgpost( 'gv_start' ),
1208
						'end' => $this->rgget_or_rgpost( 'gv_end' ),
1209
					);
1210
					break;
1211
1212
				case 'entry_id':
1213
					$updated_field['key'] = 'entry_id';
1214
					$updated_field['input'] = 'entry_id';
1215
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_id' );
1216
					break;
1217
1218
				case 'created_by':
1219
					$updated_field['key'] = 'created_by';
1220
					$updated_field['name'] = 'gv_by';
1221
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_by' );
1222
					$updated_field['choices'] = self::get_created_by_choices();
1223
					break;
1224
				
1225
				case 'is_approved':
1226
					$updated_field['key'] = 'is_approved';
1227
					$updated_field['input'] = 'checkbox';
1228
					$updated_field['value'] = $this->rgget_or_rgpost( 'filter_is_approved' );
1229
					$updated_field['choices'] = self::get_is_approved_choices();
1230
					break;
1231
			}
1232
1233 4
			$search_fields[ $k ] = $updated_field;
1234
		}
1235
1236 4
		gravityview()->log->debug( 'Calculated Search Fields: ', array( 'data' => $search_fields ) );
1237
1238
		/**
1239
		 * @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.
1240
		 * @param array $search_fields Array of search filters with `key`, `label`, `value`, `type`, `choices` keys
1241
		 * @param GravityView_Widget_Search $this Current widget object
1242
		 * @param array $widget_args Args passed to this method. {@since 1.8}
1243
		 * @param \GV\Template_Context $context {@since 2.0}
1244
		 * @var array
1245
		 */
1246 4
		$gravityview_view->search_fields = apply_filters( 'gravityview_widget_search_filters', $search_fields, $this, $widget_args, $context );
0 ignored issues
show
Bug introduced by
The property search_fields does not seem to exist. Did you mean fields?

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

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

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

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

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

Loading history...
1249
1250
		/** @since 1.14 */
1251 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...
1252
1253 4
		$custom_class = ! empty( $widget_args['custom_class'] ) ? $widget_args['custom_class'] : '';
1254
1255 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...
1256
1257 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...
1258
1259 4
		if ( $this->has_date_field( $search_fields ) ) {
1260
			// enqueue datepicker stuff only if needed!
1261
			$this->enqueue_datepicker();
1262
		}
1263
1264 4
		$this->maybe_enqueue_flexibility();
1265
1266 4
		$gravityview_view->render( 'widget', 'search', false );
1267 4
	}
1268
1269
	/**
1270
	 * Get the search class for a search form
1271
	 *
1272
	 * @since 1.5.4
1273
	 *
1274
	 * @return string Sanitized CSS class for the search form
1275
	 */
1276 4
	public static function get_search_class( $custom_class = '' ) {
1277 4
		$gravityview_view = GravityView_View::getInstance();
1278
1279 4
		$search_class = 'gv-search-'.$gravityview_view->search_layout;
0 ignored issues
show
Documentation introduced by
The property search_layout does not exist on object<GravityView_View>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1280
1281 4
		if ( ! empty( $custom_class )  ) {
1282
			$search_class .= ' '.$custom_class;
1283
		}
1284
1285
		/**
1286
		 * @filter `gravityview_search_class` Modify the CSS class for the search form
1287
		 * @param string $search_class The CSS class for the search form
1288
		 */
1289 4
		$search_class = apply_filters( 'gravityview_search_class', $search_class );
1290
1291
		// Is there an active search being performed? Used by fe-views.js
1292 4
		$search_class .= GravityView_frontend::getInstance()->isSearch() ? ' gv-is-search' : '';
1293
1294 4
		return gravityview_sanitize_html_class( $search_class );
1295
	}
1296
1297
1298
	/**
1299
	 * Calculate the search form action
1300
	 * @since 1.6
1301
	 *
1302
	 * @return string
1303
	 */
1304 4
	public static function get_search_form_action() {
1305 4
		$gravityview_view = GravityView_View::getInstance();
1306
1307 4
		$post_id = $gravityview_view->getPostId() ? $gravityview_view->getPostId() : $gravityview_view->getViewId();
1308
1309 4
		$url = add_query_arg( array(), get_permalink( $post_id ) );
1310
1311 4
		return esc_url( $url );
1312
	}
1313
1314
	/**
1315
	 * Get the label for a search form field
1316
	 * @param  array $field      Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1317
	 * @param  array $form_field Form field data, as fetched by `gravityview_get_field()`
1318
	 * @return string             Label for the search form
1319
	 */
1320 4
	private static function get_field_label( $field, $form_field = array() ) {
1321
1322 4
		$label = \GV\Utils::_GET( 'label', \GV\Utils::get( $field, 'label' ) );
1323
1324 4
		if ( ! $label ) {
1325
1326 4
			$label = isset( $form_field['label'] ) ? $form_field['label'] : '';
1327
1328 4
			switch( $field['field'] ) {
1329 4
				case 'search_all':
1330 4
					$label = __( 'Search Entries:', 'gravityview' );
1331 4
					break;
1332
				case 'entry_date':
1333
					$label = __( 'Filter by date:', 'gravityview' );
1334
					break;
1335
				case 'entry_id':
1336
					$label = __( 'Entry ID:', 'gravityview' );
1337
					break;
1338
				default:
1339
					// If this is a field input, not a field
1340
					if ( strpos( $field['field'], '.' ) > 0 && ! empty( $form_field['inputs'] ) ) {
1341
1342
						// Get the label for the field in question, which returns an array
1343
						$items = wp_list_filter( $form_field['inputs'], array( 'id' => $field['field'] ) );
1344
1345
						// Get the item with the `label` key
1346
						$values = wp_list_pluck( $items, 'label' );
1347
1348
						// There will only one item in the array, but this is easier
1349
						foreach ( $values as $value ) {
1350
							$label = $value;
1351
							break;
1352
						}
1353
					}
1354
			}
1355
		}
1356
1357
		/**
1358
		 * @filter `gravityview_search_field_label` Modify the label for a search field. Supports returning HTML
1359
		 * @since 1.17.3 Added $field parameter
1360
		 * @param[in,out] string $label Existing label text, sanitized.
1361
		 * @param[in] array $form_field Gravity Forms field array, as returned by `GFFormsModel::get_field()`
1362
		 * @param[in] array $field Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1363
		 */
1364 4
		$label = apply_filters( 'gravityview_search_field_label', esc_attr( $label ), $form_field, $field );
1365
1366 4
		return $label;
1367
	}
1368
1369
	/**
1370
	 * Prepare search fields to frontend render with other details (label, field type, searched values)
1371
	 *
1372
	 * @param array $field
1373
	 * @return array
1374
	 */
1375 4
	private function get_search_filter_details( $field ) {
1376
1377 4
		$gravityview_view = GravityView_View::getInstance();
1378
1379 4
		$form = $gravityview_view->getForm();
1380
1381
		// for advanced field ids (eg, first name / last name )
1382 4
		$name = 'filter_' . str_replace( '.', '_', $field['field'] );
1383
1384
		// get searched value from $_GET/$_POST (string or array)
1385 4
		$value = $this->rgget_or_rgpost( $name );
1386
1387
		// get form field details
1388 4
		$form_field = gravityview_get_field( $form, $field['field'] );
1389
1390
		$filter = array(
1391 4
			'key' => $field['field'],
1392 4
			'name' => $name,
1393 4
			'label' => self::get_field_label( $field, $form_field ),
1394 4
			'input' => $field['input'],
1395 4
			'value' => $value,
1396 4
			'type' => $form_field['type'],
1397
		);
1398
1399
		// collect choices
1400 4
		if ( 'post_category' === $form_field['type'] && ! empty( $form_field['displayAllCategories'] ) && empty( $form_field['choices'] ) ) {
1401
			$filter['choices'] = gravityview_get_terms_choices();
1402 4
		} elseif ( ! empty( $form_field['choices'] ) ) {
1403
			$filter['choices'] = $form_field['choices'];
1404
		}
1405
1406 4
		if ( 'date_range' === $field['input'] && empty( $value ) ) {
1407
			$filter['value'] = array( 'start' => '', 'end' => '' );
1408
		}
1409
1410 4
		return $filter;
1411
1412
	}
1413
1414
	/**
1415
	 * Calculate the search choices for the users
1416
	 *
1417
	 * @since 1.8
1418
	 *
1419
	 * @return array Array of user choices (value = ID, text = display name)
1420
	 */
1421
	private static function get_created_by_choices() {
1422
1423
		/**
1424
		 * filter gravityview/get_users/search_widget
1425
		 * @see \GVCommon::get_users
1426
		 */
1427
		$users = GVCommon::get_users( 'search_widget', array( 'fields' => array( 'ID', 'display_name' ) ) );
1428
1429
		$choices = array();
1430
		foreach ( $users as $user ) {
1431
			$choices[] = array(
1432
				'value' => $user->ID,
1433
				'text' => $user->display_name,
1434
			);
1435
		}
1436
1437
		return $choices;
1438
	}
1439
1440
	/**
1441
	 * Calculate the search checkbox choices for approval status
1442
	 *
1443
	 * @since develop
1444
	 *
1445
	 * @return array Array of approval status choices (value = status, text = display name)
1446
	 */
1447
	private static function get_is_approved_choices() {
1448
1449
		$choices = array();
1450
		foreach ( GravityView_Entry_Approval_Status::get_all() as $status ) {
1451
			$choices[] = array(
1452
				'value' => $status['value'],
1453
				'text' => $status['label'],
1454
			);
1455
		}
1456
1457
		return $choices;
1458
	}
1459
1460
	/**
1461
	 * Output the Clear Search Results button
1462
	 * @since 1.5.4
1463
	 */
1464 4
	public static function the_clear_search_button() {
1465 4
		$gravityview_view = GravityView_View::getInstance();
1466
1467 4
		if ( $gravityview_view->search_clear ) {
0 ignored issues
show
Documentation introduced by
The property search_clear does not exist on object<GravityView_View>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1468
1469
			$url = strtok( add_query_arg( array() ), '?' );
1470
1471
			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...
1472
1473
		}
1474 4
	}
1475
1476
	/**
1477
	 * Based on the search method, fetch the value for a specific key
1478
	 *
1479
	 * @since 1.16.4
1480
	 *
1481
	 * @param string $name Name of the request key to fetch the value for
1482
	 *
1483
	 * @return mixed|string Value of request at $name key. Empty string if empty.
1484
	 */
1485 4
	private function rgget_or_rgpost( $name ) {
1486 4
		$value = \GV\Utils::_REQUEST( $name );
1487
1488 4
		$value = stripslashes_deep( $value );
1489
1490 4
		$value = gv_map_deep( $value, 'rawurldecode' );
1491
1492 4
		$value = gv_map_deep( $value, '_wp_specialchars' );
1493
1494 4
		return $value;
1495
	}
1496
1497
1498
	/**
1499
	 * Require the datepicker script for the frontend GV script
1500
	 * @param array $js_dependencies Array of existing required scripts for the fe-views.js script
1501
	 * @return array Array required scripts, with `jquery-ui-datepicker` added
1502
	 */
1503
	public function add_datepicker_js_dependency( $js_dependencies ) {
1504
1505
		$js_dependencies[] = 'jquery-ui-datepicker';
1506
1507
		return $js_dependencies;
1508
	}
1509
1510
	/**
1511
	 * Modify the array passed to wp_localize_script()
1512
	 *
1513
	 * @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...
1514
	 * @param array $view_data View data array with View settings
1515
	 *
1516
	 * @return array
1517
	 */
1518
	public function add_datepicker_localization( $localizations = array(), $view_data = array() ) {
1519
		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...
1520
1521
		/**
1522
		 * @filter `gravityview_datepicker_settings` Modify the datepicker settings
1523
		 * @see http://api.jqueryui.com/datepicker/ Learn what settings are available
1524
		 * @see http://www.renegadetechconsulting.com/tutorials/jquery-datepicker-and-wordpress-i18n Thanks for the helpful information on $wp_locale
1525
		 * @param array $js_localization The data padded to the Javascript file
1526
		 * @param array $view_data View data array with View settings
1527
		 */
1528
		$datepicker_settings = apply_filters( 'gravityview_datepicker_settings', array(
1529
			'yearRange' => '-5:+5',
1530
			'changeMonth' => true,
1531
			'changeYear' => true,
1532
			'closeText' => esc_attr_x( 'Close', 'Close calendar', 'gravityview' ),
1533
			'prevText' => esc_attr_x( 'Prev', 'Previous month in calendar', 'gravityview' ),
1534
			'nextText' => esc_attr_x( 'Next', 'Next month in calendar', 'gravityview' ),
1535
			'currentText' => esc_attr_x( 'Today', 'Today in calendar', 'gravityview' ),
1536
			'weekHeader' => esc_attr_x( 'Week', 'Week in calendar', 'gravityview' ),
1537
			'monthStatus'       => __( 'Show a different month', 'gravityview' ),
1538
			'monthNames'        => array_values( $wp_locale->month ),
1539
			'monthNamesShort'   => array_values( $wp_locale->month_abbrev ),
1540
			'dayNames'          => array_values( $wp_locale->weekday ),
1541
			'dayNamesShort'     => array_values( $wp_locale->weekday_abbrev ),
1542
			'dayNamesMin'       => array_values( $wp_locale->weekday_initial ),
1543
			// get the start of week from WP general setting
1544
			'firstDay'          => get_option( 'start_of_week' ),
1545
			// is Right to left language? default is false
1546
			'isRTL'             => is_rtl(),
1547
		), $view_data );
1548
1549
		$localizations['datepicker'] = $datepicker_settings;
1550
1551
		return $localizations;
1552
1553
	}
1554
1555
	/**
1556
	 * Register search widget scripts, including Flexibility
1557
	 *
1558
	 * @see https://github.com/10up/flexibility
1559
	 *
1560
	 * @since 1.17
1561
	 *
1562
	 * @return void
1563
	 */
1564
	public function register_scripts() {
1565
		wp_register_script( 'gv-flexibility', plugins_url( 'assets/lib/flexibility/flexibility.js', GRAVITYVIEW_FILE ), array(), \GV\Plugin::$version, true );
1566
	}
1567
1568
	/**
1569
	 * If the current visitor is running IE 8 or 9, enqueue Flexibility
1570
	 *
1571
	 * @since 1.17
1572
	 *
1573
	 * @return void
1574
	 */
1575 4
	private function maybe_enqueue_flexibility() {
1576 4
		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...
1577
			wp_enqueue_script( 'gv-flexibility' );
1578
		}
1579 4
	}
1580
1581
	/**
1582
	 * Enqueue the datepicker script
1583
	 *
1584
	 * It sets the $gravityview->datepicker_class parameter
1585
	 *
1586
	 * @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.
1587
	 * @return void
1588
	 */
1589
	public function enqueue_datepicker() {
1590
		$gravityview_view = GravityView_View::getInstance();
1591
1592
		wp_enqueue_script( 'jquery-ui-datepicker' );
1593
1594
		add_filter( 'gravityview_js_dependencies', array( $this, 'add_datepicker_js_dependency' ) );
1595
		add_filter( 'gravityview_js_localization', array( $this, 'add_datepicker_localization' ), 10, 2 );
1596
1597
		$scheme = is_ssl() ? 'https://' : 'http://';
1598
		wp_enqueue_style( 'jquery-ui-datepicker', $scheme.'ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/smoothness/jquery-ui.css' );
1599
1600
		/**
1601
		 * @filter `gravityview_search_datepicker_class`
1602
		 * 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.
1603
		 * @param string $css_class CSS class to use. Default: `gv-datepicker datepicker mdy` \n
1604
		 * Options are:
1605
		 * - `mdy` (mm/dd/yyyy)
1606
		 * - `dmy` (dd/mm/yyyy)
1607
		 * - `dmy_dash` (dd-mm-yyyy)
1608
		 * - `dmy_dot` (dd.mm.yyyy)
1609
		 * - `ymd_slash` (yyyy/mm/dd)
1610
		 * - `ymd_dash` (yyyy-mm-dd)
1611
		 * - `ymd_dot` (yyyy.mm.dd)
1612
		 */
1613
		$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...
1614
1615
		$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...
1616
	}
1617
1618
	/**
1619
	 * Retrieve the datepicker format.
1620
	 *
1621
	 * @param bool $date_format Whether to return the PHP date format or the datpicker class name. Default: false.
1622
	 *
1623
	 * @see https://docs.gravityview.co/article/115-changing-the-format-of-the-search-widgets-date-picker
1624
	 *
1625
	 * @return string The datepicker format placeholder, or the PHP date format.
1626
	 */
1627 18
	private function get_datepicker_format( $date_format = false ) {
1628
1629 18
		$default_format = 'mdy';
1630
1631
		/**
1632
		 * @filter `gravityview/widgets/search/datepicker/format`
1633
		 * @since 2.1.1
1634
		 * @param string           $format Default: mdy
1635
		 * Options are:
1636
		 * - `mdy` (mm/dd/yyyy)
1637
		 * - `dmy` (dd/mm/yyyy)
1638
		 * - `dmy_dash` (dd-mm-yyyy)
1639
		 * - `dmy_dot` (dd.mm.yyyy)
1640
		 * - `ymd_slash` (yyyy/mm/dd)
1641
		 * - `ymd_dash` (yyyy-mm-dd)
1642
		 * - `ymd_dot` (yyyy.mm.dd)
1643
		 */
1644 18
		$format = apply_filters( 'gravityview/widgets/search/datepicker/format', $default_format );
1645
1646
		$gf_date_formats = array(
1647 18
			'mdy' => 'm/d/Y',
1648
1649
			'dmy_dash' => 'd-m-Y',
1650
			'dmy_dot' => 'd.m.Y',
1651
			'dmy' => 'd/m/Y',
1652
1653
			'ymd_slash' => 'Y/m/d',
1654
			'ymd_dash' => 'Y-m-d',
1655
			'ymd_dot' => 'Y.m.d',
1656
		);
1657
1658 18
		if ( ! $date_format ) {
1659
			// If the format key isn't valid, return default format key
1660
			return isset( $gf_date_formats[ $format ] ) ? $format : $default_format;
1661
		}
1662
1663
		// If the format key isn't valid, return default format value
1664 18
		return \GV\Utils::get( $gf_date_formats, $format, $gf_date_formats[ $default_format ] );
1665
	}
1666
1667
1668
} // end class
1669
1670
new GravityView_Widget_Search;
1671