Completed
Push — master ( 9cf7be...029c15 )
by Zack
04:07
created

GVCommon   D

Complexity

Total Complexity 195

Size/Duplication

Total Lines 1480
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6
Metric Value
wmc 195
lcom 1
cbo 6
dl 0
loc 1480
rs 4.4102

37 Methods

Rating   Name   Duplication   Size   Complexity  
A get_form() 0 16 4
A has_cap() 0 3 1
A get_entry_meta() 0 14 4
A get_field_array() 0 15 2
A get_form_from_entry_id() 0 8 1
A has_product_field() 0 8 2
B get_entry_id_from_slug() 0 25 3
A get_forms() 0 13 3
D get_form_fields() 0 152 26
A get_entry_ids() 0 8 2
A get_all_views() 0 20 1
D calculate_get_entries_criteria() 0 83 17
C get_entries() 0 76 12
C get_entry() 0 61 10
C matches_operation() 0 49 13
D check_entry_display() 0 93 22
B format_date() 0 47 6
A get_field_label() 0 10 4
A get_field() 0 7 2
A has_gravityview_shortcode() 0 12 4
C has_shortcode_r() 0 29 8
A get_connected_views() 0 15 1
A get_meta_form_id() 0 3 1
A get_meta_template_id() 0 3 1
A get_template_settings() 0 15 2
A get_template_setting() 0 10 2
A get_directory_fields() 0 3 1
B get_sortable_fields() 0 24 5
B get_sortable_fields_array() 0 34 3
A get_field_type() 0 11 4
C is_field_numeric() 0 31 7
B js_encrypt() 0 27 4
B gv_parse_str() 0 23 6
B get_link_html() 0 64 4
B array_merge_recursive_distinct() 0 13 5
A get_users() 0 21 1
A generate_notice() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like GVCommon often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use GVCommon, and based on these observations, apply Extract Interface, too.

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 19 and the first side effect is on line 16.

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
 * Set of common functions to separate main plugin from Gravity Forms API and other cross-plugin methods
4
 *
5
 * @package   GravityView
6
 * @license   GPL2+
7
 * @author    Katz Web Services, Inc.
8
 * @link      http://gravityview.co
9
 * @copyright Copyright 2014, Katz Web Services, Inc.
10
 *
11
 * @since 1.5.2
12
 */
13
14
/** If this file is called directly, abort. */
15
if ( ! defined( 'ABSPATH' ) ) {
16
	die;
17
}
18
19
class GVCommon {
20
21
	/**
22
	 * Returns the form object for a given Form ID.
23
	 *
24
	 * @access public
25
	 * @param mixed $form_id
26
	 * @return mixed False: no form ID specified or Gravity Forms isn't active. Array: Form returned from Gravity Forms
27
	 */
28
	public static function get_form( $form_id ) {
29
		if ( empty( $form_id ) ) {
30
			return false;
31
		}
32
33
		// Only get_form_meta is cached. ::facepalm::
34
		if ( class_exists( 'RGFormsModel' ) ) {
35
			return GFFormsModel::get_form_meta( $form_id );
36
		}
37
38
		if ( class_exists( 'GFAPI' ) ) {
39
			return GFAPI::get_form( $form_id );
40
		}
41
42
		return false;
43
	}
44
45
	/**
46
	 * Alias of GravityView_Roles_Capabilities::has_cap()
47
	 *
48
	 * @since 1.15
49
	 *
50
	 * @see GravityView_Roles_Capabilities::has_cap()
51
	 *
52
	 * @param string|array $caps Single capability or array of capabilities
53
	 * @param int $object_id (optional) Parameter can be used to check for capabilities against a specific object, such as a post or user
54
	 * @param int|null $user_id (optional) Check the capabilities for a user who is not necessarily the currently logged-in user
55
	 *
56
	 * @return bool True: user has at least one passed capability; False: user does not have any defined capabilities
57
	 */
58
	public static function has_cap( $caps = '', $object_id = null, $user_id = null ) {
59
		return GravityView_Roles_Capabilities::has_cap( $caps, $object_id, $user_id );
60
	}
61
62
	/**
63
	 * Return a Gravity Forms field array, whether using GF 1.9 or not
64
	 *
65
	 * @since 1.7
66
	 *
67
	 * @param array|GF_Fields $field Gravity Forms field or array
68
	 * @return array Array version of $field
69
	 */
70
	public static function get_field_array( $field ) {
71
72
		if ( class_exists( 'GF_Fields' ) ) {
73
74
			$field_object = GF_Fields::create( $field );
75
76
			// Convert the field object in 1.9 to an array for backward compatibility
77
			$field_array = get_object_vars( $field_object );
78
79
		} else {
80
			$field_array = $field;
81
		}
82
83
		return $field_array;
84
	}
85
86
	/**
87
	 * Get all existing Views
88
	 *
89
	 * @since  1.5.4
90
	 * @since  TODO Added $args array
91
	 *
92
	 * @param array $args Pass custom array of args, formatted as if for `get_posts()`
93
	 *
94
	 * @return array Array of Views as `WP_Post`. Empty array if none found.
95
	 */
96
	public static function get_all_views( $args = array() ) {
97
98
		$default_params = array(
99
			'post_type' => 'gravityview',
100
			'posts_per_page' => -1,
0 ignored issues
show
introduced by
Disabling pagination is prohibited in VIP context, do not set posts_per_page to -1 ever.
Loading history...
101
			'post_status' => 'publish',
102
		);
103
104
		$params = wp_parse_args( $args, $default_params );
105
106
		/**
107
		 * @filter `gravityview/get_all_views/params` Modify the parameters sent to get all views.
108
		 * @param[in,out]  array $params Array of parameters to pass to `get_posts()`
109
		 */
110
		$views_params = apply_filters( 'gravityview/get_all_views/params', $params );
111
112
		$views = get_posts( $views_params );
113
114
		return $views;
115
	}
116
117
118
	/**
119
	 * Get the form array for an entry based only on the entry ID
120
	 * @param  int|string $entry_slug Entry slug
121
	 * @return array           Gravity Forms form array
122
	 */
123
	public static function get_form_from_entry_id( $entry_slug ) {
124
125
		$entry = self::get_entry( $entry_slug, true );
126
127
		$form = self::get_form( $entry['form_id'] );
128
129
		return $form;
130
	}
131
132
	/**
133
	 * Check whether a form has product fields
134
	 *
135
	 * @since 1.16
136
	 *
137
	 * @param array $form Gravity Forms form array
138
	 *
139
	 * @return bool|GF_Field[]
140
	 */
141
	public static function has_product_field( $form = array() ) {
142
143
		$product_fields = apply_filters( 'gform_product_field_types', array( 'option', 'quantity', 'product', 'total', 'shipping', 'calculation', 'price' ) );
144
145
		$fields = GFAPI::get_fields_by_type( $form, $product_fields );
146
147
		return empty( $fields ) ? false : $fields;
148
	}
149
150
	/**
151
	 * Get the entry ID from the entry slug, which may or may not be the entry ID
152
	 *
153
	 * @since  1.5.2
154
	 * @param  string $slug The entry slug, as returned by GravityView_API::get_entry_slug()
155
	 * @return int|null       The entry ID, if exists; `NULL` if not
156
	 */
157
	public static function get_entry_id_from_slug( $slug ) {
158
		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...
159
160
		$search_criteria = array(
161
			'field_filters' => array(
162
				array(
163
					'key' => 'gravityview_unique_id', // Search the meta values
164
					'value' => $slug,
165
					'operator' => 'is',
166
					'type' => 'meta',
167
				),
168
			)
169
		);
170
171
		// Limit to one for speed
172
		$paging = array(
173
			'page_size' => 1,
174
		);
175
176
		$results = GFAPI::get_entries( 0, $search_criteria, null, $paging );
177
178
		$result = ( ! empty( $results ) && ! empty( $results[0]['id'] ) ) ? $results[0]['id'] : null;
179
180
		return $result;
181
	}
182
183
184
	/**
185
	 * Returns the list of available forms
186
	 *
187
	 * @access public
188
	 * @param mixed $form_id
0 ignored issues
show
Bug introduced by
There is no parameter named $form_id. 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...
189
	 * @return array Empty array if GFAPI isn't available or no forms. Otherwise, associative array with id, title keys
190
	 */
191
	public static function get_forms() {
192
		$forms = array();
193
		if ( class_exists( 'GFAPI' ) ) {
194
			$gf_forms = GFAPI::get_forms();
195
			foreach ( $gf_forms as $form ) {
196
				$forms[] = array(
197
					'id' => $form['id'],
198
					'title' => $form['title'],
199
				);
200
			}
201
		}
202
		return $forms;
203
	}
204
205
	/**
206
	 * Return array of fields' id and label, for a given Form ID
207
	 *
208
	 * @access public
209
	 * @param string|array $form_id (default: '') or $form object
0 ignored issues
show
Bug introduced by
There is no parameter named $form_id. 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...
210
	 * @return array
211
	 */
212
	public static function get_form_fields( $form = '', $add_default_properties = false, $include_parent_field = true ) {
213
214
		if ( ! is_array( $form ) ) {
215
			$form = self::get_form( $form );
216
		}
217
218
		$fields = array();
219
		$has_product_fields = false;
220
		$has_post_fields = false;
221
		$has_quiz_fields = false;
222
		$has_poll_fields = false;
223
224
		// If GF_Field exists, we're using GF 1.9+, where add_default_properties has been deprecated.
225
		if ( false === class_exists( 'GF_Field' ) && $add_default_properties ) {
226
			$form = RGFormsModel::add_default_properties( $form );
227
		}
228
229
		if ( $form ) {
230
			foreach ( $form['fields'] as $field ) {
231
				if ( $include_parent_field || empty( $field['inputs'] ) ) {
232
					$fields[ $field['id'] ] = array(
233
						'label' => rgar( $field, 'label' ),
234
						'parent' => null,
235
						'type' => rgar( $field, 'type' ),
236
						'adminLabel' => rgar( $field, 'adminLabel' ),
237
						'adminOnly' => rgar( $field, 'adminOnly' ),
238
					);
239
				}
240
241
				if ( $add_default_properties && ! empty( $field['inputs'] ) ) {
242
					foreach ( $field['inputs'] as $input ) {
243
                        /**
244
                         * @hack
245
                         * In case of email/email confirmation, the input for email has the same id as the parent field
246
                         */
247
                        if( 'email' == rgar( $field, 'type' ) && false === strpos( $input['id'], '.' ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
248
                            continue;
249
                        }
250
						$fields[ (string)$input['id'] ] = array(
0 ignored issues
show
introduced by
No space after closing casting parenthesis is prohibited
Loading history...
251
							'label' => rgar( $input, 'label' ),
252
							'customLabel' => rgar( $input, 'customLabel' ),
253
							'parent' => $field,
254
							'type' => rgar( $field, 'type' ),
255
							'adminLabel' => rgar( $field, 'adminLabel' ),
256
							'adminOnly' => rgar( $field, 'adminOnly' ),
257
						);
258
					}
259
				}
260
261
				/** @since 1.14 */
262
				if( 'list' === $field['type'] && !empty( $field['enableColumns'] ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
Expected 1 space after "!"; 0 found
Loading history...
263
264
					foreach ( (array)$field['choices'] as $key => $input ) {
0 ignored issues
show
introduced by
No space after closing casting parenthesis is prohibited
Loading history...
265
266
						$input_id = sprintf( '%d.%d', $field['id'], $key ); // {field_id}.{column_key}
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
267
268
						$fields[ $input_id ] = array(
269
							'label'       => rgar( $input, 'text' ),
270
							'customLabel' => '',
271
							'parent'      => $field,
272
							'type'        => rgar( $field, 'type' ),
273
							'adminLabel'  => rgar( $field, 'adminLabel' ),
274
							'adminOnly'   => rgar( $field, 'adminOnly' ),
275
						);
276
					}
277
				}
278
279
				/**
280
				 * @since 1.8
281
				 */
282
				if( 'quiz' === $field['type'] ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
283
					$has_quiz_fields = true;
284
				}
285
286
				/**
287
				 * @since 1.8
288
				 */
289
				if( 'poll' === $field['type'] ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
290
					$has_poll_fields = true;
291
				}
292
293
				if( GFCommon::is_product_field( $field['type'] ) ){
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
294
					$has_product_fields = true;
295
				}
296
297
				/**
298
				 * @hack Version 1.9
299
				 */
300
				$field_for_is_post_field = class_exists( 'GF_Fields' ) ? (object) $field : (array) $field;
301
302
				if ( GFCommon::is_post_field( $field_for_is_post_field ) ) {
303
					$has_post_fields = true;
304
				}
305
			}
306
		}
307
308
		/**
309
		 * @since 1.7
310
		 */
311
		if ( $has_post_fields ) {
312
			$fields['post_id'] = array(
313
				'label' => __( 'Post ID', 'gravityview' ),
314
				'type' => 'post_id',
315
			);
316
		}
317
318
		if ( $has_product_fields ) {
319
320
			$payment_fields = GravityView_Fields::get_all( 'pricing' );
321
322
			foreach ( $payment_fields as $payment_field ) {
323
				if( isset( $fields["{$payment_field->name}"] ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
Array keys should be surrounded by spaces unless they contain a string or an integer.
Loading history...
324
					continue;
325
				}
326
				$fields["{$payment_field->name}"] = array(
0 ignored issues
show
introduced by
Array keys should be surrounded by spaces unless they contain a string or an integer.
Loading history...
327
					'label' => $payment_field->label,
328
					'desc' => $payment_field->description,
329
					'type' => $payment_field->name,
330
				);
331
			}
332
		}
333
334
		/**
335
		 * @since 1.8
336
		 */
337
		if( $has_quiz_fields ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
338
339
			$fields['gquiz_score']   = array(
340
				'label' => __( 'Quiz Score Total', 'gravityview' ),
341
				'type'  => 'quiz_score',
342
				'desc'  => __( 'Displays the number of correct Quiz answers the user submitted.', 'gravityview' ),
343
			);
344
			$fields['gquiz_percent'] = array(
345
				'label' => __( 'Quiz Percentage Grade', 'gravityview' ),
346
				'type'  => 'quiz_percent',
347
				'desc'  => __( 'Displays the percentage of correct Quiz answers the user submitted.', 'gravityview' ),
348
			);
349
			$fields['gquiz_grade']   = array(
350
				'label' => __( 'Quiz Letter Grade', 'gravityview' ),
351
				'type'  => 'quiz_grade',
352
				'desc'  => __( 'Displays the Grade the user achieved based on Letter Grading configured in the Quiz Settings.', 'gravityview' ),
353
			);
354
			$fields['gquiz_is_pass'] = array(
355
				'label' => __( 'Quiz Pass/Fail', 'gravityview' ),
356
				'type'  => 'quiz_is_pass',
357
				'desc'  => __( 'Displays either Passed or Failed based on the Pass/Fail settings configured in the Quiz Settings.', 'gravityview' ),
358
			);
359
		}
360
361
		return $fields;
362
363
	}
364
365
	/**
366
	 * get extra fields from entry meta
367
	 * @param  string $form_id (default: '')
368
	 * @return array
369
	 */
370
	public static function get_entry_meta( $form_id, $only_default_column = true ) {
371
372
		$extra_fields = GFFormsModel::get_entry_meta( $form_id );
373
374
		$fields = array();
375
376
		foreach ( $extra_fields as $key => $field ){
377
			if ( ! empty( $only_default_column ) && ! empty( $field['is_default_column'] ) ) {
378
				$fields[ $key ] = array( 'label' => $field['label'], 'type' => 'entry_meta' );
379
			}
380
		}
381
382
		return $fields;
383
	}
384
385
386
	/**
387
	 * Wrapper for the Gravity Forms GFFormsModel::search_lead_ids() method
388
	 *
389
	 * @see  GFEntryList::leads_page()
390
	 * @param  int $form_id ID of the Gravity Forms form
391
	 * @since  1.1.6
392
	 * @return array|void          Array of entry IDs. Void if Gravity Forms isn't active.
393
	 */
394
	public static function get_entry_ids( $form_id, $search_criteria = array() ) {
395
396
		if ( ! class_exists( 'GFFormsModel' ) ) {
397
			return;
398
		}
399
400
		return GFFormsModel::search_lead_ids( $form_id, $search_criteria );
401
	}
402
403
	/**
404
	 * Calculates the Search Criteria used on the self::get_entries / self::get_entry methods
405
	 *
406
	 * @since 1.7.4
407
	 *
408
	 * @param array $passed_criteria array Input Criteria (search_criteria, sorting, paging)
409
	 * @param array $form_ids array Gravity Forms form IDs
410
	 * @return array
411
	 */
412
	public static function calculate_get_entries_criteria( $passed_criteria = array(), $form_ids = array() ) {
413
414
		$search_criteria_defaults = array(
415
			'search_criteria' => null,
416
			'sorting' => null,
417
			'paging' => null,
418
			'cache' => (isset( $passed_criteria['cache'] ) ? $passed_criteria['cache'] : true),
419
		);
420
421
		$criteria = wp_parse_args( $passed_criteria, $search_criteria_defaults );
422
423
		if ( ! empty( $criteria['search_criteria']['field_filters'] ) ) {
424
			foreach ( $criteria['search_criteria']['field_filters'] as &$filter ) {
425
426
				if ( ! is_array( $filter ) ) {
427
					continue;
428
				}
429
430
				// By default, we want searches to be wildcard for each field.
431
				$filter['operator'] = empty( $filter['operator'] ) ? 'contains' : $filter['operator'];
432
433
				/**
434
				 * @filter `gravityview_search_operator` Modify the search operator for the field (contains, is, isnot, etc)
435
				 * @param string $operator Existing search operator
436
				 * @param array $filter array with `key`, `value`, `operator`, `type` keys
437
				 */
438
				$filter['operator'] = apply_filters( 'gravityview_search_operator', $filter['operator'], $filter );
439
			}
440
441
			// don't send just the [mode] without any field filter.
442
			if( count( $criteria['search_criteria']['field_filters'] ) === 1 && array_key_exists( 'mode' , $criteria['search_criteria']['field_filters'] ) ) {
0 ignored issues
show
introduced by
Found "=== 1". Use Yoda Condition checks, you must
Loading history...
introduced by
Space after opening control structure is required
Loading history...
443
				unset( $criteria['search_criteria']['field_filters']['mode'] );
444
			}
1 ignored issue
show
introduced by
Blank line found after control structure
Loading history...
445
446
		}
447
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 3 empty lines
Loading history...
448
449
450
		/**
451
		 * Prepare date formats to be in Gravity Forms DB format;
452
		 * $passed_criteria may include date formats incompatible with Gravity Forms.
453
		 */
454
		foreach ( array('start_date', 'end_date' ) as $key ) {
0 ignored issues
show
introduced by
No space after opening parenthesis of array is bad style
Loading history...
455
456
			if ( ! empty( $criteria['search_criteria'][ $key ] ) ) {
457
458
				// Use date_create instead of new DateTime so it returns false if invalid date format.
459
				$date = date_create( $criteria['search_criteria'][ $key ] );
460
461
				if ( $date ) {
462
					// Gravity Forms wants dates in the `Y-m-d H:i:s` format.
463
					$criteria['search_criteria'][ $key ] = $date->format( 'Y-m-d H:i:s' );
464
				} else {
465
					// If it's an invalid date, unset it. Gravity Forms freaks out otherwise.
466
					unset( $criteria['search_criteria'][ $key ] );
467
468
					do_action( 'gravityview_log_error', '[filter_get_entries_criteria] '.$key.' Date format not valid:', $criteria['search_criteria'][ $key ] );
469
				}
470
			}
471
		}
472
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
473
474
		// When multiple views are embedded, OR single entry, calculate the context view id and send it to the advanced filter
475
		if ( class_exists( 'GravityView_View_Data' ) && GravityView_View_Data::getInstance()->has_multiple_views() || GravityView_frontend::getInstance()->getSingleEntry() ) {
476
			$criteria['context_view_id'] = GravityView_frontend::getInstance()->get_context_view_id();
477
		} elseif ( 'delete' === RGForms::get( 'action' ) ) {
478
			$criteria['context_view_id'] = isset( $_GET['view_id'] ) ? $_GET['view_id'] : null;
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...
479
		} elseif( !isset( $criteria['context_view_id'] ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
Expected 1 space after "!"; 0 found
Loading history...
480
            // Prevent overriding the Context View ID: Some widgets could set the context_view_id (e.g. Recent Entries widget)
481
			$criteria['context_view_id'] = null;
482
		}
483
484
		/**
485
		 * @filter `gravityview_search_criteria` Apply final criteria filter (Used by the Advanced Filter extension)
486
		 * @param array $criteria Search criteria used by GravityView
487
		 * @param array $form_ids Forms to search
488
		 * @param int $view_id ID of the view being used to search
489
		 */
490
		$criteria = apply_filters( 'gravityview_search_criteria', $criteria, $form_ids, $criteria['context_view_id'] );
491
492
		return (array)$criteria;
0 ignored issues
show
introduced by
No space after closing casting parenthesis is prohibited
Loading history...
493
494
	}
495
496
497
	/**
498
	 * Retrieve entries given search, sort, paging criteria
499
	 *
500
	 * @see  GFAPI::get_entries()
501
	 * @see GFFormsModel::get_field_filters_where()
502
	 * @access public
503
	 * @param int|array $form_ids The ID of the form or an array IDs of the Forms. Zero for all forms.
504
	 * @param mixed $passed_criteria (default: null)
505
	 * @param mixed &$total Optional. An output parameter containing the total number of entries. Pass a non-null value to generate the total count. (default: null)
506
	 * @return mixed False: Error fetching entries. Array: Multi-dimensional array of Gravity Forms entry arrays
507
	 */
508
	public static function get_entries( $form_ids = null, $passed_criteria = null, &$total = null ) {
509
510
		// Filter the criteria before query (includes Adv Filter)
511
		$criteria = self::calculate_get_entries_criteria( $passed_criteria, $form_ids );
512
513
		do_action( 'gravityview_log_debug', '[gravityview_get_entries] Final Parameters', $criteria );
514
515
		// Return value
516
		$return = null;
517
518
		/** Reduce # of database calls */
519
		add_filter( 'gform_is_encrypted_field', '__return_false' );
520
521
		if ( ! empty( $criteria['cache'] ) ) {
522
523
			$Cache = new GravityView_Cache( $form_ids, $criteria );
524
525
			if ( $entries = $Cache->get() ) {
526
527
				// Still update the total count when using cached results
528
				if ( ! is_null( $total ) ) {
529
					$total = GFAPI::count_entries( $form_ids, $criteria['search_criteria'] );
530
				}
531
532
				$return = $entries;
533
			}
534
		}
535
536
		if ( is_null( $return ) && class_exists( 'GFAPI' ) && ( is_numeric( $form_ids ) || is_array( $form_ids ) ) ) {
537
538
			/**
539
			 * @filter `gravityview_pre_get_entries` Define entries to be used before GFAPI::get_entries() is called
540
			 * @since 1.14
541
			 * @param  null $return If you want to override GFAPI::get_entries() and define entries yourself, tap in here.
542
			 * @param  array $criteria The final search criteria used to generate the request to `GFAPI::get_entries()`
543
			 * @param array $passed_criteria The original search criteria passed to `GVCommon::get_entries()`
544
			 * @param  int|null $total Optional. An output parameter containing the total number of entries. Pass a non-null value to generate
545
			 */
546
			$entries = apply_filters( 'gravityview_before_get_entries', null, $criteria, $passed_criteria, $total );
547
548
			// No entries returned from gravityview_before_get_entries
549
			if( is_null( $entries ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
550
551
				$entries = GFAPI::get_entries( $form_ids, $criteria['search_criteria'], $criteria['sorting'], $criteria['paging'], $total );
552
553
				if ( is_wp_error( $entries ) ) {
554
					do_action( 'gravityview_log_error', $entries->get_error_message(), $entries );
555
556
					return false;
557
				}
558
			}
559
560
			if ( ! empty( $criteria['cache'] ) && isset( $Cache ) ) {
561
562
				// Cache results
563
				$Cache->set( $entries, 'entries' );
564
565
			}
566
567
			$return = $entries;
568
		}
569
570
		/** Remove filter added above */
571
		remove_filter( 'gform_is_encrypted_field', '__return_false' );
572
573
		/**
574
		 * @filter `gravityview_entries` Modify the array of entries returned to GravityView after it has been fetched from the cache or from `GFAPI::get_entries()`.
575
		 * @param  array|null $entries Array of entries as returned by the cache or by `GFAPI::get_entries()`
576
		 * @param  array $criteria The final search criteria used to generate the request to `GFAPI::get_entries()`
577
		 * @param array $passed_criteria The original search criteria passed to `GVCommon::get_entries()`
578
		 * @param  int|null $total Optional. An output parameter containing the total number of entries. Pass a non-null value to generate
579
		 */
580
		$return = apply_filters( 'gravityview_entries', $return, $criteria, $passed_criteria, $total );
581
582
		return $return;
583
	}
584
585
586
	/**
587
	 * Return a single entry object
588
	 *
589
	 * Since 1.4, supports custom entry slugs. The way that GravityView fetches an entry based on the custom slug is by searching `gravityview_unique_id` meta. The `$entry_slug` is fetched by getting the current query var set by `is_single_entry()`
590
	 *
591
	 * @access public
592
	 * @param string|int $entry_slug Either entry ID or entry slug string
593
	 * @param boolean $force_allow_ids Force the get_entry() method to allow passed entry IDs, even if the `gravityview_custom_entry_slug_allow_id` filter returns false.
594
	 * @param boolean $check_entry_display Check whether the entry is visible for the current View configuration. Default: true. {@since 1.14}
595
	 * @return array|boolean
596
	 */
597
	public static function get_entry( $entry_slug, $force_allow_ids = false, $check_entry_display = true ) {
598
599
		if ( class_exists( 'GFAPI' ) && ! empty( $entry_slug ) ) {
600
601
			/**
602
			 * @filter `gravityview_custom_entry_slug` Whether to enable and use custom entry slugs.
603
			 * @param boolean True: Allow for slugs based on entry values. False: always use entry IDs (default)
604
			 */
605
			$custom_slug = apply_filters( 'gravityview_custom_entry_slug', false );
606
607
			/**
608
			 * @filter `gravityview_custom_entry_slug_allow_id` When using a custom slug, allow access to the entry using the original slug (the Entry ID).
609
			 * - If disabled (default), only allow access to an entry using the custom slug value.  (example: `/entry/custom-slug/` NOT `/entry/123/`)
610
			 * - If enabled, you could access using the custom slug OR the entry id (example: `/entry/custom-slug/` OR `/entry/123/`)
611
			 * @param boolean $custom_slug_id_access True: allow accessing the slug by ID; False: only use the slug passed to the method.
612
			 */
613
			$custom_slug_id_access = $force_allow_ids || apply_filters( 'gravityview_custom_entry_slug_allow_id', false );
614
615
			/**
616
			 * If we're using custom entry slugs, we do a meta value search
617
			 * instead of doing a straightup ID search.
618
			 */
619
			if ( $custom_slug ) {
620
621
				$entry_id = self::get_entry_id_from_slug( $entry_slug );
622
623
			}
624
625
			// If custom slug is off, search using the entry ID
626
			// ID allow ID access is on, also use entry ID as a backup
627
			if ( empty( $custom_slug ) || ! empty( $custom_slug_id_access ) ) {
628
				// Search for IDs matching $entry_slug
629
				$entry_id = $entry_slug;
630
			}
631
632
			if ( empty( $entry_id ) ) {
633
				return false;
634
			}
635
636
			// fetch the entry
637
			$entry = GFAPI::get_entry( $entry_id );
638
639
			/**
640
			 * @filter `gravityview/common/get_entry/check_entry_display` Override whether to check entry display rules against filters
641
			 * @since 1.16.2
642
			 * @param bool $check_entry_display Check whether the entry is visible for the current View configuration. Default: true.
643
			 * @param array $entry Gravity Forms entry array
644
			 */
645
			$check_entry_display = apply_filters( 'gravityview/common/get_entry/check_entry_display', $check_entry_display, $entry );
646
647
			if( $check_entry_display ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
648
				// Is the entry allowed
649
				$entry = self::check_entry_display( $entry );
650
			}
651
652
			return is_wp_error( $entry ) ? false : $entry;
653
654
		}
655
656
		return false;
657
	}
658
659
	/**
660
	 * Wrapper for the GFFormsModel::matches_operation() method that adds additional comparisons, including:
661
	 * 'equals', 'greater_than_or_is', 'greater_than_or_equals', 'less_than_or_is', 'less_than_or_equals',
662
	 * and 'not_contains'
663
	 *
664
	 * @since 1.13 You can define context, which displays/hides based on what's being displayed (single, multiple, edit)
665
	 *
666
	 * @see http://docs.gravityview.co/article/252-gvlogic-shortcode
667
	 * @uses GFFormsModel::matches_operation
668
	 * @since 1.7.5
669
	 *
670
	 * @param string $val1 Left side of comparison
671
	 * @param string $val2 Right side of comparison
672
	 * @param string $operation Type of comparison
673
	 *
674
	 * @return bool True: matches, false: not matches
675
	 */
676
	public static function matches_operation( $val1, $val2, $operation ) {
677
678
		$value = false;
679
680
		if( 'context' === $val1 ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
681
682
			$matching_contexts = array( $val2 );
683
684
			// We allow for non-standard contexts.
685
			switch( $val2 ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
686
				// Check for either single or edit
687
				case 'singular':
688
					$matching_contexts = array( 'single', 'edit' );
689
					break;
690
				// Use multiple as alias for directory for consistency
691
				case 'multiple':
692
					$matching_contexts = array( 'directory' );
693
					break;
694
			}
695
696
			$val1 = in_array( gravityview_get_context(), $matching_contexts ) ? $val2 : false;
697
		}
698
699
		switch ( $operation ) {
700
			case 'equals':
701
				$value = GFFormsModel::matches_operation( $val1, $val2, 'is' );
702
				break;
703
			case 'greater_than_or_is':
704
			case 'greater_than_or_equals':
705
				$is    = GFFormsModel::matches_operation( $val1, $val2, 'is' );
706
				$gt    = GFFormsModel::matches_operation( $val1, $val2, 'greater_than' );
707
				$value = ( $is || $gt );
708
				break;
709
			case 'less_than_or_is':
710
			case 'less_than_or_equals':
711
				$is    = GFFormsModel::matches_operation( $val1, $val2, 'is' );
712
				$gt    = GFFormsModel::matches_operation( $val1, $val2, 'less_than' );
713
				$value = ( $is || $gt );
714
				break;
715
			case 'not_contains':
716
				$contains = GFFormsModel::matches_operation( $val1, $val2, 'contains' );
717
				$value    = ! $contains;
718
				break;
719
			default:
720
				$value = GFFormsModel::matches_operation( $val1, $val2, $operation );
721
		}
722
723
		return $value;
724
	}
725
726
	/**
727
	 *
728
	 * Checks if a certain entry is valid according to the View search filters (specially the Adv Filters)
729
	 *
730
	 * @see GFFormsModel::is_value_match()
731
	 *
732
	 * @since 1.7.4
733
	 * @todo Return WP_Error instead of boolean
734
	 *
735
	 * @param array $entry Gravity Forms Entry object
736
	 * @return bool|array Returns 'false' if entry is not valid according to the view search filters (Adv Filter)
737
	 */
738
	public static function check_entry_display( $entry ) {
739
740
		if ( ! $entry || is_wp_error( $entry ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $entry 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...
741
			do_action( 'gravityview_log_debug', __METHOD__ . ' Entry was not found.', $entry );
742
			return false;
743
		}
744
745
		if ( empty( $entry['form_id'] ) ) {
746
			do_action( 'gravityview_log_debug', '[apply_filters_to_entry] Entry is empty! Entry:', $entry );
747
			return false;
748
		}
749
750
		$criteria = self::calculate_get_entries_criteria();
751
752
		// Make sure the current View is connected to the same form as the Entry
753
		if( ! empty( $criteria['context_view_id'] ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
754
			$context_view_id = intval( $criteria['context_view_id'] );
755
			$context_form_id = gravityview_get_form_id( $context_view_id );
756
			if( intval( $context_form_id ) !== intval( $entry['form_id'] ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
757
				do_action( 'gravityview_log_debug', sprintf( '[apply_filters_to_entry] Entry form ID does not match current View connected form ID:', $entry['form_id'] ), $criteria['context_view_id'] );
758
				return false;
759
			}
760
		}
761
762
		if ( empty( $criteria['search_criteria'] ) || ! is_array( $criteria['search_criteria'] ) ) {
763
			do_action( 'gravityview_log_debug', '[apply_filters_to_entry] Entry approved! No search criteria found:', $criteria );
764
			return $entry;
765
		}
766
767
		$search_criteria = $criteria['search_criteria'];
768
		unset( $criteria );
769
770
		// check entry status
771
		if ( array_key_exists( 'status', $search_criteria ) && $search_criteria['status'] != $entry['status'] ) {
772
			do_action( 'gravityview_log_debug', sprintf( '[apply_filters_to_entry] Entry status - %s - is not valid according to filter:', $entry['status'] ), $search_criteria );
773
			return false;
774
		}
775
776
		// check entry date
777
		// @todo: Does it make sense to apply the Date create filters to the single entry?
778
779
		// field_filters
780
		if ( empty( $search_criteria['field_filters'] ) || ! is_array( $search_criteria['field_filters'] ) ) {
781
			do_action( 'gravityview_log_debug', '[apply_filters_to_entry] Entry approved! No field filters criteria found:', $search_criteria );
782
			return $entry;
783
		}
784
785
		$filters = $search_criteria['field_filters'];
786
		unset( $search_criteria );
787
788
		$mode = array_key_exists( 'mode', $filters ) ? strtolower( $filters['mode'] ) : 'all';
789
		unset( $filters['mode'] );
790
791
		$form = self::get_form( $entry['form_id'] );
792
793
		foreach ( $filters as $filter ) {
794
795
			if ( ! isset( $filter['key'] ) ) {
796
				continue;
797
			}
798
799
			$k = $filter['key'];
800
801
			if ( in_array( $k, array( 'created_by', 'payment_status' ) ) ) {
802
				$field_value = $entry[ $k ];
803
				$field = null;
804
			} else {
805
				$field = self::get_field( $form, $k );
806
				$field_value  = GFFormsModel::get_lead_field_value( $entry, $field );
807
			}
808
809
			$operator = isset( $filter['operator'] ) ? strtolower( $filter['operator'] ) : 'is';
810
			$is_value_match = GFFormsModel::is_value_match( $field_value, $filter['value'], $operator, $field );
811
812
			// verify if we are already free to go!
813
			if ( ! $is_value_match && 'all' === $mode ) {
814
				do_action( 'gravityview_log_debug', '[apply_filters_to_entry] Entry cannot be displayed. Failed one criteria for ALL mode', $filter );
815
				return false;
816
			} elseif ( $is_value_match && 'any' === $mode ) {
817
				return $entry;
818
			}
819
		}
820
821
		// at this point, if in ALL mode, then entry is approved - all conditions were met.
822
		// Or, for ANY mode, means none of the conditions were satisfied, so entry is not approved
823
		if ( 'all' === $mode ) {
824
			return $entry;
825
		} else {
826
			do_action( 'gravityview_log_debug', '[apply_filters_to_entry] Entry cannot be displayed. Failed all the criteria for ANY mode', $filters );
827
			return false;
828
		}
829
830
	}
831
832
833
	/**
834
	 * Allow formatting date and time based on GravityView standards
835
	 *
836
	 * @since 1.16
837
	 *
838
	 * @see GVCommon_Test::test_format_date for examples
839
	 *
840
	 * @param string $date_string The date as stored by Gravity Forms (`Y-m-d h:i:s` GMT)
841
	 * @param string|array $args Array or string of settings for output parsed by `wp_parse_args()`; Can use `raw=1` or `array('raw' => true)` \n
842
	 * - `raw` Un-formatted date string in original `Y-m-d h:i:s` format
843
	 * - `timestamp` Integer timestamp returned by GFCommon::get_local_timestamp()
844
	 * - `diff` "%s ago" format, unless other `format` is defined
845
	 * - `human` Set $is_human parameter to true for `GFCommon::format_date()`. Shows `diff` within 24 hours or date after. Format based on blog setting, unless `format` is defined.
846
	 * - `time` Include time in the `GFCommon::format_date()` output
847
	 * - `format` Define your own date format, or `diff` format
848
	 *
849
	 * @return int|null|string Formatted date based on the original date
850
	 */
851
	public static function format_date( $date_string = '', $args = array() ) {
852
853
		$default_atts = array(
854
			'raw' => false,
855
			'timestamp' => false,
856
			'diff' => false,
857
			'human' => false,
858
			'format' => '',
859
			'time' => false,
860
		);
861
862
		$atts = wp_parse_args( $args, $default_atts );
863
864
		/**
865
		 * Gravity Forms code to adjust date to locally-configured Time Zone
866
		 * @see GFCommon::format_date() for original code
867
		 */
868
		$date_gmt_time   = mysql2date( 'G', $date_string );
869
		$date_local_timestamp = GFCommon::get_local_timestamp( $date_gmt_time );
870
871
		$format  = rgar( $atts, 'format' );
872
		$is_human  = ! empty( $atts['human'] );
873
		$is_diff  = ! empty( $atts['diff'] );
874
		$is_raw = ! empty( $atts['raw'] );
875
		$is_timestamp = ! empty( $atts['timestamp'] );
876
		$include_time = ! empty( $atts['time'] );
877
878
		// If we're using time diff, we want to have a different default format
879
		if( empty( $format ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
880
			$format = $is_diff ? esc_html__( '%s ago', 'gravityview' ) : get_option( 'date_format' );
881
		}
882
883
		// If raw was specified, don't modify the stored value
884
		if ( $is_raw ) {
885
			$formatted_date = $date_string;
886
		} elseif( $is_timestamp ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
887
			$formatted_date = $date_local_timestamp;
888
		} elseif ( $is_diff ) {
889
			$formatted_date = sprintf( $format, human_time_diff( $date_gmt_time ) );
890
		} else {
891
			$formatted_date = GFCommon::format_date( $date_string, $is_human, $format, $include_time );
892
		}
893
894
		unset( $format, $is_diff, $is_human, $is_timestamp, $is_raw, $date_gmt_time, $date_local_timestamp, $default_atts );
895
896
		return $formatted_date;
897
	}
898
899
	/**
900
	 * Retrieve the label of a given field id (for a specific form)
901
	 *
902
	 * @access public
903
	 * @param array $form
904
	 * @param string $field_id
905
	 * @return string
906
	 */
907
	public static function get_field_label( $form = array(), $field_id = '' ) {
908
909
		if ( empty( $form ) || empty( $field_id ) ) {
910
			return '';
911
		}
912
913
		$field = self::get_field( $form, $field_id );
914
		return isset( $field['label'] ) ?  $field['label'] : '';
915
916
	}
917
918
919
	/**
920
	 * Returns the field details array of a specific form given the field id
921
	 *
922
	 * Alias of GFFormsModel::get_field
923
	 *
924
	 * @uses GFFormsModel::get_field
925
	 * @see GFFormsModel::get_field
926
	 * @access public
927
	 * @param array $form
928
	 * @param string|int $field_id
929
	 * @return array|null Array: Gravity Forms field array; NULL: Gravity Forms GFFormsModel does not exist
930
	 */
931
	public static function get_field( $form, $field_id ) {
932
		if ( class_exists( 'GFFormsModel' ) ){
933
			return GFFormsModel::get_field( $form, $field_id );
934
		} else {
935
			return null;
936
		}
937
	}
938
939
940
	/**
941
	 * Check whether the post is GravityView
942
	 *
943
	 * - Check post type. Is it `gravityview`?
944
	 * - Check shortcode
945
	 *
946
	 * @param  WP_Post      $post WordPress post object
947
	 * @return boolean           True: yep, GravityView; No: not!
948
	 */
949
	public static function has_gravityview_shortcode( $post = null ) {
950
		if ( ! is_a( $post, 'WP_Post' ) ) {
951
			return false;
952
		}
953
954
		if ( 'gravityview' === get_post_type( $post ) ) {
955
			return true;
956
		}
957
958
		return self::has_shortcode_r( $post->post_content, 'gravityview' ) ? true : false;
959
960
	}
961
962
963
	/**
964
	 * Placeholder until the recursive has_shortcode() patch is merged
965
	 * @see https://core.trac.wordpress.org/ticket/26343#comment:10
966
	 * @param string $content Content to check whether there's a shortcode
967
	 * @param string $tag Current shortcode tag
968
	 */
969
	public static function has_shortcode_r( $content, $tag = 'gravityview' ) {
970
		if ( false === strpos( $content, '[' ) ) {
971
			return false;
972
		}
973
974
		if ( shortcode_exists( $tag ) ) {
975
976
			$shortcodes = array();
977
978
			preg_match_all( '/' . get_shortcode_regex() . '/s', $content, $matches, PREG_SET_ORDER );
979
			if ( empty( $matches ) ){
980
				return false;
981
			}
982
983
			foreach ( $matches as $shortcode ) {
984
				if ( $tag === $shortcode[2] ) {
985
986
					// Changed this to $shortcode instead of true so we get the parsed atts.
987
					$shortcodes[] = $shortcode;
988
989
				} else if ( isset( $shortcode[5] ) && $result = self::has_shortcode_r( $shortcode[5], $tag ) ) {
990
					$shortcodes = $result;
991
				}
992
			}
993
994
			return $shortcodes;
995
		}
996
		return false;
997
	}
998
999
1000
1001
	/**
1002
	 * Get the views for a particular form
1003
	 *
1004
	 * @since 1.15.2 Add $args array and limit posts_per_page to 500
1005
	 *
1006
	 * @uses get_posts()
1007
	 *
1008
	 * @param  int $form_id Gravity Forms form ID
1009
	 * @param  array $args Pass args sent to get_posts()
1010
	 *
1011
	 * @return array          Array with view details, as returned by get_posts()
1012
	 */
1013
	public static function get_connected_views( $form_id, $args = array() ) {
1014
1015
		$defaults = array(
1016
			'post_type' => 'gravityview',
1017
			'posts_per_page' => 100,
0 ignored issues
show
introduced by
Detected high pagination limit, posts_per_page is set to 100
Loading history...
1018
			'meta_key' => '_gravityview_form_id',
0 ignored issues
show
introduced by
Detected usage of meta_key, possible slow query.
Loading history...
1019
			'meta_value' => (int)$form_id,
0 ignored issues
show
introduced by
Detected usage of meta_value, possible slow query.
Loading history...
introduced by
No space after closing casting parenthesis is prohibited
Loading history...
1020
		);
1021
1022
		$args = wp_parse_args( $args, $defaults );
1023
1024
		$views = get_posts( $args );
1025
1026
		return $views;
1027
	}
1028
1029
	/**
1030
	 * Get the Gravity Forms form ID connected to a View
1031
	 *
1032
	 * @param int $view_id The ID of the View to get the connected form of
1033
	 *
1034
	 * @return string ID of the connected Form, if exists. Empty string if not.
1035
	 */
1036
	public static function get_meta_form_id( $view_id ) {
1037
		return get_post_meta( $view_id, '_gravityview_form_id', true );
1038
	}
1039
1040
	/**
1041
	 * Get the template ID (`list`, `table`, `datatables`, `map`) for a View
1042
	 *
1043
	 * @see GravityView_Template::template_id
1044
	 *
1045
	 * @param int $view_id The ID of the View to get the layout of
1046
	 *
1047
	 * @return string GravityView_Template::template_id value. Empty string if not.
1048
	 */
1049
	public static function get_meta_template_id( $view_id ) {
1050
		return get_post_meta( $view_id, '_gravityview_directory_template', true );
1051
	}
1052
1053
1054
	/**
1055
	 * Get all the settings for a View
1056
	 *
1057
	 * @uses  GravityView_View_Data::get_default_args() Parses the settings with the plugin defaults as backups.
1058
	 * @param  int $post_id View ID
1059
	 * @return array          Associative array of settings with plugin defaults used if not set by the View
1060
	 */
1061
	public static function get_template_settings( $post_id ) {
1062
1063
		$settings = get_post_meta( $post_id, '_gravityview_template_settings', true );
1064
1065
		if ( class_exists( 'GravityView_View_Data' ) ) {
1066
1067
			$defaults = GravityView_View_Data::get_default_args();
1068
1069
			return wp_parse_args( (array)$settings, $defaults );
0 ignored issues
show
introduced by
No space after closing casting parenthesis is prohibited
Loading history...
1070
1071
		}
1072
1073
		// Backup, in case GravityView_View_Data isn't loaded yet.
1074
		return $settings;
1075
	}
1076
1077
	/**
1078
	 * Get the setting for a View
1079
	 *
1080
	 * If the setting isn't set by the View, it returns the plugin default.
1081
	 *
1082
	 * @param  int $post_id View ID
1083
	 * @param  string $key     Key for the setting
1084
	 * @return mixed|null          Setting value, or NULL if not set.
1085
	 */
1086
	public static function get_template_setting( $post_id, $key ) {
1087
1088
		$settings = self::get_template_settings( $post_id );
1089
1090
		if ( isset( $settings[ $key ] ) ) {
1091
			return $settings[ $key ];
1092
		}
1093
1094
		return null;
1095
	}
1096
1097
	/**
1098
	 * Get the field configuration for the View
1099
	 *
1100
	 * array(
1101
	 *
1102
	 * 	[other zones]
1103
	 *
1104
	 * 	'directory_list-title' => array(
1105
	 *
1106
	 *   	[other fields]
1107
	 *
1108
	 *  	'5372653f25d44' => array(
1109
	 *  		'id' => string '9' (length=1)
1110
	 *  		'label' => string 'Screenshots' (length=11)
1111
	 *			'show_label' => string '1' (length=1)
1112
	 *			'custom_label' => string '' (length=0)
1113
	 *			'custom_class' => string 'gv-gallery' (length=10)
1114
	 * 			'only_loggedin' => string '0' (length=1)
1115
	 *			'only_loggedin_cap' => string 'read' (length=4)
1116
	 *  	)
1117
	 *
1118
	 * 		[other fields]
1119
	 *  )
1120
	 *
1121
	 * 	[other zones]
1122
	 * )
1123
	 *
1124
	 * @param  int $post_id View ID
1125
	 * @return array          Multi-array of fields with first level being the field zones. See code comment.
1126
	 */
1127
	public static function get_directory_fields( $post_id ) {
1128
		return get_post_meta( $post_id, '_gravityview_directory_fields', true );
1129
	}
1130
1131
1132
	/**
1133
	 * Render dropdown (select) with the list of sortable fields from a form ID
1134
	 *
1135
	 * @access public
1136
	 * @param  int $formid Form ID
1137
	 * @return string         html
1138
	 */
1139
	public static function get_sortable_fields( $formid, $current = '' ) {
1140
		$output = '<option value="" ' . selected( '', $current, false ).'>' . esc_html__( 'Default', 'gravityview' ) .'</option>';
1141
1142
		if ( empty( $formid ) ) {
1143
			return $output;
1144
		}
1145
1146
		$fields = self::get_sortable_fields_array( $formid );
1147
1148
		if ( ! empty( $fields ) ) {
1149
1150
			$blacklist_field_types = apply_filters( 'gravityview_blacklist_field_types', array( 'list', 'textarea' ), null );
1151
1152
			foreach ( $fields as $id => $field ) {
1153
				if ( in_array( $field['type'], $blacklist_field_types ) ) {
1154
					continue;
1155
				}
1156
1157
				$output .= '<option value="'. $id .'" '. selected( $id, $current, false ).'>'. esc_attr( $field['label'] ) .'</option>';
1158
			}
1159
		}
1160
1161
		return $output;
1162
	}
1163
1164
	/**
1165
	 *
1166
	 * @param int $formid Gravity Forms form ID
1167
	 * @param array $blacklist Field types to exclude
1168
	 *
1169
	 * @since TODO
1170
	 *
1171
	 * @todo Get all fields, check if sortable dynamically
1172
	 *
1173
	 * @return array
1174
	 */
1175
	public static function get_sortable_fields_array( $formid, $blacklist = array( 'list', 'textarea' ) ) {
1176
1177
		// Get fields with sub-inputs and no parent
1178
		$fields = self::get_form_fields( $formid, true, false );
1179
1180
		$date_created = array(
1181
			'date_created' => array(
1182
				'type' => 'date_created',
1183
				'label' => __( 'Date Created', 'gravityview' ),
1184
			),
1185
		);
1186
1187
        $fields = $date_created + $fields;
1188
1189
		$blacklist_field_types = apply_filters( 'gravityview_blacklist_field_types', $blacklist, NULL );
0 ignored issues
show
Coding Style introduced by
TRUE, FALSE and NULL must be lowercase; expected null, but found NULL.
Loading history...
1190
1191
		// TODO: Convert to using array_filter
1192
		foreach( $fields as $id => $field ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
1193
1194
			if( in_array( $field['type'], $blacklist_field_types ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
1195
				unset( $fields[ $id ] );
1196
			}
1197
		}
1198
1199
        /**
1200
         * @filter `gravityview/common/sortable_fields` Filter the sortable fields
1201
         * @since 1.12
1202
         * @param array $fields Sub-set of GF form fields that are sortable
1203
         * @param int $formid The Gravity Forms form ID that the fields are from
1204
         */
1205
        $fields = apply_filters( 'gravityview/common/sortable_fields', $fields, $formid );
1206
1207
		return $fields;
1208
	}
1209
1210
	/**
1211
	 * Returns the GF Form field type for a certain field(id) of a form
1212
	 * @param  object $form     Gravity Forms form
1213
	 * @param  mixed $field_id Field ID or Field array
1214
	 * @return string field type
1215
	 */
1216
	public static function get_field_type( $form = null, $field_id = '' ) {
1217
1218
		if ( ! empty( $field_id ) && ! is_array( $field_id ) ) {
1219
			$field = self::get_field( $form, $field_id );
1220
		} else {
1221
			$field = $field_id;
1222
		}
1223
1224
		return class_exists( 'RGFormsModel' ) ? RGFormsModel::get_input_type( $field ) : '';
1225
1226
	}
1227
1228
1229
	/**
1230
	 * Checks if the field type is a 'numeric' field type (e.g. to be used when sorting)
1231
	 * @param  int|array  $form  form ID or form array
1232
	 * @param  int|array  $field field key or field array
1233
	 * @return boolean
1234
	 */
1235
	public static function is_field_numeric(  $form = null, $field = '' ) {
1236
1237
		if ( ! is_array( $form ) && ! is_array( $field ) ) {
1238
			$form = self::get_form( $form );
1239
		}
1240
1241
		// If entry meta, it's a string. Otherwise, numeric
1242
		if( ! is_numeric( $field ) && is_string( $field ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
1243
			$type = $field;
1244
		} else {
1245
			$type = self::get_field_type( $form, $field );
1246
		}
1247
1248
		/**
1249
		 * @filter `gravityview/common/numeric_types` What types of fields are numeric?
1250
		 * @since 1.5.2
1251
		 * @param array $numeric_types Fields that are numeric. Default: `[ number, time ]`
1252
		 */
1253
		$numeric_types = apply_filters( 'gravityview/common/numeric_types', array( 'number', 'time' ) );
1254
1255
		// Defer to GravityView_Field setting, if the field type is registered and `is_numeric` is true
1256
		if( $gv_field = GravityView_Fields::get( $type ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
1257
			if( true === $gv_field->is_numeric ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
1258
				$numeric_types[] = $gv_field->is_numeric;
1259
			}
1260
		}
1261
1262
		$return = in_array( $type, $numeric_types );
1263
1264
		return $return;
1265
	}
1266
1267
	/**
1268
	 * Encrypt content using Javascript so that it's hidden when JS is disabled.
1269
	 *
1270
	 * This is mostly used to hide email addresses from scraper bots.
1271
	 *
1272
	 * @param string $content Content to encrypt
1273
	 * @param string $message Message shown if Javascript is disabled
1274
	 *
1275
	 * @see  https://github.com/jnicol/standalone-phpenkoder StandalonePHPEnkoder on Github
1276
	 *
1277
	 * @since 1.7
1278
	 *
1279
	 * @return string Content, encrypted
1280
	 */
1281
	public static function js_encrypt( $content, $message = '' ) {
1282
1283
		$output = $content;
1284
1285
		if ( ! class_exists( 'StandalonePHPEnkoder' ) ) {
1286
			include_once( GRAVITYVIEW_DIR . 'includes/lib/standalone-phpenkoder/StandalonePHPEnkoder.php' );
1287
		}
1288
1289
		if ( class_exists( 'StandalonePHPEnkoder' ) ) {
1290
1291
			$enkoder = new StandalonePHPEnkoder;
1292
1293
			$message = empty( $message ) ? __( 'Email hidden; Javascript is required.', 'gravityview' ) : $message;
1294
1295
			/**
1296
			 * @filter `gravityview/phpenkoder/msg` Modify the message shown when Javascript is disabled and an encrypted email field is displayed
1297
			 * @since 1.7
1298
			 * @param string $message Existing message
1299
			 * @param string $content Content to encrypt
1300
			 */
1301
			$enkoder->enkode_msg = apply_filters( 'gravityview/phpenkoder/msg', $message, $content );
1302
1303
			$output = $enkoder->enkode( $content );
1304
		}
1305
1306
		return $output;
1307
	}
1308
1309
	/**
1310
	 *
1311
	 * Do the same than parse_str without max_input_vars limitation:
1312
	 * Parses $string as if it were the query string passed via a URL and sets variables in the current scope.
1313
	 * @param $string array string to parse (not altered like in the original parse_str(), use the second parameter!)
1314
	 * @param $result array  If the second parameter is present, variables are stored in this variable as array elements
1315
	 * @return bool true or false if $string is an empty string
1316
	 * @since  1.5.3
1317
	 *
1318
	 * @author rubo77 at https://gist.github.com/rubo77/6821632
1319
	 **/
1320
	public static function gv_parse_str( $string, &$result ) {
1321
		if ( empty( $string ) ) {
1322
			return false;
1323
		}
1324
1325
		$result = array();
1326
1327
		// find the pairs "name=value"
1328
		$pairs = explode( '&', $string );
1329
1330
		foreach ( $pairs as $pair ) {
1331
			// use the original parse_str() on each element
1332
			parse_str( $pair, $params );
1333
1334
			$k = key( $params );
1335
			if ( ! isset( $result[ $k ] ) ) {
1336
				$result += $params;
1337
			} elseif ( array_key_exists( $k, $params ) && is_array( $params[ $k ] ) ) {
1338
				$result[ $k ] = self::array_merge_recursive_distinct( $result[ $k ], $params[ $k ] );
1339
			}
1340
		}
1341
		return true;
1342
	}
1343
1344
1345
	/**
1346
	 * Generate an HTML anchor tag with a list of supported attributes
1347
	 *
1348
	 * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a Supported attributes defined here
1349
	 * @uses esc_url_raw() to sanitize $href
1350
	 * @uses esc_attr() to sanitize $atts
1351
	 *
1352
	 * @since 1.6
1353
	 *
1354
	 * @param string $href URL of the link. Sanitized using `esc_url_raw()`
1355
	 * @param string $anchor_text The text or HTML inside the anchor. This is not sanitized in the function.
1356
	 * @param array|string $atts Attributes to be added to the anchor tag. Parsed by `wp_parse_args()`, sanitized using `esc_attr()`
1357
	 *
1358
	 * @return string HTML output of anchor link. If empty $href, returns NULL
1359
	 */
1360
	public static function get_link_html( $href = '', $anchor_text = '', $atts = array() ) {
1361
1362
		// Supported attributes for anchor tags. HREF left out intentionally.
1363
		$allowed_atts = array(
1364
			'href' => null, // Will override the $href argument if set
1365
			'title' => null,
1366
			'rel' => null,
1367
			'id' => null,
1368
			'class' => null,
1369
			'target' => null,
1370
			'style' => null,
1371
1372
			// Used by GravityView
1373
			'data-viewid' => null,
1374
1375
			// Not standard
1376
			'hreflang' => null,
1377
			'type' => null,
1378
			'tabindex' => null,
1379
1380
			// Deprecated HTML4 but still used
1381
			'name' => null,
1382
			'onclick' => null,
1383
			'onchange' => null,
1384
			'onkeyup' => null,
1385
1386
			// HTML5 only
1387
			'download' => null,
1388
			'media' => null,
1389
			'ping' => null,
1390
		);
1391
1392
		/**
1393
		 * @filter `gravityview/get_link/allowed_atts` Modify the attributes that are allowed to be used in generating links
1394
		 * @param array $allowed_atts Array of attributes allowed
1395
		 */
1396
		$allowed_atts = apply_filters( 'gravityview/get_link/allowed_atts', $allowed_atts );
1397
1398
		// Make sure the attributes are formatted as array
1399
		$passed_atts = wp_parse_args( $atts );
1400
1401
		// Make sure the allowed attributes are only the ones in the $allowed_atts list
1402
		$final_atts = shortcode_atts( $allowed_atts, $passed_atts );
1403
1404
		// Remove attributes with empty values
1405
		$final_atts = array_filter( $final_atts );
1406
1407
		// If the href wasn't passed as an attribute, use the value passed to the function
1408
		if ( empty( $final_atts['href'] ) && ! empty( $href ) ) {
1409
			$final_atts['href'] = $href;
1410
		}
1411
1412
		$final_atts['href'] = esc_url_raw( $href );
1413
1414
		// For each attribute, generate the code
1415
		$output = '';
1416
		foreach ( $final_atts as $attr => $value ) {
1417
			$output .= sprintf( ' %s="%s"', $attr, esc_attr( $value ) );
1418
		}
1419
1420
		$output = '<a'. $output .'>'. $anchor_text .'</a>';
1421
1422
		return $output;
1423
	}
1424
1425
	/**
1426
	 * array_merge_recursive does indeed merge arrays, but it converts values with duplicate
1427
	 * keys to arrays rather than overwriting the value in the first array with the duplicate
1428
	 * value in the second array, as array_merge does.
1429
	 *
1430
	 * @see http://php.net/manual/en/function.array-merge-recursive.php
1431
	 *
1432
	 * @since  1.5.3
1433
	 * @param array $array1
1434
	 * @param array $array2
1435
	 * @return array
1436
	 * @author Daniel <daniel (at) danielsmedegaardbuus (dot) dk>
1437
	 * @author Gabriel Sobrinho <gabriel (dot) sobrinho (at) gmail (dot) com>
1438
	 */
1439
	public static function array_merge_recursive_distinct( array &$array1, array &$array2 ) {
1440
		$merged = $array1;
1441
1442
		foreach ( $array2 as $key => &$value )  {
1443
			if ( is_array( $value ) && isset( $merged[ $key ] ) && is_array( $merged[ $key ] ) ) {
1444
				$merged[ $key ] = self::array_merge_recursive_distinct( $merged[ $key ], $value );
1445
			} else {
1446
				$merged[ $key ] = $value;
1447
			}
1448
		}
1449
1450
		return $merged;
1451
	}
1452
1453
	/**
1454
	 * Get WordPress users with reasonable limits set
1455
	 *
1456
	 * @param string $context Where are we using this information (e.g. change_entry_creator, search_widget ..)
1457
	 * @param array $args Arguments to modify the user query. See get_users() {@since 1.14}
1458
	 * @return array Array of WP_User objects.
1459
	 */
1460
	public static function get_users( $context = 'change_entry_creator', $args = array() ) {
1461
1462
		$default_args = array(
1463
			'number' => 2000,
1464
			'orderby' => 'display_name',
1465
			'order' => 'ASC',
1466
			'fields' => array( 'ID', 'display_name', 'user_login', 'user_nicename' )
1467
		);
1468
1469
		// Merge in the passed arg
1470
		$get_users_settings = wp_parse_args( $args, $default_args );
1471
1472
		/**
1473
		 * @filter `gravityview/get_users/{$context}` There are issues with too many users using [get_users()](http://codex.wordpress.org/Function_Reference/get_users) where it breaks the select. We try to keep it at a reasonable number. \n
1474
		 * `$context` is where are we using this information (e.g. change_entry_creator, search_widget ..)
1475
		 * @param array $settings Settings array, with `number` key defining the # of users to display
1476
		 */
1477
		$get_users_settings = apply_filters( 'gravityview/get_users/'. $context, apply_filters( 'gravityview_change_entry_creator_user_parameters', $get_users_settings ) );
1478
1479
		return get_users( $get_users_settings );
1480
	}
1481
1482
1483
    /**
1484
     * Display updated/error notice
1485
     *
1486
     * @param string $notice text/HTML of notice
1487
     * @param string $class CSS class for notice (`updated` or `error`)
1488
     *
1489
     * @return string
1490
     */
1491
    public static function generate_notice( $notice, $class = '' ) {
1492
        return '<div class="gv-notice '.gravityview_sanitize_html_class( $class ) .'">'. $notice .'</div>';
1493
    }
1494
1495
1496
1497
1498
} //end class
1499
1500
1501
/**
1502
 * Generate an HTML anchor tag with a list of supported attributes
1503
 *
1504
 * @see GVCommon::get_link_html()
1505
 *
1506
 * @since 1.6
1507
 *
1508
 * @param string $href URL of the link.
1509
 * @param string $anchor_text The text or HTML inside the anchor. This is not sanitized in the function.
1510
 * @param array|string $atts Attributes to be added to the anchor tag
1511
 *
1512
 * @return string HTML output of anchor link. If empty $href, returns NULL
1513
 */
1514
function gravityview_get_link( $href = '', $anchor_text = '', $atts = array() ) {
1515
	return GVCommon::get_link_html( $href, $anchor_text, $atts );
1516
}
1517