Completed
Push — develop ( ccc9af...aa0816 )
by Zack
06:53
created

GVCommon   F

Complexity

Total Complexity 234

Size/Duplication

Total Lines 1786
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 12

Test Coverage

Coverage 58.4%

Importance

Changes 0
Metric Value
dl 0
loc 1786
ccs 316
cts 541
cp 0.584
rs 0.8
c 0
b 0
f 0
wmc 234
lcom 2
cbo 12

43 Methods

Rating   Name   Duplication   Size   Complexity  
A get_form() 0 16 4
A has_cap() 0 3 1
A get_field_array() 0 15 2
A get_all_views() 0 20 1
A get_form_from_entry_id() 0 12 2
A get_product_field_types() 0 6 1
A has_product_field() 0 8 2
A get_entry_id_from_slug() 0 32 3
B entry_has_transaction_data() 0 26 6
A get_forms() 0 18 3
F get_form_fields() 0 99 19
A get_entry_meta() 0 14 4
A get_entry_ids() 0 8 2
D calculate_get_entries_criteria() 0 83 19
C get_entries() 0 84 12
A get_entry_id() 0 36 5
B get_entry() 0 42 7
F matches_operation() 0 99 33
C check_entry_display() 0 98 14
B format_date() 0 48 6
A get_field_label() 0 16 4
A get_field() 0 12 3
A has_gravityview_shortcode() 0 12 4
B has_shortcode_r() 0 31 9
B get_connected_views() 0 42 8
A get_meta_form_id() 0 3 1
A get_meta_template_id() 0 3 1
A get_template_settings() 0 13 2
A get_template_setting() 0 10 2
A get_directory_fields() 0 25 2
A get_directory_widgets() 0 20 2
A get_sortable_fields() 0 24 5
B get_sortable_fields_array() 0 50 5
A get_field_type() 0 11 4
B is_field_numeric() 0 31 7
A js_encrypt() 0 27 4
B gv_parse_str() 0 23 6
B get_link_html() 0 77 6
B array_merge_recursive_distinct() 0 14 7
A get_users() 0 21 1
A generate_notice() 0 9 3
A decode_shortcodes() 0 7 1
A send_email() 0 11 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
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 array|false Array: Form object returned from Gravity Forms; False: no form ID specified or Gravity Forms isn't active.
27
	 */
28 32
	public static function get_form( $form_id ) {
29 32
		if ( empty( $form_id ) ) {
30
			return false;
31
		}
32
33
		// Only get_form_meta is cached. ::facepalm::
34 32
		if ( class_exists( 'GFFormsModel' ) ) {
35 32
			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 48
	public static function has_cap( $caps = '', $object_id = null, $user_id = null ) {
59 48
		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 Added $args array
90
	 *
91
	 * @param array $args Pass custom array of args, formatted as if for `get_posts()`
92
	 *
93
	 * @return WP_Post[] Array of Views as `WP_Post`. Empty array if none found.
94
	 */
95 3
	public static function get_all_views( $args = array() ) {
96
97
		$default_params = array(
98 3
			'post_type' => 'gravityview',
99
			'posts_per_page' => -1,
100
			'post_status' => 'publish',
101
		);
102
103 3
		$params = wp_parse_args( $args, $default_params );
104
105
		/**
106
		 * @filter `gravityview/get_all_views/params` Modify the parameters sent to get all views.
107
		 * @param[in,out]  array $params Array of parameters to pass to `get_posts()`
108
		 */
109 3
		$views_params = apply_filters( 'gravityview/get_all_views/params', $params );
110
111 3
		$views = get_posts( $views_params );
112
113 3
		return $views;
114
	}
115
116
117
	/**
118
	 * Get the form array for an entry based only on the entry ID
119
	 * @param  int|string $entry_slug Entry slug
120
	 * @return array|false Array: Form object returned from Gravity Forms; False: form doesn't exist, or $entry didn't exist or $entry didn't specify form ID
121
	 */
122
	public static function get_form_from_entry_id( $entry_slug ) {
123
124
		$entry = self::get_entry( $entry_slug, true, false );
125
126
		$form = false;
127
128
		if( $entry ) {
129
			$form = GFAPI::get_form( $entry['form_id'] );
130
		}
131
132
		return $form;
133
	}
134
135
	/**
136
	 * Check whether a form has product fields
137
	 *
138
	 * @since 1.16
139
	 * @since 1.20 Refactored the field types to get_product_field_types() method
140
	 *
141
	 * @param array $form Gravity Forms form array
142
	 *
143
	 * @return bool|GF_Field[]
144
	 */
145 6
	public static function has_product_field( $form = array() ) {
146
147 6
		$product_fields = self::get_product_field_types();
148
149 6
		$fields = GFAPI::get_fields_by_type( $form, $product_fields );
150
151 6
		return empty( $fields ) ? false : $fields;
152
	}
153
154
	/**
155
	 * Return array of product field types
156
	 *
157
	 * Modify the value using the `gform_product_field_types` filter
158
	 *
159
	 * @since 1.20
160
	 *
161
	 * @return array
162
	 */
163 23
	public static function get_product_field_types() {
164
165 23
		$product_fields = apply_filters( 'gform_product_field_types', array( 'option', 'quantity', 'product', 'total', 'shipping', 'calculation', 'price', 'hiddenproduct', 'singleproduct', 'singleshipping' ) );
166
167 23
		return $product_fields;
168
	}
169
170
	/**
171
	 * Check if an entry has transaction data
172
	 *
173
	 * Checks the following keys to see if they are set: 'payment_status', 'payment_date', 'transaction_id', 'payment_amount', 'payment_method'
174
	 *
175
	 * @since 1.20
176
	 *
177
	 * @param array $entry Gravity Forms entry array
178
	 *
179
	 * @return bool True: Entry has metadata suggesting it has communicated with a payment gateway; False: it does not have that data.
180
	 */
181 23
	public static function entry_has_transaction_data( $entry = array() ) {
182
183 23
		if ( ! is_array( $entry ) ) {
184 1
			return false;
185
		}
186
187 23
		$has_transaction_data = false;
188
189 23
		$payment_meta = array( 'payment_status', 'payment_date', 'transaction_id', 'payment_amount', 'payment_method' );
190
191 23
		foreach ( $payment_meta as $meta ) {
192
193 23
			$has_transaction_data = \GV\Utils::get( $entry, $meta, false );
194
195 23
			if ( is_numeric( $has_transaction_data ) && ( ! floatval( $has_transaction_data ) > 0 ) ) {
196 3
				$has_transaction_data = false;
197 3
				continue;
198
			}
199
200 23
			if ( ! empty( $has_transaction_data ) ) {
201 17
				break;
202
			}
203
		}
204
205 23
		return (bool) $has_transaction_data;
206
	}
207
208
	/**
209
	 * Get the entry ID from the entry slug, which may or may not be the entry ID
210
	 *
211
	 * @since  1.5.2
212
	 * @param  string $slug The entry slug, as returned by GravityView_API::get_entry_slug()
213
	 * @return int|null       The entry ID, if exists; `NULL` if not
214
	 */
215
	public static function get_entry_id_from_slug( $slug ) {
216
		global $wpdb;
217
218
		$search_criteria = array(
219
			'field_filters' => array(
220
				array(
221
					'key' => 'gravityview_unique_id', // Search the meta values
222
					'value' => $slug,
223
					'operator' => 'is',
224
					'type' => 'meta',
225
				),
226
			)
227
		);
228
229
		// Limit to one for speed
230
		$paging = array(
231
			'page_size' => 1,
232
		);
233
234
		/**
235
		 * @filter `gravityview/common/get_entry_id_from_slug/form_id` The form ID used to get the custom entry ID. Change this to avoid collisions with data from other forms with the same values and the same field ID.
236
		 * @since 1.17.2
237
		 * @param int $form_id ID of the form to search. Default: `0` (searches all forms)
238
		 */
239
		$form_id = apply_filters( 'gravityview/common/get_entry_id_from_slug/form_id', 0 );
240
241
		$results = GFAPI::get_entries( intval( $form_id ), $search_criteria, null, $paging );
242
243
		$result = ( ! empty( $results ) && ! empty( $results[0]['id'] ) ) ? $results[0]['id'] : null;
244
245
		return $result;
246
	}
247
248
	/**
249
	 * Alias of GFAPI::get_forms()
250
	 *
251
	 * @see GFAPI::get_forms()
252
	 *
253
	 * @since 1.19 Allow "any" $active status option
254
	 * @since 2.7.2 Allow sorting forms using wp_list_sort()
255
	 *
256
	 * @param bool|string $active Status of forms. Use `any` to get array of forms with any status. Default: `true`
257
	 * @param bool $trash Include forms in trash? Default: `false`
258
	 * @param string|array $order_by Optional. Either the field name to order by or an array of multiple orderby fields as $orderby => $order.
259
	 * @param string $order Optional. Either 'ASC' or 'DESC'. Only used if $orderby is a string.
260
	 *
261
	 * @return array Empty array if GFAPI class isn't available or no forms. Otherwise, the array of Forms
262
	 */
263 1
	public static function get_forms(  $active = true, $trash = false, $order_by = 'date_created', $order = 'ASC' ) {
264 1
		$forms = array();
265 1
		if ( ! class_exists( 'GFAPI' ) ) {
266
			return array();
267
		}
268
269 1
		if( 'any' === $active ) {
270
			$active_forms = GFAPI::get_forms( true, $trash );
271
			$inactive_forms = GFAPI::get_forms( false, $trash );
272
			$forms = array_merge( array_filter( $active_forms ), array_filter( $inactive_forms ) );
273
		} else {
274 1
			$forms = GFAPI::get_forms( $active, $trash );
275
		}
276
277 1
		$forms = wp_list_sort( $forms, $order_by, $order, true );
278
279 1
		return $forms;
280
	}
281
282
	/**
283
	 * Return array of fields' id and label, for a given Form ID
284
	 *
285
	 * @access public
286
	 * @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...
287
	 * @param bool $add_default_properties
288
	 * @param bool $include_parent_field
289
	 * @return array
290
	 */
291
	public static function get_form_fields( $form = '', $add_default_properties = false, $include_parent_field = true ) {
292
293
		if ( ! is_array( $form ) ) {
294
			$form = self::get_form( $form );
295
		}
296
297
		$fields = array();
298
		$has_product_fields = false;
299
		$has_post_fields = false;
300
301
		if ( $form ) {
302
			foreach ( $form['fields'] as $field ) {
303
				if ( $include_parent_field || empty( $field['inputs'] ) ) {
304
					$fields["{$field['id']}"] = array(
305
						'label' => \GV\Utils::get( $field, 'label' ),
306
						'parent' => null,
307
						'type' => \GV\Utils::get( $field, 'type' ),
308
						'adminLabel' => \GV\Utils::get( $field, 'adminLabel' ),
309
						'adminOnly' => \GV\Utils::get( $field, 'adminOnly' ),
310
					);
311
				}
312
313
				if ( $add_default_properties && ! empty( $field['inputs'] ) ) {
314
					foreach ( $field['inputs'] as $input ) {
315
316
						if( ! empty( $input['isHidden'] ) ) {
317
							continue;
318
						}
319
320
						/**
321
                         * @hack
322
                         * In case of email/email confirmation, the input for email has the same id as the parent field
323
                         */
324
						if( 'email' === $field['type'] && false === strpos( $input['id'], '.' ) ) {
325
                            continue;
326
                        }
327
						$fields["{$input['id']}"] = array(
328
							'label' => \GV\Utils::get( $input, 'label' ),
329
							'customLabel' => \GV\Utils::get( $input, 'customLabel' ),
330
							'parent' => $field,
331
							'type' => \GV\Utils::get( $field, 'type' ),
332
							'adminLabel' => \GV\Utils::get( $field, 'adminLabel' ),
333
							'adminOnly' => \GV\Utils::get( $field, 'adminOnly' ),
334
						);
335
					}
336
				}
337
338
339
				if( GFCommon::is_product_field( $field['type'] ) ){
340
					$has_product_fields = true;
341
				}
342
343
				if ( GFCommon::is_post_field( $field ) ) {
344
					$has_post_fields = true;
345
				}
346
			}
347
		}
348
349
		/**
350
		 * @since 1.7
351
		 */
352
		if ( $has_post_fields ) {
353
			$fields['post_id'] = array(
354
				'label' => __( 'Post ID', 'gravityview' ),
355
				'type' => 'post_id',
356
			);
357
		}
358
359
		if ( $has_product_fields ) {
360
361
			$payment_fields = GravityView_Fields::get_all( 'pricing' );
362
363
			foreach ( $payment_fields as $payment_field ) {
364
365
				// Either the field exists ($fields['shipping']) or the form explicitly contains a `shipping` field with numeric key
366
				if( isset( $fields["{$payment_field->name}"] ) || GFCommon::get_fields_by_type( $form, $payment_field->name ) ) {
367
					continue;
368
				}
369
370
				$fields["{$payment_field->name}"] = array(
371
					'label' => $payment_field->label,
372
					'desc' => $payment_field->description,
373
					'type' => $payment_field->name,
374
				);
375
			}
376
		}
377
378
		/**
379
		 * @filter `gravityview/common/get_form_fields` Modify the form fields shown in the Add Field field picker.
380
		 * @since 1.17
381
		 * @param array $fields Associative array of fields, with keys as field type, values an array with the following keys: (string) `label` (required), (string) `type` (required), `desc`, (string) `customLabel`, (GF_Field) `parent`, (string) `adminLabel`, (bool)`adminOnly`
382
		 * @param array $form GF Form array
383
		 * @param bool $include_parent_field Whether to include the parent field when getting a field with inputs
384
		 */
385
		$fields = apply_filters( 'gravityview/common/get_form_fields', $fields, $form, $include_parent_field );
386
387
		return $fields;
388
389
	}
390
391
	/**
392
	 * get extra fields from entry meta
393
	 * @param  string $form_id (default: '')
394
	 * @return array
395
	 */
396
	public static function get_entry_meta( $form_id, $only_default_column = true ) {
397
398
		$extra_fields = GFFormsModel::get_entry_meta( $form_id );
399
400
		$fields = array();
401
402
		foreach ( $extra_fields as $key => $field ){
403
			if ( ! empty( $only_default_column ) && ! empty( $field['is_default_column'] ) ) {
404
				$fields[ $key ] = array( 'label' => $field['label'], 'type' => 'entry_meta' );
405
			}
406
		}
407
408
		return $fields;
409
	}
410
411
412
	/**
413
	 * Wrapper for the Gravity Forms GFFormsModel::search_lead_ids() method
414
	 *
415
	 * @see  GFEntryList::leads_page()
416
	 * @param  int $form_id ID of the Gravity Forms form
417
	 * @since  1.1.6
418
	 * @return array|void          Array of entry IDs. Void if Gravity Forms isn't active.
419
	 */
420
	public static function get_entry_ids( $form_id, $search_criteria = array() ) {
421
422
		if ( ! class_exists( 'GFFormsModel' ) ) {
423
			return;
424
		}
425
426
		return GFFormsModel::search_lead_ids( $form_id, $search_criteria );
427
	}
428
429
	/**
430
	 * Calculates the Search Criteria used on the self::get_entries / self::get_entry methods
431
	 *
432
	 * @since 1.7.4
433
	 *
434
	 * @param array $passed_criteria array Input Criteria (search_criteria, sorting, paging)
435
	 * @param array $form_ids array Gravity Forms form IDs
436
	 * @return array
437
	 */
438 73
	public static function calculate_get_entries_criteria( $passed_criteria = array(), $form_ids = array() ) {
439
440
		$search_criteria_defaults = array(
441 73
			'search_criteria' => null,
442
			'sorting' => null,
443
			'paging' => null,
444 73
			'cache' => (isset( $passed_criteria['cache'] ) ? (bool) $passed_criteria['cache'] : true),
445
			'context_view_id' => null,
446
		);
447
448 73
		$criteria = wp_parse_args( $passed_criteria, $search_criteria_defaults );
449
450 73
		if ( ! empty( $criteria['search_criteria']['field_filters'] ) && is_array( $criteria['search_criteria']['field_filters'] ) ) {
451 16
			foreach ( $criteria['search_criteria']['field_filters'] as &$filter ) {
452
453 16
				if ( ! is_array( $filter ) ) {
454 15
					continue;
455
				}
456
457
				// By default, we want searches to be wildcard for each field.
458 16
				$filter['operator'] = empty( $filter['operator'] ) ? 'contains' : $filter['operator'];
459
460
				/**
461
				 * @filter `gravityview_search_operator` Modify the search operator for the field (contains, is, isnot, etc)
462
				 * @param string $operator Existing search operator
463
				 * @param array $filter array with `key`, `value`, `operator`, `type` keys
464
				 */
465 16
				$filter['operator'] = apply_filters( 'gravityview_search_operator', $filter['operator'], $filter );
466
			}
467
468
			// don't send just the [mode] without any field filter.
469 16
			if( count( $criteria['search_criteria']['field_filters'] ) === 1 && array_key_exists( 'mode' , $criteria['search_criteria']['field_filters'] ) ) {
470 1
				unset( $criteria['search_criteria']['field_filters']['mode'] );
471
			}
472
473
		}
474
475
476
477
		/**
478
		 * Prepare date formats to be in Gravity Forms DB format;
479
		 * $passed_criteria may include date formats incompatible with Gravity Forms.
480
		 */
481 73
		foreach ( array('start_date', 'end_date' ) as $key ) {
482
483 73
			if ( ! empty( $criteria['search_criteria'][ $key ] ) ) {
484
485
				// Use date_create instead of new DateTime so it returns false if invalid date format.
486 2
				$date = date_create( $criteria['search_criteria'][ $key ] );
487
488 2
				if ( $date ) {
489
					// Gravity Forms wants dates in the `Y-m-d H:i:s` format.
490 2
					$criteria['search_criteria'][ $key ] = $date->format( 'Y-m-d H:i:s' );
491
				} else {
492 1
					gravityview()->log->error( '{key} Date format not valid:', array( 'key' => $key, $criteria['search_criteria'][ $key ] ) );
493
494
					// If it's an invalid date, unset it. Gravity Forms freaks out otherwise.
495 1
					unset( $criteria['search_criteria'][ $key ] );
496
				}
497
			}
498
		}
499
500 73
		if ( empty( $criteria['context_view_id'] ) ) {
501
			// Calculate the context view id and send it to the advanced filter
502 2
			if ( GravityView_frontend::getInstance()->getSingleEntry() ) {
503 1
				$criteria['context_view_id'] = GravityView_frontend::getInstance()->get_context_view_id();
504 2
			} else if ( class_exists( 'GravityView_View_Data' ) && GravityView_View_Data::getInstance() && GravityView_View_Data::getInstance()->has_multiple_views() ) {
0 ignored issues
show
Deprecated Code introduced by
The method GravityView_View_Data::has_multiple_views() has been deprecated.

This method has been deprecated.

Loading history...
505 1
				$criteria['context_view_id'] = GravityView_frontend::getInstance()->get_context_view_id();
506 2
			} else if ( 'delete' === GFForms::get( 'action' ) ) {
507 1
				$criteria['context_view_id'] = isset( $_GET['view_id'] ) ? intval( $_GET['view_id'] ) : null;
508
			}
509
		}
510
511
		/**
512
		 * @filter `gravityview_search_criteria` Apply final criteria filter (Used by the Advanced Filter extension)
513
		 * @param array $criteria Search criteria used by GravityView
514
		 * @param array $form_ids Forms to search
515
		 * @param int $view_id ID of the view being used to search
516
		 */
517 73
		$criteria = apply_filters( 'gravityview_search_criteria', $criteria, $form_ids, $criteria['context_view_id'] );
518
519 73
		return (array)$criteria;
520
	}
521
522
523
	/**
524
	 * Retrieve entries given search, sort, paging criteria
525
	 *
526
	 * @see  GFAPI::get_entries()
527
	 * @see GFFormsModel::get_field_filters_where()
528
	 * @access public
529
	 * @param int|array $form_ids The ID of the form or an array IDs of the Forms. Zero for all forms.
530
	 * @param mixed $passed_criteria (default: null)
531
	 * @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)
532
	 *
533
	 * @deprecated See \GV\View::get_entries.
534
	 *
535
	 * @return mixed False: Error fetching entries. Array: Multi-dimensional array of Gravity Forms entry arrays
536
	 */
537
	public static function get_entries( $form_ids = null, $passed_criteria = null, &$total = null ) {
538
539
		gravityview()->log->notice( '\GVCommon::get_entries is deprecated. Use \GV\View::get_entries instead.' );
540
541
		// Filter the criteria before query (includes Adv Filter)
542
		$criteria = self::calculate_get_entries_criteria( $passed_criteria, $form_ids );
543
544
		gravityview()->log->debug( '[gravityview_get_entries] Final Parameters', array( 'data' => $criteria ) );
545
546
		// Return value
547
		$return = null;
548
549
		/** Reduce # of database calls */
550
		add_filter( 'gform_is_encrypted_field', '__return_false' );
551
552
		if ( ! empty( $criteria['cache'] ) ) {
553
554
			$Cache = new GravityView_Cache( $form_ids, $criteria );
555
556
			if ( $entries = $Cache->get() ) {
557
558
				// Still update the total count when using cached results
559
				if ( ! is_null( $total ) ) {
560
					$total = GFAPI::count_entries( $form_ids, $criteria['search_criteria'] );
561
				}
562
563
				$return = $entries;
564
			}
565
		}
566
567
		if ( is_null( $return ) && class_exists( 'GFAPI' ) && ( is_numeric( $form_ids ) || is_array( $form_ids ) ) ) {
568
569
			/**
570
			 * @filter `gravityview_pre_get_entries` Define entries to be used before GFAPI::get_entries() is called
571
			 * @since 1.14
572
			 * @param  null $return If you want to override GFAPI::get_entries() and define entries yourself, tap in here.
573
			 * @param  array $criteria The final search criteria used to generate the request to `GFAPI::get_entries()`
574
			 * @param array $passed_criteria The original search criteria passed to `GVCommon::get_entries()`
575
			 * @param  int|null $total Optional. An output parameter containing the total number of entries. Pass a non-null value to generate
576
			 * @since 2.1 The $total parameter can now be overriden by reference.
577
			 * @deprecated
578
			 */
579
			$entries = apply_filters_ref_array( 'gravityview_before_get_entries', array( null, $criteria, $passed_criteria, &$total ) );
580
581
			// No entries returned from gravityview_before_get_entries
582
			if( is_null( $entries ) ) {
583
584
				$entries = GFAPI::get_entries( $form_ids, $criteria['search_criteria'], $criteria['sorting'], $criteria['paging'], $total );
585
586
				if ( is_wp_error( $entries ) ) {
587
					gravityview()->log->error( '{error}', array( 'error' => $entries->get_error_message(), 'data' => $entries ) );
588
589
					/** Remove filter added above */
590
					remove_filter( 'gform_is_encrypted_field', '__return_false' );
591
					return false;
592
				}
593
			}
594
595
			if ( ! empty( $criteria['cache'] ) && isset( $Cache ) ) {
596
597
				// Cache results
598
				$Cache->set( $entries, 'entries' );
599
600
			}
601
602
			$return = $entries;
603
		}
604
605
		/** Remove filter added above */
606
		remove_filter( 'gform_is_encrypted_field', '__return_false' );
607
608
		/**
609
		 * @filter `gravityview_entries` Modify the array of entries returned to GravityView after it has been fetched from the cache or from `GFAPI::get_entries()`.
610
		 * @param  array|null $entries Array of entries as returned by the cache or by `GFAPI::get_entries()`
611
		 * @param  array $criteria The final search criteria used to generate the request to `GFAPI::get_entries()`
612
		 * @param array $passed_criteria The original search criteria passed to `GVCommon::get_entries()`
613
		 * @param  int|null $total Optional. An output parameter containing the total number of entries. Pass a non-null value to generate
614
		 * @since 2.1 The $total parameter can now be overriden by reference.
615
		 * @deprecated
616
		 */
617
		$return = apply_filters_ref_array( 'gravityview_entries', array( $return, $criteria, $passed_criteria, &$total ) );
618
619
		return $return;
620
	}
621
622
623
	/**
624
	 * Get the entry ID from a string that may be the Entry ID or the Entry Slug
625
	 *
626
	 * @since 1.18
627
	 *
628
	 * @param string $entry_id_or_slug The ID or slug of an entry.
629
	 * @param bool $force_allow_ids Whether to force allowing getting the ID of an entry, even if custom slugs are enabled
630
	 *
631
	 * @return false|int|null Returns the ID of the entry found, if custom slugs is enabled. Returns original value if custom slugs is disabled. Returns false if not allowed to convert slug to ID. Returns NULL if entry not found for the passed slug.
632
	 */
633 3
	public static function get_entry_id( $entry_id_or_slug = '', $force_allow_ids = false ) {
634
635 3
		$entry_id = false;
636
637
		/**
638
		 * @filter `gravityview_custom_entry_slug` Whether to enable and use custom entry slugs.
639
		 * @param boolean True: Allow for slugs based on entry values. False: always use entry IDs (default)
640
		 */
641 3
		$custom_slug = apply_filters( 'gravityview_custom_entry_slug', false );
642
643
		/**
644
		 * @filter `gravityview_custom_entry_slug_allow_id` When using a custom slug, allow access to the entry using the original slug (the Entry ID).
645
		 * - If disabled (default), only allow access to an entry using the custom slug value.  (example: `/entry/custom-slug/` NOT `/entry/123/`)
646
		 * - If enabled, you could access using the custom slug OR the entry id (example: `/entry/custom-slug/` OR `/entry/123/`)
647
		 * @param boolean $custom_slug_id_access True: allow accessing the slug by ID; False: only use the slug passed to the method.
648
		 */
649 3
		$custom_slug_id_access = $force_allow_ids || apply_filters( 'gravityview_custom_entry_slug_allow_id', false );
650
651
		/**
652
		 * If we're using custom entry slugs, we do a meta value search
653
		 * instead of doing a straightup ID search.
654
		 */
655 3
		if ( $custom_slug ) {
656
			// Search for IDs matching $entry_id_or_slug
657
			$entry_id = self::get_entry_id_from_slug( $entry_id_or_slug );
658
		}
659
660
		// If custom slug is off, search using the entry ID
661
		// ID allow ID access is on, also use entry ID as a backup
662 3
		if ( false === $custom_slug || true === $custom_slug_id_access ) {
663
			// Search for IDs matching $entry_slug
664 3
			$entry_id = $entry_id_or_slug;
665
		}
666
667 3
		return $entry_id;
668
	}
669
670
	/**
671
	 * Return a single entry object
672
	 *
673
	 * 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()`
674
	 *
675
	 * @access public
676
	 * @param string|int $entry_slug Either entry ID or entry slug string
677
	 * @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.
678
	 * @param boolean $check_entry_display Check whether the entry is visible for the current View configuration. Default: true. {@since 1.14}
679
	 * @param \GV\View $view The View if $check_entry_display is set to true. {@since develop}
680
	 * @return array|boolean
681
	 */
682 32
	public static function get_entry( $entry_slug, $force_allow_ids = false, $check_entry_display = true, $view = null ) {
683
684 32
		if ( ! class_exists( 'GFAPI' ) || empty( $entry_slug ) ) {
685 30
			return false;
686
		}
687
688 3
		$entry_id = self::get_entry_id( $entry_slug, $force_allow_ids );
689
690 3
		if ( empty( $entry_id ) ) {
691
			return false;
692
		}
693
694
		// fetch the entry
695 3
		$entry = GFAPI::get_entry( $entry_id );
696
697
		/**
698
		 * @filter `gravityview/common/get_entry/check_entry_display` Override whether to check entry display rules against filters
699
		 * @since 1.16.2
700
		 * @since 2.6 Added $view parameter
701
		 * @param bool $check_entry_display Check whether the entry is visible for the current View configuration. Default: true.
702
		 * @param array $entry Gravity Forms entry array
703
		 * @param \GV\View $view The View
704
		 */
705 3
		$check_entry_display = apply_filters( 'gravityview/common/get_entry/check_entry_display', $check_entry_display, $entry, $view );
706
707 3
		if( $check_entry_display ) {
708 1
			if ( ! $view ) {
709 1
				$view = \GV\View::by_id( \GravityView_View::getInstance()->getViewId() ); // @todo Bad legacy context, provide $view parameter!
710 1
				gravityview()->log->warning( '$view parameter not provided! Context assumed from legacy context mocks. This is unreliable!' );
711
			}
712
713
			// Is the entry allowed
714 1
			$entry = self::check_entry_display( $entry, $view );
715
		}
716
717 3
		if( is_wp_error( $entry ) ) {
718 2
			gravityview()->log->error( '{error}', array( 'error' => $entry->get_error_message() ) );
719 2
			return false;
720
		}
721
722 2
		return $entry;
723
	}
724
725
	/**
726
	 * Wrapper for the GFFormsModel::matches_operation() method that adds additional comparisons, including:
727
	 * 'equals', 'greater_than_or_is', 'greater_than_or_equals', 'less_than_or_is', 'less_than_or_equals',
728
	 * 'not_contains', 'in', and 'not_in'
729
	 *
730
	 * @since 1.13 You can define context, which displays/hides based on what's being displayed (single, multiple, edit)
731
	 * @since 1.22.1 Added 'in' and 'not_in' for JSON-encoded array values, serialized non-strings
732
	 *
733
	 * @see https://docs.gravityview.co/article/252-gvlogic-shortcode
734
	 * @uses GFFormsModel::matches_operation
735
	 * @since 1.7.5
736
	 *
737
	 * @param mixed $val1 Left side of comparison
738
	 * @param mixed $val2 Right side of comparison
739
	 * @param string $operation Type of comparison
740
	 *
741
	 * @return bool True: matches, false: not matches
742
	 */
743 25
	public static function matches_operation( $val1, $val2, $operation ) {
744
745
		// Only process strings
746 25
		$val1 = ! is_string( $val1 ) ? wp_json_encode( $val1 ) : $val1;
747 25
		$val2 = ! is_string( $val2 ) ? wp_json_encode( $val2 ) : $val2;
748
749 25
		$value = false;
750
751 25
		if( 'context' === $val1 ) {
752
753
			$matching_contexts = array( $val2 );
754
755
			// We allow for non-standard contexts.
756
			switch( $val2 ) {
757
				// Check for either single or edit
758
				case 'singular':
759
					$matching_contexts = array( 'single', 'edit' );
760
					break;
761
				// Use multiple as alias for directory for consistency
762
				case 'multiple':
763
					$matching_contexts = array( 'directory' );
764
					break;
765
			}
766
767
			$val1 = in_array( gravityview_get_context(), $matching_contexts ) ? $val2 : false;
0 ignored issues
show
Deprecated Code introduced by
The function gravityview_get_context() has been deprecated with message: since 2.0.6.2 Use `gravityview()->request`

This function has been deprecated. The supplier of the file has supplied an explanatory message.

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

Loading history...
768
		}
769
770 25
		switch ( $operation ) {
771 25
			case 'equals':
772 7
				$value = self::matches_operation( $val1, $val2, 'is' );
773 7
				break;
774 25
			case 'greater_than_or_is':
775 25
			case 'greater_than_or_equals':
776 5
				$is    = self::matches_operation( $val1, $val2, 'is' );
777 5
				$gt    = self::matches_operation( $val1, $val2, 'greater_than' );
778 5
				$value = ( $is || $gt );
779 5
				break;
780 25
			case 'less_than_or_is':
781 25
			case 'less_than_or_equals':
782 3
				$is    = self::matches_operation( $val1, $val2, 'is' );
783 3
				$gt    = self::matches_operation( $val1, $val2, 'less_than' );
784 3
				$value = ( $is || $gt );
785 3
				break;
786 25
			case 'not_contains':
787 1
				$contains = self::matches_operation( $val1, $val2, 'contains' );
788 1
				$value    = ! $contains;
789 1
				break;
790
			/**
791
			 * @since 1.22.1 Handle JSON-encoded comparisons
792
			 */
793 25
			case 'in':
794 25
			case 'not_in':
795
796 1
				$json_val_1 = json_decode( $val1, true );
797 1
				$json_val_2 = json_decode( $val2, true );
798
799 1
				if( ! empty( $json_val_1 ) || ! empty( $json_val_2 ) ) {
800
801 1
					$json_in = false;
802 1
					$json_val_1 = $json_val_1 ? (array) $json_val_1 : array( $val1 );
803 1
					$json_val_2 = $json_val_2 ? (array) $json_val_2 : array( $val2 );
804
805
					// For JSON, we want to compare as "in" or "not in" rather than "contains"
806 1
					foreach ( $json_val_1 as $item_1 ) {
807 1
						foreach ( $json_val_2 as $item_2 ) {
808 1
							$json_in = self::matches_operation( $item_1, $item_2, 'is' );
809
810 1
							if( $json_in ) {
811 1
								break 2;
812
							}
813
						}
814
					}
815
816 1
					$value = ( $operation === 'in' ) ? $json_in : ! $json_in;
817
				}
818 1
				break;
819
820 25
			case 'less_than':
821 23
			case '<' :
822 5
				if ( is_string( $val1 ) && is_string( $val2 ) ) {
823 5
					$value = $val1 < $val2;
824
				} else {
825
					$value = GFFormsModel::matches_operation( $val1, $val2, $operation );
826
				}
827 5
				break;
828 23
			case 'greater_than':
829 21
			case '>' :
830 9
				if ( is_string( $val1 ) && is_string( $val2 ) ) {
831 9
					$value = $val1 > $val2;
832
				} else {
833
					$value = GFFormsModel::matches_operation( $val1, $val2, $operation );
834
				}
835 9
				break;
836
			default:
837 21
				$value = GFFormsModel::matches_operation( $val1, $val2, $operation );
838
		}
839
840 25
		return $value;
841
	}
842
843
	/**
844
	 *
845
	 * Checks if a certain entry is valid according to the View search filters (specially the Adv Filters)
846
	 *
847
	 * @uses GVCommon::calculate_get_entries_criteria();
848
	 * @see GFFormsModel::is_value_match()
849
	 *
850
	 * @since 1.7.4
851
	 * @since 2.1 Added $view parameter
852
	 *
853
	 * @param array $entry Gravity Forms Entry object
854
	 * @param \GV\View $view The View.
855
	 *
856
	 * @return WP_Error|array Returns WP_Error if entry is not valid according to the view search filters (Adv Filter). Returns original $entry value if passes.
857
	 */
858 12
	public static function check_entry_display( $entry, $view = null ) {
859
860 12
		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...
861 1
			return new WP_Error('entry_not_found', 'Entry was not found.', $entry );
862
		}
863
864 12
		if ( empty( $entry['form_id'] ) ) {
865 1
			return new WP_Error( 'form_id_not_set', '[apply_filters_to_entry] Entry is empty!', $entry );
866
		}
867
868 12
		if ( is_null( $view ) ) {
869 1
			gravityview()->log->warning( '$view was not supplied to check_entry_display, results will be non-typical.' );
870 1
			return new WP_Error( 'view_not_supplied', 'View is not supplied!', $entry );
871
		}
872
873 12
		if ( ! gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
874
			return new WP_Error( 'no_gf_query', 'GF_Query is missing.', $entry );
875
		}
876
877 12
		$view_form_id = $view->form->ID;
878
879 12
		if ( $view->joins ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $view->joins of type GV\Join[] 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...
880 1
			if ( in_array( (int)$entry['form_id'], array_keys( $view::get_joined_forms( $view->ID ) ), true ) ) {
881 1
				$view_form_id = $entry['form_id'];
882
			}
883
		}
884
885 12
		if ( $view_form_id != $entry['form_id'] ) {
886 2
			return new WP_Error( 'view_id_not_match', 'View form source does not match entry form source ID.', $entry );
887
		}
888
889
		/**
890
		 * Check whether the entry is in the entries subset by running a modified query.
891
		 */
892 11
		add_action( 'gravityview/view/query', $entry_subset_callback = function( &$query, $view, $request ) use ( $entry, $view_form_id ) {
0 ignored issues
show
Unused Code introduced by
The parameter $view 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...
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...
893 11
			$_tmp_query       = new \GF_Query( $view_form_id, array(
894
				'field_filters' => array(
895 11
					'mode' => 'all',
896
					array(
897 11
						'key' => 'id',
898 11
						'operation' => 'is',
899 11
						'value' => $entry['id']
900
					)
901
				)
902
			) );
903
904 11
			$_tmp_query_parts = $_tmp_query->_introspect();
905
906
			/** @var \GF_Query $query */
907 11
			$query_parts      = $query->_introspect();
908
909 11
			$query->where( \GF_Query_Condition::_and( $_tmp_query_parts['where'], $query_parts['where'] ) );
910
911 11
		}, 10, 3 );
912
913
		// Prevent page offset from being applied to the single entry query; it's used to return to the referring page number
914 11
		add_filter( 'gravityview_search_criteria', $remove_pagenum = function( $criteria ) {
915
916 11
			$criteria['paging'] = array(
917
				'offset' => 0,
918
				'page_size' => 25
919
			);
920
921 11
			return $criteria;
922 11
		} );
923
924 11
		$entries = $view->get_entries()->all();
925
926
		// Remove the modifying filter
927 11
		remove_filter( 'gravityview_search_criteria', $remove_pagenum );
928
929 11
		if ( ! $entries ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $entries of type GV\Entry[] 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...
930 8
			remove_action( 'gravityview/view/query', $entry_subset_callback );
931 8
			return new \WP_Error( 'failed_criteria', 'Entry failed search_criteria and field_filters' );
932
		}
933
934
		// This entry is on a View with joins
935 10
		if ( $entries[0]->is_multi() ) {
936
937 1
			$multi_entry_ids = array();
938
939 1
			foreach ( $entries[0]->entries as $multi_entry ) {
0 ignored issues
show
Bug introduced by
The property entries does not seem to exist in GV\Entry.

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...
940 1
				$multi_entry_ids[] = (int) $multi_entry->ID;
941
			}
942
943 1
			if ( ! in_array( (int) $entry['id'], $multi_entry_ids, true ) ) {
944
				remove_action( 'gravityview/view/query', $entry_subset_callback );
945 1
				return new \WP_Error( 'failed_criteria', 'Entry failed search_criteria and field_filters' );
946
			}
947
948 9
		} elseif ( (int) $entries[0]->ID !== (int) $entry['id'] ) {
949
			remove_action( 'gravityview/view/query', $entry_subset_callback );
950
			return new \WP_Error( 'failed_criteria', 'Entry failed search_criteria and field_filters' );
951
		}
952
953 10
		remove_action( 'gravityview/view/query', $entry_subset_callback );
954 10
		return $entry;
955
	}
956
957
958
	/**
959
	 * Allow formatting date and time based on GravityView standards
960
	 *
961
	 * @since 1.16
962
	 *
963
	 * @see GVCommon_Test::test_format_date for examples
964
	 *
965
	 * @param string $date_string The date as stored by Gravity Forms (`Y-m-d h:i:s` GMT)
966
	 * @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
967
	 * - `raw` Un-formatted date string in original `Y-m-d h:i:s` format
968
	 * - `timestamp` Integer timestamp returned by GFCommon::get_local_timestamp()
969
	 * - `diff` "%s ago" format, unless other `format` is defined
970
	 * - `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.
971
	 * - `time` Include time in the `GFCommon::format_date()` output
972
	 * - `format` Define your own date format, or `diff` format
973
	 *
974
	 * @return int|null|string Formatted date based on the original date
975
	 */
976 6
	public static function format_date( $date_string = '', $args = array() ) {
977
978
		$default_atts = array(
979 6
			'raw' => false,
980
			'timestamp' => false,
981
			'diff' => false,
982
			'human' => false,
983
			'format' => '',
984
			'time' => false,
985
		);
986
987 6
		$atts = wp_parse_args( $args, $default_atts );
988
989
		/**
990
		 * Gravity Forms code to adjust date to locally-configured Time Zone
991
		 * @see GFCommon::format_date() for original code
992
		 */
993 6
		$date_gmt_time   = mysql2date( 'G', $date_string );
994 6
		$date_local_timestamp = GFCommon::get_local_timestamp( $date_gmt_time );
995
996 6
		$format  = \GV\Utils::get( $atts, 'format' );
997 6
		$is_human  = ! empty( $atts['human'] );
998 6
		$is_diff  = ! empty( $atts['diff'] );
999 6
		$is_raw = ! empty( $atts['raw'] );
1000 6
		$is_timestamp = ! empty( $atts['timestamp'] );
1001 6
		$include_time = ! empty( $atts['time'] );
1002
1003
		// If we're using time diff, we want to have a different default format
1004 6
		if( empty( $format ) ) {
1005
			/* translators: %s: relative time from now, used for generic date comparisons. "1 day ago", or "20 seconds ago" */
1006 5
			$format = $is_diff ? esc_html__( '%s ago', 'gravityview' ) : get_option( 'date_format' );
1007
		}
1008
1009
		// If raw was specified, don't modify the stored value
1010 6
		if ( $is_raw ) {
1011 2
			$formatted_date = $date_string;
1012 6
		} elseif( $is_timestamp ) {
1013 3
			$formatted_date = $date_local_timestamp;
1014 5
		} elseif ( $is_diff ) {
1015 2
			$formatted_date = sprintf( $format, human_time_diff( $date_gmt_time ) );
1016
		} else {
1017 5
			$formatted_date = GFCommon::format_date( $date_string, $is_human, $format, $include_time );
1018
		}
1019
1020 6
		unset( $format, $is_diff, $is_human, $is_timestamp, $is_raw, $date_gmt_time, $date_local_timestamp, $default_atts );
1021
1022 6
		return $formatted_date;
1023
	}
1024
1025
	/**
1026
	 * Retrieve the label of a given field id (for a specific form)
1027
	 *
1028
	 * @access public
1029
	 * @since 1.17 Added $field_value parameter
1030
	 *
1031
	 * @param array $form Gravity Forms form array
1032
	 * @param string $field_id ID of the field. If an input, full input ID (like `1.3`)
1033
	 * @param string|array $field_value Raw value of the field.
1034
	 * @return string
1035
	 */
1036 1
	public static function get_field_label( $form = array(), $field_id = '', $field_value = '' ) {
1037
1038 1
		if ( empty( $form ) || empty( $field_id ) ) {
1039
			return '';
1040
		}
1041
1042 1
		$field = self::get_field( $form, $field_id );
1043
1044 1
		$label = \GV\Utils::get( $field, 'label' );
1045
1046 1
		if( floor( $field_id ) !== floatval( $field_id ) ) {
1047 1
			$label = GFFormsModel::get_choice_text( $field, $field_value, $field_id );
1048
		}
1049
1050 1
		return $label;
1051
	}
1052
1053
1054
	/**
1055
	 * Returns the field details array of a specific form given the field id
1056
	 *
1057
	 * Alias of GFFormsModel::get_field
1058
	 *
1059
	 * @since 1.19 Allow passing form ID as well as form array
1060
	 *
1061
	 * @uses GFFormsModel::get_field
1062
	 * @see GFFormsModel::get_field
1063
	 * @access public
1064
	 * @param array|int $form Form array or ID
1065
	 * @param string|int $field_id
1066
	 * @return GF_Field|null Gravity Forms field object, or NULL: Gravity Forms GFFormsModel does not exist or field at $field_id doesn't exist.
1067
	 */
1068 9
	public static function get_field( $form, $field_id ) {
1069
1070 9
		if ( is_numeric( $form ) ) {
1071
			$form = GFAPI::get_form( $form );
1072
		}
1073
1074 9
		if ( class_exists( 'GFFormsModel' ) ){
1075 9
			return GFFormsModel::get_field( $form, $field_id );
1076
		} else {
1077
			return null;
1078
		}
1079
	}
1080
1081
1082
	/**
1083
	 * Check whether the post is GravityView
1084
	 *
1085
	 * - Check post type. Is it `gravityview`?
1086
	 * - Check shortcode
1087
	 *
1088
	 * @param  WP_Post      $post WordPress post object
1089
	 * @return boolean           True: yep, GravityView; No: not!
1090
	 */
1091 2
	public static function has_gravityview_shortcode( $post = null ) {
1092 2
		if ( ! is_a( $post, 'WP_Post' ) ) {
1093 1
			return false;
1094
		}
1095
1096 2
		if ( 'gravityview' === get_post_type( $post ) ) {
1097 2
			return true;
1098
		}
1099
1100 2
		return self::has_shortcode_r( $post->post_content, 'gravityview' ) ? true : false;
1101
1102
	}
1103
1104
1105
	/**
1106
	 * Placeholder until the recursive has_shortcode() patch is merged
1107
	 * @see https://core.trac.wordpress.org/ticket/26343#comment:10
1108
	 * @param string $content Content to check whether there's a shortcode
1109
	 * @param string $tag Current shortcode tag
1110
	 */
1111 4
	public static function has_shortcode_r( $content, $tag = 'gravityview' ) {
1112 4
		if ( false === strpos( $content, '[' ) ) {
1113 2
			return false;
1114
		}
1115
1116 4
		if ( shortcode_exists( $tag ) ) {
1117
1118 4
			$shortcodes = array();
1119
1120 4
			preg_match_all( '/' . get_shortcode_regex() . '/s', $content, $matches, PREG_SET_ORDER );
1121 4
			if ( empty( $matches ) ){
1122 1
				return false;
1123
			}
1124
1125 4
			foreach ( $matches as $shortcode ) {
1126 4
				if ( $tag === $shortcode[2] ) {
1127
1128
					// Changed this to $shortcode instead of true so we get the parsed atts.
1129 4
					$shortcodes[] = $shortcode;
1130
1131 1
				} else if ( isset( $shortcode[5] ) && $results = self::has_shortcode_r( $shortcode[5], $tag ) ) {
1132 1
					foreach( $results as $result ) {
1133 1
						$shortcodes[] = $result;
1134
					}
1135
				}
1136
			}
1137
1138 4
			return $shortcodes;
1139
		}
1140
		return false;
1141
	}
1142
1143
1144
1145
	/**
1146
	 * Get the views for a particular form
1147
	 *
1148
	 * @since 1.15.2 Add $args array and limit posts_per_page to 500
1149
	 *
1150
	 * @uses get_posts()
1151
	 *
1152
	 * @param  int $form_id Gravity Forms form ID
1153
	 * @param  array $args Pass args sent to get_posts()
1154
	 *
1155
	 * @return array          Array with view details, as returned by get_posts()
1156
	 */
1157 1
	public static function get_connected_views( $form_id, $args = array() ) {
1158
1159 1
		global $wpdb;
1160
1161
		$defaults = array(
1162 1
			'post_type'      => 'gravityview',
1163 1
			'posts_per_page' => 100,
1164 1
			'meta_key'       => '_gravityview_form_id',
1165 1
			'meta_value'     => (int) $form_id,
1166
		);
1167 1
		$args     = wp_parse_args( $args, $defaults );
1168 1
		$views    = get_posts( $args );
1169
1170 1
		$views_with_joins = $wpdb->get_results( "SELECT `post_id`, `meta_value` FROM $wpdb->postmeta WHERE `meta_key` = '_gravityview_form_joins'" );
1171
1172 1
		$joined_forms = array();
1173 1
		foreach ( $views_with_joins as $view ) {
1174
1175 1
			$data = unserialize( $view->meta_value );
1176
1177 1
			if( ! $data || ! is_array( $data ) ) {
1178 1
				continue;
1179
			}
1180
1181
			foreach ( $data as $datum ) {
1182
				if ( ! empty( $datum[2] ) && (int) $datum[2] === (int) $form_id ) {
1183
					$joined_forms[] = $view->post_id;
1184
				}
1185
			}
1186
		}
1187
1188 1
		if ( $joined_forms ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $joined_forms 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...
1189
			$joined_args  = array(
1190
				'post_type'      => 'gravityview',
1191
				'posts_per_page' => $args['posts_per_page'],
1192
				'post__in'       => $joined_forms,
1193
			);
1194
			$views = array_merge( $views, get_posts( $joined_args ) );
1195
		}
1196
1197 1
		return $views;
1198
	}
1199
1200
	/**
1201
	 * Get the Gravity Forms form ID connected to a View
1202
	 *
1203
	 * @param int $view_id The ID of the View to get the connected form of
1204
	 *
1205
	 * @return false|string ID of the connected Form, if exists. Empty string if not. False if not the View ID isn't valid.
1206
	 */
1207 1
	public static function get_meta_form_id( $view_id ) {
1208 1
		return get_post_meta( $view_id, '_gravityview_form_id', true );
1209
	}
1210
1211
	/**
1212
	 * Get the template ID (`list`, `table`, `datatables`, `map`) for a View
1213
	 *
1214
	 * @see GravityView_Template::template_id
1215
	 *
1216
	 * @param int $view_id The ID of the View to get the layout of
1217
	 *
1218
	 * @return string GravityView_Template::template_id value. Empty string if not.
1219
	 */
1220 181
	public static function get_meta_template_id( $view_id ) {
1221 181
		return get_post_meta( $view_id, '_gravityview_directory_template', true );
1222
	}
1223
1224
1225
	/**
1226
	 * Get all the settings for a View
1227
	 *
1228
	 * @uses  \GV\View_Settings::defaults() Parses the settings with the plugin defaults as backups.
1229
	 * @param  int $post_id View ID
1230
	 * @return array          Associative array of settings with plugin defaults used if not set by the View
1231
	 */
1232 180
	public static function get_template_settings( $post_id ) {
1233
1234 180
		$settings = get_post_meta( $post_id, '_gravityview_template_settings', true );
1235
1236 180
		if ( class_exists( '\GV\View_Settings' ) ) {
1237
1238 180
			return wp_parse_args( (array)$settings, \GV\View_Settings::defaults() );
1239
1240
		}
1241
1242
		// Backup, in case GravityView_View_Data isn't loaded yet.
1243
		return $settings;
1244
	}
1245
1246
	/**
1247
	 * Get the setting for a View
1248
	 *
1249
	 * If the setting isn't set by the View, it returns the plugin default.
1250
	 *
1251
	 * @param  int $post_id View ID
1252
	 * @param  string $key     Key for the setting
1253
	 * @return mixed|null          Setting value, or NULL if not set.
1254
	 */
1255 1
	public static function get_template_setting( $post_id, $key ) {
1256
1257 1
		$settings = self::get_template_settings( $post_id );
1258
1259 1
		if ( isset( $settings[ $key ] ) ) {
1260 1
			return $settings[ $key ];
1261
		}
1262
1263
		return null;
1264
	}
1265
1266
	/**
1267
	 * Get the field configuration for the View
1268
	 *
1269
	 * array(
1270
	 *
1271
	 * 	[other zones]
1272
	 *
1273
	 * 	'directory_list-title' => array(
1274
	 *
1275
	 *   	[other fields]
1276
	 *
1277
	 *  	'5372653f25d44' => array(
1278
	 *  		'id' => string '9' (length=1)
1279
	 *  		'label' => string 'Screenshots' (length=11)
1280
	 *			'show_label' => string '1' (length=1)
1281
	 *			'custom_label' => string '' (length=0)
1282
	 *			'custom_class' => string 'gv-gallery' (length=10)
1283
	 * 			'only_loggedin' => string '0' (length=1)
1284
	 *			'only_loggedin_cap' => string 'read' (length=4)
1285
	 *  	)
1286
	 *
1287
	 * 		[other fields]
1288
	 *  )
1289
	 *
1290
	 * 	[other zones]
1291
	 * )
1292
	 *
1293
	 * @since 1.17.4 Added $apply_filter parameter
1294
	 *
1295
	 * @param  int $post_id View ID
1296
	 * @param  bool $apply_filter Whether to apply the `gravityview/configuration/fields` filter [Default: true]
1297
	 * @return array          Multi-array of fields with first level being the field zones. See code comment.
1298
	 */
1299 1
	public static function get_directory_fields( $post_id, $apply_filter = true ) {
1300 1
		$fields = get_post_meta( $post_id, '_gravityview_directory_fields', true );
1301
1302 1
		if ( $apply_filter ) {
1303
			/**
1304
			 * @filter `gravityview/configuration/fields` Filter the View fields' configuration array
1305
			 * @since 1.6.5
1306
			 *
1307
			 * @param $fields array Multi-array of fields with first level being the field zones
1308
			 * @param $post_id int Post ID
1309
			 */
1310 1
			$fields = apply_filters( 'gravityview/configuration/fields', $fields, $post_id );
1311
1312
			/**
1313
			 * @filter `gravityview/view/configuration/fields` Filter the View fields' configuration array.
1314
			 * @since 2.0
1315
			 *
1316
			 * @param array $fields Multi-array of fields with first level being the field zones.
1317
			 * @param \GV\View $view The View the fields are being pulled for.
1318
			 */
1319 1
			$fields = apply_filters( 'gravityview/view/configuration/fields', $fields, \GV\View::by_id( $post_id ) );
1320
		}
1321
1322 1
		return $fields;
1323
	}
1324
1325
	/**
1326
	 * Get the widget configuration for a View
1327
	 *
1328
	 * @param int $view_id View ID
1329
	 * @param bool $json_decode Whether to JSON-decode the widget values. Default: `false`
1330
	 *
1331
	 * @return array Multi-array of widgets, with the slug of each widget "zone" being the key ("header_top"), and each widget having their own "id"
1332
	 */
1333
	public static function get_directory_widgets( $view_id, $json_decode = false ) {
1334
1335
		$view_widgets = get_post_meta( $view_id, '_gravityview_directory_widgets', true );
1336
1337
		$defaults = array(
1338
			'header_top' => array(),
1339
			'header_left' => array(),
1340
			'header_right' => array(),
1341
			'footer_left' => array(),
1342
			'footer_right' => array(),
1343
		);
1344
1345
		$directory_widgets = wp_parse_args( $view_widgets, $defaults );
1346
1347
		if( $json_decode ) {
1348
			$directory_widgets = gv_map_deep( $directory_widgets, 'gv_maybe_json_decode' );
1349
		}
1350
1351
		return $directory_widgets;
1352
	}
1353
1354
1355
	/**
1356
	 * Render dropdown (select) with the list of sortable fields from a form ID
1357
	 *
1358
	 * @access public
1359
	 * @param  int $formid Form ID
1360
	 * @return string         html
1361
	 */
1362
	public static function get_sortable_fields( $formid, $current = '' ) {
1363
		$output = '<option value="" ' . selected( '', $current, false ).'>' . esc_html__( 'Default', 'gravityview' ) .'</option>';
1364
1365
		if ( empty( $formid ) ) {
1366
			return $output;
1367
		}
1368
1369
		$fields = self::get_sortable_fields_array( $formid );
1370
1371
		if ( ! empty( $fields ) ) {
1372
1373
			$blacklist_field_types = apply_filters( 'gravityview_blacklist_field_types', array( 'list', 'textarea' ), null );
1374
1375
			foreach ( $fields as $id => $field ) {
1376
				if ( in_array( $field['type'], $blacklist_field_types ) ) {
1377
					continue;
1378
				}
1379
1380
				$output .= '<option value="'. $id .'" '. selected( $id, $current, false ).'>'. esc_attr( $field['label'] ) .'</option>';
1381
			}
1382
		}
1383
1384
		return $output;
1385
	}
1386
1387
	/**
1388
	 *
1389
	 * @param int $formid Gravity Forms form ID
1390
	 * @param array $blacklist Field types to exclude
1391
	 *
1392
	 * @since 1.8
1393
	 *
1394
	 * @todo Get all fields, check if sortable dynamically
1395
	 *
1396
	 * @return array
1397
	 */
1398
	public static function get_sortable_fields_array( $formid, $blacklist = array( 'list', 'textarea' ) ) {
1399
1400
		// Get fields with sub-inputs and no parent
1401
		$fields = self::get_form_fields( $formid, true, false );
1402
1403
		$date_created = array(
1404
			'date_created' => array(
1405
				'type' => 'date_created',
1406
				'label' => __( 'Date Created', 'gravityview' ),
1407
			),
1408
		);
1409
1410
        $fields = $date_created + $fields;
1411
1412
		$blacklist_field_types = apply_filters( 'gravityview_blacklist_field_types', $blacklist, NULL );
1413
1414
		// TODO: Convert to using array_filter
1415
		foreach( $fields as $id => $field ) {
1416
1417
			if( in_array( $field['type'], $blacklist_field_types ) ) {
1418
				unset( $fields[ $id ] );
1419
			}
1420
1421
			/**
1422
			 * Merge date and time subfields.
1423
			 */
1424
			if ( in_array( $field['type'], array( 'date', 'time' ) ) && ! empty( $field['parent'] ) ) {
1425
				$fields[ intval( $id ) ] = array(
1426
					'label' => \GV\Utils::get( $field, 'parent/label' ),
1427
					'parent' => null,
1428
					'type' => \GV\Utils::get( $field, 'parent/type' ),
1429
					'adminLabel' => \GV\Utils::get( $field, 'parent/adminLabel' ),
1430
					'adminOnly' => \GV\Utils::get( $field, 'parent/adminOnly' ),
1431
				);
1432
1433
				unset( $fields[ $id ] );
1434
			}
1435
1436
		}
1437
1438
        /**
1439
         * @filter `gravityview/common/sortable_fields` Filter the sortable fields
1440
         * @since 1.12
1441
         * @param array $fields Sub-set of GF form fields that are sortable
1442
         * @param int $formid The Gravity Forms form ID that the fields are from
1443
         */
1444
        $fields = apply_filters( 'gravityview/common/sortable_fields', $fields, $formid );
1445
1446
		return $fields;
1447
	}
1448
1449
	/**
1450
	 * Returns the GF Form field type for a certain field(id) of a form
1451
	 * @param  object $form     Gravity Forms form
1452
	 * @param  mixed $field_id Field ID or Field array
1453
	 * @return string field type
1454
	 */
1455 4
	public static function get_field_type( $form = null, $field_id = '' ) {
1456
1457 4
		if ( ! empty( $field_id ) && ! is_array( $field_id ) ) {
1458 4
			$field = self::get_field( $form, $field_id );
1459
		} else {
1460
			$field = $field_id;
1461
		}
1462
1463 4
		return class_exists( 'RGFormsModel' ) ? RGFormsModel::get_input_type( $field ) : '';
1464
1465
	}
1466
1467
1468
	/**
1469
	 * Checks if the field type is a 'numeric' field type (e.g. to be used when sorting)
1470
	 * @param  int|array  $form  form ID or form array
1471
	 * @param  int|array  $field field key or field array
1472
	 * @return boolean
1473
	 */
1474 5
	public static function is_field_numeric(  $form = null, $field = '' ) {
1475
1476 5
		if ( ! is_array( $form ) && ! is_array( $field ) ) {
1477 5
			$form = self::get_form( $form );
1478
		}
1479
1480
		// If entry meta, it's a string. Otherwise, numeric
1481 5
		if( ! is_numeric( $field ) && is_string( $field ) ) {
1482 1
			$type = $field;
1483
		} else {
1484 4
			$type = self::get_field_type( $form, $field );
1485
		}
1486
1487
		/**
1488
		 * @filter `gravityview/common/numeric_types` What types of fields are numeric?
1489
		 * @since 1.5.2
1490
		 * @param array $numeric_types Fields that are numeric. Default: `[ number, time ]`
1491
		 */
1492 5
		$numeric_types = apply_filters( 'gravityview/common/numeric_types', array( 'number', 'time' ) );
1493
1494
		// Defer to GravityView_Field setting, if the field type is registered and `is_numeric` is true
1495 5
		if( $gv_field = GravityView_Fields::get( $type ) ) {
1496 4
			if( true === $gv_field->is_numeric ) {
1497
				$numeric_types[] = $gv_field->is_numeric;
1498
			}
1499
		}
1500
1501 5
		$return = in_array( $type, $numeric_types );
1502
1503 5
		return $return;
1504
	}
1505
1506
	/**
1507
	 * Encrypt content using Javascript so that it's hidden when JS is disabled.
1508
	 *
1509
	 * This is mostly used to hide email addresses from scraper bots.
1510
	 *
1511
	 * @param string $content Content to encrypt
1512
	 * @param string $message Message shown if Javascript is disabled
1513
	 *
1514
	 * @see  https://github.com/katzwebservices/standalone-phpenkoder StandalonePHPEnkoder on Github
1515
	 *
1516
	 * @since 1.7
1517
	 *
1518
	 * @return string Content, encrypted
1519
	 */
1520
	public static function js_encrypt( $content, $message = '' ) {
1521
1522
		$output = $content;
1523
1524
		if ( ! class_exists( 'StandalonePHPEnkoder' ) ) {
1525
			include_once( GRAVITYVIEW_DIR . 'includes/lib/StandalonePHPEnkoder.php' );
1526
		}
1527
1528
		if ( class_exists( 'StandalonePHPEnkoder' ) ) {
1529
1530
			$enkoder = new StandalonePHPEnkoder;
1531
1532
			$message = empty( $message ) ? __( 'Email hidden; Javascript is required.', 'gravityview' ) : $message;
1533
1534
			/**
1535
			 * @filter `gravityview/phpenkoder/msg` Modify the message shown when Javascript is disabled and an encrypted email field is displayed
1536
			 * @since 1.7
1537
			 * @param string $message Existing message
1538
			 * @param string $content Content to encrypt
1539
			 */
1540
			$enkoder->enkode_msg = apply_filters( 'gravityview/phpenkoder/msg', $message, $content );
1541
1542
			$output = $enkoder->enkode( $content );
1543
		}
1544
1545
		return $output;
1546
	}
1547
1548
	/**
1549
	 *
1550
	 * Do the same than parse_str without max_input_vars limitation:
1551
	 * Parses $string as if it were the query string passed via a URL and sets variables in the current scope.
1552
	 * @param $string string string to parse (not altered like in the original parse_str(), use the second parameter!)
1553
	 * @param $result array  If the second parameter is present, variables are stored in this variable as array elements
1554
	 * @return bool true or false if $string is an empty string
1555
	 * @since  1.5.3
1556
	 *
1557
	 * @author rubo77 at https://gist.github.com/rubo77/6821632
1558
	 **/
1559
	public static function gv_parse_str( $string, &$result ) {
1560
		if ( empty( $string ) ) {
1561
			return false;
1562
		}
1563
1564
		$result = array();
1565
1566
		// find the pairs "name=value"
1567
		$pairs = explode( '&', $string );
1568
1569
		foreach ( $pairs as $pair ) {
1570
			// use the original parse_str() on each element
1571
			parse_str( $pair, $params );
1572
1573
			$k = key( $params );
1574
			if ( ! isset( $result[ $k ] ) ) {
1575
				$result += $params;
1576
			} elseif ( array_key_exists( $k, $params ) && is_array( $params[ $k ] ) ) {
1577
				$result[ $k ] = self::array_merge_recursive_distinct( $result[ $k ], $params[ $k ] );
1578
			}
1579
		}
1580
		return true;
1581
	}
1582
1583
1584
	/**
1585
	 * Generate an HTML anchor tag with a list of supported attributes
1586
	 *
1587
	 * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a Supported attributes defined here
1588
	 * @uses esc_url_raw() to sanitize $href
1589
	 * @uses esc_attr() to sanitize $atts
1590
	 *
1591
	 * @since 1.6
1592
	 *
1593
	 * @param string $href URL of the link. Sanitized using `esc_url_raw()`
1594
	 * @param string $anchor_text The text or HTML inside the anchor. This is not sanitized in the function.
1595
	 * @param array|string $atts Attributes to be added to the anchor tag. Parsed by `wp_parse_args()`, sanitized using `esc_attr()`
1596
	 *
1597
	 * @return string HTML output of anchor link. If empty $href, returns NULL
1598
	 */
1599 56
	public static function get_link_html( $href = '', $anchor_text = '', $atts = array() ) {
1600
1601
		// Supported attributes for anchor tags. HREF left out intentionally.
1602
		$allowed_atts = array(
1603 56
			'href' => null, // Will override the $href argument if set
1604
			'title' => null,
1605
			'rel' => null,
1606
			'id' => null,
1607
			'class' => null,
1608
			'target' => null,
1609
			'style' => null,
1610
1611
			// Used by GravityView
1612
			'data-viewid' => null,
1613
1614
			// Not standard
1615
			'hreflang' => null,
1616
			'type' => null,
1617
			'tabindex' => null,
1618
1619
			// Deprecated HTML4 but still used
1620
			'name' => null,
1621
			'onclick' => null,
1622
			'onchange' => null,
1623
			'onkeyup' => null,
1624
1625
			// HTML5 only
1626
			'download' => null,
1627
			'media' => null,
1628
			'ping' => null,
1629
		);
1630
1631
		/**
1632
		 * @filter `gravityview/get_link/allowed_atts` Modify the attributes that are allowed to be used in generating links
1633
		 * @param array $allowed_atts Array of attributes allowed
1634
		 */
1635 56
		$allowed_atts = apply_filters( 'gravityview/get_link/allowed_atts', $allowed_atts );
1636
1637
		// Make sure the attributes are formatted as array
1638 56
		$passed_atts = wp_parse_args( $atts );
1639
1640
		// Make sure the allowed attributes are only the ones in the $allowed_atts list
1641 56
		$final_atts = shortcode_atts( $allowed_atts, $passed_atts );
1642
1643
		// Remove attributes with empty values
1644 56
		$final_atts = array_filter( $final_atts );
1645
1646
		// If the href wasn't passed as an attribute, use the value passed to the function
1647 56
		if ( empty( $final_atts['href'] ) && ! empty( $href ) ) {
1648 56
			$final_atts['href'] = $href;
1649
		}
1650
1651 56
		$final_atts['href'] = esc_url_raw( $href );
1652
1653
		/**
1654
		 * Fix potential security issue with target=_blank
1655
		 * @see https://dev.to/ben/the-targetblank-vulnerability-by-example
1656
		 */
1657 56
		if( '_blank' === \GV\Utils::get( $final_atts, 'target' ) ) {
1658 4
			$final_atts['rel'] = trim( \GV\Utils::get( $final_atts, 'rel', '' ) . ' noopener noreferrer' );
1659
		}
1660
1661
		// Sort the attributes alphabetically, to help testing
1662 56
		ksort( $final_atts );
1663
1664
		// For each attribute, generate the code
1665 56
		$output = '';
1666 56
		foreach ( $final_atts as $attr => $value ) {
1667 56
			$output .= sprintf( ' %s="%s"', $attr, esc_attr( $value ) );
1668
		}
1669
1670 56
		if( '' !== $output ) {
1671 56
			$output = '<a' . $output . '>' . $anchor_text . '</a>';
1672
		}
1673
1674 56
		return $output;
1675
	}
1676
1677
	/**
1678
	 * array_merge_recursive does indeed merge arrays, but it converts values with duplicate
1679
	 * keys to arrays rather than overwriting the value in the first array with the duplicate
1680
	 * value in the second array, as array_merge does.
1681
	 *
1682
	 * @see http://php.net/manual/en/function.array-merge-recursive.php
1683
	 *
1684
	 * @since  1.5.3
1685
	 * @param array $array1
1686
	 * @param array $array2
1687
	 * @return array
1688
	 * @author Daniel <daniel (at) danielsmedegaardbuus (dot) dk>
1689
	 * @author Gabriel Sobrinho <gabriel (dot) sobrinho (at) gmail (dot) com>
1690
	 */
1691
	public static function array_merge_recursive_distinct( array &$array1, array &$array2 ) {
1692
		$merged = $array1;
1693
		foreach ( $array2 as $key => $value ) {
1694
			if ( is_array( $value ) && isset( $merged[ $key ] ) && is_array( $merged[ $key ] ) ) {
1695
				$merged[ $key ] = self::array_merge_recursive_distinct( $merged[ $key ], $value );
1696
			} else if ( is_numeric( $key ) && isset( $merged[ $key ] ) ) {
1697
				$merged[] = $value;
1698
			} else {
1699
				$merged[ $key ] = $value;
1700
			}
1701
		}
1702
1703
		return $merged;
1704
	}
1705
1706
	/**
1707
	 * Get WordPress users with reasonable limits set
1708
	 *
1709
	 * @param string $context Where are we using this information (e.g. change_entry_creator, search_widget ..)
1710
	 * @param array $args Arguments to modify the user query. See get_users() {@since 1.14}
1711
	 * @return array Array of WP_User objects.
1712
	 */
1713 1
	public static function get_users( $context = 'change_entry_creator', $args = array() ) {
1714
1715
		$default_args = array(
1716 1
			'number' => 2000,
1717
			'orderby' => 'display_name',
1718
			'order' => 'ASC',
1719
			'fields' => array( 'ID', 'display_name', 'user_login', 'user_nicename' )
1720
		);
1721
1722
		// Merge in the passed arg
1723 1
		$get_users_settings = wp_parse_args( $args, $default_args );
1724
1725
		/**
1726
		 * @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
1727
		 * `$context` is where are we using this information (e.g. change_entry_creator, search_widget ..)
1728
		 * @param array $settings Settings array, with `number` key defining the # of users to display
1729
		 */
1730 1
		$get_users_settings = apply_filters( 'gravityview/get_users/'. $context, apply_filters( 'gravityview_change_entry_creator_user_parameters', $get_users_settings ) );
1731
1732 1
		return get_users( $get_users_settings );
1733
	}
1734
1735
1736
    /**
1737
     * Display updated/error notice
1738
     *
1739
     * @since 1.19.2 Added $cap and $object_id parameters
1740
     *
1741
     * @param string $notice text/HTML of notice
1742
     * @param string $class CSS class for notice (`updated` or `error`)
1743
     * @param string $cap [Optional] Define a capability required to show a notice. If not set, displays to all caps.
1744
     *
1745
     * @return string
1746
     */
1747 29
    public static function generate_notice( $notice, $class = '', $cap = '', $object_id = null ) {
1748
1749
    	// If $cap is defined, only show notice if user has capability
1750 29
    	if( $cap && ! GVCommon::has_cap( $cap, $object_id ) ) {
1751 8
    		return '';
1752
	    }
1753
1754 23
        return '<div class="gv-notice '.gravityview_sanitize_html_class( $class ) .'">'. $notice .'</div>';
1755
    }
1756
1757
	/**
1758
	 * Inspired on \GFCommon::encode_shortcodes, reverse the encoding by replacing the ascii characters by the shortcode brackets
1759
	 * @since 1.16.5
1760
	 * @param string $string Input string to decode
1761
	 * @return string $string Output string
1762
	 */
1763
	public static function decode_shortcodes( $string ) {
1764
		$replace = array( '[', ']', '"' );
1765
		$find = array( '&#91;', '&#93;', '&quot;' );
1766
		$string = str_replace( $find, $replace, $string );
1767
1768
		return $string;
1769
	}
1770
1771
1772
	/**
1773
	 * Send email using GFCommon::send_email()
1774
	 *
1775
	 * @since 1.17
1776
	 *
1777
	 * @see GFCommon::send_email This just makes the method public
1778
	 *
1779
	 * @param string $from               Sender address (required)
1780
	 * @param string $to                 Recipient address (required)
1781
	 * @param string $bcc                BCC recipients (required)
1782
	 * @param string $reply_to           Reply-to address (required)
1783
	 * @param string $subject            Subject line (required)
1784
	 * @param string $message            Message body (required)
1785
	 * @param string $from_name          Displayed name of the sender
1786
	 * @param string $message_format     If "html", sent text as `text/html`. Otherwise, `text/plain`. Default: "html".
1787
	 * @param string|array $attachments  Optional. Files to attach. {@see wp_mail()} for usage. Default: "".
1788
	 * @param array|false $entry         Gravity Forms entry array, related to the email. Default: false.
1789
	 * @param array|false $notification  Gravity Forms notification that triggered the email. {@see GFCommon::send_notification}. Default:false.
1790
	 */
1791
	public static function send_email( $from, $to, $bcc, $reply_to, $subject, $message, $from_name = '', $message_format = 'html', $attachments = '', $entry = false, $notification = false ) {
1792
1793
		$SendEmail = new ReflectionMethod( 'GFCommon', 'send_email' );
1794
1795
		// It was private; let's make it public
1796
		$SendEmail->setAccessible( true );
1797
1798
		// Required: $from, $to, $bcc, $replyTo, $subject, $message
1799
		// Optional: $from_name, $message_format, $attachments, $lead, $notification
1800
		$SendEmail->invoke( new GFCommon, $from, $to, $bcc, $reply_to, $subject, $message, $from_name, $message_format, $attachments, $entry, $notification );
1801
	}
1802
1803
1804
} //end class
1805
1806
1807
/**
1808
 * Generate an HTML anchor tag with a list of supported attributes
1809
 *
1810
 * @see GVCommon::get_link_html()
1811
 *
1812
 * @since 1.6
1813
 *
1814
 * @param string $href URL of the link.
1815
 * @param string $anchor_text The text or HTML inside the anchor. This is not sanitized in the function.
1816
 * @param array|string $atts Attributes to be added to the anchor tag
1817
 *
1818
 * @return string HTML output of anchor link. If empty $href, returns NULL
1819
 */
1820
function gravityview_get_link( $href = '', $anchor_text = '', $atts = array() ) {
1821 55
	return GVCommon::get_link_html( $href, $anchor_text, $atts );
1822
}
1823