Completed
Push — master ( 578283...5ad19b )
by Zack
15s
created

sql()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
nc 4
nop 1
dl 0
loc 41
ccs 14
cts 14
cp 1
crap 3
rs 9.264
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();
0 ignored issues
show
Unused Code introduced by
The property $search_filters is not used and could be removed.

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

Loading history...
22
23
	/**
24
	 * whether search method is GET or POST ( default: GET )
25
	 * @since 1.16.4
26
	 * @var string
27
	 */
28
	private $search_method = 'get';
29
30 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' );
0 ignored issues
show
Coding Style Compatibility introduced by
The method get_searchable_fields() contains an exit expression.

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

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

Loading history...
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 );
0 ignored issues
show
Coding Style Compatibility introduced by
The method get_searchable_fields() contains an exit expression.

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

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

Loading history...
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__( 'Approval Status', 'gravityview' ),
334
				'type' => 'multi',
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 56
	public function filter_entries( $search_criteria, $form_id = null, $args = array(), $force_search_criteria = false ) {
521 56
		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 37
			add_action( 'gravityview/view/query', array( $this, 'gf_query_filter' ), 10, 3 );
527 37
			return $search_criteria; // Return the original criteria, GF_Query modification kicks in later
528
		}
529
530 55
		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 55
			$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 55
		$view = \GV\View::by_id( \GV\Utils::get( $args, 'id' ) );
537
538 55
		gravityview()->log->debug( 'Requested $_{method}: ', array( 'method' => $this->search_method, 'data' => $get ) );
539
540 55
		if ( empty( $get ) || ! is_array( $get ) ) {
541 33
			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 36
	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 36
		$search_criteria = $this->filter_entries( array(), null, array( 'id' => $view->ID ), true /** force search_criteria */ );
727
728 36
		if ( empty( $search_criteria['field_filters'] ) ) {
729 32
			return;
730
		}
731
732 6
		$widgets = $view->widgets->by_id( $this->widget_id );
733 6
		if ( $widgets->count() ) {
734 6
			$widgets = $widgets->all();
735 6
			$widget  = $widgets[0];
736 6
			foreach ( $search_fields = json_decode( $widget->configuration->get( 'search_fields' ), true ) as $search_field ) {
737 6
				if ( 'created_by' === $search_field['field'] && 'input_text' === $search_field['input'] ) {
738 6
					$created_by_text_mode = true;
739
				}
740
			}
741
		}
742
743 6
		$extra_conditions = array();
744
745 6
		foreach ( $search_criteria['field_filters'] as &$filter ) {
746 6
			if ( ! is_array( $filter ) ) {
747 6
				continue;
748
			}
749
750
			// Construct a manual query for unapproved statuses
751 6
			if ( 'is_approved' === $filter['key'] && in_array( \GravityView_Entry_Approval_Status::UNAPPROVED, (array) $filter['value'], true ) ) {
752
				$_tmp_query       = new GF_Query( $view->form->ID, array(
753
					'field_filters' => array(
754
						array(
755
							'operator' => 'in',
756
							'key'      => 'is_approved',
757
							'value'    => $filter['value'],
758
						),
759
						array(
760
							'operator' => 'is',
761
							'key'      => 'is_approved',
762
							'value'    => '',
763
						),
764
						'mode' => 'any'
0 ignored issues
show
introduced by
Key specified for array entry; first entry has no key
Loading history...
765
					),
766
				) );
767
				$_tmp_query_parts = $_tmp_query->_introspect();
768
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 6
			if ( 'created_by' === $filter['key'] && ! empty( $created_by_text_mode ) ) {
777 1
				$extra_conditions[] = new GravityView_Widget_Search_GF_Query_Condition( $filter, $view );
778 1
				$filter = false;
779 1
				continue;
780
			}
781
782
			// By default, we want searches to be wildcard for each field.
783 5
			$filter['operator'] = empty( $filter['operator'] ) ? 'contains' : $filter['operator'];
784
785
			// For multichoice, let's have an in (OR) search.
786 5
			if ( is_array( $filter['value'] ) ) {
787 3
				$filter['operator'] = 'in'; // @todo what about in contains (OR LIKE chains)?
788
			}
789
790
			/**
791
			 * @filter `gravityview_search_operator` Modify the search operator for the field (contains, is, isnot, etc)
792
			 * @param string $operator Existing search operator
793
			 * @param array $filter array with `key`, `value`, `operator`, `type` keys
794
			 */
795 5
			$filter['operator'] = apply_filters( 'gravityview_search_operator', $filter['operator'], $filter );
796
		}
797
798 6
		$search_criteria['field_filters'] = array_filter( $search_criteria['field_filters'] );
799
800
		/**
801
		 * Parse the filter criteria to generate the needed
802
		 * WHERE clauses. This is a trick to not write our own generation
803
		 * code by reusing what's inside GF_Query already.
804
		 */
805 6
		$_tmp_query       = new GF_Query( $view->form->ID, $search_criteria );
806 6
		$_tmp_query_parts = $_tmp_query->_introspect();
807
808
		/**
809
		 * Grab the current clauses. We'll be combining them shortly.
810
		 */
811 6
		$query_parts      = $query->_introspect();
812
813
		/**
814
		 * Combine the parts as a new WHERE clause.
815
		 */
816 6
		$where = call_user_func_array( '\GF_Query_Condition::_and', array_merge( array( $query_parts['where'], $_tmp_query_parts['where'] ), $extra_conditions ) );
817 6
		$query->where( $where );
818 6
	}
819
820
	/**
821
	 * Convert $_GET/$_POST key to the field/meta ID
822
	 *
823
	 * Examples:
824
	 * - `filter_is_starred` => `is_starred`
825
	 * - `filter_1_2` => `1.2`
826
	 * - `filter_5` => `5`
827
	 *
828
	 * @since 2.0
829
	 *
830
	 * @param string $key $_GET/_$_POST search key
831
	 *
832
	 * @return string
833
	 */
834 13
	private function convert_request_key_to_filter_key( $key ) {
835
836 13
		$field_id = str_replace( 'filter_', '', $key );
837
838
		// calculates field_id, removing 'filter_' and for '_' for advanced fields ( like name or checkbox )
839 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...
840 11
			$field_id = str_replace( '_', '.', $field_id );
841
		}
842
843 13
		return $field_id;
844
	}
845
846
	/**
847
	 * Prepare the field filters to GFAPI
848
	 *
849
	 * 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.
850
	 *
851
	 * Format searched values
852
	 *
853
	 * @param  string $filter_key ID of the field, or entry meta key
854
	 * @param  string $value $_GET/$_POST search value
855
	 * @param  \GV\View $view The view we're looking at
856
	 *
857
	 * @return array        1 or 2 deph levels
858
	 */
859 12
	public function prepare_field_filter( $filter_key, $value, $view ) {
860
861
		// get form field array
862 12
		$form_field = is_numeric( $filter_key ) ? \GV\GF_Field::by_id( $view->form, $filter_key ) : \GV\Internal_Field::by_id( $filter_key );
863
864
		// default filter array
865
		$filter = array(
866 12
			'key'   => $filter_key,
867 12
			'value' => $value,
868
		);
869
870 12
		switch ( $form_field->type ) {
871
872 12
			case 'select':
873 12
			case 'radio':
874 1
				$filter['operator'] = 'is';
875 1
				break;
876
877 11
			case 'post_category':
878
879
				if ( ! is_array( $value ) ) {
880
					$value = array( $value );
881
				}
882
883
				// Reset filter variable
884
				$filter = array();
885
886
				foreach ( $value as $val ) {
887
					$cat = get_term( $val, 'category' );
888
					$filter[] = array(
889
						'key'      => $filter_key,
890
						'value'    => esc_attr( $cat->name ) . ':' . $val,
891
						'operator' => 'is',
892
					);
893
				}
894
895
				break;
896
897 11
			case 'multiselect':
898
899
				if ( ! is_array( $value ) ) {
900
					break;
901
				}
902
903
				// Reset filter variable
904
				$filter = array();
905
906
				foreach ( $value as $val ) {
907
					$filter[] = array( 'key' => $filter_key, 'value' => $val );
908
				}
909
910
				break;
911
912 11
			case 'checkbox':
913
				// convert checkbox on/off into the correct search filter
914
				if ( false !== strpos( $filter_key, '.' ) && ! empty( $form_field->inputs ) && ! empty( $form_field->choices ) ) {
915
					foreach ( $form_field->inputs as $k => $input ) {
916
						if ( $input['id'] == $filter_key ) {
917
							$filter['value'] = $form_field->choices[ $k ]['value'];
918
							$filter['operator'] = 'is';
919
							break;
920
						}
921
					}
922
				} elseif ( is_array( $value ) ) {
923
924
					// Reset filter variable
925
					$filter = array();
926
927
					foreach ( $value as $val ) {
928
						$filter[] = array(
929
							'key'      => $filter_key,
930
							'value'    => $val,
931
							'operator' => 'is',
932
						);
933
					}
934
				}
935
936
				break;
937
938 11
			case 'name':
939 11
			case 'address':
940
941
				if ( false === strpos( $filter_key, '.' ) ) {
942
943
					$words = explode( ' ', $value );
944
945
					$filters = array();
946
					foreach ( $words as $word ) {
947
						if ( ! empty( $word ) && strlen( $word ) > 1 ) {
948
							// Keep the same key for each filter
949
							$filter['value'] = $word;
950
							// Add a search for the value
951
							$filters[] = $filter;
952
						}
953
					}
954
955
					$filter = $filters;
956
				}
957
958
				// State/Province should be exact matches
959
				if ( 'address' === $form_field->field->type ) {
960
961
					$searchable_fields = $this->get_view_searchable_fields( $view, true );
962
963
					foreach ( $searchable_fields as $searchable_field ) {
964
965
						if( $form_field->ID !== $searchable_field['field'] ) {
966
							continue;
967
						}
968
969
						// Only exact-match dropdowns, not text search
970
						if( in_array( $searchable_field['input'], array( 'text', 'search' ), true ) ) {
971
							continue;
972
						}
973
974
						$input_id = gravityview_get_input_id_from_id( $form_field->ID );
975
976
						if ( 4 === $input_id ) {
977
							$filter['operator'] = 'is';
978
						};
979
					}
980
				}
981
982
				break;
983
984 11
			case 'date':
985
986 8
				$date_format = $this->get_datepicker_format( true );
987
988 8
				if ( is_array( $value ) ) {
989
990
					// Reset filter variable
991
					$filter = array();
992
993
					foreach ( $value as $k => $date ) {
994
						if ( empty( $date ) ) {
995
							continue;
996
						}
997
						$operator = 'start' === $k ? '>=' : '<=';
998
999
						/**
1000
						 * @hack
1001
						 * @since 1.16.3
1002
						 * Safeguard until GF implements '<=' operator
1003
						 */
1004
						if( !GFFormsModel::is_valid_operator( $operator ) && $operator === '<=' ) {
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
1005
							$operator = '<';
1006
							$date = date( 'Y-m-d', strtotime( self::get_formatted_date( $date, 'Y-m-d', $date_format ) . ' +1 day' ) );
1007
						}
1008
1009
						$filter[] = array(
1010
							'key'      => $filter_key,
1011
							'value'    => self::get_formatted_date( $date, 'Y-m-d', $date_format ),
1012
							'operator' => $operator,
1013
						);
1014
					}
1015
				} else {
1016 8
					$date = $value;
1017 8
					$filter['value'] = self::get_formatted_date( $date, 'Y-m-d', $date_format );
1018
				}
1019
1020 8
				break;
1021
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
1022
1023
		} // switch field type
1024
1025 12
		return $filter;
1026
	}
1027
1028
	/**
1029
	 * Get the Field Format form GravityForms
1030
	 *
1031
	 * @param GF_Field_Date $field The field object
1032
	 * @since 1.10
1033
	 *
1034
	 * @return string Format of the date in the database
1035
	 */
1036
	public static function get_date_field_format( GF_Field_Date $field ) {
1037
		$format = 'm/d/Y';
1038
		$datepicker = array(
1039
			'mdy' => 'm/d/Y',
1040
			'dmy' => 'd/m/Y',
1041
			'dmy_dash' => 'd-m-Y',
1042
			'dmy_dot' => 'd.m.Y',
1043
			'ymd_slash' => 'Y/m/d',
1044
			'ymd_dash' => 'Y-m-d',
1045
			'ymd_dot' => 'Y.m.d',
1046
		);
1047
1048
		if ( ! empty( $field->dateFormat ) && isset( $datepicker[ $field->dateFormat ] ) ){
1049
			$format = $datepicker[ $field->dateFormat ];
1050
		}
1051
1052
		return $format;
1053
	}
1054
1055
	/**
1056
	 * Format a date value
1057
	 *
1058
	 * @param string $value Date value input
1059
	 * @param string $format Wanted formatted date
1060
	 *
1061
	 * @since 2.1.2
1062
	 * @param string $value_format The value format. Default: Y-m-d
1063
	 *
1064
	 * @return string
1065
	 */
1066 8
	public static function get_formatted_date( $value = '', $format = 'Y-m-d', $value_format = 'Y-m-d' ) {
1067
1068 8
		$date = date_create_from_format( $value_format, $value );
1069
1070 8
		if ( empty( $date ) ) {
1071
			gravityview()->log->debug( 'Date format not valid: {value}', array( 'value' => $value ) );
1072
			return '';
1073
		}
1074 8
		return $date->format( $format );
1075
	}
1076
1077
1078
	/**
1079
	 * Include this extension templates path
1080
	 * @param array $file_paths List of template paths ordered
1081
	 */
1082 1
	public function add_template_path( $file_paths ) {
1083
1084
		// Index 100 is the default GravityView template path.
1085 1
		$file_paths[102] = self::$file . 'templates/';
1086
1087 1
		return $file_paths;
1088
	}
1089
1090
	/**
1091
	 * Check whether the configured search fields have a date field
1092
	 *
1093
	 * @since 1.17.5
1094
	 *
1095
	 * @param array $search_fields
1096
	 *
1097
	 * @return bool True: has a `date` or `date_range` field
1098
	 */
1099 4
	private function has_date_field( $search_fields ) {
1100
1101 4
		$has_date = false;
1102
1103 4
		foreach ( $search_fields as $k => $field ) {
1104 4
			if ( in_array( $field['input'], array( 'date', 'date_range', 'entry_date' ) ) ) {
1105
				$has_date = true;
1106 4
				break;
1107
			}
1108
		}
1109
1110 4
		return $has_date;
1111
	}
1112
1113
	/**
1114
	 * Renders the Search Widget
1115
	 * @param array $widget_args
1116
	 * @param string $content
1117
	 * @param string $context
1118
	 *
1119
	 * @return void
1120
	 */
1121 4
	public function render_frontend( $widget_args, $content = '', $context = '' ) {
1122
		/** @var GravityView_View $gravityview_view */
1123 4
		$gravityview_view = GravityView_View::getInstance();
1124
1125 4
		if ( empty( $gravityview_view ) ) {
1126
			gravityview()->log->debug( '$gravityview_view not instantiated yet.' );
1127
			return;
1128
		}
1129
1130
		// get configured search fields
1131 4
		$search_fields = ! empty( $widget_args['search_fields'] ) ? json_decode( $widget_args['search_fields'], true ) : '';
1132
1133 4
		if ( empty( $search_fields ) || ! is_array( $search_fields ) ) {
1134
			gravityview()->log->debug( 'No search fields configured for widget:', array( 'data' => $widget_args ) );
1135
			return;
1136
		}
1137
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
1138
1139
		// prepare fields
1140 4
		foreach ( $search_fields as $k => $field ) {
1141
1142 4
			$updated_field = $field;
1143
1144 4
			$updated_field = $this->get_search_filter_details( $updated_field );
1145
1146 4
			switch ( $field['field'] ) {
1147
1148 4
				case 'search_all':
1149 4
					$updated_field['key'] = 'search_all';
1150 4
					$updated_field['input'] = 'search_all';
1151 4
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_search' );
1152 4
					break;
1153
1154
				case 'entry_date':
1155
					$updated_field['key'] = 'entry_date';
1156
					$updated_field['input'] = 'entry_date';
1157
					$updated_field['value'] = array(
1158
						'start' => $this->rgget_or_rgpost( 'gv_start' ),
1159
						'end' => $this->rgget_or_rgpost( 'gv_end' ),
1160
					);
1161
					break;
1162
1163
				case 'entry_id':
1164
					$updated_field['key'] = 'entry_id';
1165
					$updated_field['input'] = 'entry_id';
1166
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_id' );
1167
					break;
1168
1169
				case 'created_by':
1170
					$updated_field['key'] = 'created_by';
1171
					$updated_field['name'] = 'gv_by';
1172
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_by' );
1173
					$updated_field['choices'] = self::get_created_by_choices();
1174
					break;
1175
				
1176
				case 'is_approved':
1177
					$updated_field['key'] = 'is_approved';
1178
					$updated_field['value'] = $this->rgget_or_rgpost( 'filter_is_approved' );
1179
					$updated_field['choices'] = self::get_is_approved_choices();
1180
					break;
1181
			}
1182
1183 4
			$search_fields[ $k ] = $updated_field;
1184
		}
1185
1186 4
		gravityview()->log->debug( 'Calculated Search Fields: ', array( 'data' => $search_fields ) );
1187
1188
		/**
1189
		 * @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.
1190
		 * @param array $search_fields Array of search filters with `key`, `label`, `value`, `type`, `choices` keys
1191
		 * @param GravityView_Widget_Search $this Current widget object
1192
		 * @param array $widget_args Args passed to this method. {@since 1.8}
1193
		 * @param \GV\Template_Context $context {@since 2.0}
1194
		 * @var array
1195
		 */
1196 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...
1197
1198 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...
1199
1200
		/** @since 1.14 */
1201 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...
1202
1203 4
		$custom_class = ! empty( $widget_args['custom_class'] ) ? $widget_args['custom_class'] : '';
1204
1205 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...
1206
1207 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...
1208
1209 4
		if ( $this->has_date_field( $search_fields ) ) {
1210
			// enqueue datepicker stuff only if needed!
1211
			$this->enqueue_datepicker();
1212
		}
1213
1214 4
		$this->maybe_enqueue_flexibility();
1215
1216 4
		$gravityview_view->render( 'widget', 'search', false );
1217 4
	}
1218
1219
	/**
1220
	 * Get the search class for a search form
1221
	 *
1222
	 * @since 1.5.4
1223
	 *
1224
	 * @return string Sanitized CSS class for the search form
1225
	 */
1226 4
	public static function get_search_class( $custom_class = '' ) {
1227 4
		$gravityview_view = GravityView_View::getInstance();
1228
1229 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...
1230
1231 4
		if ( ! empty( $custom_class )  ) {
1232
			$search_class .= ' '.$custom_class;
1233
		}
1234
1235
		/**
1236
		 * @filter `gravityview_search_class` Modify the CSS class for the search form
1237
		 * @param string $search_class The CSS class for the search form
1238
		 */
1239 4
		$search_class = apply_filters( 'gravityview_search_class', $search_class );
1240
1241
		// Is there an active search being performed? Used by fe-views.js
1242 4
		$search_class .= GravityView_frontend::getInstance()->isSearch() ? ' gv-is-search' : '';
1243
1244 4
		return gravityview_sanitize_html_class( $search_class );
1245
	}
1246
1247
1248
	/**
1249
	 * Calculate the search form action
1250
	 * @since 1.6
1251
	 *
1252
	 * @return string
1253
	 */
1254 4
	public static function get_search_form_action() {
1255 4
		$gravityview_view = GravityView_View::getInstance();
1256
1257 4
		$post_id = $gravityview_view->getPostId() ? $gravityview_view->getPostId() : $gravityview_view->getViewId();
1258
1259 4
		$url = add_query_arg( array(), get_permalink( $post_id ) );
1260
1261 4
		return esc_url( $url );
1262
	}
1263
1264
	/**
1265
	 * Get the label for a search form field
1266
	 * @param  array $field      Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1267
	 * @param  array $form_field Form field data, as fetched by `gravityview_get_field()`
1268
	 * @return string             Label for the search form
1269
	 */
1270 4
	private static function get_field_label( $field, $form_field = array() ) {
1271
1272 4
		$label = \GV\Utils::_GET( 'label', \GV\Utils::get( $field, 'label' ) );
1273
1274 4
		if ( ! $label ) {
1275
1276 4
			$label = isset( $form_field['label'] ) ? $form_field['label'] : '';
1277
1278 4
			switch( $field['field'] ) {
1279 4
				case 'search_all':
1280 4
					$label = __( 'Search Entries:', 'gravityview' );
1281 4
					break;
1282
				case 'entry_date':
1283
					$label = __( 'Filter by date:', 'gravityview' );
1284
					break;
1285
				case 'entry_id':
1286
					$label = __( 'Entry ID:', 'gravityview' );
1287
					break;
1288
				default:
1289
					// If this is a field input, not a field
1290
					if ( strpos( $field['field'], '.' ) > 0 && ! empty( $form_field['inputs'] ) ) {
1291
1292
						// Get the label for the field in question, which returns an array
1293
						$items = wp_list_filter( $form_field['inputs'], array( 'id' => $field['field'] ) );
1294
1295
						// Get the item with the `label` key
1296
						$values = wp_list_pluck( $items, 'label' );
1297
1298
						// There will only one item in the array, but this is easier
1299
						foreach ( $values as $value ) {
1300
							$label = $value;
1301
							break;
1302
						}
1303
					}
1304
			}
1305
		}
1306
1307
		/**
1308
		 * @filter `gravityview_search_field_label` Modify the label for a search field. Supports returning HTML
1309
		 * @since 1.17.3 Added $field parameter
1310
		 * @param[in,out] string $label Existing label text, sanitized.
1311
		 * @param[in] array $form_field Gravity Forms field array, as returned by `GFFormsModel::get_field()`
1312
		 * @param[in] array $field Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1313
		 */
1314 4
		$label = apply_filters( 'gravityview_search_field_label', esc_attr( $label ), $form_field, $field );
1315
1316 4
		return $label;
1317
	}
1318
1319
	/**
1320
	 * Prepare search fields to frontend render with other details (label, field type, searched values)
1321
	 *
1322
	 * @param array $field
1323
	 * @return array
1324
	 */
1325 4
	private function get_search_filter_details( $field ) {
1326
1327 4
		$gravityview_view = GravityView_View::getInstance();
1328
1329 4
		$form = $gravityview_view->getForm();
1330
1331
		// for advanced field ids (eg, first name / last name )
1332 4
		$name = 'filter_' . str_replace( '.', '_', $field['field'] );
1333
1334
		// get searched value from $_GET/$_POST (string or array)
1335 4
		$value = $this->rgget_or_rgpost( $name );
1336
1337
		// get form field details
1338 4
		$form_field = gravityview_get_field( $form, $field['field'] );
1339
1340
		$filter = array(
1341 4
			'key' => $field['field'],
1342 4
			'name' => $name,
1343 4
			'label' => self::get_field_label( $field, $form_field ),
1344 4
			'input' => $field['input'],
1345 4
			'value' => $value,
1346 4
			'type' => $form_field['type'],
1347
		);
1348
1349
		// collect choices
1350 4
		if ( 'post_category' === $form_field['type'] && ! empty( $form_field['displayAllCategories'] ) && empty( $form_field['choices'] ) ) {
1351
			$filter['choices'] = gravityview_get_terms_choices();
1352 4
		} elseif ( ! empty( $form_field['choices'] ) ) {
1353
			$filter['choices'] = $form_field['choices'];
1354
		}
1355
1356 4
		if ( 'date_range' === $field['input'] && empty( $value ) ) {
1357
			$filter['value'] = array( 'start' => '', 'end' => '' );
1358
		}
1359
1360 4
		return $filter;
1361
1362
	}
1363
1364
	/**
1365
	 * Calculate the search choices for the users
1366
	 *
1367
	 * @since 1.8
1368
	 *
1369
	 * @return array Array of user choices (value = ID, text = display name)
1370
	 */
1371
	private static function get_created_by_choices() {
1372
1373
		/**
1374
		 * filter gravityview/get_users/search_widget
1375
		 * @see \GVCommon::get_users
1376
		 */
1377
		$users = GVCommon::get_users( 'search_widget', array( 'fields' => array( 'ID', 'display_name' ) ) );
1378
1379
		$choices = array();
1380
		foreach ( $users as $user ) {
1381
			$choices[] = array(
1382
				'value' => $user->ID,
1383
				'text' => $user->display_name,
1384
			);
1385
		}
1386
1387
		return $choices;
1388
	}
1389
1390
	/**
1391
	 * Calculate the search checkbox choices for approval status
1392
	 *
1393
	 * @since develop
1394
	 *
1395
	 * @return array Array of approval status choices (value = status, text = display name)
1396
	 */
1397
	private static function get_is_approved_choices() {
1398
1399
		$choices = array();
1400
		foreach ( GravityView_Entry_Approval_Status::get_all() as $status ) {
1401
			$choices[] = array(
1402
				'value' => $status['value'],
1403
				'text' => $status['label'],
1404
			);
1405
		}
1406
1407
		return $choices;
1408
	}
1409
1410
	/**
1411
	 * Output the Clear Search Results button
1412
	 * @since 1.5.4
1413
	 */
1414 4
	public static function the_clear_search_button() {
1415 4
		$gravityview_view = GravityView_View::getInstance();
1416
1417 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...
1418
1419
			$url = strtok( add_query_arg( array() ), '?' );
1420
1421
			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...
1422
1423
		}
1424 4
	}
1425
1426
	/**
1427
	 * Based on the search method, fetch the value for a specific key
1428
	 *
1429
	 * @since 1.16.4
1430
	 *
1431
	 * @param string $name Name of the request key to fetch the value for
1432
	 *
1433
	 * @return mixed|string Value of request at $name key. Empty string if empty.
1434
	 */
1435 4
	private function rgget_or_rgpost( $name ) {
1436 4
		$value = \GV\Utils::_REQUEST( $name );
1437
1438 4
		$value = stripslashes_deep( $value );
1439
1440 4
		$value = gv_map_deep( $value, 'rawurldecode' );
1441
1442 4
		$value = gv_map_deep( $value, '_wp_specialchars' );
1443
1444 4
		return $value;
1445
	}
1446
1447
1448
	/**
1449
	 * Require the datepicker script for the frontend GV script
1450
	 * @param array $js_dependencies Array of existing required scripts for the fe-views.js script
1451
	 * @return array Array required scripts, with `jquery-ui-datepicker` added
1452
	 */
1453
	public function add_datepicker_js_dependency( $js_dependencies ) {
1454
1455
		$js_dependencies[] = 'jquery-ui-datepicker';
1456
1457
		return $js_dependencies;
1458
	}
1459
1460
	/**
1461
	 * Modify the array passed to wp_localize_script()
1462
	 *
1463
	 * @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...
1464
	 * @param array $view_data View data array with View settings
1465
	 *
1466
	 * @return array
1467
	 */
1468
	public function add_datepicker_localization( $localizations = array(), $view_data = array() ) {
1469
		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...
1470
1471
		/**
1472
		 * @filter `gravityview_datepicker_settings` Modify the datepicker settings
1473
		 * @see http://api.jqueryui.com/datepicker/ Learn what settings are available
1474
		 * @see http://www.renegadetechconsulting.com/tutorials/jquery-datepicker-and-wordpress-i18n Thanks for the helpful information on $wp_locale
1475
		 * @param array $js_localization The data padded to the Javascript file
1476
		 * @param array $view_data View data array with View settings
1477
		 */
1478
		$datepicker_settings = apply_filters( 'gravityview_datepicker_settings', array(
1479
			'yearRange' => '-5:+5',
1480
			'changeMonth' => true,
1481
			'changeYear' => true,
1482
			'closeText' => esc_attr_x( 'Close', 'Close calendar', 'gravityview' ),
1483
			'prevText' => esc_attr_x( 'Prev', 'Previous month in calendar', 'gravityview' ),
1484
			'nextText' => esc_attr_x( 'Next', 'Next month in calendar', 'gravityview' ),
1485
			'currentText' => esc_attr_x( 'Today', 'Today in calendar', 'gravityview' ),
1486
			'weekHeader' => esc_attr_x( 'Week', 'Week in calendar', 'gravityview' ),
1487
			'monthStatus'       => __( 'Show a different month', 'gravityview' ),
1488
			'monthNames'        => array_values( $wp_locale->month ),
1489
			'monthNamesShort'   => array_values( $wp_locale->month_abbrev ),
1490
			'dayNames'          => array_values( $wp_locale->weekday ),
1491
			'dayNamesShort'     => array_values( $wp_locale->weekday_abbrev ),
1492
			'dayNamesMin'       => array_values( $wp_locale->weekday_initial ),
1493
			// get the start of week from WP general setting
1494
			'firstDay'          => get_option( 'start_of_week' ),
1495
			// is Right to left language? default is false
1496
			'isRTL'             => is_rtl(),
1497
		), $view_data );
1498
1499
		$localizations['datepicker'] = $datepicker_settings;
1500
1501
		return $localizations;
1502
1503
	}
1504
1505
	/**
1506
	 * Register search widget scripts, including Flexibility
1507
	 *
1508
	 * @see https://github.com/10up/flexibility
1509
	 *
1510
	 * @since 1.17
1511
	 *
1512
	 * @return void
1513
	 */
1514
	public function register_scripts() {
1515
		wp_register_script( 'gv-flexibility', plugins_url( 'assets/lib/flexibility/flexibility.js', GRAVITYVIEW_FILE ), array(), \GV\Plugin::$version, true );
1516
	}
1517
1518
	/**
1519
	 * If the current visitor is running IE 8 or 9, enqueue Flexibility
1520
	 *
1521
	 * @since 1.17
1522
	 *
1523
	 * @return void
1524
	 */
1525 4
	private function maybe_enqueue_flexibility() {
1526 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...
1527
			wp_enqueue_script( 'gv-flexibility' );
1528
		}
1529 4
	}
1530
1531
	/**
1532
	 * Enqueue the datepicker script
1533
	 *
1534
	 * It sets the $gravityview->datepicker_class parameter
1535
	 *
1536
	 * @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.
1537
	 * @return void
1538
	 */
1539
	public function enqueue_datepicker() {
1540
		$gravityview_view = GravityView_View::getInstance();
1541
1542
		wp_enqueue_script( 'jquery-ui-datepicker' );
1543
1544
		add_filter( 'gravityview_js_dependencies', array( $this, 'add_datepicker_js_dependency' ) );
1545
		add_filter( 'gravityview_js_localization', array( $this, 'add_datepicker_localization' ), 10, 2 );
1546
1547
		$scheme = is_ssl() ? 'https://' : 'http://';
1548
		wp_enqueue_style( 'jquery-ui-datepicker', $scheme.'ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/smoothness/jquery-ui.css' );
1549
1550
		/**
1551
		 * @filter `gravityview_search_datepicker_class`
1552
		 * 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.
1553
		 * @param string $css_class CSS class to use. Default: `gv-datepicker datepicker mdy` \n
1554
		 * Options are:
1555
		 * - `mdy` (mm/dd/yyyy)
1556
		 * - `dmy` (dd/mm/yyyy)
1557
		 * - `dmy_dash` (dd-mm-yyyy)
1558
		 * - `dmy_dot` (dd.mm.yyyy)
1559
		 * - `ymd_slash` (yyyy/mm/dd)
1560
		 * - `ymd_dash` (yyyy-mm-dd)
1561
		 * - `ymd_dot` (yyyy.mm.dd)
1562
		 */
1563
		$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...
1564
1565
		$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...
1566
	}
1567
1568
	/**
1569
	 * Retrieve the datepicker format.
1570
	 *
1571
	 * @param bool $date_format Whether to return the PHP date format or the datpicker class name. Default: false.
1572
	 *
1573
	 * @see https://docs.gravityview.co/article/115-changing-the-format-of-the-search-widgets-date-picker
1574
	 *
1575
	 * @return string The datepicker format placeholder, or the PHP date format.
1576
	 */
1577 18
	private function get_datepicker_format( $date_format = false ) {
1578
1579 18
		$default_format = 'mdy';
1580
1581
		/**
1582
		 * @filter `gravityview/widgets/search/datepicker/format`
1583
		 * @since 2.1.1
1584
		 * @param string           $format Default: mdy
1585
		 * Options are:
1586
		 * - `mdy` (mm/dd/yyyy)
1587
		 * - `dmy` (dd/mm/yyyy)
1588
		 * - `dmy_dash` (dd-mm-yyyy)
1589
		 * - `dmy_dot` (dd.mm.yyyy)
1590
		 * - `ymd_slash` (yyyy/mm/dd)
1591
		 * - `ymd_dash` (yyyy-mm-dd)
1592
		 * - `ymd_dot` (yyyy.mm.dd)
1593
		 */
1594 18
		$format = apply_filters( 'gravityview/widgets/search/datepicker/format', $default_format );
1595
1596
		$gf_date_formats = array(
1597 18
			'mdy' => 'm/d/Y',
1598
1599
			'dmy_dash' => 'd-m-Y',
1600
			'dmy_dot' => 'd.m.Y',
1601
			'dmy' => 'd/m/Y',
1602
1603
			'ymd_slash' => 'Y/m/d',
1604
			'ymd_dash' => 'Y-m-d',
1605
			'ymd_dot' => 'Y.m.d',
1606
		);
1607
1608 18
		if ( ! $date_format ) {
1609
			// If the format key isn't valid, return default format key
1610
			return isset( $gf_date_formats[ $format ] ) ? $format : $default_format;
1611
		}
1612
1613
		// If the format key isn't valid, return default format value
1614 18
		return \GV\Utils::get( $gf_date_formats, $format, $gf_date_formats[ $default_format ] );
1615
	}
1616
1617
1618
} // end class
1619
1620
new GravityView_Widget_Search;
1621
1622
if ( ! gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
1623
	return;
1624
}
1625
1626
/**
1627
 * A GF_Query condition that allows user data searches.
1628
 */
1629
class GravityView_Widget_Search_GF_Query_Condition extends \GF_Query_Condition {
1630 1
	public function __construct( $filter, $view ) {
1631 1
		$this->value = $filter['value'];
1632 1
		$this->view = $view;
1633 1
	}
1634
1635 1
	public function sql( $query ) {
1636 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...
1637
1638
		$user_meta_fields = array(
1639 1
			'nickname', 'first_name', 'last_name',
1640
		);
1641
1642
		/**
1643
		 * @filter `gravityview/widgets/search/created_by/user_meta_fields` Filter the user meta fields to search.
1644
		 * @param[in,out] array The user meta fields.
1645
		 * @param \GV\View $view The view.
1646
		 */
1647 1
		$user_meta_fields = apply_filters( 'gravityview/widgets/search/created_by/user_meta_fields', $user_meta_fields, $this->view );
1648
1649
		$user_fields = array(
1650 1
			'user_nicename', 'user_login', 'display_name', 'user_email', 
1651
		);
1652
1653
		/**
1654
		 * @filter `gravityview/widgets/search/created_by/user_fields` Filter the user fields to search.
1655
		 * @param[in,out] array The user fields.
1656
		 * @param \GV\View $view The view.
1657
		 */
1658 1
		$user_fields = apply_filters( 'gravityview/widgets/search/created_by/user_fields', $user_fields, $this->view );
1659
1660 1
		$conditions = array();
1661
1662 1
		foreach ( $user_fields as $user_field ) {
1663 1
			$conditions[] = $wpdb->prepare( "`u`.`$user_field` LIKE %s", '%' . $wpdb->esc_like( $this->value ) .  '%' );
1664
		}
1665
1666 1
		foreach ( $user_meta_fields as $meta_field ) {
1667 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...
1668
		}
1669
1670 1
		$conditions = '(' . implode( ' OR ', $conditions ) . ')';
1671
1672 1
		$alias = $query->_alias( null );
1673
1674 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...
1675
	}
1676
}
1677