GravityView  1.22.6
The best, easiest way to display Gravity Forms entries on your website.
class-common.php
Go to the documentation of this file.
1 <?php
2 /**
3  * Set of common functions to separate main plugin from Gravity Forms API and other cross-plugin methods
4  *
5  * @package GravityView
6  * @license GPL2+
7  * @author Katz Web Services, Inc.
8  * @link http://gravityview.co
9  * @copyright Copyright 2014, Katz Web Services, Inc.
10  *
11  * @since 1.5.2
12  */
13 
14 /** If this file is called directly, abort. */
15 if ( ! defined( 'ABSPATH' ) ) {
16  die;
17 }
18 
19 class GVCommon {
20 
21  /**
22  * Returns the form object for a given Form ID.
23  *
24  * @access public
25  * @param mixed $form_id
26  * @return array|false Array: Form object returned from Gravity Forms; False: no form ID specified or Gravity Forms isn't active.
27  */
28  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,
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 (bool) $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;
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
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(
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(
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 
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 
353  // Either the field exists ($fields['shipping']) or the form explicitly contains a `shipping` field with numeric key
354  if( isset( $fields["{$payment_field->name}"] ) || GFCommon::get_fields_by_type( $form, $payment_field->name ) ) {
355  continue;
356  }
357 
358  $fields["{$payment_field->name}"] = array(
359  'label' => $payment_field->label,
360  'desc' => $payment_field->description,
361  'type' => $payment_field->name,
362  );
363  }
364  }
365 
366  /**
367  * @filter `gravityview/common/get_form_fields` Modify the form fields shown in the Add Field field picker.
368  * @since 1.17
369  * @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`
370  * @param array $form GF Form array
371  * @param bool $include_parent_field Whether to include the parent field when getting a field with inputs
372  */
373  $fields = apply_filters( 'gravityview/common/get_form_fields', $fields, $form, $include_parent_field );
374 
375  return $fields;
376 
377  }
378 
379  /**
380  * get extra fields from entry meta
381  * @param string $form_id (default: '')
382  * @return array
383  */
384  public static function get_entry_meta( $form_id, $only_default_column = true ) {
385 
386  $extra_fields = GFFormsModel::get_entry_meta( $form_id );
387 
388  $fields = array();
389 
390  foreach ( $extra_fields as $key => $field ){
391  if ( ! empty( $only_default_column ) && ! empty( $field['is_default_column'] ) ) {
392  $fields[ $key ] = array( 'label' => $field['label'], 'type' => 'entry_meta' );
393  }
394  }
395 
396  return $fields;
397  }
398 
399 
400  /**
401  * Wrapper for the Gravity Forms GFFormsModel::search_lead_ids() method
402  *
403  * @see GFEntryList::leads_page()
404  * @param int $form_id ID of the Gravity Forms form
405  * @since 1.1.6
406  * @return array|void Array of entry IDs. Void if Gravity Forms isn't active.
407  */
408  public static function get_entry_ids( $form_id, $search_criteria = array() ) {
409 
410  if ( ! class_exists( 'GFFormsModel' ) ) {
411  return;
412  }
413 
414  return GFFormsModel::search_lead_ids( $form_id, $search_criteria );
415  }
416 
417  /**
418  * Calculates the Search Criteria used on the self::get_entries / self::get_entry methods
419  *
420  * @since 1.7.4
421  *
422  * @param array $passed_criteria array Input Criteria (search_criteria, sorting, paging)
423  * @param array $form_ids array Gravity Forms form IDs
424  * @return array
425  */
426  public static function calculate_get_entries_criteria( $passed_criteria = array(), $form_ids = array() ) {
427 
428  $search_criteria_defaults = array(
429  'search_criteria' => null,
430  'sorting' => null,
431  'paging' => null,
432  'cache' => (isset( $passed_criteria['cache'] ) ? (bool) $passed_criteria['cache'] : true),
433  );
434 
435  $criteria = wp_parse_args( $passed_criteria, $search_criteria_defaults );
436 
437  if ( ! empty( $criteria['search_criteria']['field_filters'] ) ) {
438  foreach ( $criteria['search_criteria']['field_filters'] as &$filter ) {
439 
440  if ( ! is_array( $filter ) ) {
441  continue;
442  }
443 
444  // By default, we want searches to be wildcard for each field.
445  $filter['operator'] = empty( $filter['operator'] ) ? 'contains' : $filter['operator'];
446 
447  /**
448  * @filter `gravityview_search_operator` Modify the search operator for the field (contains, is, isnot, etc)
449  * @param string $operator Existing search operator
450  * @param array $filter array with `key`, `value`, `operator`, `type` keys
451  */
452  $filter['operator'] = apply_filters( 'gravityview_search_operator', $filter['operator'], $filter );
453  }
454 
455  // don't send just the [mode] without any field filter.
456  if( count( $criteria['search_criteria']['field_filters'] ) === 1 && array_key_exists( 'mode' , $criteria['search_criteria']['field_filters'] ) ) {
457  unset( $criteria['search_criteria']['field_filters']['mode'] );
458  }
459 
460  }
461 
462 
463 
464  /**
465  * Prepare date formats to be in Gravity Forms DB format;
466  * $passed_criteria may include date formats incompatible with Gravity Forms.
467  */
468  foreach ( array('start_date', 'end_date' ) as $key ) {
469 
470  if ( ! empty( $criteria['search_criteria'][ $key ] ) ) {
471 
472  // Use date_create instead of new DateTime so it returns false if invalid date format.
473  $date = date_create( $criteria['search_criteria'][ $key ] );
474 
475  if ( $date ) {
476  // Gravity Forms wants dates in the `Y-m-d H:i:s` format.
477  $criteria['search_criteria'][ $key ] = $date->format( 'Y-m-d H:i:s' );
478  } else {
479  do_action( 'gravityview_log_error', '[filter_get_entries_criteria] '.$key.' Date format not valid:', $criteria['search_criteria'][ $key ] );
480 
481  // If it's an invalid date, unset it. Gravity Forms freaks out otherwise.
482  unset( $criteria['search_criteria'][ $key ] );
483  }
484  }
485  }
486 
487  if ( ! GravityView_frontend::getInstance()->getSingleEntry() ) {
488  /** GravityView_View_Data::getInstance() has a side-effect :( and not one, so we can't let it run under some circumstances. */
489  if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ) {
490  $multiple_original = gravityview()->views->count() > 1;
491  GravityView_View_Data::getInstance(); /** Yes, those side-effects have to kick in. */
492  /** This weird state only happens in tests, when we play around and reset the $instance... */
493  } else {
494  /** Deprecated, do not use has_multiple_views() anymore. Thanks. */
495  $multiple_original = class_exists( 'GravityView_View_Data' ) && GravityView_View_Data::getInstance() && GravityView_View_Data::getInstance()->has_multiple_views();
496  }
497  }
498 
499  // Calculate the context view id and send it to the advanced filter
500  if ( GravityView_frontend::getInstance()->getSingleEntry() ) {
501  $criteria['context_view_id'] = GravityView_frontend::getInstance()->get_context_view_id();
502  } elseif ( $multiple_original ) {
503  $criteria['context_view_id'] = GravityView_frontend::getInstance()->get_context_view_id();
504  } elseif ( 'delete' === GFForms::get( 'action' ) ) {
505  $criteria['context_view_id'] = isset( $_GET['view_id'] ) ? intval( $_GET['view_id'] ) : null;
506  } elseif( !isset( $criteria['context_view_id'] ) ) {
507  // Prevent overriding the Context View ID: Some widgets could set the context_view_id (e.g. Recent Entries widget)
508  $criteria['context_view_id'] = null;
509  }
510 
511  /**
512  * @filter `gravityview_search_criteria` Apply final criteria filter (Used by the Advanced Filter extension)
513  * @param array $criteria Search criteria used by GravityView
514  * @param array $form_ids Forms to search
515  * @param int $view_id ID of the view being used to search
516  */
517  $criteria = apply_filters( 'gravityview_search_criteria', $criteria, $form_ids, $criteria['context_view_id'] );
518 
519  return (array)$criteria;
520  }
521 
522 
523  /**
524  * Retrieve entries given search, sort, paging criteria
525  *
526  * @see GFAPI::get_entries()
527  * @see GFFormsModel::get_field_filters_where()
528  * @access public
529  * @param int|array $form_ids The ID of the form or an array IDs of the Forms. Zero for all forms.
530  * @param mixed $passed_criteria (default: null)
531  * @param mixed &$total Optional. An output parameter containing the total number of entries. Pass a non-null value to generate the total count. (default: null)
532  * @return mixed False: Error fetching entries. Array: Multi-dimensional array of Gravity Forms entry arrays
533  */
534  public static function get_entries( $form_ids = null, $passed_criteria = null, &$total = null ) {
535 
536  // Filter the criteria before query (includes Adv Filter)
537  $criteria = self::calculate_get_entries_criteria( $passed_criteria, $form_ids );
538 
539  do_action( 'gravityview_log_debug', '[gravityview_get_entries] Final Parameters', $criteria );
540 
541  // Return value
542  $return = null;
543 
544  /** Reduce # of database calls */
545  add_filter( 'gform_is_encrypted_field', '__return_false' );
546 
547  if ( ! empty( $criteria['cache'] ) ) {
548 
549  $Cache = new GravityView_Cache( $form_ids, $criteria );
550 
551  if ( $entries = $Cache->get() ) {
552 
553  // Still update the total count when using cached results
554  if ( ! is_null( $total ) ) {
555  $total = GFAPI::count_entries( $form_ids, $criteria['search_criteria'] );
556  }
557 
558  $return = $entries;
559  }
560  }
561 
562  if ( is_null( $return ) && class_exists( 'GFAPI' ) && ( is_numeric( $form_ids ) || is_array( $form_ids ) ) ) {
563 
564  /**
565  * @filter `gravityview_pre_get_entries` Define entries to be used before GFAPI::get_entries() is called
566  * @since 1.14
567  * @param null $return If you want to override GFAPI::get_entries() and define entries yourself, tap in here.
568  * @param array $criteria The final search criteria used to generate the request to `GFAPI::get_entries()`
569  * @param array $passed_criteria The original search criteria passed to `GVCommon::get_entries()`
570  * @param int|null $total Optional. An output parameter containing the total number of entries. Pass a non-null value to generate
571  * @deprecated
572  */
573  $entries = apply_filters( 'gravityview_before_get_entries', null, $criteria, $passed_criteria, $total );
574 
575  // No entries returned from gravityview_before_get_entries
576  if( is_null( $entries ) ) {
577 
578  $entries = GFAPI::get_entries( $form_ids, $criteria['search_criteria'], $criteria['sorting'], $criteria['paging'], $total );
579 
580  if ( is_wp_error( $entries ) ) {
581  do_action( 'gravityview_log_error', $entries->get_error_message(), $entries );
582 
583  /** Remove filter added above */
584  remove_filter( 'gform_is_encrypted_field', '__return_false' );
585  return false;
586  }
587  }
588 
589  if ( ! empty( $criteria['cache'] ) && isset( $Cache ) ) {
590 
591  // Cache results
592  $Cache->set( $entries, 'entries' );
593 
594  }
595 
596  $return = $entries;
597  }
598 
599  /** Remove filter added above */
600  remove_filter( 'gform_is_encrypted_field', '__return_false' );
601 
602  /**
603  * @filter `gravityview_entries` Modify the array of entries returned to GravityView after it has been fetched from the cache or from `GFAPI::get_entries()`.
604  * @param array|null $entries Array of entries as returned by the cache or by `GFAPI::get_entries()`
605  * @param array $criteria The final search criteria used to generate the request to `GFAPI::get_entries()`
606  * @param array $passed_criteria The original search criteria passed to `GVCommon::get_entries()`
607  * @param int|null $total Optional. An output parameter containing the total number of entries. Pass a non-null value to generate
608  */
609  $return = apply_filters( 'gravityview_entries', $return, $criteria, $passed_criteria, $total );
610 
611  return $return;
612  }
613 
614 
615  /**
616  * Get the entry ID from a string that may be the Entry ID or the Entry Slug
617  *
618  * @since TODO
619  *
620  * @param string $entry_id_or_slug The ID or slug of an entry.
621  * @param bool $force_allow_ids Whether to force allowing getting the ID of an entry, even if custom slugs are enabled
622  *
623  * @return false|int|null Returns the ID of the entry found, if custom slugs is enabled. Returns original value if custom slugs is disabled. Returns false if not allowed to convert slug to ID. Returns NULL if entry not found for the passed slug.
624  */
625  public static function get_entry_id( $entry_id_or_slug = '', $force_allow_ids = false ) {
626 
627  $entry_id = false;
628 
629  /**
630  * @filter `gravityview_custom_entry_slug` Whether to enable and use custom entry slugs.
631  * @param boolean True: Allow for slugs based on entry values. False: always use entry IDs (default)
632  */
633  $custom_slug = apply_filters( 'gravityview_custom_entry_slug', false );
634 
635  /**
636  * @filter `gravityview_custom_entry_slug_allow_id` When using a custom slug, allow access to the entry using the original slug (the Entry ID).
637  * - If disabled (default), only allow access to an entry using the custom slug value. (example: `/entry/custom-slug/` NOT `/entry/123/`)
638  * - If enabled, you could access using the custom slug OR the entry id (example: `/entry/custom-slug/` OR `/entry/123/`)
639  * @param boolean $custom_slug_id_access True: allow accessing the slug by ID; False: only use the slug passed to the method.
640  */
641  $custom_slug_id_access = $force_allow_ids || apply_filters( 'gravityview_custom_entry_slug_allow_id', false );
642 
643  /**
644  * If we're using custom entry slugs, we do a meta value search
645  * instead of doing a straightup ID search.
646  */
647  if ( $custom_slug ) {
648  // Search for IDs matching $entry_id_or_slug
649  $entry_id = self::get_entry_id_from_slug( $entry_id_or_slug );
650  }
651 
652  // If custom slug is off, search using the entry ID
653  // ID allow ID access is on, also use entry ID as a backup
654  if ( false === $custom_slug || true === $custom_slug_id_access ) {
655  // Search for IDs matching $entry_slug
656  $entry_id = $entry_id_or_slug;
657  }
658 
659  return $entry_id;
660  }
661 
662  /**
663  * Return a single entry object
664  *
665  * Since 1.4, supports custom entry slugs. The way that GravityView fetches an entry based on the custom slug is by searching `gravityview_unique_id` meta. The `$entry_slug` is fetched by getting the current query var set by `is_single_entry()`
666  *
667  * @access public
668  * @param string|int $entry_slug Either entry ID or entry slug string
669  * @param boolean $force_allow_ids Force the get_entry() method to allow passed entry IDs, even if the `gravityview_custom_entry_slug_allow_id` filter returns false.
670  * @param boolean $check_entry_display Check whether the entry is visible for the current View configuration. Default: true. {@since 1.14}
671  * @return array|boolean
672  */
673  public static function get_entry( $entry_slug, $force_allow_ids = false, $check_entry_display = true ) {
674 
675  if ( class_exists( 'GFAPI' ) && ! empty( $entry_slug ) ) {
676 
677  $entry_id = self::get_entry_id( $entry_slug, $force_allow_ids );
678 
679  if ( empty( $entry_id ) ) {
680  return false;
681  }
682 
683  // fetch the entry
684  $entry = GFAPI::get_entry( $entry_id );
685 
686  /**
687  * @filter `gravityview/common/get_entry/check_entry_display` Override whether to check entry display rules against filters
688  * @since 1.16.2
689  * @param bool $check_entry_display Check whether the entry is visible for the current View configuration. Default: true.
690  * @param array $entry Gravity Forms entry array
691  */
692  $check_entry_display = apply_filters( 'gravityview/common/get_entry/check_entry_display', $check_entry_display, $entry );
693 
694  if( $check_entry_display ) {
695  // Is the entry allowed
696  $entry = self::check_entry_display( $entry );
697  }
698 
699  if( is_wp_error( $entry ) ) {
700  do_action( 'gravityview_log_error', __METHOD__ . ': ' . $entry->get_error_message() );
701  return false;
702  }
703 
704  return $entry;
705  }
706 
707  return false;
708  }
709 
710  /**
711  * Wrapper for the GFFormsModel::matches_operation() method that adds additional comparisons, including:
712  * 'equals', 'greater_than_or_is', 'greater_than_or_equals', 'less_than_or_is', 'less_than_or_equals',
713  * 'not_contains', 'in', and 'not_in'
714  *
715  * @since 1.13 You can define context, which displays/hides based on what's being displayed (single, multiple, edit)
716  * @since 1.22.1 Added 'in' and 'not_in' for JSON-encoded array values, serialized non-strings
717  *
718  * @see http://docs.gravityview.co/article/252-gvlogic-shortcode
719  * @uses GFFormsModel::matches_operation
720  * @since 1.7.5
721  *
722  * @param string $val1 Left side of comparison
723  * @param string $val2 Right side of comparison
724  * @param string $operation Type of comparison
725  *
726  * @return bool True: matches, false: not matches
727  */
728  public static function matches_operation( $val1, $val2, $operation ) {
729 
730  $json_function = function_exists('wp_json_encode') ? 'wp_json_encode' : 'json_encode';
731 
732  // Only process strings
733  $val1 = ! is_string( $val1 ) ? $json_function( $val1 ) : $val1;
734  $val2 = ! is_string( $val2 ) ? $json_function( $val2 ) : $val2;
735 
736  $value = false;
737 
738  if( 'context' === $val1 ) {
739 
740  $matching_contexts = array( $val2 );
741 
742  // We allow for non-standard contexts.
743  switch( $val2 ) {
744  // Check for either single or edit
745  case 'singular':
746  $matching_contexts = array( 'single', 'edit' );
747  break;
748  // Use multiple as alias for directory for consistency
749  case 'multiple':
750  $matching_contexts = array( 'directory' );
751  break;
752  }
753 
754  $val1 = in_array( gravityview_get_context(), $matching_contexts ) ? $val2 : false;
755  }
756 
757  switch ( $operation ) {
758  case 'equals':
759  $value = self::matches_operation( $val1, $val2, 'is' );
760  break;
761  case 'greater_than_or_is':
762  case 'greater_than_or_equals':
763  $is = self::matches_operation( $val1, $val2, 'is' );
764  $gt = self::matches_operation( $val1, $val2, 'greater_than' );
765  $value = ( $is || $gt );
766  break;
767  case 'less_than_or_is':
768  case 'less_than_or_equals':
769  $is = self::matches_operation( $val1, $val2, 'is' );
770  $gt = self::matches_operation( $val1, $val2, 'less_than' );
771  $value = ( $is || $gt );
772  break;
773  case 'not_contains':
774  $contains = self::matches_operation( $val1, $val2, 'contains' );
775  $value = ! $contains;
776  break;
777  /**
778  * @since 1.22.1 Handle JSON-encoded comparisons
779  */
780  case 'in':
781  case 'not_in':
782 
783  $json_val_1 = json_decode( $val1, true );
784  $json_val_2 = json_decode( $val2, true );
785 
786  if( ! empty( $json_val_1 ) || ! empty( $json_val_2 ) ) {
787 
788  $json_in = false;
789  $json_val_1 = $json_val_1 ? $json_val_1 : array( $val1 );
790  $json_val_2 = $json_val_2 ? $json_val_2 : array( $val2 );
791 
792  // For JSON, we want to compare as "in" or "not in" rather than "contains"
793  foreach ( $json_val_1 as $item_1 ) {
794  foreach ( $json_val_2 as $item_2 ) {
795  $json_in = self::matches_operation( $item_1, $item_2, 'is' );
796 
797  if( $json_in ) {
798  break 2;
799  }
800  }
801  }
802 
803  $value = ( $operation === 'in' ) ? $json_in : ! $json_in;
804  }
805  break;
806 
807  case 'less_than':
808  case '<' :
809  if ( is_string( $val1 ) && is_string( $val2 ) ) {
810  $value = $val1 < $val2;
811  } else {
812  $value = GFFormsModel::matches_operation( $val1, $val2, $operation );
813  }
814  break;
815  case 'greater_than':
816  case '>' :
817  if ( is_string( $val1 ) && is_string( $val2 ) ) {
818  $value = $val1 > $val2;
819  } else {
820  $value = GFFormsModel::matches_operation( $val1, $val2, $operation );
821  }
822  break;
823  default:
824  $value = GFFormsModel::matches_operation( $val1, $val2, $operation );
825  }
826 
827  return $value;
828  }
829 
830  /**
831  *
832  * Checks if a certain entry is valid according to the View search filters (specially the Adv Filters)
833  *
834  * @uses GVCommon::calculate_get_entries_criteria();
835  * @see GFFormsModel::is_value_match()
836  *
837  * @since 1.7.4
838  *
839  * @param array $entry Gravity Forms Entry object
840  * @return WP_Error|array Returns WP_Error if entry is not valid according to the view search filters (Adv Filter). Returns original $entry value if passes.
841  */
842  public static function check_entry_display( $entry ) {
843 
844  if ( ! $entry || is_wp_error( $entry ) ) {
845  return new WP_Error('entry_not_found', 'Entry was not found.', $entry );
846  }
847 
848  if ( empty( $entry['form_id'] ) ) {
849  return new WP_Error( 'form_id_not_set', '[apply_filters_to_entry] Entry is empty!', $entry );
850  }
851 
852  $criteria = self::calculate_get_entries_criteria();
853 
854  if ( empty( $criteria['search_criteria'] ) || ! is_array( $criteria['search_criteria'] ) ) {
855  do_action( 'gravityview_log_debug', '[apply_filters_to_entry] Entry approved! No search criteria found:', $criteria );
856  return $entry;
857  }
858 
859  // Make sure the current View is connected to the same form as the Entry
860  if( ! empty( $criteria['context_view_id'] ) ) {
861  $context_view_id = intval( $criteria['context_view_id'] );
862  $context_form_id = gravityview_get_form_id( $context_view_id );
863  if( intval( $context_form_id ) !== intval( $entry['form_id'] ) ) {
864  return new WP_Error( 'view_id_not_match', sprintf( '[apply_filters_to_entry] Entry form ID does not match current View connected form ID:', $entry['form_id'] ), $criteria['context_view_id'] );
865  }
866  }
867 
868  $search_criteria = $criteria['search_criteria'];
869 
870  // check entry status
871  if ( array_key_exists( 'status', $search_criteria ) && $search_criteria['status'] != $entry['status'] ) {
872  return new WP_Error( 'status_not_valid', sprintf( '[apply_filters_to_entry] Entry status - %s - is not valid according to filter:', $entry['status'] ), $search_criteria );
873  }
874 
875  // check entry date
876  // @todo: Does it make sense to apply the Date create filters to the single entry?
877 
878  // field_filters
879  if ( empty( $search_criteria['field_filters'] ) || ! is_array( $search_criteria['field_filters'] ) ) {
880  do_action( 'gravityview_log_debug', '[apply_filters_to_entry] Entry approved! No field filters criteria found:', $search_criteria );
881  return $entry;
882  }
883 
884  $filters = $search_criteria['field_filters'];
885 
886  $mode = array_key_exists( 'mode', $filters ) ? strtolower( $filters['mode'] ) : 'all';
887 
888  // Prevent the mode from being processed below
889  unset( $filters['mode'] );
890 
891  $form = self::get_form( $entry['form_id'] );
892 
893  foreach ( $filters as $filter ) {
894 
895  if ( ! isset( $filter['key'] ) ) {
896  do_action( 'gravityview_log_debug', '[apply_filters_to_entry] Filter key not set', $filter );
897  continue;
898  }
899 
900  $k = $filter['key'];
901 
902  $field = self::get_field( $form, $k );
903 
904  if ( is_null( $field ) ) {
905  $field_value = isset( $entry[ $k ] ) ? $entry[ $k ] : null;
906  $field = $k;
907  } else {
908  $field_value = GFFormsModel::get_lead_field_value( $entry, $field );
909  // If it's a complex field, then fetch the input's value, if exists at the current key. Otherwise, let GF handle it
910  $field_value = ( is_array( $field_value ) && isset( $field_value[ $k ] ) ) ? rgar( $field_value, $k ) : $field_value;
911  }
912 
913  $operator = isset( $filter['operator'] ) ? strtolower( $filter['operator'] ) : 'is';
914 
915  $is_value_match = GravityView_GFFormsModel::is_value_match( $field_value, $filter['value'], $operator, $field );
916 
917  // Any match is all we need to know
918  if ( $is_value_match && 'any' === $mode ) {
919  return $entry;
920  }
921 
922  // Any failed match is a total fail
923  if ( ! $is_value_match && 'all' === $mode ) {
924  return new WP_Error('failed_criteria', '[apply_filters_to_entry] Entry cannot be displayed. Failed a criterium for ALL mode', $filter );
925  }
926  }
927 
928  // at this point, if in ALL mode, then entry is approved - all conditions were met.
929  // Or, for ANY mode, means none of the conditions were satisfied, so entry is not approved
930  if ( 'all' === $mode ) {
931  do_action( 'gravityview_log_debug', '[apply_filters_to_entry] Entry approved: all conditions were met' );
932  return $entry;
933  } else {
934  return new WP_Error('failed_any_criteria', '[apply_filters_to_entry] Entry cannot be displayed. Failed all the criteria for ANY mode', $filters );
935  }
936 
937  }
938 
939 
940  /**
941  * Allow formatting date and time based on GravityView standards
942  *
943  * @since 1.16
944  *
945  * @see GVCommon_Test::test_format_date for examples
946  *
947  * @param string $date_string The date as stored by Gravity Forms (`Y-m-d h:i:s` GMT)
948  * @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
949  * - `raw` Un-formatted date string in original `Y-m-d h:i:s` format
950  * - `timestamp` Integer timestamp returned by GFCommon::get_local_timestamp()
951  * - `diff` "%s ago" format, unless other `format` is defined
952  * - `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.
953  * - `time` Include time in the `GFCommon::format_date()` output
954  * - `format` Define your own date format, or `diff` format
955  *
956  * @return int|null|string Formatted date based on the original date
957  */
958  public static function format_date( $date_string = '', $args = array() ) {
959 
960  $default_atts = array(
961  'raw' => false,
962  'timestamp' => false,
963  'diff' => false,
964  'human' => false,
965  'format' => '',
966  'time' => false,
967  );
968 
969  $atts = wp_parse_args( $args, $default_atts );
970 
971  /**
972  * Gravity Forms code to adjust date to locally-configured Time Zone
973  * @see GFCommon::format_date() for original code
974  */
975  $date_gmt_time = mysql2date( 'G', $date_string );
976  $date_local_timestamp = GFCommon::get_local_timestamp( $date_gmt_time );
977 
978  $format = rgar( $atts, 'format' );
979  $is_human = ! empty( $atts['human'] );
980  $is_diff = ! empty( $atts['diff'] );
981  $is_raw = ! empty( $atts['raw'] );
982  $is_timestamp = ! empty( $atts['timestamp'] );
983  $include_time = ! empty( $atts['time'] );
984 
985  // If we're using time diff, we want to have a different default format
986  if( empty( $format ) ) {
987  /* translators: %s: relative time from now, used for generic date comparisons. "1 day ago", or "20 seconds ago" */
988  $format = $is_diff ? esc_html__( '%s ago', 'gravityview' ) : get_option( 'date_format' );
989  }
990 
991  // If raw was specified, don't modify the stored value
992  if ( $is_raw ) {
993  $formatted_date = $date_string;
994  } elseif( $is_timestamp ) {
995  $formatted_date = $date_local_timestamp;
996  } elseif ( $is_diff ) {
997  $formatted_date = sprintf( $format, human_time_diff( $date_gmt_time ) );
998  } else {
999  $formatted_date = GFCommon::format_date( $date_string, $is_human, $format, $include_time );
1000  }
1001 
1002  unset( $format, $is_diff, $is_human, $is_timestamp, $is_raw, $date_gmt_time, $date_local_timestamp, $default_atts );
1003 
1004  return $formatted_date;
1005  }
1006 
1007  /**
1008  * Retrieve the label of a given field id (for a specific form)
1009  *
1010  * @access public
1011  * @since 1.17 Added $field_value parameter
1012  *
1013  * @param array $form Gravity Forms form array
1014  * @param string $field_id ID of the field. If an input, full input ID (like `1.3`)
1015  * @param string|array $field_value Raw value of the field.
1016  * @return string
1017  */
1018  public static function get_field_label( $form = array(), $field_id = '', $field_value = '' ) {
1019 
1020  if ( empty( $form ) || empty( $field_id ) ) {
1021  return '';
1022  }
1023 
1024  $field = self::get_field( $form, $field_id );
1025 
1026  $label = rgar( $field, 'label' );
1027 
1028  if( floor( $field_id ) !== floatval( $field_id ) ) {
1029  $label = GFFormsModel::get_choice_text( $field, $field_value, $field_id );
1030  }
1031 
1032  return $label;
1033  }
1034 
1035 
1036  /**
1037  * Returns the field details array of a specific form given the field id
1038  *
1039  * Alias of GFFormsModel::get_field
1040  *
1041  * @since 1.19 Allow passing form ID as well as form array
1042  *
1043  * @uses GFFormsModel::get_field
1044  * @see GFFormsModel::get_field
1045  * @access public
1046  * @param array|int $form Form array or ID
1047  * @param string|int $field_id
1048  * @return GF_Field|null Gravity Forms field object, or NULL: Gravity Forms GFFormsModel does not exist or field at $field_id doesn't exist.
1049  */
1050  public static function get_field( $form, $field_id ) {
1051 
1052  if ( is_numeric( $form ) ) {
1053  $form = GFAPI::get_form( $form );
1054  }
1055 
1056  if ( class_exists( 'GFFormsModel' ) ){
1057  return GFFormsModel::get_field( $form, $field_id );
1058  } else {
1059  return null;
1060  }
1061  }
1062 
1063 
1064  /**
1065  * Check whether the post is GravityView
1066  *
1067  * - Check post type. Is it `gravityview`?
1068  * - Check shortcode
1069  *
1070  * @param WP_Post $post WordPress post object
1071  * @return boolean True: yep, GravityView; No: not!
1072  */
1073  public static function has_gravityview_shortcode( $post = null ) {
1074  if ( ! is_a( $post, 'WP_Post' ) ) {
1075  return false;
1076  }
1077 
1078  if ( 'gravityview' === get_post_type( $post ) ) {
1079  return true;
1080  }
1081 
1082  return self::has_shortcode_r( $post->post_content, 'gravityview' ) ? true : false;
1083 
1084  }
1085 
1086 
1087  /**
1088  * Placeholder until the recursive has_shortcode() patch is merged
1089  * @see https://core.trac.wordpress.org/ticket/26343#comment:10
1090  * @param string $content Content to check whether there's a shortcode
1091  * @param string $tag Current shortcode tag
1092  */
1093  public static function has_shortcode_r( $content, $tag = 'gravityview' ) {
1094  if ( false === strpos( $content, '[' ) ) {
1095  return false;
1096  }
1097 
1098  if ( shortcode_exists( $tag ) ) {
1099 
1100  $shortcodes = array();
1101 
1102  preg_match_all( '/' . get_shortcode_regex() . '/s', $content, $matches, PREG_SET_ORDER );
1103  if ( empty( $matches ) ){
1104  return false;
1105  }
1106 
1107  foreach ( $matches as $shortcode ) {
1108  if ( $tag === $shortcode[2] ) {
1109 
1110  // Changed this to $shortcode instead of true so we get the parsed atts.
1111  $shortcodes[] = $shortcode;
1112 
1113  } else if ( isset( $shortcode[5] ) && $results = self::has_shortcode_r( $shortcode[5], $tag ) ) {
1114  foreach( $results as $result ) {
1115  $shortcodes[] = $result;
1116  }
1117  }
1118  }
1119 
1120  return $shortcodes;
1121  }
1122  return false;
1123  }
1124 
1125 
1126 
1127  /**
1128  * Get the views for a particular form
1129  *
1130  * @since 1.15.2 Add $args array and limit posts_per_page to 500
1131  *
1132  * @uses get_posts()
1133  *
1134  * @param int $form_id Gravity Forms form ID
1135  * @param array $args Pass args sent to get_posts()
1136  *
1137  * @return array Array with view details, as returned by get_posts()
1138  */
1139  public static function get_connected_views( $form_id, $args = array() ) {
1140 
1141  $defaults = array(
1142  'post_type' => 'gravityview',
1143  'posts_per_page' => 100,
1144  'meta_key' => '_gravityview_form_id',
1145  'meta_value' => (int)$form_id,
1146  );
1147 
1148  $args = wp_parse_args( $args, $defaults );
1149 
1150  $views = get_posts( $args );
1151 
1152  return $views;
1153  }
1154 
1155  /**
1156  * Get the Gravity Forms form ID connected to a View
1157  *
1158  * @param int $view_id The ID of the View to get the connected form of
1159  *
1160  * @return false|string ID of the connected Form, if exists. Empty string if not. False if not the View ID isn't valid.
1161  */
1162  public static function get_meta_form_id( $view_id ) {
1163  return get_post_meta( $view_id, '_gravityview_form_id', true );
1164  }
1165 
1166  /**
1167  * Get the template ID (`list`, `table`, `datatables`, `map`) for a View
1168  *
1169  * @see GravityView_Template::template_id
1170  *
1171  * @param int $view_id The ID of the View to get the layout of
1172  *
1173  * @return string GravityView_Template::template_id value. Empty string if not.
1174  */
1175  public static function get_meta_template_id( $view_id ) {
1176  return get_post_meta( $view_id, '_gravityview_directory_template', true );
1177  }
1178 
1179 
1180  /**
1181  * Get all the settings for a View
1182  *
1183  * @uses \GV\View_Settings::defaults() Parses the settings with the plugin defaults as backups.
1184  * @param int $post_id View ID
1185  * @return array Associative array of settings with plugin defaults used if not set by the View
1186  */
1187  public static function get_template_settings( $post_id ) {
1188 
1189  $settings = get_post_meta( $post_id, '_gravityview_template_settings', true );
1190 
1191  if ( class_exists( 'GravityView_View_Data' ) ) {
1192 
1193  $defaults = defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ? \GV\View_Settings::defaults() : GravityView_View_Data::get_default_args();
1194 
1195  return wp_parse_args( (array)$settings, $defaults );
1196 
1197  }
1198 
1199  // Backup, in case GravityView_View_Data isn't loaded yet.
1200  return $settings;
1201  }
1202 
1203  /**
1204  * Get the setting for a View
1205  *
1206  * If the setting isn't set by the View, it returns the plugin default.
1207  *
1208  * @param int $post_id View ID
1209  * @param string $key Key for the setting
1210  * @return mixed|null Setting value, or NULL if not set.
1211  */
1212  public static function get_template_setting( $post_id, $key ) {
1213 
1214  $settings = self::get_template_settings( $post_id );
1215 
1216  if ( isset( $settings[ $key ] ) ) {
1217  return $settings[ $key ];
1218  }
1219 
1220  return null;
1221  }
1222 
1223  /**
1224  * Get the field configuration for the View
1225  *
1226  * array(
1227  *
1228  * [other zones]
1229  *
1230  * 'directory_list-title' => array(
1231  *
1232  * [other fields]
1233  *
1234  * '5372653f25d44' => array(
1235  * 'id' => string '9' (length=1)
1236  * 'label' => string 'Screenshots' (length=11)
1237  * 'show_label' => string '1' (length=1)
1238  * 'custom_label' => string '' (length=0)
1239  * 'custom_class' => string 'gv-gallery' (length=10)
1240  * 'only_loggedin' => string '0' (length=1)
1241  * 'only_loggedin_cap' => string 'read' (length=4)
1242  * )
1243  *
1244  * [other fields]
1245  * )
1246  *
1247  * [other zones]
1248  * )
1249  *
1250  * @since 1.17.4 Added $apply_filter parameter
1251  *
1252  * @param int $post_id View ID
1253  * @param bool $apply_filter Whether to apply the `gravityview/configuration/fields` filter [Default: true]
1254  * @return array Multi-array of fields with first level being the field zones. See code comment.
1255  */
1256  public static function get_directory_fields( $post_id, $apply_filter = true ) {
1257  $fields = get_post_meta( $post_id, '_gravityview_directory_fields', true );
1258 
1259  if( $apply_filter ) {
1260  /**
1261  * @filter `gravityview/configuration/fields` Filter the View fields' configuration array
1262  * @since 1.6.5
1263  *
1264  * @param $fields array Multi-array of fields with first level being the field zones
1265  * @param $post_id int Post ID
1266  */
1267  $fields = apply_filters( 'gravityview/configuration/fields', $fields, $post_id );
1268  }
1269 
1270  return $fields;
1271  }
1272 
1273 
1274  /**
1275  * Render dropdown (select) with the list of sortable fields from a form ID
1276  *
1277  * @access public
1278  * @param int $formid Form ID
1279  * @return string html
1280  */
1281  public static function get_sortable_fields( $formid, $current = '' ) {
1282  $output = '<option value="" ' . selected( '', $current, false ).'>' . esc_html__( 'Default', 'gravityview' ) .'</option>';
1283 
1284  if ( empty( $formid ) ) {
1285  return $output;
1286  }
1287 
1288  $fields = self::get_sortable_fields_array( $formid );
1289 
1290  if ( ! empty( $fields ) ) {
1291 
1292  $blacklist_field_types = apply_filters( 'gravityview_blacklist_field_types', array( 'list', 'textarea' ), null );
1293 
1294  foreach ( $fields as $id => $field ) {
1295  if ( in_array( $field['type'], $blacklist_field_types ) ) {
1296  continue;
1297  }
1298 
1299  $output .= '<option value="'. $id .'" '. selected( $id, $current, false ).'>'. esc_attr( $field['label'] ) .'</option>';
1300  }
1301  }
1302 
1303  return $output;
1304  }
1305 
1306  /**
1307  *
1308  * @param int $formid Gravity Forms form ID
1309  * @param array $blacklist Field types to exclude
1310  *
1311  * @since 1.8
1312  *
1313  * @todo Get all fields, check if sortable dynamically
1314  *
1315  * @return array
1316  */
1317  public static function get_sortable_fields_array( $formid, $blacklist = array( 'list', 'textarea' ) ) {
1318 
1319  // Get fields with sub-inputs and no parent
1320  $fields = self::get_form_fields( $formid, true, false );
1321 
1322  $date_created = array(
1323  'date_created' => array(
1324  'type' => 'date_created',
1325  'label' => __( 'Date Created', 'gravityview' ),
1326  ),
1327  );
1328 
1329  $fields = $date_created + $fields;
1330 
1331  $blacklist_field_types = apply_filters( 'gravityview_blacklist_field_types', $blacklist, NULL );
1332 
1333  // TODO: Convert to using array_filter
1334  foreach( $fields as $id => $field ) {
1335 
1336  if( in_array( $field['type'], $blacklist_field_types ) ) {
1337  unset( $fields[ $id ] );
1338  }
1339  }
1340 
1341  /**
1342  * @filter `gravityview/common/sortable_fields` Filter the sortable fields
1343  * @since 1.12
1344  * @param array $fields Sub-set of GF form fields that are sortable
1345  * @param int $formid The Gravity Forms form ID that the fields are from
1346  */
1347  $fields = apply_filters( 'gravityview/common/sortable_fields', $fields, $formid );
1348 
1349  return $fields;
1350  }
1351 
1352  /**
1353  * Returns the GF Form field type for a certain field(id) of a form
1354  * @param object $form Gravity Forms form
1355  * @param mixed $field_id Field ID or Field array
1356  * @return string field type
1357  */
1358  public static function get_field_type( $form = null, $field_id = '' ) {
1359 
1360  if ( ! empty( $field_id ) && ! is_array( $field_id ) ) {
1361  $field = self::get_field( $form, $field_id );
1362  } else {
1363  $field = $field_id;
1364  }
1365 
1366  return class_exists( 'RGFormsModel' ) ? RGFormsModel::get_input_type( $field ) : '';
1367 
1368  }
1369 
1370 
1371  /**
1372  * Checks if the field type is a 'numeric' field type (e.g. to be used when sorting)
1373  * @param int|array $form form ID or form array
1374  * @param int|array $field field key or field array
1375  * @return boolean
1376  */
1377  public static function is_field_numeric( $form = null, $field = '' ) {
1378 
1379  if ( ! is_array( $form ) && ! is_array( $field ) ) {
1380  $form = self::get_form( $form );
1381  }
1382 
1383  // If entry meta, it's a string. Otherwise, numeric
1384  if( ! is_numeric( $field ) && is_string( $field ) ) {
1385  $type = $field;
1386  } else {
1387  $type = self::get_field_type( $form, $field );
1388  }
1389 
1390  /**
1391  * @filter `gravityview/common/numeric_types` What types of fields are numeric?
1392  * @since 1.5.2
1393  * @param array $numeric_types Fields that are numeric. Default: `[ number, time ]`
1394  */
1395  $numeric_types = apply_filters( 'gravityview/common/numeric_types', array( 'number', 'time' ) );
1396 
1397  // Defer to GravityView_Field setting, if the field type is registered and `is_numeric` is true
1398  if( $gv_field = GravityView_Fields::get( $type ) ) {
1399  if( true === $gv_field->is_numeric ) {
1400  $numeric_types[] = $gv_field->is_numeric;
1401  }
1402  }
1403 
1404  $return = in_array( $type, $numeric_types );
1405 
1406  return $return;
1407  }
1408 
1409  /**
1410  * Encrypt content using Javascript so that it's hidden when JS is disabled.
1411  *
1412  * This is mostly used to hide email addresses from scraper bots.
1413  *
1414  * @param string $content Content to encrypt
1415  * @param string $message Message shown if Javascript is disabled
1416  *
1417  * @see https://github.com/katzwebservices/standalone-phpenkoder StandalonePHPEnkoder on Github
1418  *
1419  * @since 1.7
1420  *
1421  * @return string Content, encrypted
1422  */
1423  public static function js_encrypt( $content, $message = '' ) {
1424 
1425  $output = $content;
1426 
1427  if ( ! class_exists( 'StandalonePHPEnkoder' ) ) {
1428  include_once( GRAVITYVIEW_DIR . 'includes/lib/StandalonePHPEnkoder.php' );
1429  }
1430 
1431  if ( class_exists( 'StandalonePHPEnkoder' ) ) {
1432 
1433  $enkoder = new StandalonePHPEnkoder;
1434 
1435  $message = empty( $message ) ? __( 'Email hidden; Javascript is required.', 'gravityview' ) : $message;
1436 
1437  /**
1438  * @filter `gravityview/phpenkoder/msg` Modify the message shown when Javascript is disabled and an encrypted email field is displayed
1439  * @since 1.7
1440  * @param string $message Existing message
1441  * @param string $content Content to encrypt
1442  */
1443  $enkoder->enkode_msg = apply_filters( 'gravityview/phpenkoder/msg', $message, $content );
1444 
1445  $output = $enkoder->enkode( $content );
1446  }
1447 
1448  return $output;
1449  }
1450 
1451  /**
1452  *
1453  * Do the same than parse_str without max_input_vars limitation:
1454  * Parses $string as if it were the query string passed via a URL and sets variables in the current scope.
1455  * @param $string array string to parse (not altered like in the original parse_str(), use the second parameter!)
1456  * @param $result array If the second parameter is present, variables are stored in this variable as array elements
1457  * @return bool true or false if $string is an empty string
1458  * @since 1.5.3
1459  *
1460  * @author rubo77 at https://gist.github.com/rubo77/6821632
1461  **/
1462  public static function gv_parse_str( $string, &$result ) {
1463  if ( empty( $string ) ) {
1464  return false;
1465  }
1466 
1467  $result = array();
1468 
1469  // find the pairs "name=value"
1470  $pairs = explode( '&', $string );
1471 
1472  foreach ( $pairs as $pair ) {
1473  // use the original parse_str() on each element
1474  parse_str( $pair, $params );
1475 
1476  $k = key( $params );
1477  if ( ! isset( $result[ $k ] ) ) {
1478  $result += $params;
1479  } elseif ( array_key_exists( $k, $params ) && is_array( $params[ $k ] ) ) {
1480  $result[ $k ] = self::array_merge_recursive_distinct( $result[ $k ], $params[ $k ] );
1481  }
1482  }
1483  return true;
1484  }
1485 
1486 
1487  /**
1488  * Generate an HTML anchor tag with a list of supported attributes
1489  *
1490  * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a Supported attributes defined here
1491  * @uses esc_url_raw() to sanitize $href
1492  * @uses esc_attr() to sanitize $atts
1493  *
1494  * @since 1.6
1495  *
1496  * @param string $href URL of the link. Sanitized using `esc_url_raw()`
1497  * @param string $anchor_text The text or HTML inside the anchor. This is not sanitized in the function.
1498  * @param array|string $atts Attributes to be added to the anchor tag. Parsed by `wp_parse_args()`, sanitized using `esc_attr()`
1499  *
1500  * @return string HTML output of anchor link. If empty $href, returns NULL
1501  */
1502  public static function get_link_html( $href = '', $anchor_text = '', $atts = array() ) {
1503 
1504  // Supported attributes for anchor tags. HREF left out intentionally.
1505  $allowed_atts = array(
1506  'href' => null, // Will override the $href argument if set
1507  'title' => null,
1508  'rel' => null,
1509  'id' => null,
1510  'class' => null,
1511  'target' => null,
1512  'style' => null,
1513 
1514  // Used by GravityView
1515  'data-viewid' => null,
1516 
1517  // Not standard
1518  'hreflang' => null,
1519  'type' => null,
1520  'tabindex' => null,
1521 
1522  // Deprecated HTML4 but still used
1523  'name' => null,
1524  'onclick' => null,
1525  'onchange' => null,
1526  'onkeyup' => null,
1527 
1528  // HTML5 only
1529  'download' => null,
1530  'media' => null,
1531  'ping' => null,
1532  );
1533 
1534  /**
1535  * @filter `gravityview/get_link/allowed_atts` Modify the attributes that are allowed to be used in generating links
1536  * @param array $allowed_atts Array of attributes allowed
1537  */
1538  $allowed_atts = apply_filters( 'gravityview/get_link/allowed_atts', $allowed_atts );
1539 
1540  // Make sure the attributes are formatted as array
1541  $passed_atts = wp_parse_args( $atts );
1542 
1543  // Make sure the allowed attributes are only the ones in the $allowed_atts list
1544  $final_atts = shortcode_atts( $allowed_atts, $passed_atts );
1545 
1546  // Remove attributes with empty values
1547  $final_atts = array_filter( $final_atts );
1548 
1549  // If the href wasn't passed as an attribute, use the value passed to the function
1550  if ( empty( $final_atts['href'] ) && ! empty( $href ) ) {
1551  $final_atts['href'] = $href;
1552  }
1553 
1554  $final_atts['href'] = esc_url_raw( $href );
1555 
1556  /**
1557  * Fix potential security issue with target=_blank
1558  * @see https://dev.to/ben/the-targetblank-vulnerability-by-example
1559  */
1560  if( '_blank' === rgar( $final_atts, 'target' ) ) {
1561  $final_atts['rel'] = trim( rgar( $final_atts, 'rel', '' ) . ' noopener noreferrer' );
1562  }
1563 
1564  // Sort the attributes alphabetically, to help testing
1565  ksort( $final_atts );
1566 
1567  // For each attribute, generate the code
1568  $output = '';
1569  foreach ( $final_atts as $attr => $value ) {
1570  $output .= sprintf( ' %s="%s"', $attr, esc_attr( $value ) );
1571  }
1572 
1573  if( '' !== $output ) {
1574  $output = '<a' . $output . '>' . $anchor_text . '</a>';
1575  }
1576 
1577  return $output;
1578  }
1579 
1580  /**
1581  * array_merge_recursive does indeed merge arrays, but it converts values with duplicate
1582  * keys to arrays rather than overwriting the value in the first array with the duplicate
1583  * value in the second array, as array_merge does.
1584  *
1585  * @see http://php.net/manual/en/function.array-merge-recursive.php
1586  *
1587  * @since 1.5.3
1588  * @param array $array1
1589  * @param array $array2
1590  * @return array
1591  * @author Daniel <daniel (at) danielsmedegaardbuus (dot) dk>
1592  * @author Gabriel Sobrinho <gabriel (dot) sobrinho (at) gmail (dot) com>
1593  */
1594  public static function array_merge_recursive_distinct( array &$array1, array &$array2 ) {
1595  $merged = $array1;
1596  foreach ( $array2 as $key => $value ) {
1597  if ( is_array( $value ) && isset( $merged[ $key ] ) && is_array( $merged[ $key ] ) ) {
1598  $merged[ $key ] = self::array_merge_recursive_distinct( $merged[ $key ], $value );
1599  } else if ( is_numeric( $key ) && isset( $merged[ $key ] ) ) {
1600  $merged[] = $value;
1601  } else {
1602  $merged[ $key ] = $value;
1603  }
1604  }
1605 
1606  return $merged;
1607  }
1608 
1609  /**
1610  * Get WordPress users with reasonable limits set
1611  *
1612  * @param string $context Where are we using this information (e.g. change_entry_creator, search_widget ..)
1613  * @param array $args Arguments to modify the user query. See get_users() {@since 1.14}
1614  * @return array Array of WP_User objects.
1615  */
1616  public static function get_users( $context = 'change_entry_creator', $args = array() ) {
1617 
1618  $default_args = array(
1619  'number' => 2000,
1620  'orderby' => 'display_name',
1621  'order' => 'ASC',
1622  'fields' => array( 'ID', 'display_name', 'user_login', 'user_nicename' )
1623  );
1624 
1625  // Merge in the passed arg
1626  $get_users_settings = wp_parse_args( $args, $default_args );
1627 
1628  /**
1629  * @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
1630  * `$context` is where are we using this information (e.g. change_entry_creator, search_widget ..)
1631  * @param array $settings Settings array, with `number` key defining the # of users to display
1632  */
1633  $get_users_settings = apply_filters( 'gravityview/get_users/'. $context, apply_filters( 'gravityview_change_entry_creator_user_parameters', $get_users_settings ) );
1634 
1635  return get_users( $get_users_settings );
1636  }
1637 
1638 
1639  /**
1640  * Display updated/error notice
1641  *
1642  * @since 1.19.2 Added $cap and $object_id parameters
1643  *
1644  * @param string $notice text/HTML of notice
1645  * @param string $class CSS class for notice (`updated` or `error`)
1646  * @param string $cap [Optional] Define a capability required to show a notice. If not set, displays to all caps.
1647  *
1648  * @return string
1649  */
1650  public static function generate_notice( $notice, $class = '', $cap = '', $object_id = null ) {
1651 
1652  // If $cap is defined, only show notice if user has capability
1653  if( $cap && ! GVCommon::has_cap( $cap, $object_id ) ) {
1654  return '';
1655  }
1656 
1657  return '<div class="gv-notice '.gravityview_sanitize_html_class( $class ) .'">'. $notice .'</div>';
1658  }
1659 
1660  /**
1661  * Inspired on \GFCommon::encode_shortcodes, reverse the encoding by replacing the ascii characters by the shortcode brackets
1662  * @since 1.16.5
1663  * @param string $string Input string to decode
1664  * @return string $string Output string
1665  */
1666  public static function decode_shortcodes( $string ) {
1667  $replace = array( '[', ']', '"' );
1668  $find = array( '&#91;', '&#93;', '&quot;' );
1669  $string = str_replace( $find, $replace, $string );
1670 
1671  return $string;
1672  }
1673 
1674 
1675  /**
1676  * Send email using GFCommon::send_email()
1677  *
1678  * @since 1.17
1679  *
1680  * @see GFCommon::send_email This just makes the method public
1681  *
1682  * @param string $from Sender address (required)
1683  * @param string $to Recipient address (required)
1684  * @param string $bcc BCC recipients (required)
1685  * @param string $reply_to Reply-to address (required)
1686  * @param string $subject Subject line (required)
1687  * @param string $message Message body (required)
1688  * @param string $from_name Displayed name of the sender
1689  * @param string $message_format If "html", sent text as `text/html`. Otherwise, `text/plain`. Default: "html".
1690  * @param string|array $attachments Optional. Files to attach. {@see wp_mail()} for usage. Default: "".
1691  * @param array|false $entry Gravity Forms entry array, related to the email. Default: false.
1692  * @param array|false $notification Gravity Forms notification that triggered the email. {@see GFCommon::send_notification}. Default:false.
1693  */
1694  public static function send_email( $from, $to, $bcc, $reply_to, $subject, $message, $from_name = '', $message_format = 'html', $attachments = '', $entry = false, $notification = false ) {
1695 
1696  $SendEmail = new ReflectionMethod( 'GFCommon', 'send_email' );
1697 
1698  // It was private; let's make it public
1699  $SendEmail->setAccessible( true );
1700 
1701  // Required: $from, $to, $bcc, $replyTo, $subject, $message
1702  // Optional: $from_name, $message_format, $attachments, $lead, $notification
1703  $SendEmail->invoke( new GFCommon, $from, $to, $bcc, $reply_to, $subject, $message, $from_name, $message_format, $attachments, $entry, $notification );
1704  }
1705 
1706 
1707 } //end class
1708 
1709 
1710 /**
1711  * Generate an HTML anchor tag with a list of supported attributes
1712  *
1713  * @see GVCommon::get_link_html()
1714  *
1715  * @since 1.6
1716  *
1717  * @param string $href URL of the link.
1718  * @param string $anchor_text The text or HTML inside the anchor. This is not sanitized in the function.
1719  * @param array|string $atts Attributes to be added to the anchor tag
1720  *
1721  * @return string HTML output of anchor link. If empty $href, returns NULL
1722  */
1723 function gravityview_get_link( $href = '', $anchor_text = '', $atts = array() ) {
1724  return GVCommon::get_link_html( $href, $anchor_text, $atts );
1725 }
const GRAVITYVIEW_DIR
"GRAVITYVIEW_DIR" "./" The absolute path to the plugin directory, with trailing slash ...
Definition: gravityview.php:35
static get_entry_meta( $form_id, $only_default_column=true)
get extra fields from entry meta
static has_product_field( $form=array())
Check whether a form has product fields.
static has_shortcode_r( $content, $tag='gravityview')
Placeholder until the recursive has_shortcode() patch is merged.
static js_encrypt( $content, $message='')
Encrypt content using Javascript so that it&#39;s hidden when JS is disabled.
$forms
Definition: data-source.php:16
static get_entry_id_from_slug( $slug)
Get the entry ID from the entry slug, which may or may not be the entry ID.
static array_merge_recursive_distinct(array &$array1, array &$array2)
array_merge_recursive does indeed merge arrays, but it converts values with duplicate keys to arrays ...
static get_connected_views( $form_id, $args=array())
Get the views for a particular form.
static get_form_fields( $form='', $add_default_properties=false, $include_parent_field=true)
Return array of fields&#39; id and label, for a given Form ID.
static entry_has_transaction_data( $entry=array())
Check if an entry has transaction data.
static calculate_get_entries_criteria( $passed_criteria=array(), $form_ids=array())
Calculates the Search Criteria used on the self::get_entries / self::get_entry methods.
new GravityView_Cache
static get_form_from_entry_id( $entry_slug)
Get the form array for an entry based only on the entry ID.
static getInstance( $passed_post=NULL)
Definition: class-data.php:164
static get_template_setting( $post_id, $key)
Get the setting for a View.
if(gv_empty( $field['value'], false, false)) $format
static get( $field_name)
Alias for get_instance()
If this file is called directly, abort.
static get_meta_form_id( $view_id)
Get the Gravity Forms form ID connected to a View.
static get_entry( $entry_slug, $force_allow_ids=false, $check_entry_display=true)
Return a single entry object.
static get_forms( $active=true, $trash=false)
Alias of GFAPI::get_forms()
static has_cap( $caps_to_check='', $object_id=null, $user_id=null)
Check whether the current user has a capability.
static get_directory_fields( $post_id, $apply_filter=true)
Get the field configuration for the View.
static decode_shortcodes( $string)
Inspired on ::encode_shortcodes, reverse the encoding by replacing the ascii characters by the shortc...
$class
static generate_notice( $notice, $class='', $cap='', $object_id=null)
Display updated/error notice.
$entries
gravityview_get_link( $href='', $anchor_text='', $atts=array())
Generate an HTML anchor tag with a list of supported attributes.
static get_sortable_fields( $formid, $current='')
Render dropdown (select) with the list of sortable fields from a form ID.
static is_value_match( $field_value, $target_value, $operation='is', $source_field=null, $rule=null, $form=null)
Determines if the field value matches the conditional logic rule value.
static gv_parse_str( $string, &$result)
Do the same than parse_str without max_input_vars limitation: Parses $string as if it were the query ...
static matches_operation( $val1, $val2, $operation)
Wrapper for the GFFormsModel::matches_operation() method that adds additional comparisons, including: &#39;equals&#39;, &#39;greater_than_or_is&#39;, &#39;greater_than_or_equals&#39;, &#39;less_than_or_is&#39;, &#39;less_than_or_equals&#39;, &#39;not_contains&#39;, &#39;in&#39;, and &#39;not_in&#39;.
$criteria['paging']
Modify the search parameters before the entries are fetched.
static get_template_settings( $post_id)
Get all the settings for a View.
if(empty( $field_settings['content'])) $content
Definition: custom.php:37
static check_entry_display( $entry)
Checks if a certain entry is valid according to the View search filters (specially the Adv Filters) ...
static get_entry_id( $entry_id_or_slug='', $force_allow_ids=false)
Get the entry ID from a string that may be the Entry ID or the Entry Slug.
static get_entry_ids( $form_id, $search_criteria=array())
Wrapper for the Gravity Forms GFFormsModel::search_lead_ids() method.
static get_field_type( $form=null, $field_id='')
Returns the GF Form field type for a certain field(id) of a form.
static get_field_array( $field)
Return a Gravity Forms field array, whether using GF 1.9 or not.
static get_product_field_types()
Return array of product field types.
static get_field( $form, $field_id)
Returns the field details array of a specific form given the field id.
$gv_field
Definition: time.php:11
static send_email( $from, $to, $bcc, $reply_to, $subject, $message, $from_name='', $message_format='html', $attachments='', $entry=false, $notification=false)
Send email using GFCommon::send_email()
static get_all( $group='')
Get all fields.
static is_field_numeric( $form=null, $field='')
Checks if the field type is a &#39;numeric&#39; field type (e.g.
static get_users( $context='change_entry_creator', $args=array())
Get WordPress users with reasonable limits set.
gravityview_get_form_id( $view_id)
Get the connected form ID from a View ID.
static format_date( $date_string='', $args=array())
Allow formatting date and time based on GravityView standards.
static get_default_args( $with_details=false, $group=NULL)
Retrieve the default args for shortcode and theme function.
Definition: class-data.php:713
$field_id
Definition: time.php:17
static get_entries( $form_ids=null, $passed_criteria=null, &$total=null)
Retrieve entries given search, sort, paging criteria.
static has_gravityview_shortcode( $post=null)
Check whether the post is GravityView.
if(empty( $created_by)) $form_id
static get_sortable_fields_array( $formid, $blacklist=array( 'list', 'textarea'))
global $post
gravityview()
The main GravityView wrapper function.
static get_field_label( $form=array(), $field_id='', $field_value='')
Retrieve the label of a given field id (for a specific form)
gravityview_get_context()
GravityView_View $gravityview_view
Definition: class-api.php:1097
static defaults( $detailed=false, $group=null)
Retrieve the default View settings.
$entry_slug
Definition: notes.php:30
static has_cap( $caps='', $object_id=null, $user_id=null)
Alias of GravityView_Roles_Capabilities::has_cap()
static get_meta_template_id( $view_id)
Get the template ID (list, table, datatables, map) for a View.
$entry
Definition: notes.php:27
static get_all_views( $args=array())
Get all existing Views.
static get_link_html( $href='', $anchor_text='', $atts=array())
Generate an HTML anchor tag with a list of supported attributes.
static get_form( $form_id)
Returns the form object for a given Form ID.
$field_value
Definition: checkbox.php:24
$field
Definition: gquiz_grade.php:11
static getInstance()
Get the one true instantiated self.