Completed
Push — develop ( ec61b1...89c7bd )
by Zack
20:07
created

GVCommon::get_link_html()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 77

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
nc 16
nop 3
dl 0
loc 77
ccs 18
cts 18
cp 1
crap 6
rs 7.8795
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 array|false Array: Form object returned from Gravity Forms; False: no form ID specified or Gravity Forms isn't active.
27
	 */
28 53
	public static function get_form( $form_id ) {
29 53
		if ( empty( $form_id ) ) {
30
			return false;
31
		}
32
33
		// Only get_form_meta is cached. ::facepalm::
34 53
		if ( class_exists( 'GFFormsModel' ) ) {
35 53
			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 33
	public static function has_cap( $caps = '', $object_id = null, $user_id = null ) {
59 33
		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,
0 ignored issues
show
introduced by
Disabling pagination is prohibited in VIP context, do not set posts_per_page to -1 ever.
Loading history...
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 5
	public static function has_product_field( $form = array() ) {
146
147 5
		$product_fields = self::get_product_field_types();
148
149 5
		$fields = GFAPI::get_fields_by_type( $form, $product_fields );
150
151 5
		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 12
	public static function get_product_field_types() {
164
165 12
		$product_fields = apply_filters( 'gform_product_field_types', array( 'option', 'quantity', 'product', 'total', 'shipping', 'calculation', 'price', 'hiddenproduct', 'singleproduct', 'singleshipping' ) );
166
167 12
		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 12
	public static function entry_has_transaction_data( $entry = array() ) {
182
183 12
		if ( ! is_array( $entry ) ) {
184 1
			return false;
185
		}
186
187 12
		$has_transaction_data = false;
188
189 12
		$payment_meta = array( 'payment_status', 'payment_date', 'transaction_id', 'payment_amount', 'payment_method' );
190
191 12
		foreach ( $payment_meta as $meta ) {
192
193 12
			$has_transaction_data = \GV\Utils::get( $entry, $meta, false );
194
195 12
			if ( is_numeric( $has_transaction_data ) && ( ! floatval( $has_transaction_data ) > 0 ) ) {
196 2
				$has_transaction_data = false;
197 2
				continue;
198
			}
199
200 12
			if ( ! empty( $has_transaction_data ) ) {
201 12
				break;
202
			}
203
		}
204
205 12
		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;
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...
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
	 *
255
	 * @param bool|string $active Status of forms. Use `any` to get array of forms with any status. Default: `true`
256
	 * @param bool $trash Include forms in trash? Default: `false`
257
	 *
258
	 * @return array Empty array if GFAPI class isn't available or no forms. Otherwise, the array of Forms
259
	 */
260 1
	public static function get_forms(  $active = true, $trash = false ) {
261 1
		$forms = array();
262 1
		if ( class_exists( 'GFAPI' ) ) {
263 1
			if( 'any' === $active ) {
264
				$active_forms = GFAPI::get_forms( true, $trash );
265
				$inactive_forms = GFAPI::get_forms( false, $trash );
266
				$forms = array_merge( array_filter( $active_forms ), array_filter( $inactive_forms ) );
267
			} else {
268 1
				$forms = GFAPI::get_forms( $active, $trash );
269
			}
270
		}
271 1
		return $forms;
272
	}
273
274
	/**
275
	 * Return array of fields' id and label, for a given Form ID
276
	 *
277
	 * @access public
278
	 * @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...
279
	 * @param bool $add_default_properties
280
	 * @param bool $include_parent_field
281
	 * @return array
282
	 */
283
	public static function get_form_fields( $form = '', $add_default_properties = false, $include_parent_field = true ) {
284
285
		if ( ! is_array( $form ) ) {
286
			$form = self::get_form( $form );
287
		}
288
289
		$fields = array();
290
		$has_product_fields = false;
291
		$has_post_fields = false;
292
293
		if ( $form ) {
294
			foreach ( $form['fields'] as $field ) {
295
				if ( $include_parent_field || empty( $field['inputs'] ) ) {
296
					$fields["{$field['id']}"] = array(
0 ignored issues
show
introduced by
Array keys should be surrounded by spaces unless they contain a string or an integer.
Loading history...
297
						'label' => \GV\Utils::get( $field, 'label' ),
298
						'parent' => null,
299
						'type' => \GV\Utils::get( $field, 'type' ),
300
						'adminLabel' => \GV\Utils::get( $field, 'adminLabel' ),
301
						'adminOnly' => \GV\Utils::get( $field, 'adminOnly' ),
302
					);
303
				}
304
305
				if ( $add_default_properties && ! empty( $field['inputs'] ) ) {
306
					foreach ( $field['inputs'] as $input ) {
307
308
						if( ! empty( $input['isHidden'] ) ) {
309
							continue;
310
						}
311
312
						/**
313
                         * @hack
314
                         * In case of email/email confirmation, the input for email has the same id as the parent field
315
                         */
316
						if( 'email' === $field['type'] && false === strpos( $input['id'], '.' ) ) {
317
                            continue;
318
                        }
319
						$fields["{$input['id']}"] = array(
0 ignored issues
show
introduced by
Array keys should be surrounded by spaces unless they contain a string or an integer.
Loading history...
320
							'label' => \GV\Utils::get( $input, 'label' ),
321
							'customLabel' => \GV\Utils::get( $input, 'customLabel' ),
322
							'parent' => $field,
323
							'type' => \GV\Utils::get( $field, 'type' ),
324
							'adminLabel' => \GV\Utils::get( $field, 'adminLabel' ),
325
							'adminOnly' => \GV\Utils::get( $field, 'adminOnly' ),
326
						);
327
					}
328
				}
329
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
330
331
				if( GFCommon::is_product_field( $field['type'] ) ){
332
					$has_product_fields = true;
333
				}
334
335
				if ( GFCommon::is_post_field( $field ) ) {
336
					$has_post_fields = true;
337
				}
338
			}
339
		}
340
341
		/**
342
		 * @since 1.7
343
		 */
344
		if ( $has_post_fields ) {
345
			$fields['post_id'] = array(
346
				'label' => __( 'Post ID', 'gravityview' ),
347
				'type' => 'post_id',
348
			);
349
		}
350
351
		if ( $has_product_fields ) {
352
353
			$payment_fields = GravityView_Fields::get_all( 'pricing' );
354
355
			foreach ( $payment_fields as $payment_field ) {
356
357
				// Either the field exists ($fields['shipping']) or the form explicitly contains a `shipping` field with numeric key
358
				if( isset( $fields["{$payment_field->name}"] ) || GFCommon::get_fields_by_type( $form, $payment_field->name ) ) {
0 ignored issues
show
introduced by
Array keys should be surrounded by spaces unless they contain a string or an integer.
Loading history...
359
					continue;
360
				}
361
362
				$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...
363
					'label' => $payment_field->label,
364
					'desc' => $payment_field->description,
365
					'type' => $payment_field->name,
366
				);
367
			}
368
		}
369
370
		/**
371
		 * @filter `gravityview/common/get_form_fields` Modify the form fields shown in the Add Field field picker.
372
		 * @since 1.17
373
		 * @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`
374
		 * @param array $form GF Form array
375
		 * @param bool $include_parent_field Whether to include the parent field when getting a field with inputs
376
		 */
377
		$fields = apply_filters( 'gravityview/common/get_form_fields', $fields, $form, $include_parent_field );
378
379
		return $fields;
380
381
	}
382
383
	/**
384
	 * get extra fields from entry meta
385
	 * @param  string $form_id (default: '')
386
	 * @return array
387
	 */
388
	public static function get_entry_meta( $form_id, $only_default_column = true ) {
389
390
		$extra_fields = GFFormsModel::get_entry_meta( $form_id );
391
392
		$fields = array();
393
394
		foreach ( $extra_fields as $key => $field ){
395
			if ( ! empty( $only_default_column ) && ! empty( $field['is_default_column'] ) ) {
396
				$fields[ $key ] = array( 'label' => $field['label'], 'type' => 'entry_meta' );
397
			}
398
		}
399
400
		return $fields;
401
	}
402
403
404
	/**
405
	 * Wrapper for the Gravity Forms GFFormsModel::search_lead_ids() method
406
	 *
407
	 * @see  GFEntryList::leads_page()
408
	 * @param  int $form_id ID of the Gravity Forms form
409
	 * @since  1.1.6
410
	 * @return array|void          Array of entry IDs. Void if Gravity Forms isn't active.
411
	 */
412
	public static function get_entry_ids( $form_id, $search_criteria = array() ) {
413
414
		if ( ! class_exists( 'GFFormsModel' ) ) {
415
			return;
416
		}
417
418
		return GFFormsModel::search_lead_ids( $form_id, $search_criteria );
419
	}
420
421
	/**
422
	 * Calculates the Search Criteria used on the self::get_entries / self::get_entry methods
423
	 *
424
	 * @since 1.7.4
425
	 *
426
	 * @param array $passed_criteria array Input Criteria (search_criteria, sorting, paging)
427
	 * @param array $form_ids array Gravity Forms form IDs
428
	 * @return array
429
	 */
430 41
	public static function calculate_get_entries_criteria( $passed_criteria = array(), $form_ids = array() ) {
431
432
		$search_criteria_defaults = array(
433 41
			'search_criteria' => null,
434
			'sorting' => null,
435
			'paging' => null,
436 41
			'cache' => (isset( $passed_criteria['cache'] ) ? (bool) $passed_criteria['cache'] : true),
437
			'context_view_id' => null,
438
		);
439
440 41
		$criteria = wp_parse_args( $passed_criteria, $search_criteria_defaults );
441
442 41
		if ( ! empty( $criteria['search_criteria']['field_filters'] ) ) {
443 8
			foreach ( $criteria['search_criteria']['field_filters'] as &$filter ) {
444
445 8
				if ( ! is_array( $filter ) ) {
446 8
					continue;
447
				}
448
449
				// By default, we want searches to be wildcard for each field.
450 8
				$filter['operator'] = empty( $filter['operator'] ) ? 'contains' : $filter['operator'];
451
452
				/**
453
				 * @filter `gravityview_search_operator` Modify the search operator for the field (contains, is, isnot, etc)
454
				 * @param string $operator Existing search operator
455
				 * @param array $filter array with `key`, `value`, `operator`, `type` keys
456
				 */
457 8
				$filter['operator'] = apply_filters( 'gravityview_search_operator', $filter['operator'], $filter );
458
			}
459
460
			// don't send just the [mode] without any field filter.
461 8
			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...
462 1
				unset( $criteria['search_criteria']['field_filters']['mode'] );
463
			}
464
465
		}
466
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 3 empty lines
Loading history...
467
468
469
		/**
470
		 * Prepare date formats to be in Gravity Forms DB format;
471
		 * $passed_criteria may include date formats incompatible with Gravity Forms.
472
		 */
473 41
		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...
474
475 41
			if ( ! empty( $criteria['search_criteria'][ $key ] ) ) {
476
477
				// Use date_create instead of new DateTime so it returns false if invalid date format.
478 1
				$date = date_create( $criteria['search_criteria'][ $key ] );
479
480 1
				if ( $date ) {
481
					// Gravity Forms wants dates in the `Y-m-d H:i:s` format.
482 1
					$criteria['search_criteria'][ $key ] = $date->format( 'Y-m-d H:i:s' );
483
				} else {
484 1
					gravityview()->log->error( '{key} Date format not valid:', array( 'key' => $key, $criteria['search_criteria'][ $key ] ) );
485
486
					// If it's an invalid date, unset it. Gravity Forms freaks out otherwise.
487 41
					unset( $criteria['search_criteria'][ $key ] );
488
				}
489
			}
490
		}
491
492 41
		if ( empty( $criteria['context_view_id'] ) ) {
493
			// Calculate the context view id and send it to the advanced filter
494 6
			if ( GravityView_frontend::getInstance()->getSingleEntry() ) {
495 1
				$criteria['context_view_id'] = GravityView_frontend::getInstance()->get_context_view_id();
496 6
			} 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...
497 1
				$criteria['context_view_id'] = GravityView_frontend::getInstance()->get_context_view_id();
498 6
			} else if ( 'delete' === GFForms::get( 'action' ) ) {
499 1
				$criteria['context_view_id'] = isset( $_GET['view_id'] ) ? intval( $_GET['view_id'] ) : null;
0 ignored issues
show
introduced by
Detected access of super global var $_GET, probably need manual inspection.
Loading history...
500
			}
501
		}
502
503
		/**
504
		 * @filter `gravityview_search_criteria` Apply final criteria filter (Used by the Advanced Filter extension)
505
		 * @param array $criteria Search criteria used by GravityView
506
		 * @param array $form_ids Forms to search
507
		 * @param int $view_id ID of the view being used to search
508
		 */
509 41
		$criteria = apply_filters( 'gravityview_search_criteria', $criteria, $form_ids, $criteria['context_view_id'] );
510
511 41
		return (array)$criteria;
0 ignored issues
show
introduced by
No space after closing casting parenthesis is prohibited
Loading history...
512
	}
513
514
515
	/**
516
	 * Retrieve entries given search, sort, paging criteria
517
	 *
518
	 * @see  GFAPI::get_entries()
519
	 * @see GFFormsModel::get_field_filters_where()
520
	 * @access public
521
	 * @param int|array $form_ids The ID of the form or an array IDs of the Forms. Zero for all forms.
522
	 * @param mixed $passed_criteria (default: null)
523
	 * @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)
524
	 *
525
	 * @deprecated See \GV\View::get_entries.
526
	 *
527
	 * @return mixed False: Error fetching entries. Array: Multi-dimensional array of Gravity Forms entry arrays
528
	 */
529
	public static function get_entries( $form_ids = null, $passed_criteria = null, &$total = null ) {
530
531
		gravityview()->log->notice( '\GVCommon::get_entries is deprecated. Use \GV\View::get_entries instead.' );
532
533
		// Filter the criteria before query (includes Adv Filter)
534
		$criteria = self::calculate_get_entries_criteria( $passed_criteria, $form_ids );
535
536
		gravityview()->log->debug( '[gravityview_get_entries] Final Parameters', array( 'data' => $criteria ) );
537
538
		// Return value
539
		$return = null;
540
541
		/** Reduce # of database calls */
542
		add_filter( 'gform_is_encrypted_field', '__return_false' );
543
544
		if ( ! empty( $criteria['cache'] ) ) {
545
546
			$Cache = new GravityView_Cache( $form_ids, $criteria );
547
548
			if ( $entries = $Cache->get() ) {
549
550
				// Still update the total count when using cached results
551
				if ( ! is_null( $total ) ) {
552
					$total = GFAPI::count_entries( $form_ids, $criteria['search_criteria'] );
553
				}
554
555
				$return = $entries;
556
			}
557
		}
558
559
		if ( is_null( $return ) && class_exists( 'GFAPI' ) && ( is_numeric( $form_ids ) || is_array( $form_ids ) ) ) {
560
561
			/**
562
			 * @filter `gravityview_pre_get_entries` Define entries to be used before GFAPI::get_entries() is called
563
			 * @since 1.14
564
			 * @param  null $return If you want to override GFAPI::get_entries() and define entries yourself, tap in here.
565
			 * @param  array $criteria The final search criteria used to generate the request to `GFAPI::get_entries()`
566
			 * @param array $passed_criteria The original search criteria passed to `GVCommon::get_entries()`
567
			 * @param  int|null $total Optional. An output parameter containing the total number of entries. Pass a non-null value to generate
568
			 * @since 2.1 The $total parameter can now be overriden by reference.
569
			 * @deprecated
570
			 */
571
			$entries = apply_filters_ref_array( 'gravityview_before_get_entries', array( null, $criteria, $passed_criteria, &$total ) );
572
573
			// No entries returned from gravityview_before_get_entries
574
			if( is_null( $entries ) ) {
575
576
				$entries = GFAPI::get_entries( $form_ids, $criteria['search_criteria'], $criteria['sorting'], $criteria['paging'], $total );
577
578
				if ( is_wp_error( $entries ) ) {
579
					gravityview()->log->error( '{error}', array( 'error' => $entries->get_error_message(), 'data' => $entries ) );
580
581
					/** Remove filter added above */
582
					remove_filter( 'gform_is_encrypted_field', '__return_false' );
583
					return false;
584
				}
585
			}
586
587
			if ( ! empty( $criteria['cache'] ) && isset( $Cache ) ) {
588
589
				// Cache results
590
				$Cache->set( $entries, 'entries' );
591
592
			}
593
594
			$return = $entries;
595
		}
596
597
		/** Remove filter added above */
598
		remove_filter( 'gform_is_encrypted_field', '__return_false' );
599
600
		/**
601
		 * @filter `gravityview_entries` Modify the array of entries returned to GravityView after it has been fetched from the cache or from `GFAPI::get_entries()`.
602
		 * @param  array|null $entries Array of entries as returned by the cache or by `GFAPI::get_entries()`
603
		 * @param  array $criteria The final search criteria used to generate the request to `GFAPI::get_entries()`
604
		 * @param array $passed_criteria The original search criteria passed to `GVCommon::get_entries()`
605
		 * @param  int|null $total Optional. An output parameter containing the total number of entries. Pass a non-null value to generate
606
		 * @since 2.1 The $total parameter can now be overriden by reference.
607
		 * @deprecated
608
		 */
609
		$return = apply_filters_ref_array( 'gravityview_entries', array( $return, $criteria, $passed_criteria, &$total ) );
610
611
		return $return;
612
	}
613
614
615
	/**
616
	 * Get the entry ID from a string that may be the Entry ID or the Entry Slug
617
	 *
618
	 * @since 1.18
619
	 *
620
	 * @param string $entry_id_or_slug The ID or slug of an entry.
621
	 * @param bool $force_allow_ids Whether to force allowing getting the ID of an entry, even if custom slugs are enabled
622
	 *
623
	 * @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.
624
	 */
625 2
	public static function get_entry_id( $entry_id_or_slug = '', $force_allow_ids = false ) {
626
627 2
		$entry_id = false;
628
629
		/**
630
		 * @filter `gravityview_custom_entry_slug` Whether to enable and use custom entry slugs.
631
		 * @param boolean True: Allow for slugs based on entry values. False: always use entry IDs (default)
632
		 */
633 2
		$custom_slug = apply_filters( 'gravityview_custom_entry_slug', false );
634
635
		/**
636
		 * @filter `gravityview_custom_entry_slug_allow_id` When using a custom slug, allow access to the entry using the original slug (the Entry ID).
637
		 * - If disabled (default), only allow access to an entry using the custom slug value.  (example: `/entry/custom-slug/` NOT `/entry/123/`)
638
		 * - If enabled, you could access using the custom slug OR the entry id (example: `/entry/custom-slug/` OR `/entry/123/`)
639
		 * @param boolean $custom_slug_id_access True: allow accessing the slug by ID; False: only use the slug passed to the method.
640
		 */
641 2
		$custom_slug_id_access = $force_allow_ids || apply_filters( 'gravityview_custom_entry_slug_allow_id', false );
642
643
		/**
644
		 * If we're using custom entry slugs, we do a meta value search
645
		 * instead of doing a straightup ID search.
646
		 */
647 2
		if ( $custom_slug ) {
648
			// Search for IDs matching $entry_id_or_slug
649
			$entry_id = self::get_entry_id_from_slug( $entry_id_or_slug );
650
		}
651
652
		// If custom slug is off, search using the entry ID
653
		// ID allow ID access is on, also use entry ID as a backup
654 2
		if ( false === $custom_slug || true === $custom_slug_id_access ) {
655
			// Search for IDs matching $entry_slug
656 2
			$entry_id = $entry_id_or_slug;
657
		}
658
659 2
		return $entry_id;
660
	}
661
662
	/**
663
	 * Return a single entry object
664
	 *
665
	 * 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()`
666
	 *
667
	 * @access public
668
	 * @param string|int $entry_slug Either entry ID or entry slug string
669
	 * @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.
670
	 * @param boolean $check_entry_display Check whether the entry is visible for the current View configuration. Default: true. {@since 1.14}
671
	 * @return array|boolean
672
	 */
673 25
	public static function get_entry( $entry_slug, $force_allow_ids = false, $check_entry_display = true ) {
674
675 25
		if ( ! class_exists( 'GFAPI' ) || empty( $entry_slug ) ) {
676 24
			return false;
677
		}
678
679 2
		$entry_id = self::get_entry_id( $entry_slug, $force_allow_ids );
680
681 2
		if ( empty( $entry_id ) ) {
682
			return false;
683
		}
684
685
		// fetch the entry
686 2
		$entry = GFAPI::get_entry( $entry_id );
687
688
		/**
689
		 * @filter `gravityview/common/get_entry/check_entry_display` Override whether to check entry display rules against filters
690
		 * @since 1.16.2
691
		 * @param bool $check_entry_display Check whether the entry is visible for the current View configuration. Default: true.
692
		 * @param array $entry Gravity Forms entry array
693
		 */
694 2
		$check_entry_display = apply_filters( 'gravityview/common/get_entry/check_entry_display', $check_entry_display, $entry );
695
696 2
		if( $check_entry_display ) {
697
			// Is the entry allowed
698 1
			$entry = self::check_entry_display( $entry );
699
		}
700
701 2
		if( is_wp_error( $entry ) ) {
702
			gravityview()->log->error( '{error}', array( 'error' => $entry->get_error_message() ) );
703
			return false;
704
		}
705
706 2
		return $entry;
707
	}
708
709
	/**
710
	 * Wrapper for the GFFormsModel::matches_operation() method that adds additional comparisons, including:
711
	 * 'equals', 'greater_than_or_is', 'greater_than_or_equals', 'less_than_or_is', 'less_than_or_equals',
712
	 * 'not_contains', 'in', and 'not_in'
713
	 *
714
	 * @since 1.13 You can define context, which displays/hides based on what's being displayed (single, multiple, edit)
715
	 * @since 1.22.1 Added 'in' and 'not_in' for JSON-encoded array values, serialized non-strings
716
	 *
717
	 * @see https://docs.gravityview.co/article/252-gvlogic-shortcode
718
	 * @uses GFFormsModel::matches_operation
719
	 * @since 1.7.5
720
	 *
721
	 * @param mixed $val1 Left side of comparison
722
	 * @param mixed $val2 Right side of comparison
723
	 * @param string $operation Type of comparison
724
	 *
725
	 * @return bool True: matches, false: not matches
726
	 */
727 5
	public static function matches_operation( $val1, $val2, $operation ) {
728
729
		// Only process strings
730 5
		$val1 = ! is_string( $val1 ) ? wp_json_encode( $val1 ) : $val1;
731 5
		$val2 = ! is_string( $val2 ) ? wp_json_encode( $val2 ) : $val2;
732
733 5
		$value = false;
734
735 5
		if( 'context' === $val1 ) {
736
737
			$matching_contexts = array( $val2 );
738
739
			// We allow for non-standard contexts.
740
			switch( $val2 ) {
741
				// Check for either single or edit
742
				case 'singular':
743
					$matching_contexts = array( 'single', 'edit' );
744
					break;
745
				// Use multiple as alias for directory for consistency
746
				case 'multiple':
747
					$matching_contexts = array( 'directory' );
748
					break;
749
			}
750
751
			$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...
752
		}
753
754 5
		switch ( $operation ) {
755 5
			case 'equals':
756 2
				$value = self::matches_operation( $val1, $val2, 'is' );
757 2
				break;
758 5
			case 'greater_than_or_is':
759 5
			case 'greater_than_or_equals':
760 2
				$is    = self::matches_operation( $val1, $val2, 'is' );
761 2
				$gt    = self::matches_operation( $val1, $val2, 'greater_than' );
762 2
				$value = ( $is || $gt );
763 2
				break;
764 5
			case 'less_than_or_is':
765 5
			case 'less_than_or_equals':
766 1
				$is    = self::matches_operation( $val1, $val2, 'is' );
767 1
				$gt    = self::matches_operation( $val1, $val2, 'less_than' );
768 1
				$value = ( $is || $gt );
769 1
				break;
770 5
			case 'not_contains':
771 1
				$contains = self::matches_operation( $val1, $val2, 'contains' );
772 1
				$value    = ! $contains;
773 1
				break;
774
			/**
775
			 * @since 1.22.1 Handle JSON-encoded comparisons
776
			 */
777 5
			case 'in':
778 5
			case 'not_in':
779
780 1
				$json_val_1 = json_decode( $val1, true );
781 1
				$json_val_2 = json_decode( $val2, true );
782
783 1
				if( ! empty( $json_val_1 ) || ! empty( $json_val_2 ) ) {
784
785 1
					$json_in = false;
786 1
					$json_val_1 = $json_val_1 ? (array) $json_val_1 : array( $val1 );
787 1
					$json_val_2 = $json_val_2 ? (array) $json_val_2 : array( $val2 );
788
789
					// For JSON, we want to compare as "in" or "not in" rather than "contains"
790 1
					foreach ( $json_val_1 as $item_1 ) {
791 1
						foreach ( $json_val_2 as $item_2 ) {
792 1
							$json_in = self::matches_operation( $item_1, $item_2, 'is' );
793
794 1
							if( $json_in ) {
795 1
								break 2;
796
							}
797
						}
798
					}
799
800 1
					$value = ( $operation === 'in' ) ? $json_in : ! $json_in;
801
				}
802 1
				break;
803
804 5
			case 'less_than':
805 5
			case '<' :
806 1
				if ( is_string( $val1 ) && is_string( $val2 ) ) {
807 1
					$value = $val1 < $val2;
808
				} else {
809
					$value = GFFormsModel::matches_operation( $val1, $val2, $operation );
810
				}
811 1
				break;
812 5
			case 'greater_than':
813 5
			case '>' :
814 3
				if ( is_string( $val1 ) && is_string( $val2 ) ) {
815 3
					$value = $val1 > $val2;
816
				} else {
817
					$value = GFFormsModel::matches_operation( $val1, $val2, $operation );
818
				}
819 3
				break;
820
			default:
821 5
				$value = GFFormsModel::matches_operation( $val1, $val2, $operation );
822
		}
823
824 5
		return $value;
825
	}
826
827
	/**
828
	 *
829
	 * Checks if a certain entry is valid according to the View search filters (specially the Adv Filters)
830
	 *
831
	 * @uses GVCommon::calculate_get_entries_criteria();
832
	 * @see GFFormsModel::is_value_match()
833
	 *
834
	 * @since 1.7.4
835
	 *
836
	 * @param array $entry Gravity Forms Entry object
837
	 *
838
	 * @since 2.1
839
	 * @param \GV\View $view The View.
840
	 *
841
	 * @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.
842
	 */
843 9
	public static function check_entry_display( $entry, $view = null ) {
844
845 9
		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...
846 1
			return new WP_Error('entry_not_found', 'Entry was not found.', $entry );
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
847
		}
848
849 9
		if ( empty( $entry['form_id'] ) ) {
850 1
			return new WP_Error( 'form_id_not_set', '[apply_filters_to_entry] Entry is empty!', $entry );
851
		}
852
853 9
		if ( $view && gravityview()->plugin->supports( \GV\Plugin::FEATURE_GFQUERY ) ) {
854
855
			/**
856
			 * Check whether the entry is in the entries subset by running a modified query.
857
			 */
858 4
			add_action( 'gravityview/view/query', $entry_subset_callback = function( &$query, $view, $request ) use ( $entry ) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

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

Loading history...
859 4
				$_tmp_query       = new \GF_Query( $view->form->ID, array(
860
					'field_filters' => array(
861 4
						'mode' => 'all',
862
						array(
863 4
							'key' => 'id',
864 4
							'operation' => 'is',
865 4
							'value' => $entry['id']
0 ignored issues
show
introduced by
Each line in an array declaration must end in a comma
Loading history...
866
						)
867
					)
868
				) );
869
870 4
				$_tmp_query_parts = $_tmp_query->_introspect();
871
872
				/** @var \GF_Query $query */
873 4
				$query_parts      = $query->_introspect();
874
875 4
				$query->where( \GF_Query_Condition::_and( $_tmp_query_parts['where'], $query_parts['where'] ) );
876
877 4
			}, 10, 3 );
878
879
			// Prevent page offset from being applied to the single entry query; it's used to return to the referring page number
880 4
			add_filter( 'gravityview_search_criteria', $remove_pagenum = function( $criteria ) {
881
882 4
				$criteria['paging'] = array(
883
					'offset' => 0,
884
					'page_size' => 25
0 ignored issues
show
introduced by
Each line in an array declaration must end in a comma
Loading history...
885
				);
886
887 4
				return $criteria;
888 4
			});
889
890 4
			$entries = $view->get_entries()->all();
891
892
			// Remove the modifying filter
893 4
			remove_filter( 'gravityview_search_criteria', $remove_pagenum );
894
895 4
			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...
896 1
				remove_action( 'gravityview/view/query', $entry_subset_callback );
897 1
				return new \WP_Error( 'failed_criteria', 'Entry failed search_criteria and field_filters' );
898
			}
899
900
			// This entry is on a View with joins
901 4
			if( $entries[0]->is_multi() ) {
902
903
				$multi_entry_ids = array();
904
905
				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...
906
					$multi_entry_ids[] = (int) $multi_entry->ID;
907
				}
908
909
				if( ! in_array( (int) $entry['id'], $multi_entry_ids, true ) ) {
910
					remove_action( 'gravityview/view/query', $entry_subset_callback );
911
					return new \WP_Error( 'failed_criteria', 'Entry failed search_criteria and field_filters' );
912
				}
913
914 4
			} elseif ( (int) $entries[0]->ID !== (int) $entry['id'] ) {
915
				remove_action( 'gravityview/view/query', $entry_subset_callback );
916
				return new \WP_Error( 'failed_criteria', 'Entry failed search_criteria and field_filters' );
917
			}
918
919 4
			remove_action( 'gravityview/view/query', $entry_subset_callback );
920 4
			return $entry;
921
		}
922
923 5
		$criteria = self::calculate_get_entries_criteria( array(
924 5
			'context_view_id' => $view ? $view->ID : null,
925
		) );
926
927 5
		if ( empty( $criteria['search_criteria'] ) || ! is_array( $criteria['search_criteria'] ) ) {
928 2
			gravityview()->log->debug( '[apply_filters_to_entry] Entry approved! No search criteria found:', array( 'data' => $criteria ) );
929 2
			return $entry;
930
		}
931
932
		// Make sure the current View is connected to the same form as the Entry
933 4
		if( ! empty( $criteria['context_view_id'] ) ) {
934 4
			$context_view_id = intval( $criteria['context_view_id'] );
935 4
			$context_form_id = gravityview_get_form_id( $context_view_id );
936 4
			if( intval( $context_form_id ) !== intval( $entry['form_id'] ) ) {
937 1
				return new WP_Error( 'view_id_not_match', sprintf( '[apply_filters_to_entry] Entry form ID does not match current View connected form ID:', $entry['form_id'] ), $criteria['context_view_id'] );
938
			}
939
		}
940
941 4
		$search_criteria = $criteria['search_criteria'];
942
943
		// check entry status
944 4
		if ( array_key_exists( 'status', $search_criteria ) && $search_criteria['status'] != $entry['status'] ) {
945 1
			return new WP_Error( 'status_not_valid', sprintf( '[apply_filters_to_entry] Entry status - %s - is not valid according to filter:', $entry['status'] ), $search_criteria );
946
		}
947
948
		// check entry date
949
		// @todo: Does it make sense to apply the Date create filters to the single entry?
950
951
		// field_filters
952 4
		if ( empty( $search_criteria['field_filters'] ) || ! is_array( $search_criteria['field_filters'] ) ) {
953 1
			gravityview()->log->debug( '[apply_filters_to_entry] Entry approved! No field filters criteria found:', array( 'data' => $search_criteria ) );
954 1
			return $entry;
955
		}
956
957 4
		$filters = $search_criteria['field_filters'];
958
959 4
		$mode = array_key_exists( 'mode', $filters ) ? strtolower( $filters['mode'] ) : 'all';
960
961 4
		$mode = $mode ? : 'all'; // If mode is an empty string, assume it's 'all'
962
963
		// Prevent the mode from being processed below
964 4
		unset( $filters['mode'] );
965
966 4
		$form = self::get_form( $entry['form_id'] );
967
968 4
		foreach ( $filters as $filter ) {
969 4
			$operator = isset( $filter['operator'] ) ? strtolower( $filter['operator'] ) : 'is';
970
971 4
			if ( ! isset( $filter['key'] ) ) {
972 1
				gravityview()->log->debug( '[apply_filters_to_entry] Filter key not set, any field mode', array( 'filter' => $filter ) );
973
				/**
974
				 * This is a cross-field search. Let's start digging'.
975
				 */
976 1
				foreach ( \GV\Utils::get( $form, 'fields', array() ) as $field ) {
977 1
					$field_value = GFFormsModel::get_lead_field_value( $entry, $field );
978 1
					if ( $is_value_match = GravityView_GFFormsModel::is_value_match( $field_value, $filter['value'], $operator, $field ) ) {
979 1
						if ( 'any' === $mode) {
0 ignored issues
show
introduced by
No space before closing parenthesis is prohibited
Loading history...
980 1
							return $entry; // All good here
981
						} // mode === 'all'
982 1
						continue 2; // Next filter
983
					}
984
					// If none of the values match and we're in all mode, drop down to the error below.
985
				}
986
987 1
				if ( 'all' === $mode ) {
988 1
					return new WP_Error('failed_criteria', '[apply_filters_to_entry] Entry cannot be displayed. Failed a subcriterium for any field in ALL mode', $filter );
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
989
				}
990
991 1
				continue;
992
			}
993
994 3
			$k = $filter['key'];
995
996 3
			$field = self::get_field( $form, $k );
997
998 3
			if ( is_null( $field ) ) {
999 1
				$field_value = isset( $entry[ $k ] ) ? $entry[ $k ] : null;
1000 1
				$field = $k;
1001
			} else {
1002 2
				$field_value  = GFFormsModel::get_lead_field_value( $entry, $field );
1003
				 // If it's a complex field, then fetch the input's value, if exists at the current key. Otherwise, let GF handle it
1004 2
				$field_value = ( is_array( $field_value ) && isset( $field_value[ $k ] ) ) ? \GV\Utils::get( $field_value, $k ) : $field_value;
1005
			}
1006
1007 3
			$is_value_match = GravityView_GFFormsModel::is_value_match( $field_value, $filter['value'], $operator, $field );
1008
1009
			// Any match is all we need to know
1010 3
			if ( $is_value_match && 'any' === $mode ) {
1011 1
				return $entry;
1012
			}
1013
1014
			// Any failed match is a total fail
1015 3
			if ( ! $is_value_match && 'all' === $mode ) {
1016 3
				return new WP_Error('failed_criteria', '[apply_filters_to_entry] Entry cannot be displayed. Failed a criterium for ALL mode', $filter );
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
1017
			}
1018
		}
1019
1020
		// at this point, if in ALL mode, then entry is approved - all conditions were met.
1021
		// Or, for ANY mode, means none of the conditions were satisfied, so entry is not approved
1022 4
		if ( 'all' === $mode ) {
1023 4
			gravityview()->log->debug( '[apply_filters_to_entry] Entry approved: all conditions were met' );
1024 4
			return $entry;
1025
		} else {
1026 1
			return new WP_Error('failed_any_criteria', '[apply_filters_to_entry] Entry cannot be displayed. Failed all the criteria for ANY mode', $filters );
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
1027
		}
1028
1029
	}
1030
1031
1032
	/**
1033
	 * Allow formatting date and time based on GravityView standards
1034
	 *
1035
	 * @since 1.16
1036
	 *
1037
	 * @see GVCommon_Test::test_format_date for examples
1038
	 *
1039
	 * @param string $date_string The date as stored by Gravity Forms (`Y-m-d h:i:s` GMT)
1040
	 * @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
1041
	 * - `raw` Un-formatted date string in original `Y-m-d h:i:s` format
1042
	 * - `timestamp` Integer timestamp returned by GFCommon::get_local_timestamp()
1043
	 * - `diff` "%s ago" format, unless other `format` is defined
1044
	 * - `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.
1045
	 * - `time` Include time in the `GFCommon::format_date()` output
1046
	 * - `format` Define your own date format, or `diff` format
1047
	 *
1048
	 * @return int|null|string Formatted date based on the original date
1049
	 */
1050 4
	public static function format_date( $date_string = '', $args = array() ) {
1051
1052
		$default_atts = array(
1053 4
			'raw' => false,
1054
			'timestamp' => false,
1055
			'diff' => false,
1056
			'human' => false,
1057
			'format' => '',
1058
			'time' => false,
1059
		);
1060
1061 4
		$atts = wp_parse_args( $args, $default_atts );
1062
1063
		/**
1064
		 * Gravity Forms code to adjust date to locally-configured Time Zone
1065
		 * @see GFCommon::format_date() for original code
1066
		 */
1067 4
		$date_gmt_time   = mysql2date( 'G', $date_string );
1068 4
		$date_local_timestamp = GFCommon::get_local_timestamp( $date_gmt_time );
1069
1070 4
		$format  = \GV\Utils::get( $atts, 'format' );
1071 4
		$is_human  = ! empty( $atts['human'] );
1072 4
		$is_diff  = ! empty( $atts['diff'] );
1073 4
		$is_raw = ! empty( $atts['raw'] );
1074 4
		$is_timestamp = ! empty( $atts['timestamp'] );
1075 4
		$include_time = ! empty( $atts['time'] );
1076
1077
		// If we're using time diff, we want to have a different default format
1078 4
		if( empty( $format ) ) {
1079
			/* translators: %s: relative time from now, used for generic date comparisons. "1 day ago", or "20 seconds ago" */
1080 4
			$format = $is_diff ? esc_html__( '%s ago', 'gravityview' ) : get_option( 'date_format' );
1081
		}
1082
1083
		// If raw was specified, don't modify the stored value
1084 4
		if ( $is_raw ) {
1085 1
			$formatted_date = $date_string;
1086 4
		} elseif( $is_timestamp ) {
1087 2
			$formatted_date = $date_local_timestamp;
1088 3
		} elseif ( $is_diff ) {
1089 1
			$formatted_date = sprintf( $format, human_time_diff( $date_gmt_time ) );
1090
		} else {
1091 3
			$formatted_date = GFCommon::format_date( $date_string, $is_human, $format, $include_time );
1092
		}
1093
1094 4
		unset( $format, $is_diff, $is_human, $is_timestamp, $is_raw, $date_gmt_time, $date_local_timestamp, $default_atts );
1095
1096 4
		return $formatted_date;
1097
	}
1098
1099
	/**
1100
	 * Retrieve the label of a given field id (for a specific form)
1101
	 *
1102
	 * @access public
1103
	 * @since 1.17 Added $field_value parameter
1104
	 *
1105
	 * @param array $form Gravity Forms form array
1106
	 * @param string $field_id ID of the field. If an input, full input ID (like `1.3`)
1107
	 * @param string|array $field_value Raw value of the field.
1108
	 * @return string
1109
	 */
1110 1
	public static function get_field_label( $form = array(), $field_id = '', $field_value = '' ) {
1111
1112 1
		if ( empty( $form ) || empty( $field_id ) ) {
1113
			return '';
1114
		}
1115
1116 1
		$field = self::get_field( $form, $field_id );
1117
1118 1
		$label = \GV\Utils::get( $field, 'label' );
1119
1120 1
		if( floor( $field_id ) !== floatval( $field_id ) ) {
1121 1
			$label = GFFormsModel::get_choice_text( $field, $field_value, $field_id );
1122
		}
1123
1124 1
		return $label;
1125
	}
1126
1127
1128
	/**
1129
	 * Returns the field details array of a specific form given the field id
1130
	 *
1131
	 * Alias of GFFormsModel::get_field
1132
	 *
1133
	 * @since 1.19 Allow passing form ID as well as form array
1134
	 *
1135
	 * @uses GFFormsModel::get_field
1136
	 * @see GFFormsModel::get_field
1137
	 * @access public
1138
	 * @param array|int $form Form array or ID
1139
	 * @param string|int $field_id
1140
	 * @return GF_Field|null Gravity Forms field object, or NULL: Gravity Forms GFFormsModel does not exist or field at $field_id doesn't exist.
1141
	 */
1142 7
	public static function get_field( $form, $field_id ) {
1143
1144 7
		if ( is_numeric( $form ) ) {
1145
			$form = GFAPI::get_form( $form );
1146
		}
1147
1148 7
		if ( class_exists( 'GFFormsModel' ) ){
1149 7
			return GFFormsModel::get_field( $form, $field_id );
1150
		} else {
1151
			return null;
1152
		}
1153
	}
1154
1155
1156
	/**
1157
	 * Check whether the post is GravityView
1158
	 *
1159
	 * - Check post type. Is it `gravityview`?
1160
	 * - Check shortcode
1161
	 *
1162
	 * @param  WP_Post      $post WordPress post object
1163
	 * @return boolean           True: yep, GravityView; No: not!
1164
	 */
1165 2
	public static function has_gravityview_shortcode( $post = null ) {
1166 2
		if ( ! is_a( $post, 'WP_Post' ) ) {
1167 1
			return false;
1168
		}
1169
1170 2
		if ( 'gravityview' === get_post_type( $post ) ) {
1171 2
			return true;
1172
		}
1173
1174 2
		return self::has_shortcode_r( $post->post_content, 'gravityview' ) ? true : false;
1175
1176
	}
1177
1178
1179
	/**
1180
	 * Placeholder until the recursive has_shortcode() patch is merged
1181
	 * @see https://core.trac.wordpress.org/ticket/26343#comment:10
1182
	 * @param string $content Content to check whether there's a shortcode
1183
	 * @param string $tag Current shortcode tag
1184
	 */
1185 3
	public static function has_shortcode_r( $content, $tag = 'gravityview' ) {
1186 3
		if ( false === strpos( $content, '[' ) ) {
1187 1
			return false;
1188
		}
1189
1190 3
		if ( shortcode_exists( $tag ) ) {
1191
1192 3
			$shortcodes = array();
1193
1194 3
			preg_match_all( '/' . get_shortcode_regex() . '/s', $content, $matches, PREG_SET_ORDER );
1195 3
			if ( empty( $matches ) ){
1196 1
				return false;
1197
			}
1198
1199 3
			foreach ( $matches as $shortcode ) {
1200 3
				if ( $tag === $shortcode[2] ) {
1201
1202
					// Changed this to $shortcode instead of true so we get the parsed atts.
1203 3
					$shortcodes[] = $shortcode;
1204
1205 1
				} else if ( isset( $shortcode[5] ) && $results = self::has_shortcode_r( $shortcode[5], $tag ) ) {
1206 1
					foreach( $results as $result ) {
1207 3
						$shortcodes[] = $result;
1208
					}
1209
				}
1210
			}
1211
1212 3
			return $shortcodes;
1213
		}
1214
		return false;
1215
	}
1216
1217
1218
1219
	/**
1220
	 * Get the views for a particular form
1221
	 *
1222
	 * @since 1.15.2 Add $args array and limit posts_per_page to 500
1223
	 *
1224
	 * @uses get_posts()
1225
	 *
1226
	 * @param  int $form_id Gravity Forms form ID
1227
	 * @param  array $args Pass args sent to get_posts()
1228
	 *
1229
	 * @return array          Array with view details, as returned by get_posts()
1230
	 */
1231 1
	public static function get_connected_views( $form_id, $args = array() ) {
1232
1233 1
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1234
1235
		$defaults = array(
1236 1
			'post_type'      => 'gravityview',
1237 1
			'posts_per_page' => 100,
0 ignored issues
show
introduced by
Detected high pagination limit, posts_per_page is set to 100
Loading history...
1238 1
			'meta_key'       => '_gravityview_form_id',
0 ignored issues
show
introduced by
Detected usage of meta_key, possible slow query.
Loading history...
1239 1
			'meta_value'     => (int) $form_id,
0 ignored issues
show
introduced by
Detected usage of meta_value, possible slow query.
Loading history...
1240
		);
1241 1
		$args     = wp_parse_args( $args, $defaults );
1242 1
		$views    = get_posts( $args );
1243
1244 1
		$views_with_joins = $wpdb->get_results( "SELECT `post_id`, `meta_value` FROM $wpdb->postmeta WHERE `meta_key` = '_gravityview_form_joins'" );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
1245
1246 1
		$joined_forms = array();
1247 1
		foreach ( $views_with_joins as $view ) {
1248
1249 1
			$data = unserialize( $view->meta_value );
1250
1251 1
			if( ! $data || ! is_array( $data ) ) {
1252 1
				continue;
1253
			}
1254
1255
			foreach ( $data as $datum ) {
1256
				if ( ! empty( $datum[2] ) && (int) $datum[2] === (int) $form_id ) {
1257
					$joined_forms[] = $view->post_id;
1258
				}
1259
			}
1260
		}
1261
1262 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...
1263
			$joined_args  = array(
1264
				'post_type'      => 'gravityview',
1265
				'posts_per_page' => $args['posts_per_page'],
1266
				'post__in'       => $joined_forms,
1267
			);
1268
			$views = array_merge( $views, get_posts( $joined_args ) );
1269
		}
1270
1271 1
		return $views;
1272
	}
1273
1274
	/**
1275
	 * Get the Gravity Forms form ID connected to a View
1276
	 *
1277
	 * @param int $view_id The ID of the View to get the connected form of
1278
	 *
1279
	 * @return false|string ID of the connected Form, if exists. Empty string if not. False if not the View ID isn't valid.
1280
	 */
1281 4
	public static function get_meta_form_id( $view_id ) {
1282 4
		return get_post_meta( $view_id, '_gravityview_form_id', true );
1283
	}
1284
1285
	/**
1286
	 * Get the template ID (`list`, `table`, `datatables`, `map`) for a View
1287
	 *
1288
	 * @see GravityView_Template::template_id
1289
	 *
1290
	 * @param int $view_id The ID of the View to get the layout of
1291
	 *
1292
	 * @return string GravityView_Template::template_id value. Empty string if not.
1293
	 */
1294 114
	public static function get_meta_template_id( $view_id ) {
1295 114
		return get_post_meta( $view_id, '_gravityview_directory_template', true );
1296
	}
1297
1298
1299
	/**
1300
	 * Get all the settings for a View
1301
	 *
1302
	 * @uses  \GV\View_Settings::defaults() Parses the settings with the plugin defaults as backups.
1303
	 * @param  int $post_id View ID
1304
	 * @return array          Associative array of settings with plugin defaults used if not set by the View
1305
	 */
1306 113
	public static function get_template_settings( $post_id ) {
1307
1308 113
		$settings = get_post_meta( $post_id, '_gravityview_template_settings', true );
1309
1310 113
		if ( class_exists( '\GV\View_Settings' ) ) {
1311
1312 113
			return wp_parse_args( (array)$settings, \GV\View_Settings::defaults() );
0 ignored issues
show
introduced by
No space after closing casting parenthesis is prohibited
Loading history...
1313
1314
		}
1315
1316
		// Backup, in case GravityView_View_Data isn't loaded yet.
1317
		return $settings;
1318
	}
1319
1320
	/**
1321
	 * Get the setting for a View
1322
	 *
1323
	 * If the setting isn't set by the View, it returns the plugin default.
1324
	 *
1325
	 * @param  int $post_id View ID
1326
	 * @param  string $key     Key for the setting
1327
	 * @return mixed|null          Setting value, or NULL if not set.
1328
	 */
1329 1
	public static function get_template_setting( $post_id, $key ) {
1330
1331 1
		$settings = self::get_template_settings( $post_id );
1332
1333 1
		if ( isset( $settings[ $key ] ) ) {
1334 1
			return $settings[ $key ];
1335
		}
1336
1337
		return null;
1338
	}
1339
1340
	/**
1341
	 * Get the field configuration for the View
1342
	 *
1343
	 * array(
1344
	 *
1345
	 * 	[other zones]
1346
	 *
1347
	 * 	'directory_list-title' => array(
1348
	 *
1349
	 *   	[other fields]
1350
	 *
1351
	 *  	'5372653f25d44' => array(
1352
	 *  		'id' => string '9' (length=1)
1353
	 *  		'label' => string 'Screenshots' (length=11)
1354
	 *			'show_label' => string '1' (length=1)
1355
	 *			'custom_label' => string '' (length=0)
1356
	 *			'custom_class' => string 'gv-gallery' (length=10)
1357
	 * 			'only_loggedin' => string '0' (length=1)
1358
	 *			'only_loggedin_cap' => string 'read' (length=4)
1359
	 *  	)
1360
	 *
1361
	 * 		[other fields]
1362
	 *  )
1363
	 *
1364
	 * 	[other zones]
1365
	 * )
1366
	 *
1367
	 * @since 1.17.4 Added $apply_filter parameter
1368
	 *
1369
	 * @param  int $post_id View ID
1370
	 * @param  bool $apply_filter Whether to apply the `gravityview/configuration/fields` filter [Default: true]
1371
	 * @return array          Multi-array of fields with first level being the field zones. See code comment.
1372
	 */
1373 1
	public static function get_directory_fields( $post_id, $apply_filter = true ) {
1374 1
		$fields = get_post_meta( $post_id, '_gravityview_directory_fields', true );
1375
1376 1
		if ( $apply_filter ) {
1377
			/**
1378
			 * @filter `gravityview/configuration/fields` Filter the View fields' configuration array
1379
			 * @since 1.6.5
1380
			 *
1381
			 * @param $fields array Multi-array of fields with first level being the field zones
1382
			 * @param $post_id int Post ID
1383
			 */
1384 1
			$fields = apply_filters( 'gravityview/configuration/fields', $fields, $post_id );
1385
1386
			/**
1387
			 * @filter `gravityview/view/configuration/fields` Filter the View fields' configuration array.
1388
			 * @since 2.0
1389
			 *
1390
			 * @param array $fields Multi-array of fields with first level being the field zones.
1391
			 * @param \GV\View $view The View the fields are being pulled for.
1392
			 */
1393 1
			$fields = apply_filters( 'gravityview/view/configuration/fields', $fields, \GV\View::by_id( $post_id ) );
1394
		}
1395
1396 1
		return $fields;
1397
	}
1398
1399
	/**
1400
	 * Get the widget configuration for a View
1401
	 *
1402
	 * @param int $view_id View ID
1403
	 * @param bool $json_decode Whether to JSON-decode the widget values. Default: `false`
1404
	 *
1405
	 * @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"
1406
	 */
1407
	public static function get_directory_widgets( $view_id, $json_decode = false ) {
1408
1409
		$view_widgets = get_post_meta( $view_id, '_gravityview_directory_widgets', true );
1410
1411
		$defaults = array(
1412
			'header_top' => array(),
1413
			'header_left' => array(),
1414
			'header_right' => array(),
1415
			'footer_left' => array(),
1416
			'footer_right' => array(),
1417
		);
1418
1419
		$directory_widgets = wp_parse_args( $view_widgets, $defaults );
1420
1421
		if( $json_decode ) {
1422
			$directory_widgets = gv_map_deep( $directory_widgets, 'gv_maybe_json_decode' );
1423
		}
1424
1425
		return $directory_widgets;
1426
	}
1427
1428
1429
	/**
1430
	 * Render dropdown (select) with the list of sortable fields from a form ID
1431
	 *
1432
	 * @access public
1433
	 * @param  int $formid Form ID
1434
	 * @return string         html
1435
	 */
1436
	public static function get_sortable_fields( $formid, $current = '' ) {
1437
		$output = '<option value="" ' . selected( '', $current, false ).'>' . esc_html__( 'Default', 'gravityview' ) .'</option>';
1438
1439
		if ( empty( $formid ) ) {
1440
			return $output;
1441
		}
1442
1443
		$fields = self::get_sortable_fields_array( $formid );
1444
1445
		if ( ! empty( $fields ) ) {
1446
1447
			$blacklist_field_types = apply_filters( 'gravityview_blacklist_field_types', array( 'list', 'textarea' ), null );
1448
1449
			foreach ( $fields as $id => $field ) {
1450
				if ( in_array( $field['type'], $blacklist_field_types ) ) {
1451
					continue;
1452
				}
1453
1454
				$output .= '<option value="'. $id .'" '. selected( $id, $current, false ).'>'. esc_attr( $field['label'] ) .'</option>';
1455
			}
1456
		}
1457
1458
		return $output;
1459
	}
1460
1461
	/**
1462
	 *
1463
	 * @param int $formid Gravity Forms form ID
1464
	 * @param array $blacklist Field types to exclude
1465
	 *
1466
	 * @since 1.8
1467
	 *
1468
	 * @todo Get all fields, check if sortable dynamically
1469
	 *
1470
	 * @return array
1471
	 */
1472
	public static function get_sortable_fields_array( $formid, $blacklist = array( 'list', 'textarea' ) ) {
1473
1474
		// Get fields with sub-inputs and no parent
1475
		$fields = self::get_form_fields( $formid, true, false );
1476
1477
		$date_created = array(
1478
			'date_created' => array(
1479
				'type' => 'date_created',
1480
				'label' => __( 'Date Created', 'gravityview' ),
1481
			),
1482
		);
1483
1484
        $fields = $date_created + $fields;
1485
1486
		$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...
1487
1488
		// TODO: Convert to using array_filter
1489
		foreach( $fields as $id => $field ) {
1490
1491
			if( in_array( $field['type'], $blacklist_field_types ) ) {
1492
				unset( $fields[ $id ] );
1493
			}
1494
		}
1495
1496
        /**
1497
         * @filter `gravityview/common/sortable_fields` Filter the sortable fields
1498
         * @since 1.12
1499
         * @param array $fields Sub-set of GF form fields that are sortable
1500
         * @param int $formid The Gravity Forms form ID that the fields are from
1501
         */
1502
        $fields = apply_filters( 'gravityview/common/sortable_fields', $fields, $formid );
1503
1504
		return $fields;
1505
	}
1506
1507
	/**
1508
	 * Returns the GF Form field type for a certain field(id) of a form
1509
	 * @param  object $form     Gravity Forms form
1510
	 * @param  mixed $field_id Field ID or Field array
1511
	 * @return string field type
1512
	 */
1513
	public static function get_field_type( $form = null, $field_id = '' ) {
1514
1515
		if ( ! empty( $field_id ) && ! is_array( $field_id ) ) {
1516
			$field = self::get_field( $form, $field_id );
1517
		} else {
1518
			$field = $field_id;
1519
		}
1520
1521
		return class_exists( 'RGFormsModel' ) ? RGFormsModel::get_input_type( $field ) : '';
1522
1523
	}
1524
1525
1526
	/**
1527
	 * Checks if the field type is a 'numeric' field type (e.g. to be used when sorting)
1528
	 * @param  int|array  $form  form ID or form array
1529
	 * @param  int|array  $field field key or field array
1530
	 * @return boolean
1531
	 */
1532
	public static function is_field_numeric(  $form = null, $field = '' ) {
1533
1534
		if ( ! is_array( $form ) && ! is_array( $field ) ) {
1535
			$form = self::get_form( $form );
1536
		}
1537
1538
		// If entry meta, it's a string. Otherwise, numeric
1539
		if( ! is_numeric( $field ) && is_string( $field ) ) {
1540
			$type = $field;
1541
		} else {
1542
			$type = self::get_field_type( $form, $field );
1543
		}
1544
1545
		/**
1546
		 * @filter `gravityview/common/numeric_types` What types of fields are numeric?
1547
		 * @since 1.5.2
1548
		 * @param array $numeric_types Fields that are numeric. Default: `[ number, time ]`
1549
		 */
1550
		$numeric_types = apply_filters( 'gravityview/common/numeric_types', array( 'number', 'time' ) );
1551
1552
		// Defer to GravityView_Field setting, if the field type is registered and `is_numeric` is true
1553
		if( $gv_field = GravityView_Fields::get( $type ) ) {
1554
			if( true === $gv_field->is_numeric ) {
1555
				$numeric_types[] = $gv_field->is_numeric;
1556
			}
1557
		}
1558
1559
		$return = in_array( $type, $numeric_types );
1560
1561
		return $return;
1562
	}
1563
1564
	/**
1565
	 * Encrypt content using Javascript so that it's hidden when JS is disabled.
1566
	 *
1567
	 * This is mostly used to hide email addresses from scraper bots.
1568
	 *
1569
	 * @param string $content Content to encrypt
1570
	 * @param string $message Message shown if Javascript is disabled
1571
	 *
1572
	 * @see  https://github.com/katzwebservices/standalone-phpenkoder StandalonePHPEnkoder on Github
1573
	 *
1574
	 * @since 1.7
1575
	 *
1576
	 * @return string Content, encrypted
1577
	 */
1578
	public static function js_encrypt( $content, $message = '' ) {
1579
1580
		$output = $content;
1581
1582
		if ( ! class_exists( 'StandalonePHPEnkoder' ) ) {
1583
			include_once( GRAVITYVIEW_DIR . 'includes/lib/StandalonePHPEnkoder.php' );
1584
		}
1585
1586
		if ( class_exists( 'StandalonePHPEnkoder' ) ) {
1587
1588
			$enkoder = new StandalonePHPEnkoder;
1589
1590
			$message = empty( $message ) ? __( 'Email hidden; Javascript is required.', 'gravityview' ) : $message;
1591
1592
			/**
1593
			 * @filter `gravityview/phpenkoder/msg` Modify the message shown when Javascript is disabled and an encrypted email field is displayed
1594
			 * @since 1.7
1595
			 * @param string $message Existing message
1596
			 * @param string $content Content to encrypt
1597
			 */
1598
			$enkoder->enkode_msg = apply_filters( 'gravityview/phpenkoder/msg', $message, $content );
1599
1600
			$output = $enkoder->enkode( $content );
1601
		}
1602
1603
		return $output;
1604
	}
1605
1606
	/**
1607
	 *
1608
	 * Do the same than parse_str without max_input_vars limitation:
1609
	 * Parses $string as if it were the query string passed via a URL and sets variables in the current scope.
1610
	 * @param $string string string to parse (not altered like in the original parse_str(), use the second parameter!)
1611
	 * @param $result array  If the second parameter is present, variables are stored in this variable as array elements
1612
	 * @return bool true or false if $string is an empty string
1613
	 * @since  1.5.3
1614
	 *
1615
	 * @author rubo77 at https://gist.github.com/rubo77/6821632
1616
	 **/
1617
	public static function gv_parse_str( $string, &$result ) {
1618
		if ( empty( $string ) ) {
1619
			return false;
1620
		}
1621
1622
		$result = array();
1623
1624
		// find the pairs "name=value"
1625
		$pairs = explode( '&', $string );
1626
1627
		foreach ( $pairs as $pair ) {
1628
			// use the original parse_str() on each element
1629
			parse_str( $pair, $params );
1630
1631
			$k = key( $params );
1632
			if ( ! isset( $result[ $k ] ) ) {
1633
				$result += $params;
1634
			} elseif ( array_key_exists( $k, $params ) && is_array( $params[ $k ] ) ) {
1635
				$result[ $k ] = self::array_merge_recursive_distinct( $result[ $k ], $params[ $k ] );
1636
			}
1637
		}
1638
		return true;
1639
	}
1640
1641
1642
	/**
1643
	 * Generate an HTML anchor tag with a list of supported attributes
1644
	 *
1645
	 * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a Supported attributes defined here
1646
	 * @uses esc_url_raw() to sanitize $href
1647
	 * @uses esc_attr() to sanitize $atts
1648
	 *
1649
	 * @since 1.6
1650
	 *
1651
	 * @param string $href URL of the link. Sanitized using `esc_url_raw()`
1652
	 * @param string $anchor_text The text or HTML inside the anchor. This is not sanitized in the function.
1653
	 * @param array|string $atts Attributes to be added to the anchor tag. Parsed by `wp_parse_args()`, sanitized using `esc_attr()`
1654
	 *
1655
	 * @return string HTML output of anchor link. If empty $href, returns NULL
1656
	 */
1657 38
	public static function get_link_html( $href = '', $anchor_text = '', $atts = array() ) {
1658
1659
		// Supported attributes for anchor tags. HREF left out intentionally.
1660
		$allowed_atts = array(
1661 38
			'href' => null, // Will override the $href argument if set
1662
			'title' => null,
1663
			'rel' => null,
1664
			'id' => null,
1665
			'class' => null,
1666
			'target' => null,
1667
			'style' => null,
1668
1669
			// Used by GravityView
1670
			'data-viewid' => null,
1671
1672
			// Not standard
1673
			'hreflang' => null,
1674
			'type' => null,
1675
			'tabindex' => null,
1676
1677
			// Deprecated HTML4 but still used
1678
			'name' => null,
1679
			'onclick' => null,
1680
			'onchange' => null,
1681
			'onkeyup' => null,
1682
1683
			// HTML5 only
1684
			'download' => null,
1685
			'media' => null,
1686
			'ping' => null,
1687
		);
1688
1689
		/**
1690
		 * @filter `gravityview/get_link/allowed_atts` Modify the attributes that are allowed to be used in generating links
1691
		 * @param array $allowed_atts Array of attributes allowed
1692
		 */
1693 38
		$allowed_atts = apply_filters( 'gravityview/get_link/allowed_atts', $allowed_atts );
1694
1695
		// Make sure the attributes are formatted as array
1696 38
		$passed_atts = wp_parse_args( $atts );
1697
1698
		// Make sure the allowed attributes are only the ones in the $allowed_atts list
1699 38
		$final_atts = shortcode_atts( $allowed_atts, $passed_atts );
1700
1701
		// Remove attributes with empty values
1702 38
		$final_atts = array_filter( $final_atts );
1703
1704
		// If the href wasn't passed as an attribute, use the value passed to the function
1705 38
		if ( empty( $final_atts['href'] ) && ! empty( $href ) ) {
1706 38
			$final_atts['href'] = $href;
1707
		}
1708
1709 38
		$final_atts['href'] = esc_url_raw( $href );
1710
1711
		/**
1712
		 * Fix potential security issue with target=_blank
1713
		 * @see https://dev.to/ben/the-targetblank-vulnerability-by-example
1714
		 */
1715 38
		if( '_blank' === \GV\Utils::get( $final_atts, 'target' ) ) {
1716 4
			$final_atts['rel'] = trim( \GV\Utils::get( $final_atts, 'rel', '' ) . ' noopener noreferrer' );
1717
		}
1718
1719
		// Sort the attributes alphabetically, to help testing
1720 38
		ksort( $final_atts );
1721
1722
		// For each attribute, generate the code
1723 38
		$output = '';
1724 38
		foreach ( $final_atts as $attr => $value ) {
1725 38
			$output .= sprintf( ' %s="%s"', $attr, esc_attr( $value ) );
1726
		}
1727
1728 38
		if( '' !== $output ) {
1729 38
			$output = '<a' . $output . '>' . $anchor_text . '</a>';
1730
		}
1731
1732 38
		return $output;
1733
	}
1734
1735
	/**
1736
	 * array_merge_recursive does indeed merge arrays, but it converts values with duplicate
1737
	 * keys to arrays rather than overwriting the value in the first array with the duplicate
1738
	 * value in the second array, as array_merge does.
1739
	 *
1740
	 * @see http://php.net/manual/en/function.array-merge-recursive.php
1741
	 *
1742
	 * @since  1.5.3
1743
	 * @param array $array1
1744
	 * @param array $array2
1745
	 * @return array
1746
	 * @author Daniel <daniel (at) danielsmedegaardbuus (dot) dk>
1747
	 * @author Gabriel Sobrinho <gabriel (dot) sobrinho (at) gmail (dot) com>
1748
	 */
1749
	public static function array_merge_recursive_distinct( array &$array1, array &$array2 ) {
1750
		$merged = $array1;
1751
		foreach ( $array2 as $key => $value ) {
1752
			if ( is_array( $value ) && isset( $merged[ $key ] ) && is_array( $merged[ $key ] ) ) {
1753
				$merged[ $key ] = self::array_merge_recursive_distinct( $merged[ $key ], $value );
1754
			} else if ( is_numeric( $key ) && isset( $merged[ $key ] ) ) {
1755
				$merged[] = $value;
1756
			} else {
1757
				$merged[ $key ] = $value;
1758
			}
1759
		}
1760
1761
		return $merged;
1762
	}
1763
1764
	/**
1765
	 * Get WordPress users with reasonable limits set
1766
	 *
1767
	 * @param string $context Where are we using this information (e.g. change_entry_creator, search_widget ..)
1768
	 * @param array $args Arguments to modify the user query. See get_users() {@since 1.14}
1769
	 * @return array Array of WP_User objects.
1770
	 */
1771
	public static function get_users( $context = 'change_entry_creator', $args = array() ) {
1772
1773
		$default_args = array(
1774
			'number' => 2000,
1775
			'orderby' => 'display_name',
1776
			'order' => 'ASC',
1777
			'fields' => array( 'ID', 'display_name', 'user_login', 'user_nicename' )
1778
		);
1779
1780
		// Merge in the passed arg
1781
		$get_users_settings = wp_parse_args( $args, $default_args );
1782
1783
		/**
1784
		 * @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
1785
		 * `$context` is where are we using this information (e.g. change_entry_creator, search_widget ..)
1786
		 * @param array $settings Settings array, with `number` key defining the # of users to display
1787
		 */
1788
		$get_users_settings = apply_filters( 'gravityview/get_users/'. $context, apply_filters( 'gravityview_change_entry_creator_user_parameters', $get_users_settings ) );
1789
1790
		return get_users( $get_users_settings );
1791
	}
1792
1793
1794
    /**
1795
     * Display updated/error notice
1796
     *
1797
     * @since 1.19.2 Added $cap and $object_id parameters
1798
     *
1799
     * @param string $notice text/HTML of notice
1800
     * @param string $class CSS class for notice (`updated` or `error`)
1801
     * @param string $cap [Optional] Define a capability required to show a notice. If not set, displays to all caps.
1802
     *
1803
     * @return string
1804
     */
1805 16
    public static function generate_notice( $notice, $class = '', $cap = '', $object_id = null ) {
1806
1807
    	// If $cap is defined, only show notice if user has capability
1808 16
    	if( $cap && ! GVCommon::has_cap( $cap, $object_id ) ) {
1809 6
    		return '';
1810
	    }
1811
1812 11
        return '<div class="gv-notice '.gravityview_sanitize_html_class( $class ) .'">'. $notice .'</div>';
1813
    }
1814
1815
	/**
1816
	 * Inspired on \GFCommon::encode_shortcodes, reverse the encoding by replacing the ascii characters by the shortcode brackets
1817
	 * @since 1.16.5
1818
	 * @param string $string Input string to decode
1819
	 * @return string $string Output string
1820
	 */
1821
	public static function decode_shortcodes( $string ) {
1822
		$replace = array( '[', ']', '"' );
1823
		$find = array( '&#91;', '&#93;', '&quot;' );
1824
		$string = str_replace( $find, $replace, $string );
1825
1826
		return $string;
1827
	}
1828
1829
1830
	/**
1831
	 * Send email using GFCommon::send_email()
1832
	 *
1833
	 * @since 1.17
1834
	 *
1835
	 * @see GFCommon::send_email This just makes the method public
1836
	 *
1837
	 * @param string $from               Sender address (required)
1838
	 * @param string $to                 Recipient address (required)
1839
	 * @param string $bcc                BCC recipients (required)
1840
	 * @param string $reply_to           Reply-to address (required)
1841
	 * @param string $subject            Subject line (required)
1842
	 * @param string $message            Message body (required)
1843
	 * @param string $from_name          Displayed name of the sender
1844
	 * @param string $message_format     If "html", sent text as `text/html`. Otherwise, `text/plain`. Default: "html".
1845
	 * @param string|array $attachments  Optional. Files to attach. {@see wp_mail()} for usage. Default: "".
1846
	 * @param array|false $entry         Gravity Forms entry array, related to the email. Default: false.
1847
	 * @param array|false $notification  Gravity Forms notification that triggered the email. {@see GFCommon::send_notification}. Default:false.
1848
	 */
1849
	public static function send_email( $from, $to, $bcc, $reply_to, $subject, $message, $from_name = '', $message_format = 'html', $attachments = '', $entry = false, $notification = false ) {
1850
1851
		$SendEmail = new ReflectionMethod( 'GFCommon', 'send_email' );
1852
1853
		// It was private; let's make it public
1854
		$SendEmail->setAccessible( true );
1855
1856
		// Required: $from, $to, $bcc, $replyTo, $subject, $message
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% 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...
1857
		// Optional: $from_name, $message_format, $attachments, $lead, $notification
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% 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...
1858
		$SendEmail->invoke( new GFCommon, $from, $to, $bcc, $reply_to, $subject, $message, $from_name, $message_format, $attachments, $entry, $notification );
1859
	}
1860
1861
1862
} //end class
1863
1864
1865
/**
1866
 * Generate an HTML anchor tag with a list of supported attributes
1867
 *
1868
 * @see GVCommon::get_link_html()
1869
 *
1870
 * @since 1.6
1871
 *
1872
 * @param string $href URL of the link.
1873
 * @param string $anchor_text The text or HTML inside the anchor. This is not sanitized in the function.
1874
 * @param array|string $atts Attributes to be added to the anchor tag
1875
 *
1876
 * @return string HTML output of anchor link. If empty $href, returns NULL
1877
 */
1878
function gravityview_get_link( $href = '', $anchor_text = '', $atts = array() ) {
1879 37
	return GVCommon::get_link_html( $href, $anchor_text, $atts );
1880
}
1881