Completed
Push — master ( 2cfa6f...8927a4 )
by Zack
10:00 queued 06:05
created

GVCommon::get_entry()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 32
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 11
nc 6
nop 3
dl 0
loc 32
rs 8.439
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 mixed False: no form ID specified or Gravity Forms isn't active. Array: Form returned from Gravity Forms
27
	 */
28
	public static function get_form( $form_id ) {
29
		if ( empty( $form_id ) ) {
30
			return false;
31
		}
32
33
		// Only get_form_meta is cached. ::facepalm::
34
		if ( class_exists( 'RGFormsModel' ) ) {
35
			return GFFormsModel::get_form_meta( $form_id );
36
		}
37
38
		if ( class_exists( 'GFAPI' ) ) {
39
			return GFAPI::get_form( $form_id );
40
		}
41
42
		return false;
43
	}
44
45
	/**
46
	 * Alias of GravityView_Roles_Capabilities::has_cap()
47
	 *
48
	 * @since 1.15
49
	 *
50
	 * @see GravityView_Roles_Capabilities::has_cap()
51
	 *
52
	 * @param string|array $caps Single capability or array of capabilities
53
	 * @param int $object_id (optional) Parameter can be used to check for capabilities against a specific object, such as a post or user
54
	 * @param int|null $user_id (optional) Check the capabilities for a user who is not necessarily the currently logged-in user
55
	 *
56
	 * @return bool True: user has at least one passed capability; False: user does not have any defined capabilities
57
	 */
58
	public static function has_cap( $caps = '', $object_id = null, $user_id = null ) {
59
		return GravityView_Roles_Capabilities::has_cap( $caps, $object_id, $user_id );
60
	}
61
62
	/**
63
	 * Return a Gravity Forms field array, whether using GF 1.9 or not
64
	 *
65
	 * @since 1.7
66
	 *
67
	 * @param array|GF_Fields $field Gravity Forms field or array
68
	 * @return array Array version of $field
69
	 */
70
	public static function get_field_array( $field ) {
71
72
		if ( class_exists( 'GF_Fields' ) ) {
73
74
			$field_object = GF_Fields::create( $field );
75
76
			// Convert the field object in 1.9 to an array for backward compatibility
77
			$field_array = get_object_vars( $field_object );
78
79
		} else {
80
			$field_array = $field;
81
		}
82
83
		return $field_array;
84
	}
85
86
	/**
87
	 * Get all existing Views
88
	 *
89
	 * @since  1.5.4
90
	 * @since  TODO Added $args array
91
	 *
92
	 * @param array $args Pass custom array of args, formatted as if for `get_posts()`
93
	 *
94
	 * @return array Array of Views as `WP_Post`. Empty array if none found.
95
	 */
96
	public static function get_all_views( $args = array() ) {
97
98
		$default_params = array(
99
			'post_type' => 'gravityview',
100
			'posts_per_page' => -1,
0 ignored issues
show
introduced by
Disabling pagination is prohibited in VIP context, do not set posts_per_page to -1 ever.
Loading history...
101
			'post_status' => 'publish',
102
		);
103
104
		$params = wp_parse_args( $args, $default_params );
105
106
		/**
107
		 * @filter `gravityview/get_all_views/params` Modify the parameters sent to get all views.
108
		 * @param[in,out]  array $params Array of parameters to pass to `get_posts()`
109
		 */
110
		$views_params = apply_filters( 'gravityview/get_all_views/params', $params );
111
112
		$views = get_posts( $views_params );
113
114
		return $views;
115
	}
116
117
118
	/**
119
	 * Get the form array for an entry based only on the entry ID
120
	 * @param  int|string $entry_slug Entry slug
121
	 * @return array           Gravity Forms form array
122
	 */
123
	public static function get_form_from_entry_id( $entry_slug ) {
124
125
		$entry = self::get_entry( $entry_slug, true );
126
127
		$form = self::get_form( $entry['form_id'] );
128
129
		return $form;
130
	}
131
132
	/**
133
	 * Check whether a form has product fields
134
	 *
135
	 * @since 1.16
136
	 *
137
	 * @param array $form Gravity Forms form array
138
	 *
139
	 * @return bool|GF_Field[]
140
	 */
141
	public static function has_product_field( $form = array() ) {
142
143
		$product_fields = apply_filters( 'gform_product_field_types', array( 'option', 'quantity', 'product', 'total', 'shipping', 'calculation', 'price' ) );
144
145
		$fields = GFAPI::get_fields_by_type( $form, $product_fields );
146
147
		return empty( $fields ) ? false : $fields;
148
	}
149
150
	/**
151
	 * Get the entry ID from the entry slug, which may or may not be the entry ID
152
	 *
153
	 * @since  1.5.2
154
	 * @param  string $slug The entry slug, as returned by GravityView_API::get_entry_slug()
155
	 * @return int|null       The entry ID, if exists; `NULL` if not
156
	 */
157
	public static function get_entry_id_from_slug( $slug ) {
158
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

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