Completed
Push — develop ( 2f2ee7...c0a108 )
by Gennady
15:45
created

get_search_input_label()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

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

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

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

Loading history...
2
/**
3
 * The GravityView New Search widget
4
 *
5
 * @package   GravityView-DataTables-Ext
6
 * @license   GPL2+
7
 * @author    Katz Web Services, Inc.
8
 * @link      http://gravityview.co
9
 * @copyright Copyright 2014, Katz Web Services, Inc.
10
 */
11
12
if ( ! defined( 'WPINC' ) ) {
13
	die;
14
}
15
16
class GravityView_Widget_Search extends \GV\Widget {
17
18
	public static $file;
19
	public static $instance;
20
21
	private $search_filters = array();
22
23
	/**
24
	 * whether search method is GET or POST ( default: GET )
25
	 * @since 1.16.4
26
	 * @var string
27
	 */
28
	private $search_method = 'get';
29
30 29
	public function __construct() {
31 29
		self::$instance = &$this;
32
33 29
		$this->widget_id = 'search_bar';
34 29
		$this->widget_description = esc_html__( 'Search form for searching entries.', 'gravityview' );
35
36 29
		self::$file = plugin_dir_path( __FILE__ );
37
38 29
		$default_values = array( 'header' => 0, 'footer' => 0 );
39
40
		$settings = array(
41 29
			'search_layout' => array(
42 29
				'type' => 'radio',
43
				'full_width' => true,
44 29
				'label' => esc_html__( 'Search Layout', 'gravityview' ),
45 29
				'value' => 'horizontal',
46
				'options' => array(
47 29
					'horizontal' => esc_html__( 'Horizontal', 'gravityview' ),
48 29
					'vertical' => esc_html__( 'Vertical', 'gravityview' ),
49
				),
50
			),
51
			'search_clear' => array(
52 29
				'type' => 'checkbox',
53 29
				'label' => __( 'Show Clear button', 'gravityview' ),
54
				'value' => false,
55
			),
56
			'show_totals' => array(
57
				'type' => 'checkbox',
58
				'label' => __( 'Show result totals', 'gravityview' ),
59
				'value' => false,
60
			),
61
			'search_fields' => array(
62
				'type' => 'hidden',
63 29
				'label' => '',
64
				'class' => 'gv-search-fields-value',
65 29
				'value' => '[{"field":"search_all","input":"input_text"}]', // Default: Search Everything text box
66 29
			),
67 29
			'search_mode' => array(
68 29
				'type' => 'radio',
69
				'full_width' => true,
70 29
				'label' => esc_html__( 'Search Mode', 'gravityview' ),
71 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...
72
				'value' => 'any',
73
				'class' => 'hide-if-js',
74
				'options' => array(
75
					'any' => esc_html__( 'Match Any Fields', 'gravityview' ),
76 29
					'all' => esc_html__( 'Match All Fields', 'gravityview' ),
77
				),
78
			),
79
		);
80
81
		if ( ! $this->is_registered() ) {
82
			// frontend - filter entries
83
			add_filter( 'gravityview_fe_search_criteria', array( $this, 'filter_entries' ), 10, 3 );
84
85
			// frontend - add template path
86
			add_filter( 'gravityview_template_paths', array( $this, 'add_template_path' ) );
87
88
			// Add hidden fields for "Default" permalink structure
89
			add_filter( 'gravityview_widget_search_filters', array( $this, 'add_no_permalink_fields' ), 10, 3 );
90
91
			// admin - add scripts - run at 1100 to make sure GravityView_Admin_Views::add_scripts_and_styles() runs first at 999
92
			add_action( 'admin_enqueue_scripts', array( $this, 'add_scripts_and_styles' ), 1100 );
93
			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...
94
			add_filter( 'gravityview_noconflict_scripts', array( $this, 'register_no_conflict' ) );
95 29
96
			// ajax - get the searchable fields
97
			add_action( 'wp_ajax_gv_searchable_fields', array( 'GravityView_Widget_Search', 'get_searchable_fields' ) );
98 29
		}
99 29
100
		parent::__construct( esc_html__( 'Search Bar', 'gravityview' ), $this->widget_id, $default_values, $settings );
101
102
		// calculate the search method (POST / GET)
103
		$this->set_search_method();
104
	}
105 1
106 1
	/**
107
	 * @deprecated Returns the latest instance of this Widget.
108
	 * @return GravityView_Widget_Search
109 1
	 */
110
	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...
111
		if ( empty( self::$instance ) ) {
112
			self::$instance = new GravityView_Widget_Search;
113
		}
114
		return self::$instance;
115
	}
116 29
117
	/**
118
	 * Sets the search method to GET (default) or POST
119
	 * @since 1.16.4
120
	 */
121
	private function set_search_method() {
122
		/**
123 29
		 * @filter `gravityview/search/method` Modify the search form method (GET / POST)
124
		 * @since 1.16.4
125 29
		 * @param string $search_method Assign an input type according to the form field type. Defaults: `boolean`, `multi`, `select`, `date`, `text`
126
		 * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
127 29
		 */
128 29
		$method = apply_filters( 'gravityview/search/method', $this->search_method );
129
130
		$method = strtolower( $method );
131
132
		$this->search_method = in_array( $method, array( 'get', 'post' ) ) ? $method : 'get';
133
	}
134
135 5
	/**
136 5
	 * Returns the search method
137
	 * @since 1.16.4
138
	 * @return string
139
	 */
140
	public function get_search_method() {
141
		return $this->search_method;
142
	}
143
144
	/**
145
	 * Get the input types available for different field types
146
	 *
147
	 * @since 1.17.5
148
	 *
149
	 * @return array [field type name] => (array|string) search bar input types
150
	 */
151
	public static function get_input_types_by_field_type() {
152
		/**
153
		 * Input Type groups
154
		 * @see admin-search-widget.js (getSelectInput)
155
		 * @var array
156
		 */
157
		$input_types = array(
158
			'text' => array( 'input_text' ),
159
			'address' => array( 'input_text' ),
160
			'number' => array( 'input_text' ),
161
			'date' => array( 'date', 'date_range' ),
162
			'boolean' => array( 'single_checkbox' ),
163
			'select' => array( 'select', 'radio', 'link' ),
164
			'multi' => array( 'select', 'multiselect', 'radio', 'checkbox', 'link' ),
165
166
			// hybrids
167
			'created_by' => array( 'select', 'radio', 'checkbox', 'multiselect', 'link', 'input_text' ),
168
		);
169
170
		/**
171
		 * @filter `gravityview/search/input_types` Change the types of search fields available to a field type
172
		 * @see GravityView_Widget_Search::get_search_input_labels() for the available input types
173
		 * @param array $input_types Associative array: key is field `name`, value is array of GravityView input types (note: use `input_text` for `text`)
174
		 */
175
		$input_types = apply_filters( 'gravityview/search/input_types', $input_types );
176
177
		return $input_types;
178
	}
179
180
	/**
181
	 * Get labels for different types of search bar inputs
182
	 *
183
	 * @since 1.17.5
184
	 *
185
	 * @return array [input type] => input type label
186
	 */
187
	public static function get_search_input_labels() {
188
		/**
189
		 * Input Type labels l10n
190
		 * @see admin-search-widget.js (getSelectInput)
191
		 * @var array
192
		 */
193
		$input_labels = array(
194
			'input_text' => esc_html__( 'Text', 'gravityview' ),
195
			'date' => esc_html__( 'Date', 'gravityview' ),
196
			'select' => esc_html__( 'Select', 'gravityview' ),
197
			'multiselect' => esc_html__( 'Select (multiple values)', 'gravityview' ),
198
			'radio' => esc_html__( 'Radio', 'gravityview' ),
199
			'checkbox' => esc_html__( 'Checkbox', 'gravityview' ),
200
			'single_checkbox' => esc_html__( 'Checkbox', 'gravityview' ),
201
			'link' => esc_html__( 'Links', 'gravityview' ),
202
			'date_range' => esc_html__( 'Date range', 'gravityview' ),
203
		);
204
205
		/**
206
		 * @filter `gravityview/search/input_types` Change the label of search field input types
207
		 * @param array $input_types Associative array: key is input type name, value is label
208
		 */
209
		$input_labels = apply_filters( 'gravityview/search/input_labels', $input_labels );
210
211
		return $input_labels;
212
	}
213
214
	public static function get_search_input_label( $input_type ) {
215
		$labels = self::get_search_input_labels();
216
217
		return \GV\Utils::get( $labels, $input_type, false );
218
	}
219
220
	/**
221
	 * Add script to Views edit screen (admin)
222
	 * @param  mixed $hook
223
	 */
224
	public function add_scripts_and_styles( $hook ) {
225
		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...
226
227
		// Don't process any scripts below here if it's not a GravityView page or the widgets screen
228
		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...
229
			return;
230
		}
231
232
		$script_min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
233
		$script_source = empty( $script_min ) ? '/source' : '';
234
235
		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 );
236
237
		wp_localize_script( 'gravityview_searchwidget_admin', 'gvSearchVar', array(
238
			'nonce' => wp_create_nonce( 'gravityview_ajaxsearchwidget' ),
239
			'label_nofields' => esc_html__( 'No search fields configured yet.', 'gravityview' ),
240
			'label_addfield' => esc_html__( 'Add Search Field', 'gravityview' ),
241
			'label_label' => esc_html__( 'Label', 'gravityview' ),
242
			'label_searchfield' => esc_html__( 'Search Field', 'gravityview' ),
243
			'label_inputtype' => esc_html__( 'Input Type', 'gravityview' ),
244
			'label_ajaxerror' => esc_html__( 'There was an error loading searchable fields. Save the View or refresh the page to fix this issue.', 'gravityview' ),
245
			'input_labels' => json_encode( self::get_search_input_labels() ),
246
			'input_types' => json_encode( self::get_input_types_by_field_type() ),
247
		) );
248
249
	}
250
251
	/**
252
	 * Add admin script to the no-conflict scripts whitelist
253
	 * @param array $allowed Scripts allowed in no-conflict mode
254
	 * @return array Scripts allowed in no-conflict mode, plus the search widget script
255
	 */
256
	public function register_no_conflict( $allowed ) {
257
		$allowed[] = 'gravityview_searchwidget_admin';
258
		return $allowed;
259
	}
260
261
	/**
262
	 * Ajax
263
	 * Returns the form fields ( only the searchable ones )
264
	 *
265
	 * @access public
266
	 * @return void
267
	 */
268
	public static function get_searchable_fields() {
269
270
		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...
271
			exit( '0' );
272
		}
273
274
		$form = '';
275
276
		// Fetch the form for the current View
277
		if ( ! empty( $_POST['view_id'] ) ) {
278
279
			$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...
280
281
		} elseif ( ! empty( $_POST['formid'] ) ) {
282
283
			$form = (int) $_POST['formid'];
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
284
285
		} elseif ( ! empty( $_POST['template_id'] ) && class_exists( 'GravityView_Ajax' ) ) {
286
287
			$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...
288
289
		}
290
291
		// fetch form id assigned to the view
292
		$response = self::render_searchable_fields( $form );
293
294
		exit( $response );
295
	}
296
297
	/**
298
	 * Generates html for the available Search Fields dropdown
299
	 * @param  int $form_id
300
	 * @param  string $current (for future use)
301
	 * @return string
302
	 */
303
	public static function render_searchable_fields( $form_id = null, $current = '' ) {
304
305
		if ( is_null( $form_id ) ) {
306
			return '';
307
		}
308
309
		// start building output
310
311
		$output = '<select class="gv-search-fields">';
312
313
		$custom_fields = array(
314
			'search_all' => array(
315
				'text' => esc_html__( 'Search Everything', 'gravityview' ),
316
				'type' => 'text',
317
			),
318
			'entry_date' => array(
319
				'text' => esc_html__( 'Entry Date', 'gravityview' ),
320
				'type' => 'date',
321
			),
322
			'entry_id' => array(
323
				'text' => esc_html__( 'Entry ID', 'gravityview' ),
324
				'type' => 'text',
325
			),
326
			'created_by' => array(
327
				'text' => esc_html__( 'Entry Creator', 'gravityview' ),
328
				'type' => 'created_by',
329
			),
330
			'is_starred' => array(
331
				'text' => esc_html__( 'Is Starred', 'gravityview' ),
332
				'type' => 'boolean',
333
			),
334
		);
335
336
		if ( gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
337
			$custom_fields['is_approved'] = array(
338
				'text' => esc_html__( 'Is Approved', 'gravityview' ),
339
				'type' => 'boolean',
340
			);
341
		}
342
343
		foreach( $custom_fields as $custom_field_key => $custom_field ) {
344
			$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...
345
		}
346
347
		// Get fields with sub-inputs and no parent
348
		$fields = gravityview_get_form_fields( $form_id, true, true );
349
350
		/**
351
		 * @filter `gravityview/search/searchable_fields` Modify the fields that are displayed as searchable in the Search Bar dropdown\n
352
		 * @since 1.17
353
		 * @see gravityview_get_form_fields() Used to fetch the fields
354
		 * @see GravityView_Widget_Search::get_search_input_types See this method to modify the type of input types allowed for a field
355
		 * @param array $fields Array of searchable fields, as fetched by gravityview_get_form_fields()
356
		 * @param  int $form_id
357
		 */
358
		$fields = apply_filters( 'gravityview/search/searchable_fields', $fields, $form_id );
359
360
		if ( ! empty( $fields ) ) {
361
362
			$blacklist_field_types = apply_filters( 'gravityview_blacklist_field_types', array( 'fileupload', 'post_image', 'post_id', 'section' ), null );
363
364
			foreach ( $fields as $id => $field ) {
365
366
				if ( in_array( $field['type'], $blacklist_field_types ) ) {
367
					continue;
368
				}
369
370
				$types = self::get_search_input_types( $id, $field['type'] );
371
372
				$output .= '<option value="'. $id .'" '. selected( $id, $current, false ).'data-inputtypes="'. esc_attr( $types ) .'">'. esc_html( $field['label'] ) .'</option>';
373
			}
374
		}
375
376
		$output .= '</select>';
377
378
		return $output;
379
380
	}
381
382
	/**
383
	 * Assign an input type according to the form field type
384
	 *
385
	 * @see admin-search-widget.js
386
	 *
387
	 * @param string|int|float $field_id Gravity Forms field ID
388
	 * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
389
	 *
390
	 * @return string GV field search input type ('multi', 'boolean', 'select', 'date', 'text')
391
	 */
392
	public static function get_search_input_types( $field_id = '', $field_type = null ) {
393
394
		// @todo - This needs to be improved - many fields have . including products and addresses
395
		if ( false !== strpos( (string) $field_id, '.' ) && in_array( $field_type, array( 'checkbox' ) ) || in_array( $field_id, array( 'is_fulfilled' ) ) ) {
396
			$input_type = 'boolean'; // on/off checkbox
397
		} elseif ( in_array( $field_type, array( 'checkbox', 'post_category', 'multiselect' ) ) ) {
398
			$input_type = 'multi'; //multiselect
399
		} elseif ( in_array( $field_type, array( 'select', 'radio' ) ) ) {
400
			$input_type = 'select';
401
		} elseif ( in_array( $field_type, array( 'date' ) ) || in_array( $field_id, array( 'payment_date' ) ) ) {
402
			$input_type = 'date';
403
		} elseif ( in_array( $field_type, array( 'number' ) ) || in_array( $field_id, array( 'payment_amount' ) ) ) {
404
			$input_type = 'number';
405
		} else {
406
			$input_type = 'text';
407
		}
408
409
		/**
410
		 * @filter `gravityview/extension/search/input_type` Modify the search form input type based on field type
411
		 * @since 1.2
412
		 * @since 1.19.2 Added $field_id parameter
413
		 * @param string $input_type Assign an input type according to the form field type. Defaults: `boolean`, `multi`, `select`, `date`, `text`
414
		 * @param string $field_type Gravity Forms field type (also the `name` parameter of GravityView_Field classes)
415
		 * @param string|int|float $field_id ID of the field being processed
416
		 */
417
		$input_type = apply_filters( 'gravityview/extension/search/input_type', $input_type, $field_type, $field_id );
418
419
		return $input_type;
420
	}
421
422
	/**
423 4
	 * Display hidden fields to add support for sites using Default permalink structure
424
	 *
425 4
	 * @since 1.8
426
	 * @return array Search fields, modified if not using permalinks
427
	 */
428 4
	public function add_no_permalink_fields( $search_fields, $object, $widget_args = array() ) {
429
		/** @global WP_Rewrite $wp_rewrite */
430
		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...
431 4
432
		// Support default permalink structure
433
		if ( false === $wp_rewrite->using_permalinks() ) {
434 4
435
			// By default, use current post.
436
			$post_id = 0;
437
438 4
			// We're in the WordPress Widget context, and an overriding post ID has been set.
439
			if ( ! empty( $widget_args['post_id'] ) ) {
440
				$post_id = absint( $widget_args['post_id'] );
441
			}
442 4
			// We're in the WordPress Widget context, and the base View ID should be used
443
			else if ( ! empty( $widget_args['view_id'] ) ) {
444
				$post_id = absint( $widget_args['view_id'] );
445 4
			}
446 4
447 4
			$args = gravityview_get_permalink_query_args( $post_id );
448 4
449 4
			// Add hidden fields to the search form
450
			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...
451
				$search_fields[] = array(
452
					'name'  => $key,
453
					'input' => 'hidden',
454 4
					'value' => $value,
455
				);
456
			}
457
		}
458
459
		return $search_fields;
460
	}
461
462
	/**
463
	 * Get the fields that are searchable for a View
464
	 *
465
	 * @since 2.0
466
	 * @since 2.0.9 Added $with_full_field parameter
467
	 *
468
	 * @param \GV\View|null $view
469
	 * @param bool $with_full_field Return full field array, or just field ID? Default: false (just field ID)
470
	 *
471 24
	 * TODO: Move to \GV\View, perhaps? And return a Field_Collection
472
	 * TODO: Use in gravityview()->request->is_search() to calculate whether a valid search
473
	 *
474
	 * @return array If no View, returns empty array. Otherwise, returns array of fields configured in widgets and Search Bar for a View
475
	 */
476 24
	private function get_view_searchable_fields( $view, $with_full_field = false ) {
477
478 24
		/**
479
		 * Find all search widgets on the view and get the searchable fields settings.
480
		 */
481
		$searchable_fields = array();
482
483
		if ( ! $view ) {
484
			return $searchable_fields;
485 24
		}
486
487 24
		/**
488 24
		 * Include the sidebar Widgets.
489
		 */
490
		$widgets = (array) get_option( 'widget_gravityview_search', array() );
491 24
492
		foreach ( $widgets as $widget ) {
493
			if ( ! empty( $widget['view_id'] ) && $widget['view_id'] == $view->ID ) {
494
				if( $_fields = json_decode( $widget['search_fields'], true ) ) {
495
					foreach ( $_fields as $field ) {
496
						$searchable_fields [] = $with_full_field ? $field : $field['field'];
497 24
					}
498 24
				}
499 24
			}
500 24
		}
501
502
		foreach ( $view->widgets->by_id( $this->get_widget_id() )->all() as $widget ) {
503
			if( $_fields = json_decode( $widget->configuration->get( 'search_fields' ), true ) ) {
504
				foreach ( $_fields as $field ) {
505 24
					$searchable_fields [] = $with_full_field ? $field : $field['field'];
506
				}
507
			}
508
		}
509
510
		return $searchable_fields;
511
	}
512
513
	/** --- Frontend --- */
514
515
	/**
516
	 * Calculate the search criteria to filter entries
517
	 * @param array $search_criteria The search criteria
518
	 * @param int $form_id The form ID
519
	 * @param array $args Some args
520 53
	 *
521 53
	 * @param bool $force_search_criteria Whether to suppress GF_Query filter, internally used in self::gf_query_filter
522
	 *
523
	 * @return array
524
	 */
525
	public function filter_entries( $search_criteria, $form_id = null, $args = array(), $force_search_criteria = false ) {
526 34
		if ( ! $force_search_criteria && gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
527 34
			/**
528
			 * If GF_Query is available, we can construct custom conditions with nested
529
			 * booleans on the query, giving up the old ways of flat search_criteria field_filters.
530 52
			 */
531
			add_action( 'gravityview/view/query', array( $this, 'gf_query_filter' ), 10, 3 );
532
			return $search_criteria; // Return the original criteria, GF_Query modification kicks in later
533 52
		}
534
535
		if( 'post' === $this->search_method ) {
536 52
			$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...
537
		} else {
538 52
			$get = $_GET;
0 ignored issues
show
introduced by
Detected access of super global var $_GET, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
539
		}
540 52
541 30
		$view = \GV\View::by_id( \GV\Utils::get( $args, 'id' ) );
542
543
		gravityview()->log->debug( 'Requested $_{method}: ', array( 'method' => $this->search_method, 'data' => $get ) );
544 25
545
		if ( empty( $get ) || ! is_array( $get ) ) {
546 25
			return $search_criteria;
547
		}
548
549 25
		$get = stripslashes_deep( $get );
550
551 25
		$get = gv_map_deep( $get, 'rawurldecode' );
552
553
		// Make sure array key is set up
554 25
		$search_criteria['field_filters'] = \GV\Utils::get( $search_criteria, 'field_filters', array() );
555
556 1
		$searchable_fields = $this->get_view_searchable_fields( $view );
557
558
		// add free search
559
		if ( isset( $get['gv_search'] ) && '' !== $get['gv_search'] && in_array( 'search_all', $searchable_fields ) ) {
560
561
			$search_all_value = trim( $get['gv_search'] );
562
563 1
			/**
564
			 * @filter `gravityview/search-all-split-words` Search for each word separately or the whole phrase?
565 1
			 * @since 1.20.2
566
			 * @param bool $split_words True: split a phrase into words; False: search whole word only [Default: true]
567
			 */
568 1
			$split_words = apply_filters( 'gravityview/search-all-split-words', true );
569
570 1
			if ( $split_words ) {
571
572
				// Search for a piece
573
				$words = explode( ' ', $search_all_value );
574
575 1
				$words = array_filter( $words );
576
577 1
			} else {
578
579
				// Replace multiple spaces with one space
580 1
				$search_all_value = preg_replace( '/\s+/ism', ' ', $search_all_value );
581 1
582 1
				$words = array( $search_all_value );
583 1
			}
584 1
585
			foreach ( $words as $word ) {
586
				$search_criteria['field_filters'][] = array(
587
					'key' => null, // The field ID to search
588
					'value' => $word, // The value to search
589
					'operator' => 'contains', // What to search in. Options: `is` or `contains`
590 25
				);
591
			}
592
		}
593
594 11
		// start date & end date
595 11
		if ( in_array( 'entry_date', $searchable_fields ) ) {
596 11
			/**
597
			 * Get and normalize the dates according to the input format.
598
			 */
599
			if ( $curr_start = ! empty( $get['gv_start'] ) ? $get['gv_start'] : '' ) {
600 11
				if( $curr_start_date = date_create_from_format( $this->get_datepicker_format( true ), $curr_start ) ) {
601 11
					$curr_start = $curr_start_date->format( 'Y-m-d' );
602 11
				}
603
			}
604
605
			if ( $curr_end = ! empty( $get['gv_start'] ) ? ( ! empty( $get['gv_end'] ) ? $get['gv_end'] : '' ) : '' ) {
606 11
				if( $curr_end_date = date_create_from_format( $this->get_datepicker_format( true ), $curr_end ) ) {
607
					$curr_end = $curr_end_date->format( 'Y-m-d' );
608
				}
609
			}
610 11
611 1
			if ( $view ) {
612 1
				/**
613
				 * Override start and end dates if View is limited to some already.
614
				 */
615 11
				if ( $start_date = $view->settings->get( 'start_date' ) ) {
616
					if ( $start_timestamp = strtotime( $curr_start ) ) {
617
						$curr_start = $start_timestamp < strtotime( $start_date ) ? $start_date : $curr_start;
618
					}
619
				}
620
				if ( $end_date = $view->settings->get( 'end_date' ) ) {
621
					if ( $end_timestamp = strtotime( $curr_end ) ) {
622
						$curr_end = $end_timestamp > strtotime( $end_date ) ? $end_date : $curr_end;
623
					}
624
				}
625
			}
626
627
			/**
628
			 * @filter `gravityview_date_created_adjust_timezone` Whether to adjust the timezone for entries. \n
629 11
			 * date_created is stored in UTC format. Convert search date into UTC (also used on templates/fields/date_created.php)
630
			 * @since 1.12
631
			 * @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
632
			 * @param[in] string $context Where the filter is being called from. `search` in this case.
633
			 */
634 11
			$adjust_tz = apply_filters( 'gravityview_date_created_adjust_timezone', true, 'search' );
635 11
636 11
			/**
637
			 * Don't set $search_criteria['start_date'] if start_date is empty as it may lead to bad query results (GFAPI::get_entries)
638
			 */
639 11
			if ( ! empty( $curr_start ) ) {
640
				$curr_start = date( 'Y-m-d H:i:s', strtotime( $curr_start ) );
641 11
				$search_criteria['start_date'] = $adjust_tz ? get_gmt_from_date( $curr_start ) : $curr_start;
642 11
			}
643 11
644 11
			if ( ! empty( $curr_end ) ) {
645
				// Fast-forward 24 hour on the end time
646
				$curr_end = date( 'Y-m-d H:i:s', strtotime( $curr_end ) + DAY_IN_SECONDS );
647
				$search_criteria['end_date'] = $adjust_tz ? get_gmt_from_date( $curr_end ) : $curr_end;
648
				if ( strpos( $search_criteria['end_date'], '00:00:00' ) ) { // See https://github.com/gravityview/GravityView/issues/1056
649
					$search_criteria['end_date'] = date( 'Y-m-d H:i:s', strtotime( $search_criteria['end_date'] ) - 1 );
650 25
				}
651 2
			}
652 2
		}
653 2
654 2
		// search for a specific entry ID
655
		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...
656
			$search_criteria['field_filters'][] = array(
657
				'key' => 'id',
658
				'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...
659 25
				'operator' => '=',
660 4
			);
661 4
		}
662 4
663 4
		// search for a specific Created_by ID
664
		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...
665
			$search_criteria['field_filters'][] = array(
666
				'key' => 'created_by',
667
				'value' => $get['gv_by'],
668 25
				'operator' => '=',
669
			);
670
		}
671 25
672
		// Get search mode passed in URL
673 25
		$mode = isset( $get['mode'] ) && in_array( $get['mode'], array( 'any', 'all' ) ) ?  $get['mode'] : 'any';
674 15
675
		// get the other search filters
676
		foreach ( $get as $key => $value ) {
677 13
678
			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...
679
				continue;
680 13
			}
681 1
682
			$filter_key = $this->convert_request_key_to_filter_key( $key );
683
684 12
			// could return simple filter or multiple filters
685
			if ( ! in_array( 'search_all', $searchable_fields ) && ! in_array( $filter_key , $searchable_fields ) ) {
686 12
				continue;
687
			}
688
689
			$filter = $this->prepare_field_filter( $filter_key, $value, $view );
690
691
			if ( isset( $filter[0]['value'] ) ) {
692
				$search_criteria['field_filters'] = array_merge( $search_criteria['field_filters'], $filter );
693 12
694 12
				// if date range type, set search mode to ALL
695
				if ( ! empty( $filter[0]['operator'] ) && in_array( $filter[0]['operator'], array( '>=', '<=', '>', '<' ) ) ) {
696
					$mode = 'all';
697
				}
698
			} elseif( !empty( $filter ) ) {
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
699
				$search_criteria['field_filters'][] = $filter;
700
			}
701
		}
702
703 25
		/**
704
		 * @filter `gravityview/search/mode` Set the Search Mode (`all` or `any`)
705 25
		 * @since 1.5.1
706
		 * @param[out,in] string $mode Search mode (`any` vs `all`)
707 25
		 */
708
		$search_criteria['field_filters']['mode'] = apply_filters( 'gravityview/search/mode', $mode );
709 25
710
		gravityview()->log->debug( 'Returned Search Criteria: ', array( 'data' => $search_criteria ) );
711
712
		unset( $get );
713
714
		return $search_criteria;
715
	}
716
717
	/**
718
	 * Filters the \GF_Query with advanced logic.
719
	 *
720
	 * Dropin for the legacy flat filters when \GF_Query is available.
721 33
	 *
722
	 * @param \GF_Query $query The current query object reference
723
	 * @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...
724
	 * @param \GV\Request $request The request object
725
	 */
726 33
	public function gf_query_filter( &$query, $view, $request ) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
727
		/**
728 33
		 * This is a shortcut to get all the needed search criteria.
729 29
		 * We feed these into an new GF_Query and tack them onto the current object.
730
		 */
731
		$search_criteria = $this->filter_entries( array(), null, array( 'id' => $view->ID ), true /** force search_criteria */ );
732 6
733 6
		if ( empty( $search_criteria['field_filters'] ) ) {
734 6
			return;
735 6
		}
736 6
737 6
		$widgets = $view->widgets->by_id( $this->widget_id );
738
		if ( $widgets->count() ) {
739
			$widget = $widgets->all()[0];
740
			foreach ( $search_fields = json_decode( $widget->configuration->get( 'search_fields' ), true ) as $search_field ) {
741
				if ( 'created_by' === $search_field['field'] && 'input_text' === $search_field['input'] ) {
742 6
					$created_by_text_mode = true;
743
				}
744 6
			}
745 6
		}
746 6
747
		$extra_conditions = array();
748
749
		foreach ( $search_criteria['field_filters'] as &$filter ) {
750 6
			if ( ! is_array( $filter ) ) {
751 2
				continue;
752 2
			}
753
754 2
			// Construct a manual query for unapproved statuses
755 2
			if ( 'is_approved' === $filter['key'] && in_array( \GravityView_Entry_Approval_Status::UNAPPROVED, $filter['value'] ) ) {
756 2
				$_tmp_query       = new GF_Query( $view->form->ID, array(
757
					'field_filters' => array(
758
						array(
759
							'operator' => 'in',
760
							'key'      => 'is_approved',
761
							'value'    => $filter['value'],
762
						),
763 2
						array(
764
							'operator' => 'is',
765
							'key'      => 'is_approved',
766 2
							'value'    => '',
767
						),
768 2
						'mode' => 'any'
0 ignored issues
show
introduced by
Key specified for array entry; first entry has no key
Loading history...
769
					),
770 2
				) );
771 2
				$_tmp_query_parts = $_tmp_query->_introspect();
772
773
				$extra_conditions[] = $_tmp_query_parts['where'];
774
775 6
				$filter = false;
776
				continue;
777 1
			}
778 1
779 1
			// Construct manual query for text mode creator search
780 1
			if ( 'created_by' === $filter['key'] && ! empty( $created_by_text_mode ) ) {
781
				$extra_conditions[] = new class( $filter, $view ) extends \GF_Query_Condition {
782 1
					public function __construct( $filter, $view ) {
783
						$this->value = $filter['value'];
784 1
						$this->view = $view;
785
					}
786
787 1
					public function sql( $query ) {
788
						$user_meta_fields = array(
789
							'nickname', 'first_name', 'last_name', 
790
						);
791
792
						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...
793
794 1
						/**
795
						 * @filter `gravityview/widgets/search/created_by/user_meta_fields` Filter the user meta fields to search by.
796
						 * @param[in,out] array The user meta fields.
797
						 * @param \GV\View $view The view.
798 1
						 */
799
						$user_meta_fields = apply_filters( 'gravityview/widgets/search/created_by/user_meta_fields', $user_meta_fields, $this->view );
800
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
801
802
						$user_fields = array(
803
							'user_nicename', 'user_login', 'display_name', 'user_email', 
804
						);
805 1
						/**
806
						 * @filter `gravityview/widgets/search/created_by/user_fields` Filter the user meta fields to search by.
807 1
						 * @param[in,out] array The user fields.
808
						 * @param \GV\View $view The view.
809 1
						 */
810
						$user_fields = apply_filters( 'gravityview/widgets/search/created_by/user_fields', $user_fields, $this->view );
811 1
812 1
						$column = sprintf( '`%s`.`created_by`', $query->_alias( null ) );
813
814
						$conditions = array();
815 1
816 1
						foreach ( $user_fields as $user_field ) {
817
							$conditions[] = $wpdb->prepare( "`u`.`$user_field` LIKE %s", '%' . $wpdb->esc_like( $this->value ) .  '%' );
818
						}
819 1
820
						foreach ( $user_meta_fields as $meta_field ) {
821 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...
822
						}
823 1
824
						$conditions = '(' . implode( ' OR ', $conditions ) . ')';
825
826
						$alias = $query->_alias( null );
827 1
828 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...
829
					}
830
				};
831
832 5
				$filter = false;
833
				continue;
834
			}
835 5
836 3
			// By default, we want searches to be wildcard for each field.
837
			$filter['operator'] = empty( $filter['operator'] ) ? 'contains' : $filter['operator'];
838
839
			// For multichoice, let's have an in (OR) search.
840
			if ( is_array( $filter['value'] ) ) {
841
				$filter['operator'] = 'in'; // @todo what about in contains (OR LIKE chains)?
842
			}
843
844 5
			/**
845
			 * @filter `gravityview_search_operator` Modify the search operator for the field (contains, is, isnot, etc)
846
			 * @param string $operator Existing search operator
847 6
			 * @param array $filter array with `key`, `value`, `operator`, `type` keys
848
			 */
849
			$filter['operator'] = apply_filters( 'gravityview_search_operator', $filter['operator'], $filter );
850
		}
851
852
		$search_criteria['field_filters'] = array_filter( $search_criteria['field_filters'] );
853
854 6
		/**
855 6
		 * Parse the filter criteria to generate the needed
856
		 * WHERE clauses. This is a trick to not write our own generation
857
		 * code by reusing what's inside GF_Query already.
858
		 */
859
		$_tmp_query       = new GF_Query( $view->form->ID, $search_criteria );
860 6
		$_tmp_query_parts = $_tmp_query->_introspect();
861
862
		/**
863
		 * Grab the current clauses. We'll be combining them shortly.
864
		 */
865 6
		$query_parts      = $query->_introspect();
866 6
867 6
		/**
868
		 * Combine the parts as a new WHERE clause.
869
		 */
870
		$where = call_user_func_array( '\GF_Query_Condition::_and', array_merge( array( $query_parts['where'], $_tmp_query_parts['where'] ), $extra_conditions ) );
871
		$query->where( $where );
872
	}
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 13
	 *
884
	 * @param string $key $_GET/_$_POST search key
885 13
	 *
886
	 * @return string
887
	 */
888 13
	private function convert_request_key_to_filter_key( $key ) {
889 11
890
		$field_id = str_replace( 'filter_', '', $key );
891
892 13
		// calculates field_id, removing 'filter_' and for '_' for advanced fields ( like name or checkbox )
893
		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
			$field_id = str_replace( '_', '.', $field_id );
895
		}
896
897
		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 12
	 * @param  string $value $_GET/$_POST search value
909
	 * @param  \GV\View $view The view we're looking at
910
	 *
911 12
	 * @return array        1 or 2 deph levels
912
	 */
913
	public function prepare_field_filter( $filter_key, $value, $view ) {
914
915 12
		// get form field array
916 12
		$form_field = is_numeric( $filter_key ) ? \GV\GF_Field::by_id( $view->form, $filter_key ) : \GV\Internal_Field::by_id( $filter_key );
917
918
		// default filter array
919 12
		$filter = array(
920
			'key'   => $filter_key,
921 12
			'value' => $value,
922 12
		);
923 1
924 1
		switch ( $form_field->type ) {
925
926 11
			case 'select':
927
			case 'radio':
928
				$filter['operator'] = 'is';
929
				break;
930
931
			case 'post_category':
932
933
				if ( ! is_array( $value ) ) {
934
					$value = array( $value );
935
				}
936
937
				// Reset filter variable
938
				$filter = array();
939
940
				foreach ( $value as $val ) {
941
					$cat = get_term( $val, 'category' );
942
					$filter[] = array(
943
						'key'      => $filter_key,
944
						'value'    => esc_attr( $cat->name ) . ':' . $val,
945
						'operator' => 'is',
946 11
					);
947
				}
948
949
				break;
950
951
			case 'multiselect':
952
953
				if ( ! is_array( $value ) ) {
954
					break;
955
				}
956
957
				// Reset filter variable
958
				$filter = array();
959
960
				foreach ( $value as $val ) {
961 11
					$filter[] = array( 'key' => $filter_key, 'value' => $val );
962
				}
963
964
				break;
965
966
			case 'checkbox':
967
				// convert checkbox on/off into the correct search filter
968
				if ( false !== strpos( $filter_key, '.' ) && ! empty( $form_field->inputs ) && ! empty( $form_field->choices ) ) {
969
					foreach ( $form_field->inputs as $k => $input ) {
970
						if ( $input['id'] == $filter_key ) {
971
							$filter['value'] = $form_field->choices[ $k ]['value'];
972
							$filter['operator'] = 'is';
973
							break;
974
						}
975
					}
976
				} elseif ( is_array( $value ) ) {
977
978
					// Reset filter variable
979
					$filter = array();
980
981
					foreach ( $value as $val ) {
982
						$filter[] = array(
983
							'key'      => $filter_key,
984
							'value'    => $val,
985
							'operator' => 'is',
986
						);
987 11
					}
988 11
				}
989
990
				break;
991
992
			case 'name':
993
			case 'address':
994
995
				if ( false === strpos( $filter_key, '.' ) ) {
996
997
					$words = explode( ' ', $value );
998
999
					$filters = array();
1000
					foreach ( $words as $word ) {
1001
						if ( ! empty( $word ) && strlen( $word ) > 1 ) {
1002
							// Keep the same key for each filter
1003
							$filter['value'] = $word;
1004
							// Add a search for the value
1005
							$filters[] = $filter;
1006
						}
1007
					}
1008
1009
					$filter = $filters;
1010
				}
1011
1012
				// State/Province should be exact matches
1013
				if ( 'address' === $form_field->field->type ) {
1014
1015
					$searchable_fields = $this->get_view_searchable_fields( $view, true );
1016
1017
					foreach ( $searchable_fields as $searchable_field ) {
1018
1019
						if( $form_field->ID !== $searchable_field['field'] ) {
1020
							continue;
1021
						}
1022
1023
						// Only exact-match dropdowns, not text search
1024
						if( in_array( $searchable_field['input'], array( 'text', 'search' ), true ) ) {
1025
							continue;
1026
						}
1027
1028
						$input_id = gravityview_get_input_id_from_id( $form_field->ID );
1029
1030
						if ( 4 === $input_id ) {
1031
							$filter['operator'] = 'is';
1032
						};
1033 11
					}
1034
				}
1035 8
1036
				break;
1037 8
1038
			case 'date':
1039
1040
				$date_format = $this->get_datepicker_format( true );
1041
1042
				if ( is_array( $value ) ) {
1043
1044
					// Reset filter variable
1045
					$filter = array();
1046
1047
					foreach ( $value as $k => $date ) {
1048
						if ( empty( $date ) ) {
1049
							continue;
1050
						}
1051
						$operator = 'start' === $k ? '>=' : '<=';
1052
1053
						/**
1054
						 * @hack
1055
						 * @since 1.16.3
1056
						 * Safeguard until GF implements '<=' operator
1057
						 */
1058
						if( !GFFormsModel::is_valid_operator( $operator ) && $operator === '<=' ) {
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
1059
							$operator = '<';
1060
							$date = date( 'Y-m-d', strtotime( self::get_formatted_date( $date, 'Y-m-d', $date_format ) . ' +1 day' ) );
1061
						}
1062
1063
						$filter[] = array(
1064
							'key'      => $filter_key,
1065 8
							'value'    => self::get_formatted_date( $date, 'Y-m-d', $date_format ),
1066 8
							'operator' => $operator,
1067
						);
1068
					}
1069 8
				} else {
1070
					$date = $value;
1071
					$filter['value'] = self::get_formatted_date( $date, 'Y-m-d', $date_format );
1072
				}
1073
1074 12
				break;
1075
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
1076
1077
		} // switch field type
1078
1079
		return $filter;
1080
	}
1081
1082
	/**
1083
	 * Get the Field Format form GravityForms
1084
	 *
1085
	 * @param GF_Field_Date $field The field object
1086
	 * @since 1.10
1087
	 *
1088
	 * @return string Format of the date in the database
1089
	 */
1090
	public static function get_date_field_format( GF_Field_Date $field ) {
1091
		$format = 'm/d/Y';
1092
		$datepicker = array(
1093
			'mdy' => 'm/d/Y',
1094
			'dmy' => 'd/m/Y',
1095
			'dmy_dash' => 'd-m-Y',
1096
			'dmy_dot' => 'd.m.Y',
1097
			'ymd_slash' => 'Y/m/d',
1098
			'ymd_dash' => 'Y-m-d',
1099
			'ymd_dot' => 'Y.m.d',
1100
		);
1101
1102
		if ( ! empty( $field->dateFormat ) && isset( $datepicker[ $field->dateFormat ] ) ){
1103
			$format = $datepicker[ $field->dateFormat ];
1104
		}
1105
1106
		return $format;
1107
	}
1108
1109
	/**
1110
	 * Format a date value
1111
	 *
1112
	 * @param string $value Date value input
1113
	 * @param string $format Wanted formatted date
1114
	 *
1115 8
	 * @since 2.1.2
1116
	 * @param string $value_format The value format. Default: Y-m-d
1117 8
	 *
1118
	 * @return string
1119 8
	 */
1120
	public static function get_formatted_date( $value = '', $format = 'Y-m-d', $value_format = 'Y-m-d' ) {
1121
1122
		$date = date_create_from_format( $value_format, $value );
1123 8
1124
		if ( empty( $date ) ) {
1125
			gravityview()->log->debug( 'Date format not valid: {value}', array( 'value' => $value ) );
1126
			return '';
1127
		}
1128
		return $date->format( $format );
1129
	}
1130
1131 1
1132
	/**
1133
	 * Include this extension templates path
1134 1
	 * @param array $file_paths List of template paths ordered
1135
	 */
1136 1
	public function add_template_path( $file_paths ) {
1137
1138
		// Index 100 is the default GravityView template path.
1139
		$file_paths[102] = self::$file . 'templates/';
1140
1141
		return $file_paths;
1142
	}
1143
1144
	/**
1145
	 * Check whether the configured search fields have a date field
1146
	 *
1147
	 * @since 1.17.5
1148 4
	 *
1149
	 * @param array $search_fields
1150 4
	 *
1151
	 * @return bool True: has a `date` or `date_range` field
1152 4
	 */
1153 4
	private function has_date_field( $search_fields ) {
1154
1155 4
		$has_date = false;
1156
1157
		foreach ( $search_fields as $k => $field ) {
1158
			if ( in_array( $field['input'], array( 'date', 'date_range', 'entry_date' ) ) ) {
1159 4
				$has_date = true;
1160
				break;
1161
			}
1162
		}
1163
1164
		return $has_date;
1165 4
	}
1166 4
1167
	/**
1168
	 * @inheritDoc
1169
	 */
1170
	public function render_frontend( $widget_args, $content = '', $context = '' ) {
1171
		if ( ! $context instanceof \GV\Template_Context ) {
1172 4
			gravityview()->log->debug( sprintf( '%s[render_frontend]: No context.', get_class( $this ) ) );
1173
			return;
1174 4
		}
1175
1176
		// get configured search fields
1177
		$search_fields = ! empty( $widget_args['search_fields'] ) ? json_decode( $widget_args['search_fields'], true ) : '';
1178
1179
		if ( empty( $search_fields ) || ! is_array( $search_fields ) ) {
1180
			gravityview()->log->debug( 'No search fields configured for widget:', array( 'data' => $widget_args ) );
1181 4
			return;
1182
		}
1183 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...
1184
1185 4
		// prepare fields
1186
		foreach ( $search_fields as $k => $field ) {
1187 4
1188
			$updated_field = $field;
1189 4
1190 4
			$updated_field = $this->get_search_filter_details( $updated_field, $context );
1191 4
1192 4
			switch ( $field['field'] ) {
1193 4
1194
				case 'search_all':
1195
					$updated_field['key'] = 'search_all';
1196
					$updated_field['input'] = 'search_all';
1197
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_search' );
1198
					break;
1199
1200
				case 'entry_date':
1201
					$updated_field['key'] = 'entry_date';
1202
					$updated_field['input'] = 'entry_date';
1203
					$updated_field['value'] = array(
1204
						'start' => $this->rgget_or_rgpost( 'gv_start' ),
1205
						'end' => $this->rgget_or_rgpost( 'gv_end' ),
1206
					);
1207
					break;
1208
1209
				case 'entry_id':
1210
					$updated_field['key'] = 'entry_id';
1211
					$updated_field['input'] = 'entry_id';
1212
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_id' );
1213
					break;
1214
1215
				case 'created_by':
1216
					$updated_field['key'] = 'created_by';
1217
					$updated_field['name'] = 'gv_by';
1218
					$updated_field['value'] = $this->rgget_or_rgpost( 'gv_by' );
1219
					$updated_field['choices'] = self::get_created_by_choices();
1220
					break;
1221
				
1222
				case 'is_approved':
1223
					$updated_field['key'] = 'is_approved';
1224
					$updated_field['input'] = 'checkbox';
1225 4
					$updated_field['value'] = $this->rgget_or_rgpost( 'filter_is_approved' );
1226
					$updated_field['choices'] = self::get_is_approved_choices();
1227
					break;
1228 4
			}
1229
1230
			$search_fields[ $k ] = $updated_field;
1231
		}
1232
1233
		gravityview()->log->debug( 'Calculated Search Fields: ', array( 'data' => $search_fields ) );
1234
1235
		/**
1236
		 * @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.
1237
		 * @param array $search_fields Array of search filters with `key`, `label`, `value`, `type`, `choices` keys
1238 4
		 * @param GravityView_Widget_Search $this Current widget object
1239
		 * @param array $widget_args Args passed to this method. {@since 1.8}
1240 4
		 * @param \GV\Template_Context $context {@since 2.0}
1241 4
		 * @var array
1242
		 */
1243 4
		$this->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 exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
1244 4
1245
		$this->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 exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
1246 4
		$this->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 exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
1247
1248
		$custom_class = ! empty( $widget_args['custom_class'] ) ? $widget_args['custom_class'] : '';
1249
		$this->search_class = self::get_search_class( $custom_class );
0 ignored issues
show
Bug introduced by
The property search_class does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
Deprecated Code introduced by
The method GravityView_Widget_Search::get_search_class() has been deprecated with message: See search_class()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1250
1251 4
		$this->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 exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
1252 4
1253 4
		/**
1254 4
		 * @deprecated Set legacy state for old custom code that still uses it.
1255 4
		 */
1256 4
		$gravityview_view = GravityView_View::getInstance();
1257
		$gravityview_view->search_fields = $this->search_fields;
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...
1258 4
		$gravityview_view->search_layout = $this->search_layout;
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...
1259
		$gravityview_view->search_mode   = $this->search_mode;
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...
1260
		$gravityview_view->search_class  = $this->search_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...
1261
		$gravityview_view->search_clear  = $this->search_clear;
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...
1262
1263
		if ( $this->has_date_field( $search_fields ) ) {
1264
			// enqueue datepicker stuff only if needed!
1265
			$this->enqueue_datepicker();
1266
1267
			/**
1268 4
			 * @deprecated Set legacy state for old custom code that still uses it.
1269
			 */
1270
			$gravityview_view->datepicker_class = $this->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...
Bug introduced by
The property datepicker_class does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
1271
		}
1272
1273
		$this->maybe_enqueue_flexibility();
1274 4
1275 4
		$template = new class extends \GV\Template {
0 ignored issues
show
Coding Style introduced by
Class name "extends" is not in camel caps format
Loading history...
1276 4
			protected $filter_prefix = 'gravityview/widgets/search';
1277 4
			protected $theme_template_directory = 'gravityview/widgets/search';
1278
1279
			public function __construct() {
1280 4
				$this->plugin_directory = gravityview()->plugin->dir();
1281 4
				$this->plugin_template_directory = 'includes/widgets/search-widget/templates/';
1282
			}
1283 4
		};
1284
1285 4
		$template->push_template_data( $this, 'widget' );
1286 4
		$template->push_template_data( $template, 'template' );
1287 4
1288
		$template->get_template_part( 'widget' );
1289
1290
		$template->pop_template_data( 'template' );
1291
		$template->pop_template_data( 'widget' );
1292
	}
1293
1294
	/**
1295
	 * Get the search class for a search form
1296
	 *
1297 4
	 * @since 1.5.4
1298 4
	 * @deprecated See search_class()
1299
	 *
1300 4
	 * @return string Sanitized CSS class for the search form
1301
	 */
1302 4
	public static function get_search_class( $custom_class = '' ) {
1303
		$gravityview_view = GravityView_View::getInstance();
1304
1305
		$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...
1306
1307
		if ( ! empty( $custom_class )  ) {
1308
			$search_class .= ' '.$custom_class;
1309
		}
1310 4
1311
		/**
1312
		 * @filter `gravityview_search_class` Modify the CSS class for the search form
1313 4
		 * @param string $search_class The CSS class for the search form
1314
		 */
1315 4
		$search_class = apply_filters( 'gravityview_search_class', $search_class );
1316
1317
		// Is there an active search being performed? Used by fe-views.js
1318
		$search_class .= GravityView_frontend::getInstance()->isSearch() ? ' gv-is-search' : '';
1319
1320
		return gravityview_sanitize_html_class( $search_class );
1321
	}
1322
1323
	/**
1324
	 * Get the search class for a search form
1325
	 *
1326 4
	 * @param \GV\Template_Context $context The context.
1327 4
	 * @param \GV\$context The context.
1328
	 *
1329 4
	 * @return string Sanitized CSS class for the search form
1330
	 */
1331
	public function search_class( $context, $custom_class = '' ) {
1332
		$search_class = 'gv-search-' . $this->search_layout;
1333
1334
		if ( ! empty( $custom_class )  ) {
1335
			$search_class .= ' '.$custom_class;
1336
		}
1337
1338
		/**
1339 4
		 * @filter `gravityview_search_class` Modify the CSS class for the search form
1340
		 * @param string $search_class The CSS class for the search form
1341
		 * @param \GV\Template_Context $context The context.
1342 4
		 * @param \GV\Widget $this The search widget.
1343
		 */
1344 4
		$search_class = apply_filters( 'gravityview_search_class', $search_class, $context, $this );
1345
1346
		// Is there an active search being performed? Used by fe-views.js
1347
		$search_class .= gravityview()->request->is_search() ? ' gv-is-search' : '';
1348
1349
		return gravityview_sanitize_html_class( $search_class );
1350
	}
1351
1352
	/**
1353
	 * Calculate the search form action
1354
	 * @since 1.6
1355
	 * @deprecated See search_form_action()
1356
	 *
1357
	 * @return string
1358
	 */
1359
	public static function get_search_form_action() {
1360
		$gravityview_view = GravityView_View::getInstance();
1361
1362
		$post_id = $gravityview_view->getPostId() ? $gravityview_view->getPostId() : $gravityview_view->getViewId();
1363
1364
		$url = add_query_arg( array(), get_permalink( $post_id ) );
1365
1366
		return esc_url( $url );
1367
	}
1368
1369
	/**
1370
	 * Calculate the form action.
1371 4
	 *
1372 4
	 * @param \GV\Template_Context $context The context.
1373 4
	 *
1374 4
	 * @return string Unescaped.
1375
	 */
1376
	public function search_form_action( $context ) {
1377
		global $post;
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...
1378 4
		if ( $post ) {
1379
			$base = get_permalink( $post->ID );
1380
		} else {
1381
			$base = get_permalink( $context->view->ID );
1382
		}
1383
		return $base;
1384
	}
1385
1386
	/**
1387 4
	 * Get the label for a search form field
1388
	 * @param  array $field      Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1389 4
	 * @param  array $form_field Form field data, as fetched by `gravityview_get_field()`
1390
	 * @return string             Label for the search form
1391 4
	 */
1392
	private static function get_field_label( $field, $form_field = array() ) {
1393 4
1394
		$label = \GV\Utils::_GET( 'label', \GV\Utils::get( $field, 'label' ) );
1395 4
1396 4
		if ( ! $label ) {
1397 4
1398 4
			$label = isset( $form_field['label'] ) ? $form_field['label'] : '';
1399
1400
			switch( $field['field'] ) {
1401
				case 'search_all':
1402
					$label = __( 'Search Entries:', 'gravityview' );
1403
					break;
1404
				case 'entry_date':
1405
					$label = __( 'Filter by date:', 'gravityview' );
1406
					break;
1407
				case 'entry_id':
1408
					$label = __( 'Entry ID:', 'gravityview' );
1409
					break;
1410
				default:
1411
					// If this is a field input, not a field
1412
					if ( strpos( $field['field'], '.' ) > 0 && ! empty( $form_field['inputs'] ) ) {
1413
1414
						// Get the label for the field in question, which returns an array
1415
						$items = wp_list_filter( $form_field['inputs'], array( 'id' => $field['field'] ) );
1416
1417
						// Get the item with the `label` key
1418
						$values = wp_list_pluck( $items, 'label' );
1419
1420
						// There will only one item in the array, but this is easier
1421
						foreach ( $values as $value ) {
1422
							$label = $value;
1423
							break;
1424
						}
1425
					}
1426
			}
1427
		}
1428
1429
		/**
1430
		 * @filter `gravityview_search_field_label` Modify the label for a search field. Supports returning HTML
1431 4
		 * @since 1.17.3 Added $field parameter
1432
		 * @param[in,out] string $label Existing label text, sanitized.
1433 4
		 * @param[in] array $form_field Gravity Forms field array, as returned by `GFFormsModel::get_field()`
1434
		 * @param[in] array $field Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
1435
		 */
1436
		$label = apply_filters( 'gravityview_search_field_label', esc_attr( $label ), $form_field, $field );
1437
1438
		return $label;
1439
	}
1440
1441
	/**
1442 4
	 * Prepare search fields to frontend render with other details (label, field type, searched values)
1443 4
	 *
1444
	 * @param array $field
1445
	 * @return array
1446 4
	 */
1447
	private function get_search_filter_details( $field, $context ) {
1448
		$form = $context->view->form->form;
1449 4
1450
		// for advanced field ids (eg, first name / last name )
1451
		$name = 'filter_' . str_replace( '.', '_', $field['field'] );
1452 4
1453
		// get searched value from $_GET/$_POST (string or array)
1454
		$value = $this->rgget_or_rgpost( $name );
1455 4
1456 4
		// get form field details
1457 4
		$form_field = gravityview_get_field( $form, $field['field'] );
1458 4
1459 4
		$filter = array(
1460 4
			'key' => $field['field'],
1461
			'name' => $name,
1462
			'label' => self::get_field_label( $field, $form_field ),
1463
			'input' => $field['input'],
1464 4
			'value' => $value,
1465
			'type' => $form_field['type'],
1466 4
		);
1467
1468
		// collect choices
1469
		if ( 'post_category' === $form_field['type'] && ! empty( $form_field['displayAllCategories'] ) && empty( $form_field['choices'] ) ) {
1470 4
			$filter['choices'] = gravityview_get_terms_choices();
1471
		} elseif ( ! empty( $form_field['choices'] ) ) {
1472
			$filter['choices'] = $form_field['choices'];
1473
		}
1474 4
1475
		if ( 'date_range' === $field['input'] && empty( $value ) ) {
1476
			$filter['value'] = array( 'start' => '', 'end' => '' );
1477
		}
1478
1479
		return $filter;
1480
1481
	}
1482
1483
	/**
1484
	 * Calculate the search choices for the users
1485
	 *
1486
	 * @since 1.8
1487
	 *
1488
	 * @return array Array of user choices (value = ID, text = display name)
1489
	 */
1490
	private static function get_created_by_choices() {
1491
1492
		/**
1493
		 * filter gravityview/get_users/search_widget
1494
		 * @see \GVCommon::get_users
1495
		 */
1496
		$users = GVCommon::get_users( 'search_widget', array( 'fields' => array( 'ID', 'display_name' ) ) );
1497
1498
		$choices = array();
1499
		foreach ( $users as $user ) {
1500
			$choices[] = array(
1501
				'value' => $user->ID,
1502
				'text' => $user->display_name,
1503
			);
1504
		}
1505
1506
		return $choices;
1507
	}
1508
1509
	/**
1510
	 * Calculate the search checkbox choices for approval status
1511
	 *
1512
	 * @since develop
1513
	 *
1514
	 * @return array Array of approval status choices (value = status, text = display name)
1515
	 */
1516
	private static function get_is_approved_choices() {
1517
1518
		$choices = array();
1519
		foreach ( GravityView_Entry_Approval_Status::get_all() as $status ) {
1520
			$choices[] = array(
1521
				'value' => $status['value'],
1522
				'text' => $status['label'],
1523
			);
1524
		}
1525
1526
		return $choices;
1527
	}
1528
1529
	/**
1530
	 * Output the Clear Search Results button
1531
	 * @deprecated Use inline in templates
1532
	 * @since 1.5.4
1533
	 */
1534
	public static function the_clear_search_button() {
1535
		if ( $this->search_clear ) {
0 ignored issues
show
Bug introduced by
The variable $this does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1536
			$url = strtok( add_query_arg( array() ), '?' );
1537
			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...
1538
1539
		}
1540
	}
1541
1542
	/**
1543
	 * Output the result totals.
1544
	 */
1545
	public static function show_totals() {
1546
		$gravityview_view = GravityView_View::getInstance();
1547 4
1548 4
		if ( $gravityview_view->show_totals ) {
0 ignored issues
show
Documentation introduced by
The property show_totals 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...
1549
			if ( gravityview()->request->is_search() ) {
1550 4
				printf( __( 'Found: %d results', 'gravityview' ), 0 );
1551
			}
1552 4
		}
1553
	}
1554 4
1555
	/**
1556 4
	 * Based on the search method, fetch the value for a specific key
1557
	 *
1558
	 * @since 1.16.4
1559
	 *
1560
	 * @param string $name Name of the request key to fetch the value for
1561
	 *
1562
	 * @return mixed|string Value of request at $name key. Empty string if empty.
1563
	 */
1564
	private function rgget_or_rgpost( $name ) {
1565
		$value = \GV\Utils::_REQUEST( $name );
1566
1567
		$value = stripslashes_deep( $value );
1568
1569
		$value = gv_map_deep( $value, 'rawurldecode' );
1570
1571
		$value = gv_map_deep( $value, '_wp_specialchars' );
1572
1573
		return $value;
1574
	}
1575
1576
1577
	/**
1578
	 * Require the datepicker script for the frontend GV script
1579
	 * @param array $js_dependencies Array of existing required scripts for the fe-views.js script
1580
	 * @return array Array required scripts, with `jquery-ui-datepicker` added
1581
	 */
1582
	public function add_datepicker_js_dependency( $js_dependencies ) {
1583
1584
		$js_dependencies[] = 'jquery-ui-datepicker';
1585
1586
		return $js_dependencies;
1587
	}
1588
1589
	/**
1590
	 * Modify the array passed to wp_localize_script()
1591
	 *
1592
	 * @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...
1593
	 * @param array $view_data View data array with View settings
1594
	 *
1595
	 * @return array
1596
	 */
1597
	public function add_datepicker_localization( $localizations = array(), $view_data = array() ) {
1598
		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...
1599
1600
		/**
1601
		 * @filter `gravityview_datepicker_settings` Modify the datepicker settings
1602
		 * @see http://api.jqueryui.com/datepicker/ Learn what settings are available
1603
		 * @see http://www.renegadetechconsulting.com/tutorials/jquery-datepicker-and-wordpress-i18n Thanks for the helpful information on $wp_locale
1604
		 * @param array $js_localization The data padded to the Javascript file
1605
		 * @param array $view_data View data array with View settings
1606
		 */
1607
		$datepicker_settings = apply_filters( 'gravityview_datepicker_settings', array(
1608
			'yearRange' => '-5:+5',
1609
			'changeMonth' => true,
1610
			'changeYear' => true,
1611
			'closeText' => esc_attr_x( 'Close', 'Close calendar', 'gravityview' ),
1612
			'prevText' => esc_attr_x( 'Prev', 'Previous month in calendar', 'gravityview' ),
1613
			'nextText' => esc_attr_x( 'Next', 'Next month in calendar', 'gravityview' ),
1614
			'currentText' => esc_attr_x( 'Today', 'Today in calendar', 'gravityview' ),
1615
			'weekHeader' => esc_attr_x( 'Week', 'Week in calendar', 'gravityview' ),
1616
			'monthStatus'       => __( 'Show a different month', 'gravityview' ),
1617
			'monthNames'        => array_values( $wp_locale->month ),
1618
			'monthNamesShort'   => array_values( $wp_locale->month_abbrev ),
1619
			'dayNames'          => array_values( $wp_locale->weekday ),
1620
			'dayNamesShort'     => array_values( $wp_locale->weekday_abbrev ),
1621
			'dayNamesMin'       => array_values( $wp_locale->weekday_initial ),
1622
			// get the start of week from WP general setting
1623
			'firstDay'          => get_option( 'start_of_week' ),
1624
			// is Right to left language? default is false
1625
			'isRTL'             => is_rtl(),
1626
		), $view_data );
1627
1628
		$localizations['datepicker'] = $datepicker_settings;
1629
1630
		return $localizations;
1631
1632
	}
1633
1634
	/**
1635
	 * Register search widget scripts, including Flexibility
1636
	 *
1637 4
	 * @see https://github.com/10up/flexibility
1638 4
	 *
1639
	 * @since 1.17
1640
	 *
1641 4
	 * @return void
1642
	 */
1643
	public function register_scripts() {
1644
		wp_register_script( 'gv-flexibility', plugins_url( 'assets/lib/flexibility/flexibility.js', GRAVITYVIEW_FILE ), array(), \GV\Plugin::$version, true );
1645
	}
1646
1647
	/**
1648
	 * If the current visitor is running IE 8 or 9, enqueue Flexibility
1649
	 *
1650
	 * @since 1.17
1651
	 *
1652
	 * @return void
1653
	 */
1654
	private function maybe_enqueue_flexibility() {
1655
		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...
1656
			wp_enqueue_script( 'gv-flexibility' );
1657
		}
1658
	}
1659
1660
	/**
1661
	 * Enqueue the datepicker script
1662
	 *
1663
	 * It sets the $gravityview->datepicker_class parameter
1664
	 *
1665
	 * @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.
1666
	 * @return void
1667
	 */
1668
	public function enqueue_datepicker() {
1669
		wp_enqueue_script( 'jquery-ui-datepicker' );
1670
1671
		add_filter( 'gravityview_js_dependencies', array( $this, 'add_datepicker_js_dependency' ) );
1672
		add_filter( 'gravityview_js_localization', array( $this, 'add_datepicker_localization' ), 10, 2 );
1673
1674
		$scheme = is_ssl() ? 'https://' : 'http://';
1675
		wp_enqueue_style( 'jquery-ui-datepicker', $scheme.'ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/smoothness/jquery-ui.css' );
1676
1677
		/**
1678
		 * @filter `gravityview_search_datepicker_class`
1679
		 * 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.
1680
		 * @param string $css_class CSS class to use. Default: `gv-datepicker datepicker mdy` \n
1681
		 * Options are:
1682
		 * - `mdy` (mm/dd/yyyy)
1683
		 * - `dmy` (dd/mm/yyyy)
1684
		 * - `dmy_dash` (dd-mm-yyyy)
1685 18
		 * - `dmy_dot` (dd.mm.yyyy)
1686
		 * - `ymd_slash` (yyyy/mm/dd)
1687 18
		 * - `ymd_dash` (yyyy-mm-dd)
1688
		 * - `ymd_dot` (yyyy.mm.dd)
1689
		 */
1690
		$this->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...
1691
	}
1692
1693
	/**
1694
	 * Retrieve the datepicker format.
1695
	 *
1696
	 * @param bool $date_format Whether to return the PHP date format or the datpicker class name. Default: false.
1697
	 *
1698
	 * @see https://docs.gravityview.co/article/115-changing-the-format-of-the-search-widgets-date-picker
1699
	 *
1700
	 * @return string The datepicker format placeholder, or the PHP date format.
1701
	 */
1702 18
	private function get_datepicker_format( $date_format = false ) {
1703
1704
		$default_format = 'mdy';
1705 18
1706
		/**
1707
		 * @filter `gravityview/widgets/search/datepicker/format`
1708
		 * @since 2.1.1
1709
		 * @param string           $format Default: mdy
1710
		 * Options are:
1711
		 * - `mdy` (mm/dd/yyyy)
1712
		 * - `dmy` (dd/mm/yyyy)
1713
		 * - `dmy_dash` (dd-mm-yyyy)
1714
		 * - `dmy_dot` (dd.mm.yyyy)
1715
		 * - `ymd_slash` (yyyy/mm/dd)
1716 18
		 * - `ymd_dash` (yyyy-mm-dd)
1717
		 * - `ymd_dot` (yyyy.mm.dd)
1718
		 */
1719
		$format = apply_filters( 'gravityview/widgets/search/datepicker/format', $default_format );
1720
1721
		$gf_date_formats = array(
1722 18
			'mdy' => 'm/d/Y',
1723
1724
			'dmy_dash' => 'd-m-Y',
1725
			'dmy_dot' => 'd.m.Y',
1726
			'dmy' => 'd/m/Y',
1727
1728
			'ymd_slash' => 'Y/m/d',
1729
			'ymd_dash' => 'Y-m-d',
1730
			'ymd_dot' => 'Y.m.d',
1731
		);
1732
1733
		if ( ! $date_format ) {
1734
			// If the format key isn't valid, return default format key
1735
			return isset( $gf_date_formats[ $format ] ) ? $format : $default_format;
1736
		}
1737
1738
		// If the format key isn't valid, return default format value
1739
		return \GV\Utils::get( $gf_date_formats, $format, $gf_date_formats[ $default_format ] );
1740
	}
1741
1742
1743
} // end class
1744
1745
new GravityView_Widget_Search;
1746