Completed
Push — develop ( 2f9743...a63668 )
by Zack
18:47
created

GravityView_Widget_Search::has_date_field()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.0261

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 0
loc 13
ccs 6
cts 7
cp 0.8571
crap 3.0261
rs 9.8333
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 16 and the first side effect is on line 13.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * The GravityView New Search widget
4
 *
5
 * @package   GravityView-DataTables-Ext
6
 * @license   GPL2+
7
 * @author    Katz Web Services, Inc.
8
 * @link      http://gravityview.co
9
 * @copyright Copyright 2014, Katz Web Services, Inc.
10
 */
11
12
if ( ! defined( 'WPINC' ) ) {
13
	die;
14
}
15
16
class GravityView_Widget_Search extends \GV\Widget {
17
18
	public static $file;
19
	public static $instance;
20
21
	private $search_filters = array();
0 ignored issues
show
Unused Code introduced by
The property $search_filters is not used and could be removed.

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

Loading history...
22
23
	/**
24
	 * whether search method is GET or POST ( default: GET )
25
	 * @since 1.16.4
26
	 * @var string
27
	 */
28
	private $search_method = 'get';
29
30 29
	public function __construct() {
31
32 29
		$this->widget_id = 'search_bar';
33 29
		$this->widget_description = esc_html__( 'Search form for searching entries.', 'gravityview' );
34
35 29
		self::$instance = &$this;
36
37 29
		self::$file = plugin_dir_path( __FILE__ );
38
39 29
		$default_values = array( 'header' => 0, 'footer' => 0 );
40
41
		$settings = array(
42 29
			'search_layout' => array(
43 29
				'type' => 'radio',
44
				'full_width' => true,
45 29
				'label' => esc_html__( 'Search Layout', 'gravityview' ),
46 29
				'value' => 'horizontal',
47
				'options' => array(
48 29
					'horizontal' => esc_html__( 'Horizontal', 'gravityview' ),
49 29
					'vertical' => esc_html__( 'Vertical', 'gravityview' ),
50
				),
51
			),
52
			'search_clear' => array(
53 29
				'type' => 'checkbox',
54 29
				'label' => __( 'Show Clear button', 'gravityview' ),
55
				'value' => false,
56
			),
57
			'search_fields' => array(
58
				'type' => 'hidden',
59
				'label' => '',
60
				'class' => 'gv-search-fields-value',
61
				'value' => '[{"field":"search_all","input":"input_text"}]', // Default: Search Everything text box
62
			),
63
			'search_mode' => array(
64 29
				'type' => 'radio',
65
				'full_width' => true,
66 29
				'label' => esc_html__( 'Search Mode', 'gravityview' ),
67 29
				'desc' => __('Should search results match all search fields, or any?', 'gravityview'),
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
68 29
				'value' => 'any',
69 29
				'class' => 'hide-if-js',
70
				'options' => array(
71 29
					'any' => esc_html__( 'Match Any Fields', 'gravityview' ),
72 29
					'all' => esc_html__( 'Match All Fields', 'gravityview' ),
73
				),
74
			),
75
		);
76
77 29
		if ( ! $this->is_registered() ) {
78
			// frontend - filter entries
79
			add_filter( 'gravityview_fe_search_criteria', array( $this, 'filter_entries' ), 10, 3 );
80
81
			// frontend - add template path
82
			add_filter( 'gravityview_template_paths', array( $this, 'add_template_path' ) );
83
84
			// Add hidden fields for "Default" permalink structure
85
			add_filter( 'gravityview_widget_search_filters', array( $this, 'add_no_permalink_fields' ), 10, 3 );
86
87
			// admin - add scripts - run at 1100 to make sure GravityView_Admin_Views::add_scripts_and_styles() runs first at 999
88
			add_action( 'admin_enqueue_scripts', array( $this, 'add_scripts_and_styles' ), 1100 );
89
			add_action( 'wp_enqueue_scripts', array( $this, 'register_scripts') );
0 ignored issues
show
introduced by
No space before closing parenthesis of array is bad style
Loading history...
90
			add_filter( 'gravityview_noconflict_scripts', array( $this, 'register_no_conflict' ) );
91
92
			// ajax - get the searchable fields
93
			add_action( 'wp_ajax_gv_searchable_fields', array( 'GravityView_Widget_Search', 'get_searchable_fields' ) );
94
95
			add_action( 'gravityview_search_widget_fields_after', array( $this, 'add_preview_inputs' ) );
96
		}
97
98 29
		parent::__construct( esc_html__( 'Search Bar', 'gravityview' ), null, $default_values, $settings );
99
100
		// calculate the search method (POST / GET)
101 29
		$this->set_search_method();
102 29
	}
103
104
	/**
105
	 * @return GravityView_Widget_Search
106
	 */
107 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...
108 5
		if ( empty( self::$instance ) ) {
109
			self::$instance = new GravityView_Widget_Search;
110
		}
111 5
		return self::$instance;
112
	}
113
114
	/**
115
	 * Sets the search method to GET (default) or POST
116
	 * @since 1.16.4
117
	 */
118 29
	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
		 * @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
129 29
		$this->search_method = in_array( $method, array( 'get', 'post' ) ) ? $method : 'get';
130 29
	}
131
132
	/**
133
	 * Returns the search method
134
	 * @since 1.16.4
135
	 * @return string
136
	 */
137 5
	public function get_search_method() {
138 5
		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
	 * @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 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...
428
429
		// Support default permalink structure
430 4
		if ( false === $wp_rewrite->using_permalinks() ) {
431
432
			// By default, use current post.
433 4
			$post_id = 0;
434
435
			// We're in the WordPress Widget context, and an overriding post ID has been set.
436 4
			if ( ! empty( $widget_args['post_id'] ) ) {
437
				$post_id = absint( $widget_args['post_id'] );
438
			}
439
			// We're in the WordPress Widget context, and the base View ID should be used
440 4
			else if ( ! empty( $widget_args['view_id'] ) ) {
441
				$post_id = absint( $widget_args['view_id'] );
442
			}
443
444 4
			$args = gravityview_get_permalink_query_args( $post_id );
445
446
			// 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 4
					'input' => 'hidden',
451 4
					'value' => $value,
452
				);
453
			}
454
		}
455
456 4
		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
	 * @return array If no View, returns empty array. Otherwise, returns array of fields configured in widgets and Search Bar for a View
472
	 */
473 25
	private function get_view_searchable_fields( $view, $with_full_field = false ) {
474
475
		/**
476
		 * Find all search widgets on the view and get the searchable fields settings.
477
		 */
478 25
		$searchable_fields = array();
479
480 25
		if ( ! $view ) {
481
			return $searchable_fields;
482
		}
483
484
		/**
485
		 * Include the sidebar Widgets.
486
		 */
487 25
		$widgets = (array) get_option( 'widget_gravityview_search', array() );
488
489 25
		foreach ( $widgets as $widget ) {
490 25
			if ( ! empty( $widget['view_id'] ) && $widget['view_id'] == $view->ID ) {
491
				if( $_fields = json_decode( $widget['search_fields'], true ) ) {
492
					foreach ( $_fields as $field ) {
493
						if ( empty( $field['form_id'] ) ) {
494
							$field['form_id'] = $view->form ? $view->form->ID : 0;
495
						}
496 25
						$searchable_fields[] = $with_full_field ? $field : $field['field'];
497
					}
498
				}
499
			}
500
		}
501
502 25
		foreach ( $view->widgets->by_id( $this->get_widget_id() )->all() as $widget ) {
503 24
			if( $_fields = json_decode( $widget->configuration->get( 'search_fields' ), true ) ) {
504 24
				foreach ( $_fields as $field ) {
505 24
					if ( empty( $field['form_id'] ) ) {
506 24
						$field['form_id'] = $view->form ? $view->form->ID : 0;
507
					}
508 24
					$searchable_fields[] = $with_full_field ? $field : $field['field'];
509
				}
510
			}
511
		}
512
513 25
		return $searchable_fields;
514
	}
515
516
	/** --- Frontend --- */
517
518
	/**
519
	 * Calculate the search criteria to filter entries
520
	 * @param array $search_criteria The search criteria
521
	 * @param int $form_id The form ID
522
	 * @param array $args Some args
523
	 *
524
	 * @param bool $force_search_criteria Whether to suppress GF_Query filter, internally used in self::gf_query_filter
525
	 *
526
	 * @return array
527
	 */
528 52
	public function filter_entries( $search_criteria, $form_id = null, $args = array(), $force_search_criteria = false ) {
529 52
		if ( ! $force_search_criteria && gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
530
			/**
531
			 * If GF_Query is available, we can construct custom conditions with nested
532
			 * booleans on the query, giving up the old ways of flat search_criteria field_filters.
533
			 */
534 33
			add_action( 'gravityview/view/query', array( $this, 'gf_query_filter' ), 10, 3 );
535 33
			return $search_criteria; // Return the original criteria, GF_Query modification kicks in later
536
		}
537
538 51
		if( 'post' === $this->search_method ) {
539
			$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...
540
		} else {
541 51
			$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...
542
		}
543
544 51
		$view = \GV\View::by_id( \GV\Utils::get( $args, 'id' ) );
545
546 51
		gravityview()->log->debug( 'Requested $_{method}: ', array( 'method' => $this->search_method, 'data' => $get ) );
547
548 51
		if ( empty( $get ) || ! is_array( $get ) ) {
549 29
			return $search_criteria;
550
		}
551
552 26
		$get = stripslashes_deep( $get );
553
554 26
		$get = gv_map_deep( $get, 'rawurldecode' );
555
556
		// Make sure array key is set up
557 26
		$search_criteria['field_filters'] = \GV\Utils::get( $search_criteria, 'field_filters', array() );
558
559 26
		$searchable_fields = $this->get_view_searchable_fields( $view );
560 26
		$searchable_field_objects = $this->get_view_searchable_fields( $view, true );
561
562
		// add free search
563 26
		if ( isset( $get['gv_search'] ) && '' !== $get['gv_search'] && in_array( 'search_all', $searchable_fields ) ) {
564
565 1
			$search_all_value = trim( $get['gv_search'] );
566
567
			/**
568
			 * @filter `gravityview/search-all-split-words` Search for each word separately or the whole phrase?
569
			 * @since 1.20.2
570
			 * @param bool $split_words True: split a phrase into words; False: search whole word only [Default: true]
571
			 */
572 1
			$split_words = apply_filters( 'gravityview/search-all-split-words', true );
573
574 1
			if ( $split_words ) {
575
576
				// Search for a piece
577 1
				$words = explode( ' ', $search_all_value );
578
579 1
				$words = array_filter( $words );
580
581
			} else {
582
583
				// Replace multiple spaces with one space
584 1
				$search_all_value = preg_replace( '/\s+/ism', ' ', $search_all_value );
585
586 1
				$words = array( $search_all_value );
587
			}
588
589 1
			foreach ( $words as $word ) {
590 1
				$search_criteria['field_filters'][] = array(
591 1
					'key' => null, // The field ID to search
592 1
					'value' => $word, // The value to search
593 1
					'operator' => 'contains', // What to search in. Options: `is` or `contains`
594
				);
595
			}
596
		}
597
598
		// start date & end date
599 26
		if ( in_array( 'entry_date', $searchable_fields ) ) {
600
			/**
601
			 * Get and normalize the dates according to the input format.
602
			 */
603 11
			if ( $curr_start = ! empty( $get['gv_start'] ) ? $get['gv_start'] : '' ) {
604 11
				if( $curr_start_date = date_create_from_format( $this->get_datepicker_format( true ), $curr_start ) ) {
605 11
					$curr_start = $curr_start_date->format( 'Y-m-d' );
606
				}
607
			}
608
609 11
			if ( $curr_end = ! empty( $get['gv_start'] ) ? ( ! empty( $get['gv_end'] ) ? $get['gv_end'] : '' ) : '' ) {
610 11
				if( $curr_end_date = date_create_from_format( $this->get_datepicker_format( true ), $curr_end ) ) {
611 11
					$curr_end = $curr_end_date->format( 'Y-m-d' );
612
				}
613
			}
614
615 11
			if ( $view ) {
616
				/**
617
				 * Override start and end dates if View is limited to some already.
618
				 */
619 11
				if ( $start_date = $view->settings->get( 'start_date' ) ) {
620 1
					if ( $start_timestamp = strtotime( $curr_start ) ) {
621 1
						$curr_start = $start_timestamp < strtotime( $start_date ) ? $start_date : $curr_start;
622
					}
623
				}
624 11
				if ( $end_date = $view->settings->get( 'end_date' ) ) {
625
					if ( $end_timestamp = strtotime( $curr_end ) ) {
626
						$curr_end = $end_timestamp > strtotime( $end_date ) ? $end_date : $curr_end;
627
					}
628
				}
629
			}
630
631
			/**
632
			 * @filter `gravityview_date_created_adjust_timezone` Whether to adjust the timezone for entries. \n
633
			 * date_created is stored in UTC format. Convert search date into UTC (also used on templates/fields/date_created.php)
634
			 * @since 1.12
635
			 * @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
636
			 * @param[in] string $context Where the filter is being called from. `search` in this case.
637
			 */
638 11
			$adjust_tz = apply_filters( 'gravityview_date_created_adjust_timezone', true, 'search' );
639
640
			/**
641
			 * Don't set $search_criteria['start_date'] if start_date is empty as it may lead to bad query results (GFAPI::get_entries)
642
			 */
643 11
			if ( ! empty( $curr_start ) ) {
644 11
				$curr_start = date( 'Y-m-d H:i:s', strtotime( $curr_start ) );
645 11
				$search_criteria['start_date'] = $adjust_tz ? get_gmt_from_date( $curr_start ) : $curr_start;
646
			}
647
648 11
			if ( ! empty( $curr_end ) ) {
649
				// Fast-forward 24 hour on the end time
650 11
				$curr_end = date( 'Y-m-d H:i:s', strtotime( $curr_end ) + DAY_IN_SECONDS );
651 11
				$search_criteria['end_date'] = $adjust_tz ? get_gmt_from_date( $curr_end ) : $curr_end;
652 11
				if ( strpos( $search_criteria['end_date'], '00:00:00' ) ) { // See https://github.com/gravityview/GravityView/issues/1056
653 11
					$search_criteria['end_date'] = date( 'Y-m-d H:i:s', strtotime( $search_criteria['end_date'] ) - 1 );
654
				}
655
			}
656
		}
657
658
		// search for a specific entry ID
659 26
		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...
660 2
			$search_criteria['field_filters'][] = array(
661 2
				'key' => 'id',
662 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...
663 2
				'operator' => '=',
664
			);
665
		}
666
667
		// search for a specific Created_by ID
668 26
		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...
669 4
			$search_criteria['field_filters'][] = array(
670 4
				'key' => 'created_by',
671 4
				'value' => $get['gv_by'],
672 4
				'operator' => '=',
673
			);
674
		}
675
676
		// Get search mode passed in URL
677 26
		$mode = isset( $get['mode'] ) && in_array( $get['mode'], array( 'any', 'all' ) ) ?  $get['mode'] : 'any';
678
679
		// get the other search filters
680 26
		foreach ( $get as $key => $value ) {
681
682 26
			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...
683 16
				continue;
684
			}
685
686 13
			$filter_key = $this->convert_request_key_to_filter_key( $key );
687
688 13
			if ( ! $filter = $this->prepare_field_filter( $filter_key, $value, $view, $searchable_field_objects ) ) {
689 1
				continue;
690
			}
691
692 12
			if ( isset( $filter[0]['value'] ) ) {
693
				$search_criteria['field_filters'] = array_merge( $search_criteria['field_filters'], $filter );
694
695
				// if date range type, set search mode to ALL
696
				if ( ! empty( $filter[0]['operator'] ) && in_array( $filter[0]['operator'], array( '>=', '<=', '>', '<' ) ) ) {
697
					$mode = 'all';
698
				}
699 12
			} elseif( !empty( $filter ) ) {
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
700 12
				$search_criteria['field_filters'][] = $filter;
701
			}
702
		}
703
704
		/**
705
		 * @filter `gravityview/search/mode` Set the Search Mode (`all` or `any`)
706
		 * @since 1.5.1
707
		 * @param[out,in] string $mode Search mode (`any` vs `all`)
708
		 */
709 26
		$search_criteria['field_filters']['mode'] = apply_filters( 'gravityview/search/mode', $mode );
710
711 26
		gravityview()->log->debug( 'Returned Search Criteria: ', array( 'data' => $search_criteria ) );
712
713 26
		unset( $get );
714
715 26
		return $search_criteria;
716
	}
717
718
	/**
719
	 * Filters the \GF_Query with advanced logic.
720
	 *
721
	 * Dropin for the legacy flat filters when \GF_Query is available.
722
	 *
723
	 * @param \GF_Query $query The current query object reference
724
	 * @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...
725
	 * @param \GV\Request $request The request object
726
	 */
727 32
	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...
728
		/**
729
		 * This is a shortcut to get all the needed search criteria.
730
		 * We feed these into an new GF_Query and tack them onto the current object.
731
		 */
732 32
		$search_criteria = $this->filter_entries( array(), null, array( 'id' => $view->ID ), true /** force search_criteria */ );
733
734 32
		$query_class = $view->get_query_class();
735
736 32
		if ( empty( $search_criteria['field_filters'] ) ) {
737 28
			return;
738
		}
739
740 7
		$widgets = $view->widgets->by_id( $this->widget_id );
741 7
		if ( $widgets->count() ) {
742 6
			$widgets = $widgets->all();
743 6
			$widget  = $widgets[0];
744
745 6
			$search_fields = json_decode( $widget->configuration->get( 'search_fields' ), true );
746
747 6
			foreach ( (array) $search_fields as $search_field ) {
748 6
				if ( 'created_by' === $search_field['field'] && 'input_text' === $search_field['input'] ) {
749 6
					$created_by_text_mode = true;
750
				}
751
			}
752
		}
753
754 7
		$extra_conditions = array();
755 7
		$mode = 'any';
756
757 7
		foreach ( $search_criteria['field_filters'] as &$filter ) {
758 7
			if ( ! is_array( $filter ) ) {
759 7
				if ( in_array( strtolower( $filter ), array( 'any', 'all' ) ) ) {
760 7
					$mode = $filter;
761
				}
762 7
				continue;
763
			}
764
765
			// Construct a manual query for unapproved statuses
766 6
			if ( 'is_approved' === $filter['key'] && in_array( \GravityView_Entry_Approval_Status::UNAPPROVED, (array) $filter['value'] ) ) {
767 2
				$_tmp_query       = new $query_class( $view->form->ID, array(
768 2
					'field_filters' => array(
769
						array(
770 2
							'operator' => 'in',
771 2
							'key'      => 'is_approved',
772 2
							'value'    => (array) $filter['value'],
773
						),
774
						array(
775
							'operator' => 'is',
776
							'key'      => 'is_approved',
777
							'value'    => '',
778
						),
779 2
						'mode' => 'any'
0 ignored issues
show
introduced by
Key specified for array entry; first entry has no key
Loading history...
780
					),
781
				) );
782 2
				$_tmp_query_parts = $_tmp_query->_introspect();
783
784 2
				$extra_conditions[] = $_tmp_query_parts['where'];
785
786 2
				$filter = false;
787 2
				continue;
788
			}
789
790
			// Construct manual query for text mode creator search
791 6
			if ( 'created_by' === $filter['key'] && ! empty( $created_by_text_mode ) ) {
792 1
				$extra_conditions[] = new GravityView_Widget_Search_Author_GF_Query_Condition( $filter, $view );
793 1
				$filter = false;
794 1
				continue;
795
			}
796
797
			// By default, we want searches to be wildcard for each field.
798 5
			$filter['operator'] = empty( $filter['operator'] ) ? 'contains' : $filter['operator'];
799
800
			// For multichoice, let's have an in (OR) search.
801 5
			if ( is_array( $filter['value'] ) ) {
802 3
				$filter['operator'] = 'in'; // @todo what about in contains (OR LIKE chains)?
803
			}
804
805
			// Default form with joins functionality
806 5
			if ( empty( $filter['form_id'] ) ) {
807 1
				$filter['form_id'] = $view->form ? $view->form->ID : 0;
808
			}
809
810
			/**
811
			 * @filter `gravityview_search_operator` Modify the search operator for the field (contains, is, isnot, etc)
812
			 * @param string $operator Existing search operator
813
			 * @param array $filter array with `key`, `value`, `operator`, `type` keys
814
			 */
815 5
			$filter['operator'] = apply_filters( 'gravityview_search_operator', $filter['operator'], $filter );
816
		}
817
818 7
		$search_conditions = array();
819
820 7
		if ( $filters = array_filter( $search_criteria['field_filters'] ) ) {
821
822 7
			foreach ( $filters as $filter ) {
823 7
				if ( ! is_array( $filter ) ) {
824 4
					continue;
825
				}
826
827
				/**
828
				 * Parse the filter criteria to generate the needed
829
				 * WHERE condition. This is a trick to not write our own generation
830
				 * code by reusing what's inside GF_Query already as they
831
				 * take care of many small things like forcing numeric, etc.
832
				 */
833 5
				$_tmp_query       = new $query_class( $filter['form_id'], array( 'mode' => 'any', 'field_filters' => array( $filter ) ) );
834 5
				$_tmp_query_parts = $_tmp_query->_introspect();
835 5
				$search_condition = $_tmp_query_parts['where'];
836
837 5
				if ( empty( $filter['key'] ) &&  $search_condition->expressions ) {
838
					 foreach ( $search_condition->expressions as $condition ) {
839
						$search_conditions[] = new GravityView_Widget_Search_All_GF_Query_Condition( $condition, $view );
840
					 }
841
				} else {
842 5
					$left = $search_condition->left;
843 5
					$alias = $query->_alias( $left->field_id, $left->source, $left->is_entry_column() ? 't' : 'm' );
844
845 5
					if ( $view->joins && $left->field_id == GF_Query_Column::META ) {
846
						$search_conditions[] = new GravityView_Widget_Search_All_GF_Query_Condition( $search_condition, $view );
847
					} else {
848 5
						$search_conditions[] = new GF_Query_Condition(
849 5
							new GF_Query_Column( $left->field_id, $left->source, $alias ),
850 5
							$search_condition->operator,
851 5
							$search_condition->right
852
						);
853
					}
854
				}
855
			}
856
857 7
			if ( $search_conditions ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $search_conditions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
858 5
				$search_conditions = array( call_user_func_array( '\GF_Query_Condition::' . ( $mode == 'all' ? '_and' : '_or' ), $search_conditions ) );
859
			}
860
		}
861
862
		/**
863
		 * Grab the current clauses. We'll be combining them shortly.
864
		 */
865 7
		$query_parts = $query->_introspect();
866
867
		/**
868
		 * Combine the parts as a new WHERE clause.
869
		 */
870 7
		$where = call_user_func_array( '\GF_Query_Condition::_and', array_merge( array( $query_parts['where'] ), $search_conditions, $extra_conditions ) );
871 7
		$query->where( $where );
872 7
	}
873
874
	/**
875
	 * Convert $_GET/$_POST key to the field/meta ID
876
	 *
877
	 * Examples:
878
	 * - `filter_is_starred` => `is_starred`
879
	 * - `filter_1_2` => `1.2`
880
	 * - `filter_5` => `5`
881
	 *
882
	 * @since 2.0
883
	 *
884
	 * @param string $key $_GET/_$_POST search key
885
	 *
886
	 * @return string
887
	 */
888 13
	private function convert_request_key_to_filter_key( $key ) {
889
890 13
		$field_id = str_replace( 'filter_', '', $key );
891
892
		// calculates field_id, removing 'filter_' and for '_' for advanced fields ( like name or checkbox )
893 13
		if ( preg_match('/^[0-9_]+$/ism', $field_id ) ) {
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
894 11
			$field_id = str_replace( '_', '.', $field_id );
895
		}
896
897 13
		return $field_id;
898
	}
899
900
	/**
901
	 * Prepare the field filters to GFAPI
902
	 *
903
	 * 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.
904
	 *
905
	 * Format searched values
906
	 *
907
	 * @param  string $filter_key ID of the field, or entry meta key
908
	 * @param  string $value $_GET/$_POST search value
909
	 * @param  \GV\View $view The view we're looking at
910
	 * @param array[] $searchable_fields The searchable fields as configured by the widget.
911
	 *
912
	 * @return array|false 1 or 2 deph levels, false if not allowed
913
	 */
914 13
	public function prepare_field_filter( $filter_key, $value, $view, $searchable_fields ) {
915 13
		$filter_key = explode( ':', $filter_key ); // field_id, form_id
916
917 13
		$form = null;
918
919 13
		if ( count( $filter_key ) > 1 ) {
920
			// form is specified
921
			list( $field_id, $form_id ) = $filter_key;
922
923
			if ( $forms = \GV\View::get_joined_forms( $view->ID ) ) {
924
				if ( ! $form = \GV\GF_Form::by_id( $form_id ) ) {
925
					return false;
926
				}
927
			}
928
929
			// form is allowed
930
			$found = false;
931
			foreach ( $forms as $form ) {
932
				if ( $form->ID == $form_id ) {
933
					$found = true;
934
					break;
935
				}
936
			}
937
938
			if ( ! $found ) {
939
				return false;
940
			}
941
942
			// form is in searchable fields
943
			$found = false;
944
			foreach ( $searchable_fields as $field ) {
945
				if ( $field_id == $field['field'] && $form->ID == $field['form_id'] ) {
946
					$found = true;
947
					break;
948
				}
949
			}
950
951
			if ( ! $found ) {
952
				return false;
953
			}
954
		} else {
955 13
			$field_id = reset( $filter_key );
956 13
			$searchable_fields = wp_list_pluck( $searchable_fields, 'field' );
957 13
			if ( ! in_array( 'search_all', $searchable_fields ) && ! in_array( $field_id, $searchable_fields ) ) {
958 1
				return false;
959
			}
960
		}
961
		
962 12
		if ( ! $form ) {
963
			// fallback
964 12
			$form = $view->form;
965
		}
966
967
		// get form field array
968 12
		$form_field = is_numeric( $field_id ) ? \GV\GF_Field::by_id( $form, $field_id ) : \GV\Internal_Field::by_id( $field_id );
969
970
		// default filter array
971
		$filter = array(
972 12
			'key'   => $field_id,
973 12
			'value' => $value,
974 12
			'form_id' => $form->ID,
975
		);
976
977 12
		switch ( $form_field->type ) {
978
979 12
			case 'select':
980 12
			case 'radio':
981 1
				$filter['operator'] = 'is';
982 1
				break;
983
984 11
			case 'post_category':
985
986
				if ( ! is_array( $value ) ) {
987
					$value = array( $value );
988
				}
989
990
				// Reset filter variable
991
				$filter = array();
992
993
				foreach ( $value as $val ) {
994
					$cat = get_term( $val, 'category' );
995
					$filter[] = array(
996
						'key'      => $field_id,
997
						'value'    => esc_attr( $cat->name ) . ':' . $val,
998
						'operator' => 'is',
999
					);
1000
				}
1001
1002
				break;
1003
1004 11
			case 'multiselect':
1005
1006
				if ( ! is_array( $value ) ) {
1007
					break;
1008
				}
1009
1010
				// Reset filter variable
1011
				$filter = array();
1012
1013
				foreach ( $value as $val ) {
1014
					$filter[] = array( 'key' => $field_id, 'value' => $val );
1015
				}
1016
1017
				break;
1018
1019 11
			case 'checkbox':
1020
				// convert checkbox on/off into the correct search filter
1021
				if ( false !== strpos( $field_id, '.' ) && ! empty( $form_field->inputs ) && ! empty( $form_field->choices ) ) {
1022
					foreach ( $form_field->inputs as $k => $input ) {
1023
						if ( $input['id'] == $field_id ) {
1024
							$filter['value'] = $form_field->choices[ $k ]['value'];
1025
							$filter['operator'] = 'is';
1026
							break;
1027
						}
1028
					}
1029
				} elseif ( is_array( $value ) ) {
1030
1031
					// Reset filter variable
1032
					$filter = array();
1033
1034
					foreach ( $value as $val ) {
1035
						$filter[] = array(
1036
							'key'      => $field_id,
1037
							'value'    => $val,
1038
							'operator' => 'is',
1039
						);
1040
					}
1041
				}
1042
1043
				break;
1044
1045 11
			case 'name':
1046 11
			case 'address':
1047
1048
				if ( false === strpos( $field_id, '.' ) ) {
1049
1050
					$words = explode( ' ', $value );
1051
1052
					$filters = array();
1053
					foreach ( $words as $word ) {
1054
						if ( ! empty( $word ) && strlen( $word ) > 1 ) {
1055
							// Keep the same key for each filter
1056
							$filter['value'] = $word;
1057
							// Add a search for the value
1058
							$filters[] = $filter;
1059
						}
1060
					}
1061
1062
					$filter = $filters;
1063
				}
1064
1065
				// State/Province should be exact matches
1066
				if ( 'address' === $form_field->field->type ) {
1067
1068
					$searchable_fields = $this->get_view_searchable_fields( $view, true );
1069
1070
					foreach ( $searchable_fields as $searchable_field ) {
1071
1072
						if( $form_field->ID !== $searchable_field['field'] ) {
1073
							continue;
1074
						}
1075
1076
						// Only exact-match dropdowns, not text search
1077
						if( in_array( $searchable_field['input'], array( 'text', 'search' ), true ) ) {
1078
							continue;
1079
						}
1080
1081
						$input_id = gravityview_get_input_id_from_id( $form_field->ID );
1082
1083
						if ( 4 === $input_id ) {
1084
							$filter['operator'] = 'is';
1085
						};
1086
					}
1087
				}
1088
1089
				break;
1090
1091 11
			case 'date':
1092
1093 8
				$date_format = $this->get_datepicker_format( true );
1094
1095 8
				if ( is_array( $value ) ) {
1096
1097
					// Reset filter variable
1098
					$filter = array();
1099
1100
					foreach ( $value as $k => $date ) {
1101
						if ( empty( $date ) ) {
1102
							continue;
1103
						}
1104
						$operator = 'start' === $k ? '>=' : '<=';
1105
1106
						/**
1107
						 * @hack
1108
						 * @since 1.16.3
1109
						 * Safeguard until GF implements '<=' operator
1110
						 */
1111
						if( !GFFormsModel::is_valid_operator( $operator ) && $operator === '<=' ) {
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
1112
							$operator = '<';
1113
							$date = date( 'Y-m-d', strtotime( self::get_formatted_date( $date, 'Y-m-d', $date_format ) . ' +1 day' ) );
1114
						}
1115
1116
						$filter[] = array(
1117
							'key'      => $field_id,
1118
							'value'    => self::get_formatted_date( $date, 'Y-m-d', $date_format ),
1119
							'operator' => $operator,
1120
						);
1121
					}
1122
				} else {
1123 8
					$date = $value;
1124 8
					$filter['value'] = self::get_formatted_date( $date, 'Y-m-d', $date_format );
1125
				}
1126
1127 8
				break;
1128
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
1129
1130
		} // switch field type
1131
1132 12
		return $filter;
1133
	}
1134
1135
	/**
1136
	 * Get the Field Format form GravityForms
1137
	 *
1138
	 * @param GF_Field_Date $field The field object
1139
	 * @since 1.10
1140
	 *
1141
	 * @return string Format of the date in the database
1142
	 */
1143
	public static function get_date_field_format( GF_Field_Date $field ) {
1144
		$format = 'm/d/Y';
1145
		$datepicker = array(
1146
			'mdy' => 'm/d/Y',
1147
			'dmy' => 'd/m/Y',
1148
			'dmy_dash' => 'd-m-Y',
1149
			'dmy_dot' => 'd.m.Y',
1150
			'ymd_slash' => 'Y/m/d',
1151
			'ymd_dash' => 'Y-m-d',
1152
			'ymd_dot' => 'Y.m.d',
1153
		);
1154
1155
		if ( ! empty( $field->dateFormat ) && isset( $datepicker[ $field->dateFormat ] ) ){
1156
			$format = $datepicker[ $field->dateFormat ];
1157
		}
1158
1159
		return $format;
1160
	}
1161
1162
	/**
1163
	 * Format a date value
1164
	 *
1165
	 * @param string $value Date value input
1166
	 * @param string $format Wanted formatted date
1167
	 *
1168
	 * @since 2.1.2
1169
	 * @param string $value_format The value format. Default: Y-m-d
1170
	 *
1171
	 * @return string
1172
	 */
1173 8
	public static function get_formatted_date( $value = '', $format = 'Y-m-d', $value_format = 'Y-m-d' ) {
1174
1175 8
		$date = date_create_from_format( $value_format, $value );
1176
1177 8
		if ( empty( $date ) ) {
1178
			gravityview()->log->debug( 'Date format not valid: {value}', array( 'value' => $value ) );
1179
			return '';
1180
		}
1181 8
		return $date->format( $format );
1182
	}
1183
1184
1185
	/**
1186
	 * Include this extension templates path
1187
	 * @param array $file_paths List of template paths ordered
1188
	 */
1189 1
	public function add_template_path( $file_paths ) {
1190
1191
		// Index 100 is the default GravityView template path.
1192 1
		$file_paths[102] = self::$file . 'templates/';
1193
1194 1
		return $file_paths;
1195
	}
1196
1197
	/**
1198
	 * Check whether the configured search fields have a date field
1199
	 *
1200
	 * @since 1.17.5
1201
	 *
1202
	 * @param array $search_fields
1203
	 *
1204
	 * @return bool True: has a `date` or `date_range` field
1205
	 */
1206 4
	private function has_date_field( $search_fields ) {
1207
1208 4
		$has_date = false;
1209
1210 4
		foreach ( $search_fields as $k => $field ) {
1211 4
			if ( in_array( $field['input'], array( 'date', 'date_range', 'entry_date' ) ) ) {
1212
				$has_date = true;
1213 4
				break;
1214
			}
1215
		}
1216
1217 4
		return $has_date;
1218
	}
1219
1220
	/**
1221
	 * Renders the Search Widget
1222
	 * @param array $widget_args
1223
	 * @param string $content
1224
	 * @param string $context
1225
	 *
1226
	 * @return void
1227
	 */
1228 4
	public function render_frontend( $widget_args, $content = '', $context = '' ) {
1229
		/** @var GravityView_View $gravityview_view */
1230 4
		$gravityview_view = GravityView_View::getInstance();
1231
1232 4
		if ( empty( $gravityview_view ) ) {
1233
			gravityview()->log->debug( '$gravityview_view not instantiated yet.' );
1234
			return;
1235
		}
1236
1237
		// get configured search fields
1238 4
		$search_fields = ! empty( $widget_args['search_fields'] ) ? json_decode( $widget_args['search_fields'], true ) : '';
1239
1240 4
		if ( empty( $search_fields ) || ! is_array( $search_fields ) ) {
1241
			gravityview()->log->debug( 'No search fields configured for widget:', array( 'data' => $widget_args ) );
1242
			return;
1243
		}
1244
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
1245
1246
		// prepare fields
1247 4
		foreach ( $search_fields as $k => $field ) {
1248
1249 4
			$updated_field = $field;
1250
1251 4
			$updated_field = $this->get_search_filter_details( $updated_field );
1252
1253 4
			switch ( $field['field'] ) {
1254
1255 4
				case 'search_all':
1256 4
					$updated_field['key'] = 'search_all';
1257 4
					$updated_field['input'] = 'search_all';
1258 4
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_search' );
1259 4
					break;
1260
1261
				case 'entry_date':
1262
					$updated_field['key'] = 'entry_date';
1263
					$updated_field['input'] = 'entry_date';
1264
					$updated_field['value'] = array(
1265
						'start' => $this->rgget_or_rgpost( 'gv_start' ),
1266
						'end' => $this->rgget_or_rgpost( 'gv_end' ),
1267
					);
1268
					break;
1269
1270
				case 'entry_id':
1271
					$updated_field['key'] = 'entry_id';
1272
					$updated_field['input'] = 'entry_id';
1273
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_id' );
1274
					break;
1275
1276
				case 'created_by':
1277
					$updated_field['key'] = 'created_by';
1278
					$updated_field['name'] = 'gv_by';
1279
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_by' );
1280
					$updated_field['choices'] = self::get_created_by_choices();
1281
					break;
1282
				
1283
				case 'is_approved':
1284
					$updated_field['key'] = 'is_approved';
1285
					$updated_field['value'] = $this->rgget_or_rgpost( 'filter_is_approved' );
1286
					$updated_field['choices'] = self::get_is_approved_choices();
1287
					break;
1288
			}
1289
1290 4
			$search_fields[ $k ] = $updated_field;
1291
		}
1292
1293 4
		gravityview()->log->debug( 'Calculated Search Fields: ', array( 'data' => $search_fields ) );
1294
1295
		/**
1296
		 * @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.
1297
		 * @param array $search_fields Array of search filters with `key`, `label`, `value`, `type`, `choices` keys
1298
		 * @param GravityView_Widget_Search $this Current widget object
1299
		 * @param array $widget_args Args passed to this method. {@since 1.8}
1300
		 * @param \GV\Template_Context $context {@since 2.0}
1301
		 * @var array
1302
		 */
1303 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...
1304
1305 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...
1306
1307
		/** @since 1.14 */
1308 4
		$gravityview_view->search_mode = ! empty( $widget_args['search_mode'] ) ? $widget_args['search_mode'] : 'any';
0 ignored issues
show
Bug introduced by
The property search_mode does not seem to exist in GravityView_View.

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

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

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

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

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

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

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

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

Loading history...
1315
1316 4
		if ( $this->has_date_field( $search_fields ) ) {
1317
			// enqueue datepicker stuff only if needed!
1318
			$this->enqueue_datepicker();
1319
		}
1320
1321 4
		$this->maybe_enqueue_flexibility();
1322
1323 4
		$gravityview_view->render( 'widget', 'search', false );
1324 4
	}
1325
1326
	/**
1327
	 * Get the search class for a search form
1328
	 *
1329
	 * @since 1.5.4
1330
	 *
1331
	 * @return string Sanitized CSS class for the search form
1332
	 */
1333 4
	public static function get_search_class( $custom_class = '' ) {
1334 4
		$gravityview_view = GravityView_View::getInstance();
1335
1336 4
		$search_class = 'gv-search-'.$gravityview_view->search_layout;
0 ignored issues
show
Documentation introduced by
The property search_layout does not exist on object<GravityView_View>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
1337
1338 4
		if ( ! empty( $custom_class )  ) {
1339
			$search_class .= ' '.$custom_class;
1340
		}
1341
1342
		/**
1343
		 * @filter `gravityview_search_class` Modify the CSS class for the search form
1344
		 * @param string $search_class The CSS class for the search form
1345
		 */
1346 4
		$search_class = apply_filters( 'gravityview_search_class', $search_class );
1347
1348
		// Is there an active search being performed? Used by fe-views.js
1349 4
		$search_class .= GravityView_frontend::getInstance()->isSearch() ? ' gv-is-search' : '';
1350
1351 4
		return gravityview_sanitize_html_class( $search_class );
1352
	}
1353
1354
1355
	/**
1356
	 * Calculate the search form action
1357
	 * @since 1.6
1358
	 *
1359
	 * @return string
1360
	 */
1361 4
	public static function get_search_form_action() {
1362 4
		$gravityview_view = GravityView_View::getInstance();
1363
1364 4
		$post_id = $gravityview_view->getPostId() ? $gravityview_view->getPostId() : $gravityview_view->getViewId();
1365
1366 4
		$url = add_query_arg( array(), get_permalink( $post_id ) );
1367
1368 4
		return esc_url( $url );
1369
	}
1370
1371
	/**
1372
	 * Get the label for a search form field
1373
	 * @param  array $field      Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1374
	 * @param  array $form_field Form field data, as fetched by `gravityview_get_field()`
1375
	 * @return string             Label for the search form
1376
	 */
1377 4
	private static function get_field_label( $field, $form_field = array() ) {
1378
1379 4
		$label = \GV\Utils::_GET( 'label', \GV\Utils::get( $field, 'label' ) );
1380
1381 4
		if ( ! $label ) {
1382
1383 4
			$label = isset( $form_field['label'] ) ? $form_field['label'] : '';
1384
1385 4
			switch( $field['field'] ) {
1386 4
				case 'search_all':
1387 4
					$label = __( 'Search Entries:', 'gravityview' );
1388 4
					break;
1389
				case 'entry_date':
1390
					$label = __( 'Filter by date:', 'gravityview' );
1391
					break;
1392
				case 'entry_id':
1393
					$label = __( 'Entry ID:', 'gravityview' );
1394
					break;
1395
				default:
1396
					// If this is a field input, not a field
1397
					if ( strpos( $field['field'], '.' ) > 0 && ! empty( $form_field['inputs'] ) ) {
1398
1399
						// Get the label for the field in question, which returns an array
1400
						$items = wp_list_filter( $form_field['inputs'], array( 'id' => $field['field'] ) );
1401
1402
						// Get the item with the `label` key
1403
						$values = wp_list_pluck( $items, 'label' );
1404
1405
						// There will only one item in the array, but this is easier
1406
						foreach ( $values as $value ) {
1407
							$label = $value;
1408
							break;
1409
						}
1410
					}
1411
			}
1412
		}
1413
1414
		/**
1415
		 * @filter `gravityview_search_field_label` Modify the label for a search field. Supports returning HTML
1416
		 * @since 1.17.3 Added $field parameter
1417
		 * @param[in,out] string $label Existing label text, sanitized.
1418
		 * @param[in] array $form_field Gravity Forms field array, as returned by `GFFormsModel::get_field()`
1419
		 * @param[in] array $field Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1420
		 */
1421 4
		$label = apply_filters( 'gravityview_search_field_label', esc_attr( $label ), $form_field, $field );
1422
1423 4
		return $label;
1424
	}
1425
1426
	/**
1427
	 * Prepare search fields to frontend render with other details (label, field type, searched values)
1428
	 *
1429
	 * @param array $field
1430
	 * @return array
1431
	 */
1432 4
	private function get_search_filter_details( $field ) {
1433
1434 4
		$gravityview_view = GravityView_View::getInstance();
1435
1436 4
		$form = $gravityview_view->getForm();
1437
1438
		// for advanced field ids (eg, first name / last name )
1439 4
		$name = 'filter_' . str_replace( '.', '_', $field['field'] );
1440
1441
		// get searched value from $_GET/$_POST (string or array)
1442 4
		$value = $this->rgget_or_rgpost( $name );
1443
1444
		// get form field details
1445 4
		$form_field = gravityview_get_field( $form, $field['field'] );
1446
1447
		$filter = array(
1448 4
			'key' => $field['field'],
1449 4
			'name' => $name,
1450 4
			'label' => self::get_field_label( $field, $form_field ),
1451 4
			'input' => $field['input'],
1452 4
			'value' => $value,
1453 4
			'type' => $form_field['type'],
1454
		);
1455
1456
		// collect choices
1457 4
		if ( 'post_category' === $form_field['type'] && ! empty( $form_field['displayAllCategories'] ) && empty( $form_field['choices'] ) ) {
1458
			$filter['choices'] = gravityview_get_terms_choices();
1459 4
		} elseif ( ! empty( $form_field['choices'] ) ) {
1460
			$filter['choices'] = $form_field['choices'];
1461
		}
1462
1463 4
		if ( 'date_range' === $field['input'] && empty( $value ) ) {
1464
			$filter['value'] = array( 'start' => '', 'end' => '' );
1465
		}
1466
1467 4
		return $filter;
1468
1469
	}
1470
1471
	/**
1472
	 * Calculate the search choices for the users
1473
	 *
1474
	 * @since 1.8
1475
	 *
1476
	 * @return array Array of user choices (value = ID, text = display name)
1477
	 */
1478
	private static function get_created_by_choices() {
1479
1480
		/**
1481
		 * filter gravityview/get_users/search_widget
1482
		 * @see \GVCommon::get_users
1483
		 */
1484
		$users = GVCommon::get_users( 'search_widget', array( 'fields' => array( 'ID', 'display_name' ) ) );
1485
1486
		$choices = array();
1487
		foreach ( $users as $user ) {
1488
			$choices[] = array(
1489
				'value' => $user->ID,
1490
				'text' => $user->display_name,
1491
			);
1492
		}
1493
1494
		return $choices;
1495
	}
1496
1497
	/**
1498
	 * Calculate the search checkbox choices for approval status
1499
	 *
1500
	 * @since develop
1501
	 *
1502
	 * @return array Array of approval status choices (value = status, text = display name)
1503
	 */
1504
	private static function get_is_approved_choices() {
1505
1506
		$choices = array();
1507
		foreach ( GravityView_Entry_Approval_Status::get_all() as $status ) {
1508
			$choices[] = array(
1509
				'value' => $status['value'],
1510
				'text' => $status['label'],
1511
			);
1512
		}
1513
1514
		return $choices;
1515
	}
1516
1517
	/**
1518
	 * Output the Clear Search Results button
1519
	 * @since 1.5.4
1520
	 */
1521 4
	public static function the_clear_search_button() {
1522 4
		$gravityview_view = GravityView_View::getInstance();
1523
1524 4
		if ( $gravityview_view->search_clear ) {
0 ignored issues
show
Documentation introduced by
The property search_clear does not exist on object<GravityView_View>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
1525
1526
			$url = strtok( add_query_arg( array() ), '?' );
1527
1528
			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...
1529
1530
		}
1531 4
	}
1532
1533
	/**
1534
	 * Based on the search method, fetch the value for a specific key
1535
	 *
1536
	 * @since 1.16.4
1537
	 *
1538
	 * @param string $name Name of the request key to fetch the value for
1539
	 *
1540
	 * @return mixed|string Value of request at $name key. Empty string if empty.
1541
	 */
1542 4
	private function rgget_or_rgpost( $name ) {
1543 4
		$value = \GV\Utils::_REQUEST( $name );
1544
1545 4
		$value = stripslashes_deep( $value );
1546
1547 4
		$value = gv_map_deep( $value, 'rawurldecode' );
1548
1549 4
		$value = gv_map_deep( $value, '_wp_specialchars' );
1550
1551 4
		return $value;
1552
	}
1553
1554
1555
	/**
1556
	 * Require the datepicker script for the frontend GV script
1557
	 * @param array $js_dependencies Array of existing required scripts for the fe-views.js script
1558
	 * @return array Array required scripts, with `jquery-ui-datepicker` added
1559
	 */
1560
	public function add_datepicker_js_dependency( $js_dependencies ) {
1561
1562
		$js_dependencies[] = 'jquery-ui-datepicker';
1563
1564
		return $js_dependencies;
1565
	}
1566
1567
	/**
1568
	 * Modify the array passed to wp_localize_script()
1569
	 *
1570
	 * @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...
1571
	 * @param array $view_data View data array with View settings
1572
	 *
1573
	 * @return array
1574
	 */
1575
	public function add_datepicker_localization( $localizations = array(), $view_data = array() ) {
1576
		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...
1577
1578
		/**
1579
		 * @filter `gravityview_datepicker_settings` Modify the datepicker settings
1580
		 * @see http://api.jqueryui.com/datepicker/ Learn what settings are available
1581
		 * @see http://www.renegadetechconsulting.com/tutorials/jquery-datepicker-and-wordpress-i18n Thanks for the helpful information on $wp_locale
1582
		 * @param array $js_localization The data padded to the Javascript file
1583
		 * @param array $view_data View data array with View settings
1584
		 */
1585
		$datepicker_settings = apply_filters( 'gravityview_datepicker_settings', array(
1586
			'yearRange' => '-5:+5',
1587
			'changeMonth' => true,
1588
			'changeYear' => true,
1589
			'closeText' => esc_attr_x( 'Close', 'Close calendar', 'gravityview' ),
1590
			'prevText' => esc_attr_x( 'Prev', 'Previous month in calendar', 'gravityview' ),
1591
			'nextText' => esc_attr_x( 'Next', 'Next month in calendar', 'gravityview' ),
1592
			'currentText' => esc_attr_x( 'Today', 'Today in calendar', 'gravityview' ),
1593
			'weekHeader' => esc_attr_x( 'Week', 'Week in calendar', 'gravityview' ),
1594
			'monthStatus'       => __( 'Show a different month', 'gravityview' ),
1595
			'monthNames'        => array_values( $wp_locale->month ),
1596
			'monthNamesShort'   => array_values( $wp_locale->month_abbrev ),
1597
			'dayNames'          => array_values( $wp_locale->weekday ),
1598
			'dayNamesShort'     => array_values( $wp_locale->weekday_abbrev ),
1599
			'dayNamesMin'       => array_values( $wp_locale->weekday_initial ),
1600
			// get the start of week from WP general setting
1601
			'firstDay'          => get_option( 'start_of_week' ),
1602
			// is Right to left language? default is false
1603
			'isRTL'             => is_rtl(),
1604
		), $view_data );
1605
1606
		$localizations['datepicker'] = $datepicker_settings;
1607
1608
		return $localizations;
1609
1610
	}
1611
1612
	/**
1613
	 * Register search widget scripts, including Flexibility
1614
	 *
1615
	 * @see https://github.com/10up/flexibility
1616
	 *
1617
	 * @since 1.17
1618
	 *
1619
	 * @return void
1620
	 */
1621
	public function register_scripts() {
1622
		wp_register_script( 'gv-flexibility', plugins_url( 'assets/lib/flexibility/flexibility.js', GRAVITYVIEW_FILE ), array(), \GV\Plugin::$version, true );
1623
	}
1624
1625
	/**
1626
	 * If the current visitor is running IE 8 or 9, enqueue Flexibility
1627
	 *
1628
	 * @since 1.17
1629
	 *
1630
	 * @return void
1631
	 */
1632 4
	private function maybe_enqueue_flexibility() {
1633 4
		if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && preg_match( '/MSIE [8-9]/', $_SERVER['HTTP_USER_AGENT'] ) ) {
0 ignored issues
show
introduced by
Due to using Batcache, server side based client related logic will not work, use JS instead.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_SERVER
Loading history...
1634
			wp_enqueue_script( 'gv-flexibility' );
1635
		}
1636 4
	}
1637
1638
	/**
1639
	 * Enqueue the datepicker script
1640
	 *
1641
	 * It sets the $gravityview->datepicker_class parameter
1642
	 *
1643
	 * @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.
1644
	 * @return void
1645
	 */
1646
	public function enqueue_datepicker() {
1647
		$gravityview_view = GravityView_View::getInstance();
1648
1649
		wp_enqueue_script( 'jquery-ui-datepicker' );
1650
1651
		add_filter( 'gravityview_js_dependencies', array( $this, 'add_datepicker_js_dependency' ) );
1652
		add_filter( 'gravityview_js_localization', array( $this, 'add_datepicker_localization' ), 10, 2 );
1653
1654
		$scheme = is_ssl() ? 'https://' : 'http://';
1655
		wp_enqueue_style( 'jquery-ui-datepicker', $scheme.'ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/smoothness/jquery-ui.css' );
1656
1657
		/**
1658
		 * @filter `gravityview_search_datepicker_class`
1659
		 * 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.
1660
		 * @param string $css_class CSS class to use. Default: `gv-datepicker datepicker mdy` \n
1661
		 * Options are:
1662
		 * - `mdy` (mm/dd/yyyy)
1663
		 * - `dmy` (dd/mm/yyyy)
1664
		 * - `dmy_dash` (dd-mm-yyyy)
1665
		 * - `dmy_dot` (dd.mm.yyyy)
1666
		 * - `ymd_slash` (yyyy/mm/dd)
1667
		 * - `ymd_dash` (yyyy-mm-dd)
1668
		 * - `ymd_dot` (yyyy.mm.dd)
1669
		 */
1670
		$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...
1671
1672
		$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...
1673
	}
1674
1675
	/**
1676
	 * Retrieve the datepicker format.
1677
	 *
1678
	 * @param bool $date_format Whether to return the PHP date format or the datpicker class name. Default: false.
1679
	 *
1680
	 * @see https://docs.gravityview.co/article/115-changing-the-format-of-the-search-widgets-date-picker
1681
	 *
1682
	 * @return string The datepicker format placeholder, or the PHP date format.
1683
	 */
1684 18
	private function get_datepicker_format( $date_format = false ) {
1685
1686 18
		$default_format = 'mdy';
1687
1688
		/**
1689
		 * @filter `gravityview/widgets/search/datepicker/format`
1690
		 * @since 2.1.1
1691
		 * @param string           $format Default: mdy
1692
		 * Options are:
1693
		 * - `mdy` (mm/dd/yyyy)
1694
		 * - `dmy` (dd/mm/yyyy)
1695
		 * - `dmy_dash` (dd-mm-yyyy)
1696
		 * - `dmy_dot` (dd.mm.yyyy)
1697
		 * - `ymd_slash` (yyyy/mm/dd)
1698
		 * - `ymd_dash` (yyyy-mm-dd)
1699
		 * - `ymd_dot` (yyyy.mm.dd)
1700
		 */
1701 18
		$format = apply_filters( 'gravityview/widgets/search/datepicker/format', $default_format );
1702
1703
		$gf_date_formats = array(
1704 18
			'mdy' => 'm/d/Y',
1705
1706
			'dmy_dash' => 'd-m-Y',
1707
			'dmy_dot' => 'd.m.Y',
1708
			'dmy' => 'd/m/Y',
1709
1710
			'ymd_slash' => 'Y/m/d',
1711
			'ymd_dash' => 'Y-m-d',
1712
			'ymd_dot' => 'Y.m.d',
1713
		);
1714
1715 18
		if ( ! $date_format ) {
1716
			// If the format key isn't valid, return default format key
1717
			return isset( $gf_date_formats[ $format ] ) ? $format : $default_format;
1718
		}
1719
1720
		// If the format key isn't valid, return default format value
1721 18
		return \GV\Utils::get( $gf_date_formats, $format, $gf_date_formats[ $default_format ] );
1722
	}
1723
1724
	/**
1725
	 * If previewing a View or page with embedded Views, make the search work properly by adding hidden fields with query vars
1726
	 *
1727
	 * @since 2.2.1
1728
	 *
1729
	 * @return void
1730
	 */
1731 4
	public function add_preview_inputs() {
1732 4
		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...
1733
1734 4
		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...
1735 4
			return;
1736
		}
1737
1738
		// 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...
1739
		foreach ( $wp->query_vars as $key => $value ) {
1740
			printf( '<input type="hidden" name="%s" value="%s" />', esc_attr( $key ), esc_attr( $value ) );
1741
		}
1742
1743
	}
1744
1745
1746
} // end class
1747
1748
new GravityView_Widget_Search;
1749
1750
if ( ! gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
1751
	return;
1752
}
1753
1754
/**
1755
 * A GF_Query condition that allows user data searches.
1756
 */
1757
class GravityView_Widget_Search_Author_GF_Query_Condition extends \GF_Query_Condition {
1758 1
	public function __construct( $filter, $view ) {
1759 1
		$this->value = $filter['value'];
1760 1
		$this->view = $view;
1761 1
	}
1762
1763 1
	public function sql( $query ) {
1764 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...
1765
1766
		$user_meta_fields = array(
1767 1
			'nickname', 'first_name', 'last_name',
1768
		);
1769
1770
		/**
1771
		 * @filter `gravityview/widgets/search/created_by/user_meta_fields` Filter the user meta fields to search.
1772
		 * @param[in,out] array The user meta fields.
1773
		 * @param \GV\View $view The view.
1774
		 */
1775 1
		$user_meta_fields = apply_filters( 'gravityview/widgets/search/created_by/user_meta_fields', $user_meta_fields, $this->view );
1776
1777
		$user_fields = array(
1778 1
			'user_nicename', 'user_login', 'display_name', 'user_email', 
1779
		);
1780
1781
		/**
1782
		 * @filter `gravityview/widgets/search/created_by/user_fields` Filter the user fields to search.
1783
		 * @param[in,out] array The user fields.
1784
		 * @param \GV\View $view The view.
1785
		 */
1786 1
		$user_fields = apply_filters( 'gravityview/widgets/search/created_by/user_fields', $user_fields, $this->view );
1787
1788 1
		$conditions = array();
1789
1790 1
		foreach ( $user_fields as $user_field ) {
1791 1
			$conditions[] = $wpdb->prepare( "`u`.`$user_field` LIKE %s", '%' . $wpdb->esc_like( $this->value ) .  '%' );
1792
		}
1793
1794 1
		foreach ( $user_meta_fields as $meta_field ) {
1795 1
			$conditions[] = $wpdb->prepare( "(`um`.`meta_key` = %s AND `um`.`meta_value` LIKE %s)", $meta_field, '%' . $wpdb->esc_like( $this->value ) .  '%' );
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal (`um`.`meta_key` = %s AN...`.`meta_value` LIKE %s) does not require double quotes, as per coding-style, please use single quotes.

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

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

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

<?php

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

print $doubleQuoted;

will print an indented: Single is Value

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

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

Loading history...
1796
		}
1797
1798 1
		$conditions = '(' . implode( ' OR ', $conditions ) . ')';
1799
1800 1
		$alias = $query->_alias( null );
1801
1802 1
		return "(EXISTS (SELECT 1 FROM $wpdb->users u LEFT JOIN $wpdb->usermeta um ON u.ID = um.user_id WHERE (u.ID = `$alias`.`created_by` AND $conditions)))";
0 ignored issues
show
introduced by
Usage of users/usermeta tables is highly discouraged in VIP context, For storing user additional user metadata, you should look at User Attributes.
Loading history...
1803
	}
1804
}
1805
1806
/**
1807
 * A GF_Query condition that allows searching across all fields.
1808
 */
1809
class GravityView_Widget_Search_All_GF_Query_Condition extends \GF_Query_Condition {
1810
	public function __construct( $search_condition, $view ) {
1811
		$this->search_condition = $search_condition;
1812
		$this->view = $view;
1813
	}
1814
1815
	public function sql( $query ) {
1816
		// @todo Search limit to only known fields
1817
		$parameters = $query->_introspect();
1818
1819
		// @todo We can search by properties as well in the future
1820
		$table = GFFormsModel::get_entry_meta_table_name();
1821
1822
		$conditions = array();
1823
1824
		foreach ( $parameters['aliases'] as $key => $alias ) {
1825
			if ( 'm' == $alias[0] && preg_match( '#\d+_\d+#', $key ) ) {
1826
				$conditions[] = sprintf( "EXISTS(SELECT * FROM `$table` WHERE `meta_value` %s %s AND `entry_id` = `%s`.`entry_id`)",
1827
					$this->search_condition->operator, $this->search_condition->right->sql( $query ), $alias );
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 16 spaces, but found 20.
Loading history...
1828
			}
1829
		}
1830
1831
		if ( $conditions ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $conditions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1832
			return '(' . implode( ' OR ', $conditions ) . ')';
1833
		}
1834
1835
		return '';
1836
	}
1837
}
1838