GravityView  2.1.1
The best, easiest way to display Gravity Forms entries on your website.
class-gv-view.php
Go to the documentation of this file.
1 <?php
2 namespace GV;
3 
4 /** If this file is called directly, abort. */
5 if ( ! defined( 'GRAVITYVIEW_DIR' ) ) {
6  die();
7 }
8 
9 /**
10  * The default GravityView View class.
11  *
12  * Houses all base View functionality.
13  *
14  * Can be accessed as an array for old compatibility's sake
15  * in line with the elements inside the \GravityView_View_Data::$views array.
16  */
17 class View implements \ArrayAccess {
18 
19  /**
20  * @var \WP_Post The backing post instance.
21  */
22  private $post;
23 
24  /**
25  * @var \GV\View_Settings The settings.
26  *
27  * @api
28  * @since 2.0
29  */
30  public $settings;
31 
32  /**
33  * @var \GV\Widget_Collection The widets attached here.
34  *
35  * @api
36  * @since 2.0
37  */
38  public $widgets;
39 
40  /**
41  * @var \GV\GF_Form|\GV\Form The backing form for this view.
42  *
43  * Contains the form that is sourced for entries in this view.
44  *
45  * @api
46  * @since 2.0
47  */
48  public $form;
49 
50  /**
51  * @var \GV\Field_Collection The fields for this view.
52  *
53  * Contains all the fields that are attached to this view.
54  *
55  * @api
56  * @since 2.0
57  */
58  public $fields;
59 
60  /**
61  * @var array
62  *
63  * Internal static cache for gets, and whatnot.
64  * This is not persistent, resets across requests.
65 
66  * @internal
67  */
68  private static $cache = array();
69 
70  /**
71  * @var \GV\Join[] The joins for all sources in this view.
72  *
73  * @api
74  * @since future
75  */
76  public $joins = array();
77 
78  /**
79  * The constructor.
80  */
81  public function __construct() {
82  $this->settings = new View_Settings();
83  $this->fields = new Field_Collection();
84  $this->widgets = new Widget_Collection();
85  }
86 
87  /**
88  * Register the gravityview WordPress Custom Post Type.
89  *
90  * @internal
91  * @return void
92  */
93  public static function register_post_type() {
94 
95  /** Register only once */
96  if ( post_type_exists( 'gravityview' ) ) {
97  return;
98  }
99 
100  /**
101  * @filter `gravityview_is_hierarchical` Make GravityView Views hierarchical by returning TRUE
102  * This will allow for Views to be nested with Parents and also allows for menu order to be set in the Page Attributes metabox
103  * @since 1.13
104  * @param boolean $is_hierarchical Default: false
105  */
106  $is_hierarchical = (bool)apply_filters( 'gravityview_is_hierarchical', false );
107 
108  $supports = array( 'title', 'revisions' );
109 
110  if ( $is_hierarchical ) {
111  $supports[] = 'page-attributes';
112  }
113 
114  /**
115  * @filter `gravityview_post_type_supports` Modify post type support values for `gravityview` post type
116  * @see add_post_type_support()
117  * @since 1.15.2
118  * @param array $supports Array of features associated with a functional area of the edit screen. Default: 'title', 'revisions'. If $is_hierarchical, also 'page-attributes'
119  * @param[in] boolean $is_hierarchical Do Views support parent/child relationships? See `gravityview_is_hierarchical` filter.
120  */
121  $supports = apply_filters( 'gravityview_post_type_support', $supports, $is_hierarchical );
122 
123  /** Register Custom Post Type - gravityview */
124  $labels = array(
125  'name' => _x( 'Views', 'Post Type General Name', 'gravityview' ),
126  'singular_name' => _x( 'View', 'Post Type Singular Name', 'gravityview' ),
127  'menu_name' => _x( 'Views', 'Menu name', 'gravityview' ),
128  'parent_item_colon' => __( 'Parent View:', 'gravityview' ),
129  'all_items' => __( 'All Views', 'gravityview' ),
130  'view_item' => _x( 'View', 'View Item', 'gravityview' ),
131  'add_new_item' => __( 'Add New View', 'gravityview' ),
132  'add_new' => __( 'New View', 'gravityview' ),
133  'edit_item' => __( 'Edit View', 'gravityview' ),
134  'update_item' => __( 'Update View', 'gravityview' ),
135  'search_items' => __( 'Search Views', 'gravityview' ),
136  'not_found' => \GravityView_Admin::no_views_text(),
137  'not_found_in_trash' => __( 'No Views found in Trash', 'gravityview' ),
138  'filter_items_list' => __( 'Filter Views list', 'gravityview' ),
139  'items_list_navigation' => __( 'Views list navigation', 'gravityview' ),
140  'items_list' => __( 'Views list', 'gravityview' ),
141  'view_items' => __( 'See Views', 'gravityview' ),
142  'attributes' => __( 'View Attributes', 'gravityview' ),
143  );
144  $args = array(
145  'label' => __( 'view', 'gravityview' ),
146  'description' => __( 'Create views based on a Gravity Forms form', 'gravityview' ),
147  'labels' => $labels,
148  'supports' => $supports,
149  'hierarchical' => $is_hierarchical,
150  /**
151  * @filter `gravityview_direct_access` Should Views be directly accessible, or only visible using the shortcode?
152  * @see https://codex.wordpress.org/Function_Reference/register_post_type#public
153  * @since 1.15.2
154  * @param[in,out] boolean `true`: allow Views to be accessible directly. `false`: Only allow Views to be embedded via shortcode. Default: `true`
155  * @param int $view_id The ID of the View currently being requested. `0` for general setting
156  */
157  'public' => apply_filters( 'gravityview_direct_access', gravityview()->plugin->is_compatible(), 0 ),
158  'show_ui' => gravityview()->plugin->is_compatible(),
159  'show_in_menu' => gravityview()->plugin->is_compatible(),
160  'show_in_nav_menus' => true,
161  'show_in_admin_bar' => true,
162  'menu_position' => 17,
163  'menu_icon' => '',
164  'can_export' => true,
165  /**
166  * @filter `gravityview_has_archive` Enable Custom Post Type archive?
167  * @since 1.7.3
168  * @param boolean False: don't have frontend archive; True: yes, have archive. Default: false
169  */
170  'has_archive' => apply_filters( 'gravityview_has_archive', false ),
171  'exclude_from_search' => true,
172  'rewrite' => array(
173  /**
174  * @filter `gravityview_slug` Modify the url part for a View.
175  * @see https://docs.gravityview.co/article/62-changing-the-view-slug
176  * @param string $slug The slug shown in the URL
177  */
178  'slug' => apply_filters( 'gravityview_slug', 'view' ),
179 
180  /**
181  * @filter `gravityview/post_type/with_front` Should the permalink structure
182  * be prepended with the front base.
183  * (example: if your permalink structure is /blog/, then your links will be: false->/view/, true->/blog/view/).
184  * Defaults to true.
185  * @see https://codex.wordpress.org/Function_Reference/register_post_type
186  * @since 2.0
187  * @param bool $with_front
188  */
189  'with_front' => apply_filters( 'gravityview/post_type/with_front', true ),
190  ),
191  'capability_type' => 'gravityview',
192  'map_meta_cap' => true,
193  );
194 
195  register_post_type( 'gravityview', $args );
196  }
197 
198  /**
199  * Add extra rewrite endpoints.
200  *
201  * @return void
202  */
203  public static function add_rewrite_endpoint() {
204  /**
205  * CSV.
206  */
207  global $wp_rewrite;
208 
209  $slug = apply_filters( 'gravityview_slug', 'view' );
210  $rule = array( sprintf( '%s/([^/]+)/csv/?', $slug ), 'index.php?gravityview=$matches[1]&csv=1', 'top' );
211 
212  add_filter( 'query_vars', function( $query_vars ) {
213  $query_vars[] = 'csv';
214  return $query_vars;
215  } );
216 
217  if ( ! isset( $wp_rewrite->extra_rules_top[ $rule[0] ] ) ) {
218  call_user_func_array( 'add_rewrite_rule', $rule );
219  }
220  }
221 
222  /**
223  * A renderer filter for the View post type content.
224  *
225  * @param string $content Should be empty, as we don't store anything there.
226  *
227  * @return string $content The view content as output by the renderers.
228  */
229  public static function content( $content ) {
230  $request = gravityview()->request;
231 
232  // Plugins may run through the content in the header. WP SEO does this for its OpenGraph functionality.
233  if ( ! defined( 'DOING_GRAVITYVIEW_TESTS' ) ) {
234  if ( ! did_action( 'loop_start' ) ) {
235  gravityview()->log->debug( 'Not processing yet: loop_start hasn\'t run yet. Current action: {action}', array( 'action' => current_filter() ) );
236  return $content;
237  }
238 
239  // We don't want this filter to run infinite loop on any post content fields
240  remove_filter( 'the_content', array( __CLASS__, __METHOD__ ) );
241  }
242 
243  /**
244  * This is not a View. Bail.
245  *
246  * Shortcodes and oEmbeds and whatnot will be handled
247  * elsewhere.
248  */
249  if ( ! $view = $request->is_view() ) {
250  return $content;
251  }
252 
253  /**
254  * Check permissions.
255  */
256  while ( $error = $view->can_render( null, $request ) ) {
257  if ( ! is_wp_error( $error ) )
258  break;
259 
260  switch ( str_replace( 'gravityview/', '', $error->get_error_code() ) ) {
261  case 'post_password_required':
262  return get_the_password_form( $view->ID );
263  case 'no_form_attached':
264 
265  gravityview()->log->error( 'View #{view_id} cannot render: {error_code} {error_message}', array( 'error_code' => $error->get_error_code(), 'error_message' => $error->get_error_message() ) );
266 
267  /**
268  * This View has no data source. There's nothing to show really.
269  * ...apart from a nice message if the user can do anything about it.
270  */
271  if ( \GVCommon::has_cap( array( 'edit_gravityviews', 'edit_gravityview' ), $view->ID ) ) {
272  return __( sprintf( 'This View is not configured properly. Start by <a href="%s">selecting a form</a>.', esc_url( get_edit_post_link( $view->ID, false ) ) ), 'gravityview' );
273  }
274  break;
275  case 'no_direct_access':
276  case 'embed_only':
277  case 'not_public':
278  default:
279  gravityview()->log->notice( 'View #{view_id} cannot render: {error_code} {error_message}', array( 'error_code' => $error->get_error_code(), 'error_message' => $error->get_error_message() ) );
280  return __( 'You are not allowed to view this content.', 'gravityview' );
281  }
282 
283  return $content;
284  }
285 
286  $is_admin_and_can_view = $view->settings->get( 'admin_show_all_statuses' ) && \GVCommon::has_cap('gravityview_moderate_entries', $view->ID );
287 
288  /**
289  * Editing a single entry.
290  */
291  if ( $entry = $request->is_edit_entry( $view->form ? $view->form->ID : 0 ) ) {
292  if ( $entry['status'] != 'active' ) {
293  gravityview()->log->notice( 'Entry ID #{entry_id} is not active', array( 'entry_id' => $entry->ID ) );
294  return __( 'You are not allowed to view this content.', 'gravityview' );
295  }
296 
297  if ( apply_filters( 'gravityview_custom_entry_slug', false ) && $entry->slug != get_query_var( \GV\Entry::get_endpoint_name() ) ) {
298  gravityview()->log->error( 'Entry ID #{entry_id} was accessed by a bad slug', array( 'entry_id' => $entry->ID ) );
299  return __( 'You are not allowed to view this content.', 'gravityview' );
300  }
301 
302  if ( $view->settings->get( 'show_only_approved' ) && ! $is_admin_and_can_view ) {
304  gravityview()->log->error( 'Entry ID #{entry_id} is not approved for viewing', array( 'entry_id' => $entry->ID ) );
305  return __( 'You are not allowed to view this content.', 'gravityview' );
306  }
307  }
308 
309  $renderer = new Edit_Entry_Renderer();
310  return $renderer->render( $entry, $view, $request );
311 
312  /**
313  * Viewing a single entry.
314  */
315  } else if ( $entry = $request->is_entry( $view->form ? $view->form->ID : 0 ) ) {
316  if ( $entry['status'] != 'active' ) {
317  gravityview()->log->notice( 'Entry ID #{entry_id} is not active', array( 'entry_id' => $entry->ID ) );
318  return __( 'You are not allowed to view this content.', 'gravityview' );
319  }
320 
321  if ( apply_filters( 'gravityview_custom_entry_slug', false ) && $entry->slug != get_query_var( \GV\Entry::get_endpoint_name() ) ) {
322  gravityview()->log->error( 'Entry ID #{entry_id} was accessed by a bad slug', array( 'entry_id' => $entry->ID ) );
323  return __( 'You are not allowed to view this content.', 'gravityview' );
324  }
325 
326  if ( $view->settings->get( 'show_only_approved' ) && ! $is_admin_and_can_view ) {
328  gravityview()->log->error( 'Entry ID #{entry_id} is not approved for viewing', array( 'entry_id' => $entry->ID ) );
329  return __( 'You are not allowed to view this content.', 'gravityview' );
330  }
331  }
332 
333  $error = \GVCommon::check_entry_display( $entry->as_entry(), $view );
334 
335  if( is_wp_error( $error ) ) {
336  gravityview()->log->error( 'Entry ID #{entry_id} is not approved for viewing: {message}', array( 'entry_id' => $entry->ID, 'message' => $error->get_error_message() ) );
337  return __( 'You are not allowed to view this content.', 'gravityview' );
338  }
339 
340  $renderer = new Entry_Renderer();
341  return $renderer->render( $entry, $view, $request );
342  }
343 
344  /**
345  * Plain old View.
346  */
347  $renderer = new View_Renderer();
348  return $renderer->render( $view, $request );
349  }
350 
351  /**
352  * Checks whether this view can be accessed or not.
353  *
354  * @param string[] $context The context we're asking for access from.
355  * Can any and as many of one of:
356  * edit An edit context.
357  * single A single context.
358  * cpt The custom post type single page acessed.
359  * shortcode Embedded as a shortcode.
360  * oembed Embedded as an oEmbed.
361  * rest A REST call.
362  * @param \GV\Request $request The request
363  *
364  * @return bool|\WP_Error An error if this View shouldn't be rendered here.
365  */
366  public function can_render( $context = null, $request = null ) {
367  if ( ! $request ) {
368  $request = gravityview()->request;
369  }
370 
371  if ( ! is_array( $context ) ) {
372  $context = array();
373  }
374 
375  /**
376  * @filter `gravityview/view/can_render` Whether the view can be rendered or not.
377  * @param bool|\WP_Error $result The result. Default: null.
378  * @param \GV\View $view The view.
379  * @param string[] $context See \GV\View::can_render
380  * @param \GV\Request $request The request.
381  */
382  if ( ! is_null( $result = apply_filters( 'gravityview/view/can_render', null, $this, $context, $request ) ) ) {
383  return $result;
384  }
385 
386  if ( in_array( 'rest', $context ) ) {
387  // REST
388  if ( gravityview()->plugin->settings->get( 'rest_api' ) === '1' && $this->settings->get( 'rest_disable' ) === '1' ) {
389  return new \WP_Error( 'gravityview/rest_disabled' );
390  } elseif ( gravityview()->plugin->settings->get( 'rest_api' ) !== '1' && $this->settings->get( 'rest_enable' ) !== '1' ) {
391  return new \WP_Error( 'gravityview/rest_disabled' );
392  }
393  }
394 
395  if ( in_array( 'csv', $context ) ) {
396  if ( $this->settings->get( 'csv_enable' ) !== '1' ) {
397  return new \WP_Error( 'gravityview/csv_disabled', 'The CSV endpoint is not enabled for this View' );
398  }
399  }
400 
401  /**
402  * This View is password protected. Nothing to do here.
403  */
404  if ( post_password_required( $this->ID ) ) {
405  gravityview()->log->notice( 'Post password is required for View #{view_id}', array( 'view_id' => $this->ID ) );
406  return new \WP_Error( 'gravityview/post_password_required' );
407  }
408 
409  if ( ! $this->form ) {
410  gravityview()->log->notice( 'View #{id} has no form attached to it.', array( 'id' => $this->ID ) );
411  return new \WP_Error( 'gravityview/no_form_attached' );
412  }
413 
414  if ( ! in_array( 'shortcode', $context ) ) {
415  /**
416  * Is this View directly accessible via a post URL?
417  *
418  * @see https://codex.wordpress.org/Function_Reference/register_post_type#public
419  */
420 
421  /**
422  * @filter `gravityview_direct_access` Should Views be directly accessible, or only visible using the shortcode?
423  * @deprecated
424  * @param[in,out] boolean `true`: allow Views to be accessible directly. `false`: Only allow Views to be embedded. Default: `true`
425  * @param int $view_id The ID of the View currently being requested. `0` for general setting
426  */
427  $direct_access = apply_filters( 'gravityview_direct_access', true, $this->ID );
428 
429  /**
430  * @filter `gravityview/request/output/direct` Should this View be directly accessbile?
431  * @since 2.0
432  * @param[in,out] boolean Accessible or not. Default: accessbile.
433  * @param \GV\View $view The View we're trying to directly render here.
434  * @param \GV\Request $request The current request.
435  */
436  if ( ! apply_filters( 'gravityview/view/output/direct', $direct_access, $this, $request ) ) {
437  return new \WP_Error( 'gravityview/no_direct_access' );
438  }
439 
440  /**
441  * Is this View an embed-only View? If so, don't allow rendering here,
442  * as this is a direct request.
443  */
444  if ( $this->settings->get( 'embed_only' ) && ! \GVCommon::has_cap( 'read_private_gravityviews' ) ) {
445  return new \WP_Error( 'gravityview/embed_only' );
446  }
447  }
448 
449  /** Private, pending, draft, etc. */
450  $public_states = get_post_stati( array( 'public' => true ) );
451  if ( ! in_array( $this->post_status, $public_states ) && ! \GVCommon::has_cap( 'read_gravityview', $this->ID ) ) {
452  gravityview()->log->notice( 'The current user cannot access this View #{view_id}', array( 'view_id' => $this->ID ) );
453  return new \WP_Error( 'gravityview/not_public' );
454  }
455 
456  return true;
457  }
458 
459  /**
460  * Get joins associated with a view
461  *
462  * @param \WP_Post $post GravityView CPT to get joins for
463  *
464  * @since 2.0.11
465  *
466  * @return \GV\Join[] Array of \GV\Join instances
467  */
468  public static function get_joins( $post ) {
469 
470  if ( ! gravityview()->plugin->supports( Plugin::FEATURE_JOINS ) ) {
471  gravityview()->log->error( 'Cannot get joined forms; joins feature not supported.' );
472  return array();
473  }
474 
475  if ( ! $post || 'gravityview' !== get_post_type( $post ) ) {
476  gravityview()->log->error( 'Only "gravityview" post types can be \GV\View instances.' );
477  return array();
478  }
479 
480  $joins_meta = get_post_meta( $post->ID, '_gravityview_form_joins', true );
481 
482  if ( empty( $joins_meta ) ) {
483  return array();
484  }
485 
486  $joins = array();
487 
488  foreach ( $joins_meta as $meta ) {
489  if ( ! is_array( $meta ) || count( $meta ) != 4 ) {
490  continue;
491  }
492 
493  list( $join, $join_column, $join_on, $join_on_column ) = $meta;
494 
495  $join = GF_Form::by_id( $join );
496  $join_on = GF_Form::by_id( $join_on );
497 
498  $join_column = is_numeric( $join_column ) ? GF_Field::by_id( $join, $join_column ) : Internal_Field( $join_column );
499  $join_on_column = is_numeric( $join_on_column ) ? GF_Field::by_id( $join_on, $join_on_column ) : Internal_Field( $join_on_column );
500 
501  $joins [] = new Join( $join, $join_column, $join_on, $join_on_column );
502  }
503 
504  return $joins;
505  }
506 
507  /**
508  * Get joined forms associated with a view
509  *
510  * @since 2.0.11
511  *
512  * @param int $post_id ID of the View
513  *
514  * @return \GV\GF_Form[] Array of \GV\GF_Form instances
515  */
516  public static function get_joined_forms( $post_id = 0 ) {
517 
518  if ( ! gravityview()->plugin->supports( Plugin::FEATURE_JOINS ) ) {
519  gravityview()->log->error( 'Cannot get joined forms; joins feature not supported.' );
520  return array();
521  }
522 
523  if ( empty( $post_id ) ) {
524  gravityview()->log->error( 'Cannot get joined forms; $post_id was empty' );
525  return array();
526  }
527 
528  $joins_meta = get_post_meta( $post_id, '_gravityview_form_joins', true );
529 
530  if ( empty( $joins_meta ) ) {
531  return array();
532  }
533 
534  $forms_ids = array();
535 
536  foreach ( $joins_meta as $meta ) {
537  if ( ! is_array( $meta ) || count( $meta ) != 4 ) {
538  continue;
539  }
540 
541  list( $join, $join_column, $join_on, $join_on_column ) = $meta;
542 
543  $forms_ids [] = GF_Form::by_id( $join_on );
544  }
545 
546  return ( !empty( $forms_ids) ) ? $forms_ids : null;
547  }
548 
549  /**
550  * Construct a \GV\View instance from a \WP_Post.
551  *
552  * @param \WP_Post $post The \WP_Post instance to wrap.
553  *
554  * @api
555  * @since 2.0
556  * @return \GV\View|null An instance around this \WP_Post if valid, null otherwise.
557  */
558  public static function from_post( $post ) {
559 
560  if ( ! $post || 'gravityview' !== get_post_type( $post ) ) {
561  gravityview()->log->error( 'Only gravityview post types can be \GV\View instances.' );
562  return null;
563  }
564 
565  if ( $view = Utils::get( self::$cache, "View::from_post:{$post->ID}" ) ) {
566  /**
567  * @filter `gravityview/view/get` Override View.
568  * @param \GV\View $view The View instance pointer.
569  * @since 2.1
570  */
571  do_action_ref_array( 'gravityview/view/get', array( &$view ) );
572 
573  return $view;
574  }
575 
576  $view = new self();
577  $view->post = $post;
578 
579  /** Get connected form. */
580  $view->form = GF_Form::by_id( $view->_gravityview_form_id );
581  if ( ! $view->form ) {
582  gravityview()->log->error( 'View #{view_id} tried attaching non-existent Form #{form_id} to it.', array(
583  'view_id' => $view->ID,
584  'form_id' => $view->_gravityview_form_id ? : 0,
585  ) );
586  }
587 
588  $view->joins = $view->get_joins( $post );
589 
590  /**
591  * @filter `gravityview/configuration/fields` Filter the View fields' configuration array.
592  * @since 1.6.5
593  *
594  * @deprecated Use `gravityview/view/configuration/fields` or `gravityview/view/fields` filters.
595  *
596  * @param $fields array Multi-array of fields with first level being the field zones.
597  * @param $view_id int The View the fields are being pulled for.
598  */
599  $configuration = apply_filters( 'gravityview/configuration/fields', (array)$view->_gravityview_directory_fields, $view->ID );
600 
601  /**
602  * @filter `gravityview/view/configuration/fields` Filter the View fields' configuration array.
603  * @since 2.0
604  *
605  * @param array $fields Multi-array of fields with first level being the field zones.
606  * @param \GV\View $view The View the fields are being pulled for.
607  */
608  $configuration = apply_filters( 'gravityview/view/configuration/fields', $configuration, $view );
609 
610  /**
611  * @filter `gravityview/view/fields` Filter the Field Collection for this View.
612  * @since 2.0
613  *
614  * @param \GV\Field_Collection $fields A collection of fields.
615  * @param \GV\View $view The View the fields are being pulled for.
616  */
617  $view->fields = apply_filters( 'gravityview/view/fields', Field_Collection::from_configuration( $configuration ), $view );
618 
619  /**
620  * @filter `gravityview/view/configuration/widgets` Filter the View widgets' configuration array.
621  * @since 2.0
622  *
623  * @param array $fields Multi-array of widgets with first level being the field zones.
624  * @param \GV\View $view The View the widgets are being pulled for.
625  */
626  $configuration = apply_filters( 'gravityview/view/configuration/widgets', (array)$view->_gravityview_directory_widgets, $view );
627 
628  /**
629  * @filter `gravityview/view/widgets` Filter the Widget Collection for this View.
630  * @since 2.0
631  *
632  * @param \GV\Widget_Collection $widgets A collection of widgets.
633  * @param \GV\View $view The View the widgets are being pulled for.
634  */
635  $view->widgets = apply_filters( 'gravityview/view/widgets', Widget_Collection::from_configuration( $configuration ), $view );
636 
637  /** View configuration. */
638  $view->settings->update( gravityview_get_template_settings( $view->ID ) );
639 
640  /** Add the template name into the settings. */
641  $view->settings->update( array( 'template' => gravityview_get_template_id( $view->ID ) ) );
642 
643  /** View basics. */
644  $view->settings->update( array(
645  'id' => $view->ID,
646  ) );
647 
648  self::$cache[ "View::from_post:{$post->ID}" ] = &$view;
649 
650  /**
651  * @filter `gravityview/view/get` Override View.
652  * @param \GV\View $view The View instance pointer.
653  * @since 2.1
654  */
655  do_action_ref_array( 'gravityview/view/get', array( &$view ) );
656 
657  return $view;
658  }
659 
660  /**
661  * Flush the view cache.
662  *
663  * @param int $view_id The View to reset cache for. Optional. Default: resets everything.
664  *
665  * @internal
666  */
667  public static function _flush_cache( $view_id = null ) {
668  if ( $view_id ) {
669  unset( self::$cache[ "View::from_post:$view_id" ] );
670  return;
671  }
672  self::$cache = array();
673  }
674 
675  /**
676  * Construct a \GV\View instance from a post ID.
677  *
678  * @param int|string $post_id The post ID.
679  *
680  * @api
681  * @since 2.0
682  * @return \GV\View|null An instance around this \WP_Post or null if not found.
683  */
684  public static function by_id( $post_id ) {
685  if ( ! $post_id || ! $post = get_post( $post_id ) ) {
686  return null;
687  }
688  return self::from_post( $post );
689  }
690 
691  /**
692  * Determines if a view exists to begin with.
693  *
694  * @param int|\WP_Post|null $view The WordPress post ID, a \WP_Post object or null for global $post;
695  *
696  * @api
697  * @since 2.0
698  * @return bool Whether the post exists or not.
699  */
700  public static function exists( $view ) {
701  return get_post_type( $view ) == 'gravityview';
702  }
703 
704  /**
705  * ArrayAccess compatibility layer with GravityView_View_Data::$views
706  *
707  * @internal
708  * @deprecated
709  * @since 2.0
710  * @return bool Whether the offset exists or not, limited to GravityView_View_Data::$views element keys.
711  */
712  public function offsetExists( $offset ) {
713  $data_keys = array( 'id', 'view_id', 'form_id', 'template_id', 'atts', 'fields', 'widgets', 'form' );
714  return in_array( $offset, $data_keys );
715  }
716 
717  /**
718  * ArrayAccess compatibility layer with GravityView_View_Data::$views
719  *
720  * Maps the old keys to the new data;
721  *
722  * @internal
723  * @deprecated
724  * @since 2.0
725  *
726  * @return mixed The value of the requested view data key limited to GravityView_View_Data::$views element keys.
727  */
728  public function offsetGet( $offset ) {
729 
730  gravityview()->log->notice( 'This is a \GV\View object should not be accessed as an array.' );
731 
732  if ( ! isset( $this[ $offset ] ) ) {
733  return null;
734  }
735 
736  switch ( $offset ) {
737  case 'id':
738  case 'view_id':
739  return $this->ID;
740  case 'form':
741  return $this->form;
742  case 'form_id':
743  return $this->form ? $this->form->ID : null;
744  case 'atts':
745  return $this->settings->as_atts();
746  case 'template_id':
747  return $this->settings->get( 'template' );
748  case 'widgets':
749  return $this->widgets->as_configuration();
750  }
751  }
752 
753  /**
754  * ArrayAccess compatibility layer with GravityView_View_Data::$views
755  *
756  * @internal
757  * @deprecated
758  * @since 2.0
759  *
760  * @return void
761  */
762  public function offsetSet( $offset, $value ) {
763  gravityview()->log->error( 'The old view data is no longer mutable. This is a \GV\View object should not be accessed as an array.' );
764  }
765 
766  /**
767  * ArrayAccess compatibility layer with GravityView_View_Data::$views
768  *
769  * @internal
770  * @deprecated
771  * @since 2.0
772  * @return void
773  */
774  public function offsetUnset( $offset ) {
775  gravityview()->log->error( 'The old view data is no longer mutable. This is a \GV\View object should not be accessed as an array.' );
776  }
777 
778  /**
779  * Be compatible with the old data object.
780  *
781  * Some external code expects an array (doing things like foreach on this, or array_keys)
782  * so let's return an array in the old format for such cases. Do not use unless using
783  * for back-compatibility.
784  *
785  * @internal
786  * @deprecated
787  * @since 2.0
788  * @return array
789  */
790  public function as_data() {
791  return array(
792  'id' => $this->ID,
793  'view_id' => $this->ID,
794  'form_id' => $this->form ? $this->form->ID : null,
795  'form' => $this->form ? gravityview_get_form( $this->form->ID ) : null,
796  'atts' => $this->settings->as_atts(),
797  'fields' => $this->fields->by_visible()->as_configuration(),
798  'template_id' => $this->settings->get( 'template' ),
799  'widgets' => $this->widgets->as_configuration(),
800  );
801  }
802 
803  /**
804  * Retrieve the entries for the current view and request.
805  *
806  * @param \GV\Request The request. Unused for now.
807  *
808  * @return \GV\Entry_Collection The entries.
809  */
810  public function get_entries( $request = null ) {
811  $entries = new \GV\Entry_Collection();
812  if ( $this->form ) {
813  /**
814  * @todo: Stop using _frontend and use something like $request->get_search_criteria() instead
815  */
816  $parameters = \GravityView_frontend::get_view_entries_parameters( $this->settings->as_atts(), $this->form->ID );
817  $parameters['context_view_id'] = $this->ID;
818  $parameters = \GVCommon::calculate_get_entries_criteria( $parameters, $this->form->ID );
819 
820  if ( $request instanceof REST\Request ) {
821  $atts = $this->settings->as_atts();
822  $paging_parameters = wp_parse_args( $request->get_paging(), array(
823  'paging' => array( 'page_size' => $atts['page_size'] ),
824  ) );
825  $parameters['paging'] = $paging_parameters['paging'];
826  }
827 
828  $page = Utils::get( $parameters['paging'], 'current_page' ) ?
829  : ( ( ( $parameters['paging']['offset'] - $this->settings->get( 'offset' ) ) / $parameters['paging']['page_size'] ) + 1 );
830 
831  /**
832  * Cleanup duplicate field_filter parameters to simplify the query.
833  */
834  $unique_field_filters = array();
835  foreach ( $parameters['search_criteria']['field_filters'] as $key => $filter ) {
836  if ( 'mode' === $key ) {
837  $unique_field_filters['mode'] = $filter;
838  } else if ( ! in_array( $filter, $unique_field_filters ) ) {
839  $unique_field_filters[] = $filter;
840  }
841  }
842  $parameters['search_criteria']['field_filters'] = $unique_field_filters;
843 
844  if ( ! empty( $parameters['search_criteria']['field_filters'] ) ) {
845  gravityview()->log->notice( 'search_criteria/field_filters is not empty, third-party code may be using legacy search_criteria filters.' );
846  }
847 
848  if ( gravityview()->plugin->supports( Plugin::FEATURE_GFQUERY ) ) {
849  /**
850  * New \GF_Query stuff :)
851  */
852  $query = new \GF_Query( $this->form->ID, $parameters['search_criteria'], $parameters['sorting'] );
853 
854  $query->limit( $parameters['paging']['page_size'] )
855  ->offset( ( ( $page - 1 ) * $parameters['paging']['page_size'] ) + $this->settings->get( 'offset' ) );
856 
857  /**
858  * Any joins?
859  */
860  if ( Plugin::FEATURE_JOINS && count( $this->joins ) ) {
861  foreach ( $this->joins as $join ) {
862  $query = $join->as_query_join( $query );
863  }
864  }
865 
866  /**
867  * @action `gravityview/view/query` Override the \GF_Query before the get() call.
868  * @param \GF_Query $query The current query object reference
869  * @param \GV\View $this The current view object
870  * @param \GV\Request $request The request object
871  */
872  do_action_ref_array( 'gravityview/view/query', array( &$query, $this, $request ) );
873 
874  gravityview()->log->debug( 'GF_Query parameters: ', array( 'data' => Utils::gf_query_debug( $query ) ) );
875 
876  /**
877  * Map from Gravity Forms entries arrays to an Entry_Collection.
878  */
879  if ( count( $this->joins ) ) {
880  foreach ( $query->get() as $entry ) {
881  $entries->add(
882  Multi_Entry::from_entries( array_map( '\GV\GF_Entry::from_entry', $entry ) )
883  );
884  }
885  } else {
886  array_map( array( $entries, 'add' ), array_map( '\GV\GF_Entry::from_entry', $query->get() ) );
887  }
888 
889  /**
890  * Add total count callback.
891  */
892  $entries->add_count_callback( function() use ( $query ) {
893  return $query->total_found;
894  } );
895  } else {
896  $entries = $this->form->entries
897  ->filter( \GV\GF_Entry_Filter::from_search_criteria( $parameters['search_criteria'] ) )
898  ->offset( $this->settings->get( 'offset' ) )
899  ->limit( $parameters['paging']['page_size'] )
900  ->page( $page );
901 
902  if ( ! empty( $parameters['sorting'] ) && ! empty( $parameters['sorting']['key'] ) ) {
903  $field = new \GV\Field();
904  $field->ID = $parameters['sorting']['key'];
905  $direction = strtolower( $parameters['sorting']['direction'] ) == 'asc' ? \GV\Entry_Sort::ASC : \GV\Entry_Sort::DESC;
906  $entries = $entries->sort( new \GV\Entry_Sort( $field, $direction ) );
907  }
908  }
909  }
910 
911  /**
912  * @filter `gravityview/view/entries` Modify the entry fetching filters, sorts, offsets, limits.
913  * @param \GV\Entry_Collection $entries The entries for this view.
914  * @param \GV\View $view The view.
915  * @param \GV\Request $request The request.
916  */
917  return apply_filters( 'gravityview/view/entries', $entries, $this, $request );
918  }
919 
920  /**
921  * Last chance to configure the output.
922  *
923  * Used for CSV output, for example.
924  *
925  * @return void
926  */
927  public static function template_redirect() {
928  /**
929  * CSV output.
930  */
931  if ( ! get_query_var( 'csv' ) ) {
932  return;
933  }
934 
935  if ( ! $view = gravityview()->request->is_view() ) {
936  return;
937  }
938 
939  if ( is_wp_error( $error = $view->can_render( array( 'csv' ) ) ) ) {
940  gravityview()->log->error( 'Not rendering CSV: ' . $error->get_error_message() );
941  return;
942  }
943 
944  /**
945  * Modify the name of the generated CSV file. Name will be sanitized using sanitize_file_name() before output.
946  * @see sanitize_file_name()
947  * @since 2.1
948  * @param string $filename File name used when downloading a CSV. Default is "{View title}.csv"
949  * @param \GV\View $view Current View being rendered
950  */
951  $filename = apply_filters( 'gravityview/output/csv/filename', get_the_title( $view->post ), $view );
952 
953  if ( ! defined( 'DOING_GRAVITYVIEW_TESTS' ) ) {
954  header( sprintf( 'Content-Disposition: attachment;filename="%s.csv"', sanitize_file_name( $filename ) ) );
955  header( 'Content-Transfer-Encoding: binary' );
956  header( 'Content-Type: text/csv' );
957  }
958 
959  ob_start();
960  $csv = fopen( 'php://output', 'w' );
961 
962  /**
963  * Add da' BOM if GF uses it
964  * @see GFExport::start_export()
965  */
966  if ( apply_filters( 'gform_include_bom_export_entries', true, $view->form ? $view->form->form : null ) ) {
967  fputs( $csv, "\xef\xbb\xbf" );
968  }
969 
970  $entries = $view->get_entries();
971 
972  $headers_done = false;
973  $allowed = $headers = array();
974 
975  foreach ( $view->fields->by_position( "directory_*" )->by_visible()->all() as $field ) {
976  $allowed[ $field->ID ] = $field;
977  }
978 
979  $renderer = new Field_Renderer();
980 
981  foreach ( $entries->all() as $entry ) {
982 
983  $return = array();
984 
985  /**
986  * @filter `gravityview/csv/entry/fields` Whitelist more entry fields that are output in CSV requests.
987  * @param[in,out] array $allowed The allowed ones, default by_visible, by_position( "context_*" ), i.e. as set in the View.
988  * @param \GV\View $view The view.
989  * @param \GV\Entry $entry WordPress representation of the item.
990  */
991  $allowed_field_ids = apply_filters( 'gravityview/csv/entry/fields', array_keys( $allowed ), $view, $entry );
992 
993  foreach ( $allowed_field_ids as $field_id ) {
994  $source = is_numeric( $field_id ) ? $view->form : new \GV\Internal_Source();
995 
996  if ( isset( $allowed[ $field_id ] ) ) {
997  $field = $allowed[ $field_id ];
998  } else {
999  $field = is_numeric( $field_id ) ? \GV\GF_Field::by_id( $view->form, $field_id ) : \GV\Internal_Field::by_id( $field_id );
1000  }
1001 
1002  $return[ $field->ID ] = $renderer->render( $field, $view, $source, $entry, gravityview()->request, '\GV\Field_CSV_Template' );
1003 
1004  if ( ! $headers_done ) {
1005  $label = $field->get_label( $view, $source, $entry );
1006  $headers[ $field->ID ] = $label ? $label : $field->ID;
1007  }
1008  }
1009 
1010  if ( ! $headers_done ) {
1011  $headers_done = fputcsv( $csv, array_map( array( '\GV\Utils', 'strip_excel_formulas' ), array_values( $headers ) ) );
1012  }
1013 
1014  fputcsv( $csv, array_map( array( '\GV\Utils', 'strip_excel_formulas' ), $return ) );
1015  }
1016 
1017  fflush( $csv );
1018 
1019  echo rtrim( ob_get_clean() );
1020 
1021  if ( ! defined( 'DOING_GRAVITYVIEW_TESTS' ) ) {
1022  exit;
1023  }
1024  }
1025 
1026  public function __get( $key ) {
1027  if ( $this->post ) {
1028  $raw_post = $this->post->filter( 'raw' );
1029  return $raw_post->{$key};
1030  }
1031  return isset( $this->{$key} ) ? $this->{$key} : null;
1032  }
1033 }
If this file is called directly, abort.
If this file is called directly, abort.
$labels
static get_joins( $post)
Get joins associated with a view.
If this file is called directly, abort.
__construct()
The constructor.
static content( $content)
A renderer filter for the View post type content.
__get( $key)
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.
as_data()
Be compatible with the old data object.
offsetUnset( $offset)
ArrayAccess compatibility layer with GravityView_View_Data::$views.
offsetSet( $offset, $value)
ArrayAccess compatibility layer with GravityView_View_Data::$views.
can_render( $context=null, $request=null)
Checks whether this view can be accessed or not.
gravityview_get_form( $form_id)
Returns the form object for a given Form ID.
gravityview_get_template_settings( $post_id)
Get all the settings for a View.
If this file is called directly, abort.
$entries
offsetGet( $offset)
ArrayAccess compatibility layer with GravityView_View_Data::$views.
gravityview()
Definition: _stubs.php:26
get( $key, $default=null)
Retrieve a setting.
static by_id( $form, $field_id)
Get a by and Field ID.
const DESC
if(empty( $field_settings['content'])) $content
Definition: custom.php:37
static _flush_cache( $view_id=null)
Flush the view cache.
static no_views_text()
Get text for no views found.
Definition: class-admin.php:65
static check_entry_display( $entry, $view=null)
Checks if a certain entry is valid according to the View search filters (specially the Adv Filters) ...
static get_joined_forms( $post_id=0)
Get joined forms associated with a view.
If this file is called directly, abort.
const ASC
static exists( $view)
Determines if a view exists to begin with.
static by_id( $post_id)
Construct a instance from a post ID.
If this file is called directly, abort.
static add_rewrite_endpoint()
Add extra rewrite endpoints.
$field_id
Definition: time.php:17
offsetExists( $offset)
ArrayAccess compatibility layer with GravityView_View_Data::$views.
static template_redirect()
Last chance to configure the output.
static by_id( $field_id)
Get a from an internal Gravity Forms field ID.
If this file is called directly, abort.
global $post
If this file is called directly, abort.
If this file is called directly, abort.
gravityview_get_template_id( $post_id)
Get the template ID (list, table, datatables, map) for a View.
get_entries( $request=null)
Retrieve the entries for the current view and request.
static is_approved( $status)
If this file is called directly, abort.
static has_cap( $caps='', $object_id=null, $user_id=null)
Alias of GravityView_Roles_Capabilities::has_cap()
$entry
Definition: notes.php:27
static register_post_type()
Register the gravityview WordPress Custom Post Type.
static from_post( $post)
Construct a instance from a .
const meta_key
If this file is called directly, abort.
$field
Definition: gquiz_grade.php:11
If this file is called directly, abort.