Completed
Push — develop ( b56630...d45961 )
by Zack
16:53
created

get_is_approved_choices()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
dl 0
loc 12
ccs 0
cts 3
cp 0
crap 6
rs 9.8666
c 0
b 0
f 0
nc 2
nop 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
			add_action( 'gravityview_search_widget_fields_after', array( $this, 'add_preview_inputs' ) );
96 29
		}
97
98
		parent::__construct( esc_html__( 'Search Bar', 'gravityview' ), null, $default_values, $settings );
99 29
100 29
		// calculate the search method (POST / GET)
101
		$this->set_search_method();
102
	}
103
104
	/**
105 5
	 * @return GravityView_Widget_Search
106 5
	 */
107
	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...
108
		if ( empty( self::$instance ) ) {
109 5
			self::$instance = new GravityView_Widget_Search;
110
		}
111
		return self::$instance;
112
	}
113
114
	/**
115
	 * Sets the search method to GET (default) or POST
116 29
	 * @since 1.16.4
117
	 */
118
	private function set_search_method() {
119
		/**
120
		 * @filter `gravityview/search/method` Modify the search form method (GET / POST)
121
		 * @since 1.16.4
122
		 * @param string $search_method Assign an input type according to the form field type. Defaults: `boolean`, `multi`, `select`, `date`, `text`
123 29
		 * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
124
		 */
125 29
		$method = apply_filters( 'gravityview/search/method', $this->search_method );
126
127 29
		$method = strtolower( $method );
128 29
129
		$this->search_method = in_array( $method, array( 'get', 'post' ) ) ? $method : 'get';
130
	}
131
132
	/**
133
	 * Returns the search method
134
	 * @since 1.16.4
135 5
	 * @return string
136 5
	 */
137
	public function get_search_method() {
138
		return $this->search_method;
139
	}
140
141
	/**
142
	 * Get the input types available for different field types
143
	 *
144
	 * @since 1.17.5
145
	 *
146
	 * @return array [field type name] => (array|string) search bar input types
147
	 */
148
	public static function get_input_types_by_field_type() {
149
		/**
150
		 * Input Type groups
151
		 * @see admin-search-widget.js (getSelectInput)
152
		 * @var array
153
		 */
154
		$input_types = array(
155
			'text' => array( 'input_text' ),
156
			'address' => array( 'input_text' ),
157
			'number' => array( 'input_text' ),
158
			'date' => array( 'date', 'date_range' ),
159
			'boolean' => array( 'single_checkbox' ),
160
			'select' => array( 'select', 'radio', 'link' ),
161
			'multi' => array( 'select', 'multiselect', 'radio', 'checkbox', 'link' ),
162
163
			// hybrids
164
			'created_by' => array( 'select', 'radio', 'checkbox', 'multiselect', 'link', 'input_text' ),
165
		);
166
167
		/**
168
		 * @filter `gravityview/search/input_types` Change the types of search fields available to a field type
169
		 * @see GravityView_Widget_Search::get_search_input_labels() for the available input types
170
		 * @param array $input_types Associative array: key is field `name`, value is array of GravityView input types (note: use `input_text` for `text`)
171
		 */
172
		$input_types = apply_filters( 'gravityview/search/input_types', $input_types );
173
174
		return $input_types;
175
	}
176
177
	/**
178
	 * Get labels for different types of search bar inputs
179
	 *
180
	 * @since 1.17.5
181
	 *
182
	 * @return array [input type] => input type label
183
	 */
184
	public static function get_search_input_labels() {
185
		/**
186
		 * Input Type labels l10n
187
		 * @see admin-search-widget.js (getSelectInput)
188
		 * @var array
189
		 */
190
		$input_labels = array(
191
			'input_text' => esc_html__( 'Text', 'gravityview' ),
192
			'date' => esc_html__( 'Date', 'gravityview' ),
193
			'select' => esc_html__( 'Select', 'gravityview' ),
194
			'multiselect' => esc_html__( 'Select (multiple values)', 'gravityview' ),
195
			'radio' => esc_html__( 'Radio', 'gravityview' ),
196
			'checkbox' => esc_html__( 'Checkbox', 'gravityview' ),
197
			'single_checkbox' => esc_html__( 'Checkbox', 'gravityview' ),
198
			'link' => esc_html__( 'Links', 'gravityview' ),
199
			'date_range' => esc_html__( 'Date range', 'gravityview' ),
200
		);
201
202
		/**
203
		 * @filter `gravityview/search/input_types` Change the label of search field input types
204
		 * @param array $input_types Associative array: key is input type name, value is label
205
		 */
206
		$input_labels = apply_filters( 'gravityview/search/input_labels', $input_labels );
207
208
		return $input_labels;
209
	}
210
211
	public static function get_search_input_label( $input_type ) {
212
		$labels = self::get_search_input_labels();
213
214
		return \GV\Utils::get( $labels, $input_type, false );
215
	}
216
217
	/**
218
	 * Add script to Views edit screen (admin)
219
	 * @param  mixed $hook
220
	 */
221
	public function add_scripts_and_styles( $hook ) {
222
		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...
223
224
		// Don't process any scripts below here if it's not a GravityView page or the widgets screen
225
		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...
226
			return;
227
		}
228
229
		$script_min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
230
		$script_source = empty( $script_min ) ? '/source' : '';
231
232
		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 );
233
234
		wp_localize_script( 'gravityview_searchwidget_admin', 'gvSearchVar', array(
235
			'nonce' => wp_create_nonce( 'gravityview_ajaxsearchwidget' ),
236
			'label_nofields' => esc_html__( 'No search fields configured yet.', 'gravityview' ),
237
			'label_addfield' => esc_html__( 'Add Search Field', 'gravityview' ),
238
			'label_label' => esc_html__( 'Label', 'gravityview' ),
239
			'label_searchfield' => esc_html__( 'Search Field', 'gravityview' ),
240
			'label_inputtype' => esc_html__( 'Input Type', 'gravityview' ),
241
			'label_ajaxerror' => esc_html__( 'There was an error loading searchable fields. Save the View or refresh the page to fix this issue.', 'gravityview' ),
242
			'input_labels' => json_encode( self::get_search_input_labels() ),
243
			'input_types' => json_encode( self::get_input_types_by_field_type() ),
244
		) );
245
246
	}
247
248
	/**
249
	 * Add admin script to the no-conflict scripts whitelist
250
	 * @param array $allowed Scripts allowed in no-conflict mode
251
	 * @return array Scripts allowed in no-conflict mode, plus the search widget script
252
	 */
253
	public function register_no_conflict( $allowed ) {
254
		$allowed[] = 'gravityview_searchwidget_admin';
255
		return $allowed;
256
	}
257
258
	/**
259
	 * Ajax
260
	 * Returns the form fields ( only the searchable ones )
261
	 *
262
	 * @access public
263
	 * @return void
264
	 */
265
	public static function get_searchable_fields() {
266
267
		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...
268
			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...
269
		}
270
271
		$form = '';
272
273
		// Fetch the form for the current View
274
		if ( ! empty( $_POST['view_id'] ) ) {
275
276
			$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...
277
278
		} elseif ( ! empty( $_POST['formid'] ) ) {
279
280
			$form = (int) $_POST['formid'];
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
281
282
		} elseif ( ! empty( $_POST['template_id'] ) && class_exists( 'GravityView_Ajax' ) ) {
283
284
			$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...
285
286
		}
287
288
		// fetch form id assigned to the view
289
		$response = self::render_searchable_fields( $form );
290
291
		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...
292
	}
293
294
	/**
295
	 * Generates html for the available Search Fields dropdown
296
	 * @param  int $form_id
297
	 * @param  string $current (for future use)
298
	 * @return string
299
	 */
300
	public static function render_searchable_fields( $form_id = null, $current = '' ) {
301
302
		if ( is_null( $form_id ) ) {
303
			return '';
304
		}
305
306
		// start building output
307
308
		$output = '<select class="gv-search-fields">';
309
310
		$custom_fields = array(
311
			'search_all' => array(
312
				'text' => esc_html__( 'Search Everything', 'gravityview' ),
313
				'type' => 'text',
314
			),
315
			'entry_date' => array(
316
				'text' => esc_html__( 'Entry Date', 'gravityview' ),
317
				'type' => 'date',
318
			),
319
			'entry_id' => array(
320
				'text' => esc_html__( 'Entry ID', 'gravityview' ),
321
				'type' => 'text',
322
			),
323
			'created_by' => array(
324
				'text' => esc_html__( 'Entry Creator', 'gravityview' ),
325
				'type' => 'created_by',
326
			),
327
			'is_starred' => array(
328
				'text' => esc_html__( 'Is Starred', 'gravityview' ),
329
				'type' => 'boolean',
330
			),
331
		);
332
333
		if ( gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
334
			$custom_fields['is_approved'] = array(
335
				'text' => esc_html__( 'Approval Status', 'gravityview' ),
336
				'type' => 'multi',
337
			);
338
		}
339
340
		foreach( $custom_fields as $custom_field_key => $custom_field ) {
341
			$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...
342
		}
343
344
		// Get fields with sub-inputs and no parent
345
		$fields = gravityview_get_form_fields( $form_id, true, true );
346
347
		/**
348
		 * @filter `gravityview/search/searchable_fields` Modify the fields that are displayed as searchable in the Search Bar dropdown\n
349
		 * @since 1.17
350
		 * @see gravityview_get_form_fields() Used to fetch the fields
351
		 * @see GravityView_Widget_Search::get_search_input_types See this method to modify the type of input types allowed for a field
352
		 * @param array $fields Array of searchable fields, as fetched by gravityview_get_form_fields()
353
		 * @param  int $form_id
354
		 */
355
		$fields = apply_filters( 'gravityview/search/searchable_fields', $fields, $form_id );
356
357
		if ( ! empty( $fields ) ) {
358
359
			$blacklist_field_types = apply_filters( 'gravityview_blacklist_field_types', array( 'fileupload', 'post_image', 'post_id', 'section' ), null );
360
361
			foreach ( $fields as $id => $field ) {
362
363
				if ( in_array( $field['type'], $blacklist_field_types ) ) {
364
					continue;
365
				}
366
367
				$types = self::get_search_input_types( $id, $field['type'] );
368
369
				$output .= '<option value="'. $id .'" '. selected( $id, $current, false ).'data-inputtypes="'. esc_attr( $types ) .'">'. esc_html( $field['label'] ) .'</option>';
370
			}
371
		}
372
373
		$output .= '</select>';
374
375
		return $output;
376
377
	}
378
379
	/**
380
	 * Assign an input type according to the form field type
381
	 *
382
	 * @see admin-search-widget.js
383
	 *
384
	 * @param string|int|float $field_id Gravity Forms field ID
385
	 * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
386
	 *
387
	 * @return string GV field search input type ('multi', 'boolean', 'select', 'date', 'text')
388
	 */
389
	public static function get_search_input_types( $field_id = '', $field_type = null ) {
390
391
		// @todo - This needs to be improved - many fields have . including products and addresses
392
		if ( false !== strpos( (string) $field_id, '.' ) && in_array( $field_type, array( 'checkbox' ) ) || in_array( $field_id, array( 'is_fulfilled' ) ) ) {
393
			$input_type = 'boolean'; // on/off checkbox
394
		} elseif ( in_array( $field_type, array( 'checkbox', 'post_category', 'multiselect' ) ) ) {
395
			$input_type = 'multi'; //multiselect
396
		} elseif ( in_array( $field_type, array( 'select', 'radio' ) ) ) {
397
			$input_type = 'select';
398
		} elseif ( in_array( $field_type, array( 'date' ) ) || in_array( $field_id, array( 'payment_date' ) ) ) {
399
			$input_type = 'date';
400
		} elseif ( in_array( $field_type, array( 'number' ) ) || in_array( $field_id, array( 'payment_amount' ) ) ) {
401
			$input_type = 'number';
402
		} else {
403
			$input_type = 'text';
404
		}
405
406
		/**
407
		 * @filter `gravityview/extension/search/input_type` Modify the search form input type based on field type
408
		 * @since 1.2
409
		 * @since 1.19.2 Added $field_id parameter
410
		 * @param string $input_type Assign an input type according to the form field type. Defaults: `boolean`, `multi`, `select`, `date`, `text`
411
		 * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
412
		 * @param string|int|float $field_id ID of the field being processed
413
		 */
414
		$input_type = apply_filters( 'gravityview/extension/search/input_type', $input_type, $field_type, $field_id );
415
416
		return $input_type;
417
	}
418
419
	/**
420
	 * Display hidden fields to add support for sites using Default permalink structure
421
	 *
422
	 * @since 1.8
423 4
	 * @return array Search fields, modified if not using permalinks
424
	 */
425 4
	public function add_no_permalink_fields( $search_fields, $object, $widget_args = array() ) {
426
		/** @global WP_Rewrite $wp_rewrite */
427
		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...
428 4
429
		// Support default permalink structure
430
		if ( false === $wp_rewrite->using_permalinks() ) {
431 4
432
			// By default, use current post.
433
			$post_id = 0;
434 4
435
			// We're in the WordPress Widget context, and an overriding post ID has been set.
436
			if ( ! empty( $widget_args['post_id'] ) ) {
437
				$post_id = absint( $widget_args['post_id'] );
438 4
			}
439
			// We're in the WordPress Widget context, and the base View ID should be used
440
			else if ( ! empty( $widget_args['view_id'] ) ) {
441
				$post_id = absint( $widget_args['view_id'] );
442 4
			}
443
444
			$args = gravityview_get_permalink_query_args( $post_id );
445 4
446 4
			// Add hidden fields to the search form
447 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...
448 4
				$search_fields[] = array(
449 4
					'name'  => $key,
450
					'input' => 'hidden',
451
					'value' => $value,
452
				);
453
			}
454 4
		}
455
456
		return $search_fields;
457
	}
458
459
	/**
460
	 * Get the fields that are searchable for a View
461
	 *
462
	 * @since 2.0
463
	 * @since 2.0.9 Added $with_full_field parameter
464
	 *
465
	 * @param \GV\View|null $view
466
	 * @param bool $with_full_field Return full field array, or just field ID? Default: false (just field ID)
467
	 *
468
	 * TODO: Move to \GV\View, perhaps? And return a Field_Collection
469
	 * TODO: Use in gravityview()->request->is_search() to calculate whether a valid search
470
	 *
471 24
	 * @return array If no View, returns empty array. Otherwise, returns array of fields configured in widgets and Search Bar for a View
472
	 */
473
	private function get_view_searchable_fields( $view, $with_full_field = false ) {
474
475
		/**
476 24
		 * Find all search widgets on the view and get the searchable fields settings.
477
		 */
478 24
		$searchable_fields = array();
479
480
		if ( ! $view ) {
481
			return $searchable_fields;
482
		}
483
484
		/**
485 24
		 * Include the sidebar Widgets.
486
		 */
487 24
		$widgets = (array) get_option( 'widget_gravityview_search', array() );
488 24
489
		foreach ( $widgets as $widget ) {
490
			if ( ! empty( $widget['view_id'] ) && $widget['view_id'] == $view->ID ) {
491 24
				if( $_fields = json_decode( $widget['search_fields'], true ) ) {
492
					foreach ( $_fields as $field ) {
493
						$searchable_fields [] = $with_full_field ? $field : $field['field'];
494
					}
495
				}
496
			}
497 24
		}
498 24
499 24
		foreach ( $view->widgets->by_id( $this->get_widget_id() )->all() as $widget ) {
500 24
			if( $_fields = json_decode( $widget->configuration->get( 'search_fields' ), true ) ) {
501
				foreach ( $_fields as $field ) {
502
					$searchable_fields [] = $with_full_field ? $field : $field['field'];
503
				}
504
			}
505 24
		}
506
507
		return $searchable_fields;
508
	}
509
510
	/** --- Frontend --- */
511
512
	/**
513
	 * Calculate the search criteria to filter entries
514
	 * @param array $search_criteria The search criteria
515
	 * @param int $form_id The form ID
516
	 * @param array $args Some args
517
	 *
518
	 * @param bool $force_search_criteria Whether to suppress GF_Query filter, internally used in self::gf_query_filter
519
	 *
520 56
	 * @return array
521 56
	 */
522
	public function filter_entries( $search_criteria, $form_id = null, $args = array(), $force_search_criteria = false ) {
523
		if ( ! $force_search_criteria && gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
524
			/**
525
			 * If GF_Query is available, we can construct custom conditions with nested
526 37
			 * booleans on the query, giving up the old ways of flat search_criteria field_filters.
527 37
			 */
528
			add_action( 'gravityview/view/query', array( $this, 'gf_query_filter' ), 10, 3 );
529
			return $search_criteria; // Return the original criteria, GF_Query modification kicks in later
530 55
		}
531
532
		if( 'post' === $this->search_method ) {
533 55
			$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...
534
		} else {
535
			$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...
536 55
		}
537
538 55
		$view = \GV\View::by_id( \GV\Utils::get( $args, 'id' ) );
539
540 55
		gravityview()->log->debug( 'Requested $_{method}: ', array( 'method' => $this->search_method, 'data' => $get ) );
541 33
542
		if ( empty( $get ) || ! is_array( $get ) ) {
543
			return $search_criteria;
544 25
		}
545
546 25
		$get = stripslashes_deep( $get );
547
548
		$get = gv_map_deep( $get, 'rawurldecode' );
549 25
550
		// Make sure array key is set up
551 25
		$search_criteria['field_filters'] = \GV\Utils::get( $search_criteria, 'field_filters', array() );
552
553
		$searchable_fields = $this->get_view_searchable_fields( $view );
554 25
555
		// add free search
556 1
		if ( isset( $get['gv_search'] ) && '' !== $get['gv_search'] && in_array( 'search_all', $searchable_fields ) ) {
557
558
			$search_all_value = trim( $get['gv_search'] );
559
560
			/**
561
			 * @filter `gravityview/search-all-split-words` Search for each word separately or the whole phrase?
562
			 * @since 1.20.2
563 1
			 * @param bool $split_words True: split a phrase into words; False: search whole word only [Default: true]
564
			 */
565 1
			$split_words = apply_filters( 'gravityview/search-all-split-words', true );
566
567
			if ( $split_words ) {
568 1
569
				// Search for a piece
570 1
				$words = explode( ' ', $search_all_value );
571
572
				$words = array_filter( $words );
573
574
			} else {
575 1
576
				// Replace multiple spaces with one space
577 1
				$search_all_value = preg_replace( '/\s+/ism', ' ', $search_all_value );
578
579
				$words = array( $search_all_value );
580 1
			}
581 1
582 1
			foreach ( $words as $word ) {
583 1
				$search_criteria['field_filters'][] = array(
584 1
					'key' => null, // The field ID to search
585
					'value' => $word, // The value to search
586
					'operator' => 'contains', // What to search in. Options: `is` or `contains`
587
				);
588
			}
589
		}
590 25
591
		// start date & end date
592
		if ( in_array( 'entry_date', $searchable_fields ) ) {
593
			/**
594 11
			 * Get and normalize the dates according to the input format.
595 11
			 */
596 11
			if ( $curr_start = ! empty( $get['gv_start'] ) ? $get['gv_start'] : '' ) {
597
				if( $curr_start_date = date_create_from_format( $this->get_datepicker_format( true ), $curr_start ) ) {
598
					$curr_start = $curr_start_date->format( 'Y-m-d' );
599
				}
600 11
			}
601 11
602 11
			if ( $curr_end = ! empty( $get['gv_start'] ) ? ( ! empty( $get['gv_end'] ) ? $get['gv_end'] : '' ) : '' ) {
603
				if( $curr_end_date = date_create_from_format( $this->get_datepicker_format( true ), $curr_end ) ) {
604
					$curr_end = $curr_end_date->format( 'Y-m-d' );
605
				}
606 11
			}
607
608
			if ( $view ) {
609
				/**
610 11
				 * Override start and end dates if View is limited to some already.
611 1
				 */
612 1
				if ( $start_date = $view->settings->get( 'start_date' ) ) {
613
					if ( $start_timestamp = strtotime( $curr_start ) ) {
614
						$curr_start = $start_timestamp < strtotime( $start_date ) ? $start_date : $curr_start;
615 11
					}
616
				}
617
				if ( $end_date = $view->settings->get( 'end_date' ) ) {
618
					if ( $end_timestamp = strtotime( $curr_end ) ) {
619
						$curr_end = $end_timestamp > strtotime( $end_date ) ? $end_date : $curr_end;
620
					}
621
				}
622
			}
623
624
			/**
625
			 * @filter `gravityview_date_created_adjust_timezone` Whether to adjust the timezone for entries. \n
626
			 * date_created is stored in UTC format. Convert search date into UTC (also used on templates/fields/date_created.php)
627
			 * @since 1.12
628
			 * @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
629 11
			 * @param[in] string $context Where the filter is being called from. `search` in this case.
630
			 */
631
			$adjust_tz = apply_filters( 'gravityview_date_created_adjust_timezone', true, 'search' );
632
633
			/**
634 11
			 * Don't set $search_criteria['start_date'] if start_date is empty as it may lead to bad query results (GFAPI::get_entries)
635 11
			 */
636 11
			if ( ! empty( $curr_start ) ) {
637
				$curr_start = date( 'Y-m-d H:i:s', strtotime( $curr_start ) );
638
				$search_criteria['start_date'] = $adjust_tz ? get_gmt_from_date( $curr_start ) : $curr_start;
639 11
			}
640
641 11
			if ( ! empty( $curr_end ) ) {
642 11
				// Fast-forward 24 hour on the end time
643 11
				$curr_end = date( 'Y-m-d H:i:s', strtotime( $curr_end ) + DAY_IN_SECONDS );
644 11
				$search_criteria['end_date'] = $adjust_tz ? get_gmt_from_date( $curr_end ) : $curr_end;
645
				if ( strpos( $search_criteria['end_date'], '00:00:00' ) ) { // See https://github.com/gravityview/GravityView/issues/1056
646
					$search_criteria['end_date'] = date( 'Y-m-d H:i:s', strtotime( $search_criteria['end_date'] ) - 1 );
647
				}
648
			}
649
		}
650 25
651 2
		// search for a specific entry ID
652 2
		if ( ! empty( $get[ 'gv_id' ] ) && in_array( 'entry_id', $searchable_fields ) ) {
0 ignored issues
show
introduced by
Array keys should NOT be surrounded by spaces if they only contain a string or an integer.
Loading history...
653 2
			$search_criteria['field_filters'][] = array(
654 2
				'key' => 'id',
655
				'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...
656
				'operator' => '=',
657
			);
658
		}
659 25
660 4
		// search for a specific Created_by ID
661 4
		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...
662 4
			$search_criteria['field_filters'][] = array(
663 4
				'key' => 'created_by',
664
				'value' => $get['gv_by'],
665
				'operator' => '=',
666
			);
667
		}
668 25
669
		// Get search mode passed in URL
670
		$mode = isset( $get['mode'] ) && in_array( $get['mode'], array( 'any', 'all' ) ) ?  $get['mode'] : 'any';
671 25
672
		// get the other search filters
673 25
		foreach ( $get as $key => $value ) {
674 15
675
			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...
676
				continue;
677 13
			}
678
679
			$filter_key = $this->convert_request_key_to_filter_key( $key );
680 13
681 1
			// could return simple filter or multiple filters
682
			if ( ! in_array( 'search_all', $searchable_fields ) && ! in_array( $filter_key , $searchable_fields ) ) {
683
				continue;
684 12
			}
685
686 12
			$filter = $this->prepare_field_filter( $filter_key, $value, $view );
687
688
			if ( isset( $filter[0]['value'] ) ) {
689
				$search_criteria['field_filters'] = array_merge( $search_criteria['field_filters'], $filter );
690
691
				// if date range type, set search mode to ALL
692
				if ( ! empty( $filter[0]['operator'] ) && in_array( $filter[0]['operator'], array( '>=', '<=', '>', '<' ) ) ) {
693 12
					$mode = 'all';
694 12
				}
695
			} elseif( !empty( $filter ) ) {
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
696
				$search_criteria['field_filters'][] = $filter;
697
			}
698
		}
699
700
		/**
701
		 * @filter `gravityview/search/mode` Set the Search Mode (`all` or `any`)
702
		 * @since 1.5.1
703 25
		 * @param[out,in] string $mode Search mode (`any` vs `all`)
704
		 */
705 25
		$search_criteria['field_filters']['mode'] = apply_filters( 'gravityview/search/mode', $mode );
706
707 25
		gravityview()->log->debug( 'Returned Search Criteria: ', array( 'data' => $search_criteria ) );
708
709 25
		unset( $get );
710
711
		return $search_criteria;
712
	}
713
714
	/**
715
	 * Filters the \GF_Query with advanced logic.
716
	 *
717
	 * Dropin for the legacy flat filters when \GF_Query is available.
718
	 *
719
	 * @param \GF_Query $query The current query object reference
720
	 * @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...
721 36
	 * @param \GV\Request $request The request object
722
	 */
723
	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...
724
		/**
725
		 * This is a shortcut to get all the needed search criteria.
726 36
		 * We feed these into an new GF_Query and tack them onto the current object.
727
		 */
728 36
		$search_criteria = $this->filter_entries( array(), null, array( 'id' => $view->ID ), true /** force search_criteria */ );
729 32
730
		if ( empty( $search_criteria['field_filters'] ) ) {
731
			return;
732 6
		}
733 6
734 6
		$widgets = $view->widgets->by_id( $this->widget_id );
735 6
		if ( $widgets->count() ) {
736 6
			$widgets = $widgets->all();
737 6
			$widget  = $widgets[0];
738 6
739
			$search_fields = json_decode( $widget->configuration->get( 'search_fields' ), true );
740
741
			foreach ( (array) $search_fields as $search_field ) {
742
				if ( 'created_by' === $search_field['field'] && 'input_text' === $search_field['input'] ) {
743 6
					$created_by_text_mode = true;
744
				}
745 6
			}
746 6
		}
747 6
748
		$extra_conditions = array();
749
750
		foreach ( $search_criteria['field_filters'] as &$filter ) {
751 6
			if ( ! is_array( $filter ) ) {
752 2
				continue;
753 2
			}
754
755 2
			// Construct a manual query for unapproved statuses
756 2
			if ( 'is_approved' === $filter['key'] && in_array( \GravityView_Entry_Approval_Status::UNAPPROVED, (array) $filter['value'] ) ) {
757 2
				$_tmp_query       = new GF_Query( $view->form->ID, array(
758
					'field_filters' => array(
759
						array(
760
							'operator' => 'in',
761
							'key'      => 'is_approved',
762
							'value'    => (array) $filter['value'],
763
						),
764 2
						array(
765
							'operator' => 'is',
766
							'key'      => 'is_approved',
767 2
							'value'    => '',
768
						),
769 2
						'mode' => 'any'
0 ignored issues
show
introduced by
Key specified for array entry; first entry has no key
Loading history...
770
					),
771 2
				) );
772 2
				$_tmp_query_parts = $_tmp_query->_introspect();
773
774
				$extra_conditions[] = $_tmp_query_parts['where'];
775
776 6
				$filter = false;
777 1
				continue;
778 1
			}
779 1
780
			// Construct manual query for text mode creator search
781
			if ( 'created_by' === $filter['key'] && ! empty( $created_by_text_mode ) ) {
782
				$extra_conditions[] = new GravityView_Widget_Search_GF_Query_Condition( $filter, $view );
783 5
				$filter = false;
784
				continue;
785
			}
786 5
787 3
			// By default, we want searches to be wildcard for each field.
788
			$filter['operator'] = empty( $filter['operator'] ) ? 'contains' : $filter['operator'];
789
790
			// For multichoice, let's have an in (OR) search.
791
			if ( is_array( $filter['value'] ) ) {
792
				$filter['operator'] = 'in'; // @todo what about in contains (OR LIKE chains)?
793
			}
794
795 5
			/**
796
			 * @filter `gravityview_search_operator` Modify the search operator for the field (contains, is, isnot, etc)
797
			 * @param string $operator Existing search operator
798 6
			 * @param array $filter array with `key`, `value`, `operator`, `type` keys
799
			 */
800
			$filter['operator'] = apply_filters( 'gravityview_search_operator', $filter['operator'], $filter );
801
		}
802
803
		$search_criteria['field_filters'] = array_filter( $search_criteria['field_filters'] );
804
805 6
		/**
806 6
		 * Parse the filter criteria to generate the needed
807
		 * WHERE clauses. This is a trick to not write our own generation
808
		 * code by reusing what's inside GF_Query already.
809
		 */
810
		$_tmp_query       = new GF_Query( $view->form->ID, $search_criteria );
811 6
		$_tmp_query_parts = $_tmp_query->_introspect();
812
813
		/**
814
		 * Grab the current clauses. We'll be combining them shortly.
815
		 */
816 6
		$query_parts      = $query->_introspect();
817 6
818 6
		/**
819
		 * Combine the parts as a new WHERE clause.
820
		 */
821
		$where = call_user_func_array( '\GF_Query_Condition::_and', array_merge( array( $query_parts['where'], $_tmp_query_parts['where'] ), $extra_conditions ) );
822
		$query->where( $where );
823
	}
824
825
	/**
826
	 * Convert $_GET/$_POST key to the field/meta ID
827
	 *
828
	 * Examples:
829
	 * - `filter_is_starred` => `is_starred`
830
	 * - `filter_1_2` => `1.2`
831
	 * - `filter_5` => `5`
832
	 *
833
	 * @since 2.0
834 13
	 *
835
	 * @param string $key $_GET/_$_POST search key
836 13
	 *
837
	 * @return string
838
	 */
839 13
	private function convert_request_key_to_filter_key( $key ) {
840 11
841
		$field_id = str_replace( 'filter_', '', $key );
842
843 13
		// calculates field_id, removing 'filter_' and for '_' for advanced fields ( like name or checkbox )
844
		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...
845
			$field_id = str_replace( '_', '.', $field_id );
846
		}
847
848
		return $field_id;
849
	}
850
851
	/**
852
	 * Prepare the field filters to GFAPI
853
	 *
854
	 * 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.
855
	 *
856
	 * Format searched values
857
	 *
858
	 * @param  string $filter_key ID of the field, or entry meta key
859 12
	 * @param  string $value $_GET/$_POST search value
860
	 * @param  \GV\View $view The view we're looking at
861
	 *
862 12
	 * @return array        1 or 2 deph levels
863
	 */
864
	public function prepare_field_filter( $filter_key, $value, $view ) {
865
866 12
		// get form field array
867 12
		$form_field = is_numeric( $filter_key ) ? \GV\GF_Field::by_id( $view->form, $filter_key ) : \GV\Internal_Field::by_id( $filter_key );
868
869
		// default filter array
870 12
		$filter = array(
871
			'key'   => $filter_key,
872 12
			'value' => $value,
873 12
		);
874 1
875 1
		switch ( $form_field->type ) {
876
877 11
			case 'select':
878
			case 'radio':
879
				$filter['operator'] = 'is';
880
				break;
881
882
			case 'post_category':
883
884
				if ( ! is_array( $value ) ) {
885
					$value = array( $value );
886
				}
887
888
				// Reset filter variable
889
				$filter = array();
890
891
				foreach ( $value as $val ) {
892
					$cat = get_term( $val, 'category' );
893
					$filter[] = array(
894
						'key'      => $filter_key,
895
						'value'    => esc_attr( $cat->name ) . ':' . $val,
896
						'operator' => 'is',
897 11
					);
898
				}
899
900
				break;
901
902
			case 'multiselect':
903
904
				if ( ! is_array( $value ) ) {
905
					break;
906
				}
907
908
				// Reset filter variable
909
				$filter = array();
910
911
				foreach ( $value as $val ) {
912 11
					$filter[] = array( 'key' => $filter_key, 'value' => $val );
913
				}
914
915
				break;
916
917
			case 'checkbox':
918
				// convert checkbox on/off into the correct search filter
919
				if ( false !== strpos( $filter_key, '.' ) && ! empty( $form_field->inputs ) && ! empty( $form_field->choices ) ) {
920
					foreach ( $form_field->inputs as $k => $input ) {
921
						if ( $input['id'] == $filter_key ) {
922
							$filter['value'] = $form_field->choices[ $k ]['value'];
923
							$filter['operator'] = 'is';
924
							break;
925
						}
926
					}
927
				} elseif ( is_array( $value ) ) {
928
929
					// Reset filter variable
930
					$filter = array();
931
932
					foreach ( $value as $val ) {
933
						$filter[] = array(
934
							'key'      => $filter_key,
935
							'value'    => $val,
936
							'operator' => 'is',
937
						);
938 11
					}
939 11
				}
940
941
				break;
942
943
			case 'name':
944
			case 'address':
945
946
				if ( false === strpos( $filter_key, '.' ) ) {
947
948
					$words = explode( ' ', $value );
949
950
					$filters = array();
951
					foreach ( $words as $word ) {
952
						if ( ! empty( $word ) && strlen( $word ) > 1 ) {
953
							// Keep the same key for each filter
954
							$filter['value'] = $word;
955
							// Add a search for the value
956
							$filters[] = $filter;
957
						}
958
					}
959
960
					$filter = $filters;
961
				}
962
963
				// State/Province should be exact matches
964
				if ( 'address' === $form_field->field->type ) {
965
966
					$searchable_fields = $this->get_view_searchable_fields( $view, true );
967
968
					foreach ( $searchable_fields as $searchable_field ) {
969
970
						if( $form_field->ID !== $searchable_field['field'] ) {
971
							continue;
972
						}
973
974
						// Only exact-match dropdowns, not text search
975
						if( in_array( $searchable_field['input'], array( 'text', 'search' ), true ) ) {
976
							continue;
977
						}
978
979
						$input_id = gravityview_get_input_id_from_id( $form_field->ID );
980
981
						if ( 4 === $input_id ) {
982
							$filter['operator'] = 'is';
983
						};
984 11
					}
985
				}
986 8
987
				break;
988 8
989
			case 'date':
990
991
				$date_format = $this->get_datepicker_format( true );
992
993
				if ( is_array( $value ) ) {
994
995
					// Reset filter variable
996
					$filter = array();
997
998
					foreach ( $value as $k => $date ) {
999
						if ( empty( $date ) ) {
1000
							continue;
1001
						}
1002
						$operator = 'start' === $k ? '>=' : '<=';
1003
1004
						/**
1005
						 * @hack
1006
						 * @since 1.16.3
1007
						 * Safeguard until GF implements '<=' operator
1008
						 */
1009
						if( !GFFormsModel::is_valid_operator( $operator ) && $operator === '<=' ) {
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
1010
							$operator = '<';
1011
							$date = date( 'Y-m-d', strtotime( self::get_formatted_date( $date, 'Y-m-d', $date_format ) . ' +1 day' ) );
1012
						}
1013
1014
						$filter[] = array(
1015
							'key'      => $filter_key,
1016 8
							'value'    => self::get_formatted_date( $date, 'Y-m-d', $date_format ),
1017 8
							'operator' => $operator,
1018
						);
1019
					}
1020 8
				} else {
1021
					$date = $value;
1022
					$filter['value'] = self::get_formatted_date( $date, 'Y-m-d', $date_format );
1023
				}
1024
1025 12
				break;
1026
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
1027
1028
		} // switch field type
1029
1030
		return $filter;
1031
	}
1032
1033
	/**
1034
	 * Get the Field Format form GravityForms
1035
	 *
1036
	 * @param GF_Field_Date $field The field object
1037
	 * @since 1.10
1038
	 *
1039
	 * @return string Format of the date in the database
1040
	 */
1041
	public static function get_date_field_format( GF_Field_Date $field ) {
1042
		$format = 'm/d/Y';
1043
		$datepicker = array(
1044
			'mdy' => 'm/d/Y',
1045
			'dmy' => 'd/m/Y',
1046
			'dmy_dash' => 'd-m-Y',
1047
			'dmy_dot' => 'd.m.Y',
1048
			'ymd_slash' => 'Y/m/d',
1049
			'ymd_dash' => 'Y-m-d',
1050
			'ymd_dot' => 'Y.m.d',
1051
		);
1052
1053
		if ( ! empty( $field->dateFormat ) && isset( $datepicker[ $field->dateFormat ] ) ){
1054
			$format = $datepicker[ $field->dateFormat ];
1055
		}
1056
1057
		return $format;
1058
	}
1059
1060
	/**
1061
	 * Format a date value
1062
	 *
1063
	 * @param string $value Date value input
1064
	 * @param string $format Wanted formatted date
1065
	 *
1066 8
	 * @since 2.1.2
1067
	 * @param string $value_format The value format. Default: Y-m-d
1068 8
	 *
1069
	 * @return string
1070 8
	 */
1071
	public static function get_formatted_date( $value = '', $format = 'Y-m-d', $value_format = 'Y-m-d' ) {
1072
1073
		$date = date_create_from_format( $value_format, $value );
1074 8
1075
		if ( empty( $date ) ) {
1076
			gravityview()->log->debug( 'Date format not valid: {value}', array( 'value' => $value ) );
1077
			return '';
1078
		}
1079
		return $date->format( $format );
1080
	}
1081
1082 1
1083
	/**
1084
	 * Include this extension templates path
1085 1
	 * @param array $file_paths List of template paths ordered
1086
	 */
1087 1
	public function add_template_path( $file_paths ) {
1088
1089
		// Index 100 is the default GravityView template path.
1090
		$file_paths[102] = self::$file . 'templates/';
1091
1092
		return $file_paths;
1093
	}
1094
1095
	/**
1096
	 * Check whether the configured search fields have a date field
1097
	 *
1098
	 * @since 1.17.5
1099 4
	 *
1100
	 * @param array $search_fields
1101 4
	 *
1102
	 * @return bool True: has a `date` or `date_range` field
1103 4
	 */
1104 4
	private function has_date_field( $search_fields ) {
1105
1106 4
		$has_date = false;
1107
1108
		foreach ( $search_fields as $k => $field ) {
1109
			if ( in_array( $field['input'], array( 'date', 'date_range', 'entry_date' ) ) ) {
1110 4
				$has_date = true;
1111
				break;
1112
			}
1113
		}
1114
1115
		return $has_date;
1116
	}
1117
1118
	/**
1119
	 * Renders the Search Widget
1120
	 * @param array $widget_args
1121 4
	 * @param string $content
1122
	 * @param string $context
1123 4
	 *
1124
	 * @return void
1125 4
	 */
1126
	public function render_frontend( $widget_args, $content = '', $context = '' ) {
1127
		/** @var GravityView_View $gravityview_view */
1128
		$gravityview_view = GravityView_View::getInstance();
1129
1130
		if ( empty( $gravityview_view ) ) {
1131 4
			gravityview()->log->debug( '$gravityview_view not instantiated yet.' );
1132
			return;
1133 4
		}
1134
1135
		// get configured search fields
1136
		$search_fields = ! empty( $widget_args['search_fields'] ) ? json_decode( $widget_args['search_fields'], true ) : '';
1137
1138
		if ( empty( $search_fields ) || ! is_array( $search_fields ) ) {
1139
			gravityview()->log->debug( 'No search fields configured for widget:', array( 'data' => $widget_args ) );
1140 4
			return;
1141
		}
1142 4
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
1143
1144 4
		// prepare fields
1145
		foreach ( $search_fields as $k => $field ) {
1146 4
1147
			$updated_field = $field;
1148 4
1149 4
			$updated_field = $this->get_search_filter_details( $updated_field );
1150 4
1151 4
			switch ( $field['field'] ) {
1152 4
1153
				case 'search_all':
1154
					$updated_field['key'] = 'search_all';
1155
					$updated_field['input'] = 'search_all';
1156
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_search' );
1157
					break;
1158
1159
				case 'entry_date':
1160
					$updated_field['key'] = 'entry_date';
1161
					$updated_field['input'] = 'entry_date';
1162
					$updated_field['value'] = array(
1163
						'start' => $this->rgget_or_rgpost( 'gv_start' ),
1164
						'end' => $this->rgget_or_rgpost( 'gv_end' ),
1165
					);
1166
					break;
1167
1168
				case 'entry_id':
1169
					$updated_field['key'] = 'entry_id';
1170
					$updated_field['input'] = 'entry_id';
1171
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_id' );
1172
					break;
1173
1174
				case 'created_by':
1175
					$updated_field['key'] = 'created_by';
1176
					$updated_field['name'] = 'gv_by';
1177
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_by' );
1178
					$updated_field['choices'] = self::get_created_by_choices();
1179
					break;
1180
				
1181
				case 'is_approved':
1182
					$updated_field['key'] = 'is_approved';
1183 4
					$updated_field['value'] = $this->rgget_or_rgpost( 'filter_is_approved' );
1184
					$updated_field['choices'] = self::get_is_approved_choices();
1185
					break;
1186 4
			}
1187
1188
			$search_fields[ $k ] = $updated_field;
1189
		}
1190
1191
		gravityview()->log->debug( 'Calculated Search Fields: ', array( 'data' => $search_fields ) );
1192
1193
		/**
1194
		 * @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.
1195
		 * @param array $search_fields Array of search filters with `key`, `label`, `value`, `type`, `choices` keys
1196 4
		 * @param GravityView_Widget_Search $this Current widget object
1197
		 * @param array $widget_args Args passed to this method. {@since 1.8}
1198 4
		 * @param \GV\Template_Context $context {@since 2.0}
1199
		 * @var array
1200
		 */
1201 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...
1202
1203 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...
1204
1205 4
		/** @since 1.14 */
1206
		$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...
1207 4
1208
		$custom_class = ! empty( $widget_args['custom_class'] ) ? $widget_args['custom_class'] : '';
1209 4
1210
		$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...
1211
1212
		$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...
1213
1214 4
		if ( $this->has_date_field( $search_fields ) ) {
1215
			// enqueue datepicker stuff only if needed!
1216 4
			$this->enqueue_datepicker();
1217 4
		}
1218
1219
		$this->maybe_enqueue_flexibility();
1220
1221
		$gravityview_view->render( 'widget', 'search', false );
1222
	}
1223
1224
	/**
1225
	 * Get the search class for a search form
1226 4
	 *
1227 4
	 * @since 1.5.4
1228
	 *
1229 4
	 * @return string Sanitized CSS class for the search form
1230
	 */
1231 4
	public static function get_search_class( $custom_class = '' ) {
1232
		$gravityview_view = GravityView_View::getInstance();
1233
1234
		$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...
1235
1236
		if ( ! empty( $custom_class )  ) {
1237
			$search_class .= ' '.$custom_class;
1238
		}
1239 4
1240
		/**
1241
		 * @filter `gravityview_search_class` Modify the CSS class for the search form
1242 4
		 * @param string $search_class The CSS class for the search form
1243
		 */
1244 4
		$search_class = apply_filters( 'gravityview_search_class', $search_class );
1245
1246
		// Is there an active search being performed? Used by fe-views.js
1247
		$search_class .= GravityView_frontend::getInstance()->isSearch() ? ' gv-is-search' : '';
1248
1249
		return gravityview_sanitize_html_class( $search_class );
1250
	}
1251
1252
1253
	/**
1254 4
	 * Calculate the search form action
1255 4
	 * @since 1.6
1256
	 *
1257 4
	 * @return string
1258
	 */
1259 4
	public static function get_search_form_action() {
1260
		$gravityview_view = GravityView_View::getInstance();
1261 4
1262
		$post_id = $gravityview_view->getPostId() ? $gravityview_view->getPostId() : $gravityview_view->getViewId();
1263
1264
		$url = add_query_arg( array(), get_permalink( $post_id ) );
1265
1266
		return esc_url( $url );
1267
	}
1268
1269
	/**
1270 4
	 * Get the label for a search form field
1271
	 * @param  array $field      Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1272 4
	 * @param  array $form_field Form field data, as fetched by `gravityview_get_field()`
1273
	 * @return string             Label for the search form
1274 4
	 */
1275
	private static function get_field_label( $field, $form_field = array() ) {
1276 4
1277
		$label = \GV\Utils::_GET( 'label', \GV\Utils::get( $field, 'label' ) );
1278 4
1279 4
		if ( ! $label ) {
1280 4
1281 4
			$label = isset( $form_field['label'] ) ? $form_field['label'] : '';
1282
1283
			switch( $field['field'] ) {
1284
				case 'search_all':
1285
					$label = __( 'Search Entries:', 'gravityview' );
1286
					break;
1287
				case 'entry_date':
1288
					$label = __( 'Filter by date:', 'gravityview' );
1289
					break;
1290
				case 'entry_id':
1291
					$label = __( 'Entry ID:', 'gravityview' );
1292
					break;
1293
				default:
1294
					// If this is a field input, not a field
1295
					if ( strpos( $field['field'], '.' ) > 0 && ! empty( $form_field['inputs'] ) ) {
1296
1297
						// Get the label for the field in question, which returns an array
1298
						$items = wp_list_filter( $form_field['inputs'], array( 'id' => $field['field'] ) );
1299
1300
						// Get the item with the `label` key
1301
						$values = wp_list_pluck( $items, 'label' );
1302
1303
						// There will only one item in the array, but this is easier
1304
						foreach ( $values as $value ) {
1305
							$label = $value;
1306
							break;
1307
						}
1308
					}
1309
			}
1310
		}
1311
1312
		/**
1313
		 * @filter `gravityview_search_field_label` Modify the label for a search field. Supports returning HTML
1314 4
		 * @since 1.17.3 Added $field parameter
1315
		 * @param[in,out] string $label Existing label text, sanitized.
1316 4
		 * @param[in] array $form_field Gravity Forms field array, as returned by `GFFormsModel::get_field()`
1317
		 * @param[in] array $field Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1318
		 */
1319
		$label = apply_filters( 'gravityview_search_field_label', esc_attr( $label ), $form_field, $field );
1320
1321
		return $label;
1322
	}
1323
1324
	/**
1325 4
	 * Prepare search fields to frontend render with other details (label, field type, searched values)
1326
	 *
1327 4
	 * @param array $field
1328
	 * @return array
1329 4
	 */
1330
	private function get_search_filter_details( $field ) {
1331
1332 4
		$gravityview_view = GravityView_View::getInstance();
1333
1334
		$form = $gravityview_view->getForm();
1335 4
1336
		// for advanced field ids (eg, first name / last name )
1337
		$name = 'filter_' . str_replace( '.', '_', $field['field'] );
1338 4
1339
		// get searched value from $_GET/$_POST (string or array)
1340
		$value = $this->rgget_or_rgpost( $name );
1341 4
1342 4
		// get form field details
1343 4
		$form_field = gravityview_get_field( $form, $field['field'] );
1344 4
1345 4
		$filter = array(
1346 4
			'key' => $field['field'],
1347
			'name' => $name,
1348
			'label' => self::get_field_label( $field, $form_field ),
1349
			'input' => $field['input'],
1350 4
			'value' => $value,
1351
			'type' => $form_field['type'],
1352 4
		);
1353
1354
		// collect choices
1355
		if ( 'post_category' === $form_field['type'] && ! empty( $form_field['displayAllCategories'] ) && empty( $form_field['choices'] ) ) {
1356 4
			$filter['choices'] = gravityview_get_terms_choices();
1357
		} elseif ( ! empty( $form_field['choices'] ) ) {
1358
			$filter['choices'] = $form_field['choices'];
1359
		}
1360 4
1361
		if ( 'date_range' === $field['input'] && empty( $value ) ) {
1362
			$filter['value'] = array( 'start' => '', 'end' => '' );
1363
		}
1364
1365
		return $filter;
1366
1367
	}
1368
1369
	/**
1370
	 * Calculate the search choices for the users
1371
	 *
1372
	 * @since 1.8
1373
	 *
1374
	 * @return array Array of user choices (value = ID, text = display name)
1375
	 */
1376
	private static function get_created_by_choices() {
1377
1378
		/**
1379
		 * filter gravityview/get_users/search_widget
1380
		 * @see \GVCommon::get_users
1381
		 */
1382
		$users = GVCommon::get_users( 'search_widget', array( 'fields' => array( 'ID', 'display_name' ) ) );
1383
1384
		$choices = array();
1385
		foreach ( $users as $user ) {
1386
			$choices[] = array(
1387
				'value' => $user->ID,
1388
				'text' => $user->display_name,
1389
			);
1390
		}
1391
1392
		return $choices;
1393
	}
1394
1395
	/**
1396
	 * Calculate the search checkbox choices for approval status
1397
	 *
1398
	 * @since develop
1399
	 *
1400
	 * @return array Array of approval status choices (value = status, text = display name)
1401
	 */
1402
	private static function get_is_approved_choices() {
1403
1404
		$choices = array();
1405
		foreach ( GravityView_Entry_Approval_Status::get_all() as $status ) {
1406
			$choices[] = array(
1407
				'value' => $status['value'],
1408
				'text' => $status['label'],
1409
			);
1410
		}
1411
1412
		return $choices;
1413
	}
1414 4
1415 4
	/**
1416
	 * Output the Clear Search Results button
1417 4
	 * @since 1.5.4
1418
	 */
1419
	public static function the_clear_search_button() {
1420
		$gravityview_view = GravityView_View::getInstance();
1421
1422
		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...
1423
1424 4
			$url = strtok( add_query_arg( array() ), '?' );
1425
1426
			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...
1427
1428
		}
1429
	}
1430
1431
	/**
1432
	 * Based on the search method, fetch the value for a specific key
1433
	 *
1434
	 * @since 1.16.4
1435 4
	 *
1436 4
	 * @param string $name Name of the request key to fetch the value for
1437
	 *
1438 4
	 * @return mixed|string Value of request at $name key. Empty string if empty.
1439
	 */
1440 4
	private function rgget_or_rgpost( $name ) {
1441
		$value = \GV\Utils::_REQUEST( $name );
1442 4
1443
		$value = stripslashes_deep( $value );
1444 4
1445
		$value = gv_map_deep( $value, 'rawurldecode' );
1446
1447
		$value = gv_map_deep( $value, '_wp_specialchars' );
1448
1449
		return $value;
1450
	}
1451
1452
1453
	/**
1454
	 * Require the datepicker script for the frontend GV script
1455
	 * @param array $js_dependencies Array of existing required scripts for the fe-views.js script
1456
	 * @return array Array required scripts, with `jquery-ui-datepicker` added
1457
	 */
1458
	public function add_datepicker_js_dependency( $js_dependencies ) {
1459
1460
		$js_dependencies[] = 'jquery-ui-datepicker';
1461
1462
		return $js_dependencies;
1463
	}
1464
1465
	/**
1466
	 * Modify the array passed to wp_localize_script()
1467
	 *
1468
	 * @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...
1469
	 * @param array $view_data View data array with View settings
1470
	 *
1471
	 * @return array
1472
	 */
1473
	public function add_datepicker_localization( $localizations = array(), $view_data = array() ) {
1474
		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...
1475
1476
		/**
1477
		 * @filter `gravityview_datepicker_settings` Modify the datepicker settings
1478
		 * @see http://api.jqueryui.com/datepicker/ Learn what settings are available
1479
		 * @see http://www.renegadetechconsulting.com/tutorials/jquery-datepicker-and-wordpress-i18n Thanks for the helpful information on $wp_locale
1480
		 * @param array $js_localization The data padded to the Javascript file
1481
		 * @param array $view_data View data array with View settings
1482
		 */
1483
		$datepicker_settings = apply_filters( 'gravityview_datepicker_settings', array(
1484
			'yearRange' => '-5:+5',
1485
			'changeMonth' => true,
1486
			'changeYear' => true,
1487
			'closeText' => esc_attr_x( 'Close', 'Close calendar', 'gravityview' ),
1488
			'prevText' => esc_attr_x( 'Prev', 'Previous month in calendar', 'gravityview' ),
1489
			'nextText' => esc_attr_x( 'Next', 'Next month in calendar', 'gravityview' ),
1490
			'currentText' => esc_attr_x( 'Today', 'Today in calendar', 'gravityview' ),
1491
			'weekHeader' => esc_attr_x( 'Week', 'Week in calendar', 'gravityview' ),
1492
			'monthStatus'       => __( 'Show a different month', 'gravityview' ),
1493
			'monthNames'        => array_values( $wp_locale->month ),
1494
			'monthNamesShort'   => array_values( $wp_locale->month_abbrev ),
1495
			'dayNames'          => array_values( $wp_locale->weekday ),
1496
			'dayNamesShort'     => array_values( $wp_locale->weekday_abbrev ),
1497
			'dayNamesMin'       => array_values( $wp_locale->weekday_initial ),
1498
			// get the start of week from WP general setting
1499
			'firstDay'          => get_option( 'start_of_week' ),
1500
			// is Right to left language? default is false
1501
			'isRTL'             => is_rtl(),
1502
		), $view_data );
1503
1504
		$localizations['datepicker'] = $datepicker_settings;
1505
1506
		return $localizations;
1507
1508
	}
1509
1510
	/**
1511
	 * Register search widget scripts, including Flexibility
1512
	 *
1513
	 * @see https://github.com/10up/flexibility
1514
	 *
1515
	 * @since 1.17
1516
	 *
1517
	 * @return void
1518
	 */
1519
	public function register_scripts() {
1520
		wp_register_script( 'gv-flexibility', plugins_url( 'assets/lib/flexibility/flexibility.js', GRAVITYVIEW_FILE ), array(), \GV\Plugin::$version, true );
1521
	}
1522
1523
	/**
1524
	 * If the current visitor is running IE 8 or 9, enqueue Flexibility
1525 4
	 *
1526 4
	 * @since 1.17
1527
	 *
1528
	 * @return void
1529 4
	 */
1530
	private function maybe_enqueue_flexibility() {
1531
		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...
1532
			wp_enqueue_script( 'gv-flexibility' );
1533
		}
1534
	}
1535
1536
	/**
1537
	 * Enqueue the datepicker script
1538
	 *
1539
	 * It sets the $gravityview->datepicker_class parameter
1540
	 *
1541
	 * @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.
1542
	 * @return void
1543
	 */
1544
	public function enqueue_datepicker() {
1545
		$gravityview_view = GravityView_View::getInstance();
1546
1547
		wp_enqueue_script( 'jquery-ui-datepicker' );
1548
1549
		add_filter( 'gravityview_js_dependencies', array( $this, 'add_datepicker_js_dependency' ) );
1550
		add_filter( 'gravityview_js_localization', array( $this, 'add_datepicker_localization' ), 10, 2 );
1551
1552
		$scheme = is_ssl() ? 'https://' : 'http://';
1553
		wp_enqueue_style( 'jquery-ui-datepicker', $scheme.'ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/smoothness/jquery-ui.css' );
1554
1555
		/**
1556
		 * @filter `gravityview_search_datepicker_class`
1557
		 * 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.
1558
		 * @param string $css_class CSS class to use. Default: `gv-datepicker datepicker mdy` \n
1559
		 * Options are:
1560
		 * - `mdy` (mm/dd/yyyy)
1561
		 * - `dmy` (dd/mm/yyyy)
1562
		 * - `dmy_dash` (dd-mm-yyyy)
1563
		 * - `dmy_dot` (dd.mm.yyyy)
1564
		 * - `ymd_slash` (yyyy/mm/dd)
1565
		 * - `ymd_dash` (yyyy-mm-dd)
1566
		 * - `ymd_dot` (yyyy.mm.dd)
1567
		 */
1568
		$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...
1569
1570
		$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...
1571
	}
1572
1573
	/**
1574
	 * Retrieve the datepicker format.
1575
	 *
1576
	 * @param bool $date_format Whether to return the PHP date format or the datpicker class name. Default: false.
1577 18
	 *
1578
	 * @see https://docs.gravityview.co/article/115-changing-the-format-of-the-search-widgets-date-picker
1579 18
	 *
1580
	 * @return string The datepicker format placeholder, or the PHP date format.
1581
	 */
1582
	private function get_datepicker_format( $date_format = false ) {
1583
1584
		$default_format = 'mdy';
1585
1586
		/**
1587
		 * @filter `gravityview/widgets/search/datepicker/format`
1588
		 * @since 2.1.1
1589
		 * @param string           $format Default: mdy
1590
		 * Options are:
1591
		 * - `mdy` (mm/dd/yyyy)
1592
		 * - `dmy` (dd/mm/yyyy)
1593
		 * - `dmy_dash` (dd-mm-yyyy)
1594 18
		 * - `dmy_dot` (dd.mm.yyyy)
1595
		 * - `ymd_slash` (yyyy/mm/dd)
1596
		 * - `ymd_dash` (yyyy-mm-dd)
1597 18
		 * - `ymd_dot` (yyyy.mm.dd)
1598
		 */
1599
		$format = apply_filters( 'gravityview/widgets/search/datepicker/format', $default_format );
1600
1601
		$gf_date_formats = array(
1602
			'mdy' => 'm/d/Y',
1603
1604
			'dmy_dash' => 'd-m-Y',
1605
			'dmy_dot' => 'd.m.Y',
1606
			'dmy' => 'd/m/Y',
1607
1608 18
			'ymd_slash' => 'Y/m/d',
1609
			'ymd_dash' => 'Y-m-d',
1610
			'ymd_dot' => 'Y.m.d',
1611
		);
1612
1613
		if ( ! $date_format ) {
1614 18
			// If the format key isn't valid, return default format key
1615
			return isset( $gf_date_formats[ $format ] ) ? $format : $default_format;
1616
		}
1617
1618
		// If the format key isn't valid, return default format value
1619
		return \GV\Utils::get( $gf_date_formats, $format, $gf_date_formats[ $default_format ] );
1620
	}
1621
1622
	/**
1623
	 * If previewing a View or page with embedded Views, make the search work properly by adding hidden fields with query vars
1624
	 *
1625
	 * @since 2.2.1
1626
	 *
1627
	 * @return void
1628
	 */
1629
	public function add_preview_inputs() {
1630 1
		global $wp;
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...
1631 1
1632 1
		if ( ! is_preview() || ! current_user_can( 'publish_gravityviews') ) {
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
1633 1
			return;
1634
		}
1635 1
1636 1
		// Outputs `preview` and `post_id` variables
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1637
		foreach ( $wp->query_vars as $key => $value ) {
1638
			printf( '<input type="hidden" name="%s" value="%s" />', esc_attr( $key ), esc_attr( $value ) );
1639 1
		}
1640
1641
	}
1642
1643
1644
} // end class
1645
1646
new GravityView_Widget_Search;
1647 1
1648
if ( ! gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
1649
	return;
1650 1
}
1651
1652
/**
1653
 * A GF_Query condition that allows user data searches.
1654
 */
1655
class GravityView_Widget_Search_GF_Query_Condition extends \GF_Query_Condition {
1656
	public function __construct( $filter, $view ) {
1657
		$this->value = $filter['value'];
1658 1
		$this->view = $view;
1659
	}
1660 1
1661
	public function sql( $query ) {
1662 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...
1663 1
1664
		$user_meta_fields = array(
1665
			'nickname', 'first_name', 'last_name',
1666 1
		);
1667 1
1668
		/**
1669
		 * @filter `gravityview/widgets/search/created_by/user_meta_fields` Filter the user meta fields to search.
1670 1
		 * @param[in,out] array The user meta fields.
1671
		 * @param \GV\View $view The view.
1672 1
		 */
1673
		$user_meta_fields = apply_filters( 'gravityview/widgets/search/created_by/user_meta_fields', $user_meta_fields, $this->view );
1674 1
1675
		$user_fields = array(
1676
			'user_nicename', 'user_login', 'display_name', 'user_email', 
1677
		);
1678
1679
		/**
1680
		 * @filter `gravityview/widgets/search/created_by/user_fields` Filter the user fields to search.
1681
		 * @param[in,out] array The user fields.
1682
		 * @param \GV\View $view The view.
1683
		 */
1684
		$user_fields = apply_filters( 'gravityview/widgets/search/created_by/user_fields', $user_fields, $this->view );
1685
1686
		$conditions = array();
1687
1688
		foreach ( $user_fields as $user_field ) {
1689
			$conditions[] = $wpdb->prepare( "`u`.`$user_field` LIKE %s", '%' . $wpdb->esc_like( $this->value ) .  '%' );
1690
		}
1691
1692
		foreach ( $user_meta_fields as $meta_field ) {
1693
			$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...
1694
		}
1695
1696
		$conditions = '(' . implode( ' OR ', $conditions ) . ')';
1697
1698
		$alias = $query->_alias( null );
1699
1700
		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...
1701
	}
1702
}
1703