Completed
Push — develop ( ba52e7...22f9e6 )
by Gennady
17:40
created

GravityView_Widget_Search_GF_Query_Condition   A

Complexity

Total Complexity 4

Size/Duplication

Total Lines 50
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 0

Test Coverage

Coverage 80%

Importance

Changes 0
Metric Value
dl 0
loc 50
ccs 4
cts 5
cp 0.8
rs 10
c 0
b 0
f 0
wmc 4
lcom 0
cbo 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A sql() 0 43 3
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__( '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
			$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
					$created_by_text_mode = true;
739
				}
740
			}
741
		}
742 6
743
		$extra_conditions = array();
744 6
745 6
		foreach ( $search_criteria['field_filters'] as &$filter ) {
746 6
			if ( ! is_array( $filter ) ) {
747
				continue;
748
			}
749
750 6
			// Construct a manual query for unapproved statuses
751 2
			if ( 'is_approved' === $filter['key'] && in_array( \GravityView_Entry_Approval_Status::UNAPPROVED, $filter['value'] ) ) {
752 2
				$_tmp_query       = new GF_Query( $view->form->ID, array(
753
					'field_filters' => array(
754 2
						array(
755 2
							'operator' => 'in',
756 2
							'key'      => 'is_approved',
757
							'value'    => $filter['value'],
758
						),
759
						array(
760
							'operator' => 'is',
761
							'key'      => 'is_approved',
762
							'value'    => '',
763 2
						),
764
						'mode' => 'any'
0 ignored issues
show
introduced by
Key specified for array entry; first entry has no key
Loading history...
765
					),
766 2
				) );
767
				$_tmp_query_parts = $_tmp_query->_introspect();
768 2
769
				$extra_conditions[] = $_tmp_query_parts['where'];
770 2
771 2
				$filter = false;
772
				continue;
773
			}
774
775 6
			// Construct manual query for text mode creator search
776
			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 1
			}
781
782 1
			// By default, we want searches to be wildcard for each field.
783
			$filter['operator'] = empty( $filter['operator'] ) ? 'contains' : $filter['operator'];
784 1
785
			// For multichoice, let's have an in (OR) search.
786
			if ( is_array( $filter['value'] ) ) {
787 1
				$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 1
			 */
795
			$filter['operator'] = apply_filters( 'gravityview_search_operator', $filter['operator'], $filter );
796
		}
797
798 1
		$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 1
		$_tmp_query       = new GF_Query( $view->form->ID, $search_criteria );
806
		$_tmp_query_parts = $_tmp_query->_introspect();
807 1
808
		/**
809 1
		 * Grab the current clauses. We'll be combining them shortly.
810
		 */
811 1
		$query_parts      = $query->_introspect();
812 1
813
		/**
814
		 * Combine the parts as a new WHERE clause.
815 1
		 */
816 1
		$where = call_user_func_array( '\GF_Query_Condition::_and', array_merge( array( $query_parts['where'], $_tmp_query_parts['where'] ), $extra_conditions ) );
817
		$query->where( $where );
818
	}
819 1
820
	/**
821 1
	 * Convert $_GET/$_POST key to the field/meta ID
822
	 *
823 1
	 * Examples:
824
	 * - `filter_is_starred` => `is_starred`
825
	 * - `filter_1_2` => `1.2`
826
	 * - `filter_5` => `5`
827 1
	 *
828 1
	 * @since 2.0
829
	 *
830
	 * @param string $key $_GET/_$_POST search key
831
	 *
832 5
	 * @return string
833
	 */
834
	private function convert_request_key_to_filter_key( $key ) {
835 5
836 3
		$field_id = str_replace( 'filter_', '', $key );
837
838
		// calculates field_id, removing 'filter_' and for '_' for advanced fields ( like name or checkbox )
839
		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
			$field_id = str_replace( '_', '.', $field_id );
841
		}
842
843
		return $field_id;
844 5
	}
845
846
	/**
847 6
	 * 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 6
	 * @param  string $value $_GET/$_POST search value
855 6
	 * @param  \GV\View $view The view we're looking at
856
	 *
857
	 * @return array        1 or 2 deph levels
858
	 */
859
	public function prepare_field_filter( $filter_key, $value, $view ) {
860 6
861
		// get form field array
862
		$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 6
		$filter = array(
866 6
			'key'   => $filter_key,
867 6
			'value' => $value,
868
		);
869
870
		switch ( $form_field->type ) {
871
872
			case 'select':
873
			case 'radio':
874
				$filter['operator'] = 'is';
875
				break;
876
877
			case 'post_category':
878
879
				if ( ! is_array( $value ) ) {
880
					$value = array( $value );
881
				}
882
883 13
				// Reset filter variable
884
				$filter = array();
885 13
886
				foreach ( $value as $val ) {
887
					$cat = get_term( $val, 'category' );
888 13
					$filter[] = array(
889 11
						'key'      => $filter_key,
890
						'value'    => esc_attr( $cat->name ) . ':' . $val,
891
						'operator' => 'is',
892 13
					);
893
				}
894
895
				break;
896
897
			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 12
				}
909
910
				break;
911 12
912
			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 12
					foreach ( $form_field->inputs as $k => $input ) {
916 12
						if ( $input['id'] == $filter_key ) {
917
							$filter['value'] = $form_field->choices[ $k ]['value'];
918
							$filter['operator'] = 'is';
919 12
							break;
920
						}
921 12
					}
922 12
				} elseif ( is_array( $value ) ) {
923 1
924 1
					// Reset filter variable
925
					$filter = array();
926 11
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
			case 'name':
939
			case 'address':
940
941
				if ( false === strpos( $filter_key, '.' ) ) {
942
943
					$words = explode( ' ', $value );
944
945
					$filters = array();
946 11
					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 11
					$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
			case 'date':
985
986
				$date_format = $this->get_datepicker_format( true );
987 11
988 11
				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
					$date = $value;
1017
					$filter['value'] = self::get_formatted_date( $date, 'Y-m-d', $date_format );
1018
				}
1019
1020
				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
		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 11
	 *
1034
	 * @return string Format of the date in the database
1035 8
	 */
1036
	public static function get_date_field_format( GF_Field_Date $field ) {
1037 8
		$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 8
	 */
1066 8
	public static function get_formatted_date( $value = '', $format = 'Y-m-d', $value_format = 'Y-m-d' ) {
1067
1068
		$date = date_create_from_format( $value_format, $value );
1069 8
1070
		if ( empty( $date ) ) {
1071
			gravityview()->log->debug( 'Date format not valid: {value}', array( 'value' => $value ) );
1072
			return '';
1073
		}
1074 12
		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
	public function add_template_path( $file_paths ) {
1083
1084
		// Index 100 is the default GravityView template path.
1085
		$file_paths[102] = self::$file . 'templates/';
1086
1087
		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
	private function has_date_field( $search_fields ) {
1100
1101
		$has_date = false;
1102
1103
		foreach ( $search_fields as $k => $field ) {
1104
			if ( in_array( $field['input'], array( 'date', 'date_range', 'entry_date' ) ) ) {
1105
				$has_date = true;
1106
				break;
1107
			}
1108
		}
1109
1110
		return $has_date;
1111
	}
1112
1113
	/**
1114
	 * Renders the Search Widget
1115 8
	 * @param array $widget_args
1116
	 * @param string $content
1117 8
	 * @param string $context
1118
	 *
1119 8
	 * @return void
1120
	 */
1121
	public function render_frontend( $widget_args, $content = '', $context = '' ) {
1122
		/** @var GravityView_View $gravityview_view */
1123 8
		$gravityview_view = GravityView_View::getInstance();
1124
1125
		if ( empty( $gravityview_view ) ) {
1126
			gravityview()->log->debug( '$gravityview_view not instantiated yet.' );
1127
			return;
1128
		}
1129
1130
		// get configured search fields
1131 1
		$search_fields = ! empty( $widget_args['search_fields'] ) ? json_decode( $widget_args['search_fields'], true ) : '';
1132
1133
		if ( empty( $search_fields ) || ! is_array( $search_fields ) ) {
1134 1
			gravityview()->log->debug( 'No search fields configured for widget:', array( 'data' => $widget_args ) );
1135
			return;
1136 1
		}
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
		foreach ( $search_fields as $k => $field ) {
1141
1142
			$updated_field = $field;
1143
1144
			$updated_field = $this->get_search_filter_details( $updated_field );
1145
1146
			switch ( $field['field'] ) {
1147
1148 4
				case 'search_all':
1149
					$updated_field['key'] = 'search_all';
1150 4
					$updated_field['input'] = 'search_all';
1151
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_search' );
1152 4
					break;
1153 4
1154
				case 'entry_date':
1155 4
					$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 4
						'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 4
					$updated_field['key'] = 'created_by';
1171
					$updated_field['name'] = 'gv_by';
1172 4
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_by' );
1173
					$updated_field['choices'] = self::get_created_by_choices();
1174 4
					break;
1175
				
1176
				case 'is_approved':
1177
					$updated_field['key'] = 'is_approved';
1178
					$updated_field['input'] = 'checkbox';
1179
					$updated_field['value'] = $this->rgget_or_rgpost( 'filter_is_approved' );
1180 4
					$updated_field['choices'] = self::get_is_approved_choices();
1181
					break;
1182 4
			}
1183
1184
			$search_fields[ $k ] = $updated_field;
1185
		}
1186
1187
		gravityview()->log->debug( 'Calculated Search Fields: ', array( 'data' => $search_fields ) );
1188
1189 4
		/**
1190
		 * @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.
1191 4
		 * @param array $search_fields Array of search filters with `key`, `label`, `value`, `type`, `choices` keys
1192
		 * @param GravityView_Widget_Search $this Current widget object
1193 4
		 * @param array $widget_args Args passed to this method. {@since 1.8}
1194
		 * @param \GV\Template_Context $context {@since 2.0}
1195 4
		 * @var array
1196
		 */
1197 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...
1198 4
1199 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...
1200 4
1201 4
		/** @since 1.14 */
1202
		$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...
1203
1204
		$custom_class = ! empty( $widget_args['custom_class'] ) ? $widget_args['custom_class'] : '';
1205
1206
		$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...
1207
1208
		$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...
1209
1210
		if ( $this->has_date_field( $search_fields ) ) {
1211
			// enqueue datepicker stuff only if needed!
1212
			$this->enqueue_datepicker();
1213
		}
1214
1215
		$this->maybe_enqueue_flexibility();
1216
1217
		$gravityview_view->render( 'widget', 'search', false );
1218
	}
1219
1220
	/**
1221
	 * Get the search class for a search form
1222
	 *
1223
	 * @since 1.5.4
1224
	 *
1225
	 * @return string Sanitized CSS class for the search form
1226
	 */
1227
	public static function get_search_class( $custom_class = '' ) {
1228
		$gravityview_view = GravityView_View::getInstance();
1229
1230
		$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...
1231
1232
		if ( ! empty( $custom_class )  ) {
1233 4
			$search_class .= ' '.$custom_class;
1234
		}
1235
1236 4
		/**
1237
		 * @filter `gravityview_search_class` Modify the CSS class for the search form
1238
		 * @param string $search_class The CSS class for the search form
1239
		 */
1240
		$search_class = apply_filters( 'gravityview_search_class', $search_class );
1241
1242
		// Is there an active search being performed? Used by fe-views.js
1243
		$search_class .= GravityView_frontend::getInstance()->isSearch() ? ' gv-is-search' : '';
1244
1245
		return gravityview_sanitize_html_class( $search_class );
1246 4
	}
1247
1248 4
1249
	/**
1250
	 * Calculate the search form action
1251 4
	 * @since 1.6
1252
	 *
1253 4
	 * @return string
1254
	 */
1255 4
	public static function get_search_form_action() {
1256
		$gravityview_view = GravityView_View::getInstance();
1257 4
1258
		$post_id = $gravityview_view->getPostId() ? $gravityview_view->getPostId() : $gravityview_view->getViewId();
1259 4
1260
		$url = add_query_arg( array(), get_permalink( $post_id ) );
1261
1262
		return esc_url( $url );
1263
	}
1264 4
1265
	/**
1266 4
	 * Get the label for a search form field
1267 4
	 * @param  array $field      Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1268
	 * @param  array $form_field Form field data, as fetched by `gravityview_get_field()`
1269
	 * @return string             Label for the search form
1270
	 */
1271
	private static function get_field_label( $field, $form_field = array() ) {
1272
1273
		$label = \GV\Utils::_GET( 'label', \GV\Utils::get( $field, 'label' ) );
1274
1275
		if ( ! $label ) {
1276 4
1277 4
			$label = isset( $form_field['label'] ) ? $form_field['label'] : '';
1278
1279 4
			switch( $field['field'] ) {
1280
				case 'search_all':
1281 4
					$label = __( 'Search Entries:', 'gravityview' );
1282
					break;
1283
				case 'entry_date':
1284
					$label = __( 'Filter by date:', 'gravityview' );
1285
					break;
1286
				case 'entry_id':
1287
					$label = __( 'Entry ID:', 'gravityview' );
1288
					break;
1289 4
				default:
1290
					// If this is a field input, not a field
1291
					if ( strpos( $field['field'], '.' ) > 0 && ! empty( $form_field['inputs'] ) ) {
1292 4
1293
						// Get the label for the field in question, which returns an array
1294 4
						$items = wp_list_filter( $form_field['inputs'], array( 'id' => $field['field'] ) );
1295
1296
						// Get the item with the `label` key
1297
						$values = wp_list_pluck( $items, 'label' );
1298
1299
						// There will only one item in the array, but this is easier
1300
						foreach ( $values as $value ) {
1301
							$label = $value;
1302
							break;
1303
						}
1304 4
					}
1305 4
			}
1306
		}
1307 4
1308
		/**
1309 4
		 * @filter `gravityview_search_field_label` Modify the label for a search field. Supports returning HTML
1310
		 * @since 1.17.3 Added $field parameter
1311 4
		 * @param[in,out] string $label Existing label text, sanitized.
1312
		 * @param[in] array $form_field Gravity Forms field array, as returned by `GFFormsModel::get_field()`
1313
		 * @param[in] array $field Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1314
		 */
1315
		$label = apply_filters( 'gravityview_search_field_label', esc_attr( $label ), $form_field, $field );
1316
1317
		return $label;
1318
	}
1319
1320 4
	/**
1321
	 * Prepare search fields to frontend render with other details (label, field type, searched values)
1322 4
	 *
1323
	 * @param array $field
1324 4
	 * @return array
1325
	 */
1326 4
	private function get_search_filter_details( $field ) {
1327
1328 4
		$gravityview_view = GravityView_View::getInstance();
1329 4
1330 4
		$form = $gravityview_view->getForm();
1331 4
1332
		// for advanced field ids (eg, first name / last name )
1333
		$name = 'filter_' . str_replace( '.', '_', $field['field'] );
1334
1335
		// get searched value from $_GET/$_POST (string or array)
1336
		$value = $this->rgget_or_rgpost( $name );
1337
1338
		// get form field details
1339
		$form_field = gravityview_get_field( $form, $field['field'] );
1340
1341
		$filter = array(
1342
			'key' => $field['field'],
1343
			'name' => $name,
1344
			'label' => self::get_field_label( $field, $form_field ),
1345
			'input' => $field['input'],
1346
			'value' => $value,
1347
			'type' => $form_field['type'],
1348
		);
1349
1350
		// collect choices
1351
		if ( 'post_category' === $form_field['type'] && ! empty( $form_field['displayAllCategories'] ) && empty( $form_field['choices'] ) ) {
1352
			$filter['choices'] = gravityview_get_terms_choices();
1353
		} elseif ( ! empty( $form_field['choices'] ) ) {
1354
			$filter['choices'] = $form_field['choices'];
1355
		}
1356
1357
		if ( 'date_range' === $field['input'] && empty( $value ) ) {
1358
			$filter['value'] = array( 'start' => '', 'end' => '' );
1359
		}
1360
1361
		return $filter;
1362
1363
	}
1364 4
1365
	/**
1366 4
	 * Calculate the search choices for the users
1367
	 *
1368
	 * @since 1.8
1369
	 *
1370
	 * @return array Array of user choices (value = ID, text = display name)
1371
	 */
1372
	private static function get_created_by_choices() {
1373
1374
		/**
1375 4
		 * filter gravityview/get_users/search_widget
1376
		 * @see \GVCommon::get_users
1377 4
		 */
1378
		$users = GVCommon::get_users( 'search_widget', array( 'fields' => array( 'ID', 'display_name' ) ) );
1379 4
1380
		$choices = array();
1381
		foreach ( $users as $user ) {
1382 4
			$choices[] = array(
1383
				'value' => $user->ID,
1384
				'text' => $user->display_name,
1385 4
			);
1386
		}
1387
1388 4
		return $choices;
1389
	}
1390
1391 4
	/**
1392 4
	 * Calculate the search checkbox choices for approval status
1393 4
	 *
1394 4
	 * @since develop
1395 4
	 *
1396 4
	 * @return array Array of approval status choices (value = status, text = display name)
1397
	 */
1398
	private static function get_is_approved_choices() {
1399
1400 4
		$choices = array();
1401
		foreach ( GravityView_Entry_Approval_Status::get_all() as $status ) {
1402 4
			$choices[] = array(
1403
				'value' => $status['value'],
1404
				'text' => $status['label'],
1405
			);
1406 4
		}
1407
1408
		return $choices;
1409
	}
1410 4
1411
	/**
1412
	 * Output the Clear Search Results button
1413
	 * @since 1.5.4
1414
	 */
1415
	public static function the_clear_search_button() {
1416
		$gravityview_view = GravityView_View::getInstance();
1417
1418
		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...
1419
1420
			$url = strtok( add_query_arg( array() ), '?' );
1421
1422
			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...
1423
1424
		}
1425
	}
1426
1427
	/**
1428
	 * Based on the search method, fetch the value for a specific key
1429
	 *
1430
	 * @since 1.16.4
1431
	 *
1432
	 * @param string $name Name of the request key to fetch the value for
1433
	 *
1434
	 * @return mixed|string Value of request at $name key. Empty string if empty.
1435
	 */
1436
	private function rgget_or_rgpost( $name ) {
1437
		$value = \GV\Utils::_REQUEST( $name );
1438
1439
		$value = stripslashes_deep( $value );
1440
1441
		$value = gv_map_deep( $value, 'rawurldecode' );
1442
1443
		$value = gv_map_deep( $value, '_wp_specialchars' );
1444
1445
		return $value;
1446
	}
1447
1448
1449
	/**
1450
	 * Require the datepicker script for the frontend GV script
1451
	 * @param array $js_dependencies Array of existing required scripts for the fe-views.js script
1452
	 * @return array Array required scripts, with `jquery-ui-datepicker` added
1453
	 */
1454
	public function add_datepicker_js_dependency( $js_dependencies ) {
1455
1456
		$js_dependencies[] = 'jquery-ui-datepicker';
1457
1458
		return $js_dependencies;
1459
	}
1460
1461
	/**
1462
	 * Modify the array passed to wp_localize_script()
1463
	 *
1464 4
	 * @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...
1465 4
	 * @param array $view_data View data array with View settings
1466
	 *
1467 4
	 * @return array
1468
	 */
1469
	public function add_datepicker_localization( $localizations = array(), $view_data = array() ) {
1470
		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...
1471
1472
		/**
1473
		 * @filter `gravityview_datepicker_settings` Modify the datepicker settings
1474 4
		 * @see http://api.jqueryui.com/datepicker/ Learn what settings are available
1475
		 * @see http://www.renegadetechconsulting.com/tutorials/jquery-datepicker-and-wordpress-i18n Thanks for the helpful information on $wp_locale
1476
		 * @param array $js_localization The data padded to the Javascript file
1477
		 * @param array $view_data View data array with View settings
1478
		 */
1479
		$datepicker_settings = apply_filters( 'gravityview_datepicker_settings', array(
1480
			'yearRange' => '-5:+5',
1481
			'changeMonth' => true,
1482
			'changeYear' => true,
1483
			'closeText' => esc_attr_x( 'Close', 'Close calendar', 'gravityview' ),
1484
			'prevText' => esc_attr_x( 'Prev', 'Previous month in calendar', 'gravityview' ),
1485 4
			'nextText' => esc_attr_x( 'Next', 'Next month in calendar', 'gravityview' ),
1486 4
			'currentText' => esc_attr_x( 'Today', 'Today in calendar', 'gravityview' ),
1487
			'weekHeader' => esc_attr_x( 'Week', 'Week in calendar', 'gravityview' ),
1488 4
			'monthStatus'       => __( 'Show a different month', 'gravityview' ),
1489
			'monthNames'        => array_values( $wp_locale->month ),
1490 4
			'monthNamesShort'   => array_values( $wp_locale->month_abbrev ),
1491
			'dayNames'          => array_values( $wp_locale->weekday ),
1492 4
			'dayNamesShort'     => array_values( $wp_locale->weekday_abbrev ),
1493
			'dayNamesMin'       => array_values( $wp_locale->weekday_initial ),
1494 4
			// get the start of week from WP general setting
1495
			'firstDay'          => get_option( 'start_of_week' ),
1496
			// is Right to left language? default is false
1497
			'isRTL'             => is_rtl(),
1498
		), $view_data );
1499
1500
		$localizations['datepicker'] = $datepicker_settings;
1501
1502
		return $localizations;
1503
1504
	}
1505
1506
	/**
1507
	 * Register search widget scripts, including Flexibility
1508
	 *
1509
	 * @see https://github.com/10up/flexibility
1510
	 *
1511
	 * @since 1.17
1512
	 *
1513
	 * @return void
1514
	 */
1515
	public function register_scripts() {
1516
		wp_register_script( 'gv-flexibility', plugins_url( 'assets/lib/flexibility/flexibility.js', GRAVITYVIEW_FILE ), array(), \GV\Plugin::$version, true );
1517
	}
1518
1519
	/**
1520
	 * If the current visitor is running IE 8 or 9, enqueue Flexibility
1521
	 *
1522
	 * @since 1.17
1523
	 *
1524
	 * @return void
1525
	 */
1526
	private function maybe_enqueue_flexibility() {
1527
		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...
1528
			wp_enqueue_script( 'gv-flexibility' );
1529
		}
1530
	}
1531
1532
	/**
1533
	 * Enqueue the datepicker script
1534
	 *
1535
	 * It sets the $gravityview->datepicker_class parameter
1536
	 *
1537
	 * @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.
1538
	 * @return void
1539
	 */
1540
	public function enqueue_datepicker() {
1541
		$gravityview_view = GravityView_View::getInstance();
1542
1543
		wp_enqueue_script( 'jquery-ui-datepicker' );
1544
1545
		add_filter( 'gravityview_js_dependencies', array( $this, 'add_datepicker_js_dependency' ) );
1546
		add_filter( 'gravityview_js_localization', array( $this, 'add_datepicker_localization' ), 10, 2 );
1547
1548
		$scheme = is_ssl() ? 'https://' : 'http://';
1549
		wp_enqueue_style( 'jquery-ui-datepicker', $scheme.'ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/smoothness/jquery-ui.css' );
1550
1551
		/**
1552
		 * @filter `gravityview_search_datepicker_class`
1553
		 * 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.
1554
		 * @param string $css_class CSS class to use. Default: `gv-datepicker datepicker mdy` \n
1555
		 * Options are:
1556
		 * - `mdy` (mm/dd/yyyy)
1557
		 * - `dmy` (dd/mm/yyyy)
1558
		 * - `dmy_dash` (dd-mm-yyyy)
1559
		 * - `dmy_dot` (dd.mm.yyyy)
1560
		 * - `ymd_slash` (yyyy/mm/dd)
1561
		 * - `ymd_dash` (yyyy-mm-dd)
1562
		 * - `ymd_dot` (yyyy.mm.dd)
1563
		 */
1564
		$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...
1565
1566
		$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...
1567
	}
1568
1569
	/**
1570
	 * Retrieve the datepicker format.
1571
	 *
1572
	 * @param bool $date_format Whether to return the PHP date format or the datpicker class name. Default: false.
1573
	 *
1574
	 * @see https://docs.gravityview.co/article/115-changing-the-format-of-the-search-widgets-date-picker
1575 4
	 *
1576 4
	 * @return string The datepicker format placeholder, or the PHP date format.
1577
	 */
1578
	private function get_datepicker_format( $date_format = false ) {
1579 4
1580
		$default_format = 'mdy';
1581
1582
		/**
1583
		 * @filter `gravityview/widgets/search/datepicker/format`
1584
		 * @since 2.1.1
1585
		 * @param string           $format Default: mdy
1586
		 * Options are:
1587
		 * - `mdy` (mm/dd/yyyy)
1588
		 * - `dmy` (dd/mm/yyyy)
1589
		 * - `dmy_dash` (dd-mm-yyyy)
1590
		 * - `dmy_dot` (dd.mm.yyyy)
1591
		 * - `ymd_slash` (yyyy/mm/dd)
1592
		 * - `ymd_dash` (yyyy-mm-dd)
1593
		 * - `ymd_dot` (yyyy.mm.dd)
1594
		 */
1595
		$format = apply_filters( 'gravityview/widgets/search/datepicker/format', $default_format );
1596
1597
		$gf_date_formats = array(
1598
			'mdy' => 'm/d/Y',
1599
1600
			'dmy_dash' => 'd-m-Y',
1601
			'dmy_dot' => 'd.m.Y',
1602
			'dmy' => 'd/m/Y',
1603
1604
			'ymd_slash' => 'Y/m/d',
1605
			'ymd_dash' => 'Y-m-d',
1606
			'ymd_dot' => 'Y.m.d',
1607
		);
1608
1609
		if ( ! $date_format ) {
1610
			// If the format key isn't valid, return default format key
1611
			return isset( $gf_date_formats[ $format ] ) ? $format : $default_format;
1612
		}
1613
1614
		// If the format key isn't valid, return default format value
1615
		return \GV\Utils::get( $gf_date_formats, $format, $gf_date_formats[ $default_format ] );
1616
	}
1617
1618
1619
} // end class
1620
1621
new GravityView_Widget_Search;
1622
1623
if ( ! gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
1624
	return;
1625
}
1626
1627 18
/**
1628
 * A GF_Query condition that allows user data searches.
1629 18
 */
1630
class GravityView_Widget_Search_GF_Query_Condition extends \GF_Query_Condition {
1631
	public function __construct( $filter, $view ) {
1632
		$this->value = $filter['value'];
1633
		$this->view = $view;
1634
	}
1635
1636
	public function sql( $query ) {
1637
		$user_meta_fields = array(
1638
			'nickname', 'first_name', 'last_name', 
1639
		);
1640
1641
		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...
1642
1643
		/**
1644 18
		 * @filter `gravityview/widgets/search/created_by/user_meta_fields` Filter the user meta fields to search by.
1645
		 * @param[in,out] array The user meta fields.
1646
		 * @param \GV\View $view The view.
1647 18
		 */
1648
		$user_meta_fields = apply_filters( 'gravityview/widgets/search/created_by/user_meta_fields', $user_meta_fields, $this->view );
1649
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
1650
1651
		$user_fields = array(
1652
			'user_nicename', 'user_login', 'display_name', 'user_email', 
1653
		);
1654
		/**
1655
		 * @filter `gravityview/widgets/search/created_by/user_fields` Filter the user meta fields to search by.
1656
		 * @param[in,out] array The user fields.
1657
		 * @param \GV\View $view The view.
1658 18
		 */
1659
		$user_fields = apply_filters( 'gravityview/widgets/search/created_by/user_fields', $user_fields, $this->view );
1660
1661
		$column = sprintf( '`%s`.`created_by`', $query->_alias( null ) );
1662
1663
		$conditions = array();
1664 18
1665
		foreach ( $user_fields as $user_field ) {
1666
			$conditions[] = $wpdb->prepare( "`u`.`$user_field` LIKE %s", '%' . $wpdb->esc_like( $this->value ) .  '%' );
1667
		}
1668
1669
		foreach ( $user_meta_fields as $meta_field ) {
1670
			$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...
1671
		}
1672
1673
		$conditions = '(' . implode( ' OR ', $conditions ) . ')';
1674
1675
		$alias = $query->_alias( null );
1676
1677
		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...
1678
	}
1679
}
1680