Completed
Push — develop ( 6c202f...281de0 )
by Zack
04:23
created

GVCommon::get_entry_meta()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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