GravityView  1.22.6
The best, easiest way to display Gravity Forms entries on your website.
class-frontend-views.php
Go to the documentation of this file.
1 <?php
2 /**
3  * GravityView Frontend functions
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.0.0
12  */
13 
14 
16 
17  /**
18  * Regex strings that are used to determine whether the current request is a GravityView search or not.
19  * @see GravityView_frontend::is_searching()
20  * @since 1.7.4.1
21  * @var array
22  */
23  private static $search_parameters = array( 'gv_search', 'gv_start', 'gv_end', 'gv_id', 'gv_by', 'filter_*' );
24 
25  /**
26  * Is the currently viewed post a `gravityview` post type?
27  * @var boolean
28  */
30 
31  /**
32  * Does the current post have a `[gravityview]` shortcode?
33  * @var boolean
34  */
35  var $post_has_shortcode = false;
36 
37  /**
38  * The Post ID of the currently viewed post. Not necessarily GV
39  * @var int
40  */
41  var $post_id = null;
42 
43  /**
44  * Are we currently viewing a single entry?
45  * If so, the int value of the entry ID. Otherwise, false.
46  * @var int|boolean
47  */
48  var $single_entry = false;
49 
50  /**
51  * If we are viewing a single entry, the entry data
52  * @var array|false
53  */
54  var $entry = false;
55 
56  /**
57  * When displaying the single entry we should always know to which View it belongs (the context is everything!)
58  * @var null
59  */
60  var $context_view_id = null;
61 
62  /**
63  * The View is showing search results
64  * @since 1.5.4
65  * @var boolean
66  */
67  var $is_search = false;
68 
69  /**
70  * The view data parsed from the $post
71  *
72  * @see GravityView_View_Data::__construct()
73  * @var GravityView_View_Data
74  */
75  var $gv_output_data = null;
76 
77  /**
78  * @var GravityView_frontend
79  */
80  static $instance;
81 
82  /**
83  * Class constructor, enforce Singleton pattern
84  */
85  private function __construct() {}
86 
87  private function initialize() {
88  add_action( 'wp', array( $this, 'parse_content'), 11 );
89  add_filter( 'parse_query', array( $this, 'parse_query_fix_frontpage' ), 10 );
90  add_action( 'template_redirect', array( $this, 'set_entry_data'), 1 );
91 
92  // Enqueue scripts and styles after GravityView_Template::register_styles()
93  add_action( 'wp_enqueue_scripts', array( $this, 'add_scripts_and_styles' ), 20 );
94 
95  // Enqueue and print styles in the footer. Added 1 priorty so stuff gets printed at 10 priority.
96  add_action( 'wp_print_footer_scripts', array( $this, 'add_scripts_and_styles' ), 1 );
97 
98  add_filter( 'the_title', array( $this, 'single_entry_title' ), 1, 2 );
99  add_filter( 'the_content', array( $this, 'insert_view_in_content' ) );
100  add_filter( 'comments_open', array( $this, 'comments_open' ), 10, 2 );
101 
102  add_action( 'gravityview_after', array( $this, 'context_not_configured_warning' ) );
103  }
104 
105  /**
106  * Get the one true instantiated self
107  * @return GravityView_frontend
108  */
109  public static function getInstance() {
110 
111  if ( empty( self::$instance ) ) {
112  self::$instance = new self;
113  self::$instance->initialize();
114  }
115 
116  return self::$instance;
117  }
118 
119  /**
120  * @return GravityView_View_Data
121  */
122  public function getGvOutputData() {
123  return $this->gv_output_data;
124  }
125 
126  /**
127  * @param GravityView_View_Data $gv_output_data
128  */
129  public function setGvOutputData( $gv_output_data ) {
130  $this->gv_output_data = $gv_output_data;
131  }
132 
133  /**
134  * @return boolean
135  */
136  public function isSearch() {
137  return $this->is_search;
138  }
139 
140  /**
141  * @param boolean $is_search
142  */
143  public function setIsSearch( $is_search ) {
144  $this->is_search = $is_search;
145  }
146 
147  /**
148  * @return bool|int
149  */
150  public function getSingleEntry() {
151  return $this->single_entry;
152  }
153 
154  /**
155  * Sets the single entry ID and also the entry
156  * @param bool|int|string $single_entry
157  */
158  public function setSingleEntry( $single_entry ) {
159 
160  $this->single_entry = $single_entry;
161 
162  }
163 
164  /**
165  * @return array
166  */
167  public function getEntry() {
168  return $this->entry;
169  }
170 
171  /**
172  * Set the current entry
173  * @param array|int $entry Entry array or entry slug or ID
174  */
175  public function setEntry( $entry ) {
176 
177  if ( ! is_array( $entry ) ) {
178  $entry = GVCommon::get_entry( $entry );
179  }
180 
181  $this->entry = $entry;
182  }
183 
184  /**
185  * @return int
186  */
187  public function getPostId() {
188  return $this->post_id;
189  }
190 
191  /**
192  * @param int $post_id
193  */
194  public function setPostId( $post_id ) {
195  $this->post_id = $post_id;
196  }
197 
198  /**
199  * @return boolean
200  */
201  public function isPostHasShortcode() {
203  }
204 
205  /**
206  * @param boolean $post_has_shortcode
207  */
208  public function setPostHasShortcode( $post_has_shortcode ) {
209  $this->post_has_shortcode = $post_has_shortcode;
210  }
211 
212  /**
213  * @return boolean
214  */
215  public function isGravityviewPostType() {
217  }
218 
219  /**
220  * @param boolean $is_gravityview_post_type
221  */
222  public function setIsGravityviewPostType( $is_gravityview_post_type ) {
223  $this->is_gravityview_post_type = $is_gravityview_post_type;
224  }
225 
226  /**
227  * Set the context view ID used when page contains multiple embedded views or displaying the single entry view
228  *
229  *
230  *
231  * @param null $view_id
232  */
233  public function set_context_view_id( $view_id = null ) {
234  $multiple_views = defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ? gravityview()->views->count() > 1 : ( $this->getGvOutputData() && $this->getGvOutputData()->has_multiple_views() );
235 
236  if ( ! empty( $view_id ) ) {
237 
238  $this->context_view_id = $view_id;
239 
240  } elseif ( isset( $_GET['gvid'] ) && $multiple_views ) {
241  /**
242  * used on a has_multiple_views context
243  * @see GravityView_API::entry_link
244  */
245  $this->context_view_id = $_GET['gvid'];
246 
247  } elseif ( ! $multiple_views ) {
248  if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ) {
249  $view = gravityview()->views->last();
250  $this->context_view_id = $view ? $view->ID : null;
251  } else {
252  /** GravityView_View_Data::get_views is deprecated. */
253  $array_keys = array_keys( $this->getGvOutputData()->get_views() );
254  $this->context_view_id = array_pop( $array_keys );
255  unset( $array_keys );
256  }
257  }
258 
259  }
260 
261  /**
262  * Returns the the view_id context when page contains multiple embedded views or displaying single entry view
263  *
264  * @since 1.5.4
265  *
266  * @return string
267  */
268  public function get_context_view_id() {
269  return $this->context_view_id;
270  }
271 
272  /**
273  * Allow GravityView entry endpoints on the front page of a site
274  *
275  * @link https://core.trac.wordpress.org/ticket/23867 Fixes this core issue
276  * @link https://wordpress.org/plugins/cpt-on-front-page/ Code is based on this
277  *
278  * @since 1.17.3
279  *
280  * @param WP_Query &$query (passed by reference)
281  *
282  * @return void
283  */
284  public function parse_query_fix_frontpage( &$query ) {
285  global $wp_rewrite;
286 
287  $is_front_page = ( $query->is_home || $query->is_page );
288  $show_on_front = ( 'page' === get_option('show_on_front') );
289  $front_page_id = get_option('page_on_front');
290 
291  if ( $is_front_page && $show_on_front && $front_page_id ) {
292 
293  // Force to be an array, potentially a query string ( entry=16 )
294  $_query = wp_parse_args( $query->query );
295 
296  // pagename can be set and empty depending on matched rewrite rules. Ignore an empty pagename.
297  if ( isset( $_query['pagename'] ) && '' === $_query['pagename'] ) {
298  unset( $_query['pagename'] );
299  }
300 
301  // this is where will break from core wordpress
302  /** @internal Don't use this filter; it will be unnecessary soon - it's just a patch for specific use case */
303  $ignore = apply_filters( 'gravityview/internal/ignored_endpoints', array( 'preview', 'page', 'paged', 'cpage' ), $query );
304  $endpoints = rgobj( $wp_rewrite, 'endpoints' );
305  foreach ( (array) $endpoints as $endpoint ) {
306  $ignore[] = $endpoint[1];
307  }
308  unset( $endpoints );
309 
310  // Modify the query if:
311  // - We're on the "Page on front" page (which we are), and:
312  // - The query is empty OR
313  // - The query includes keys that are associated with registered endpoints. `entry`, for example.
314  if ( empty( $_query ) || ! array_diff( array_keys( $_query ), $ignore ) ) {
315 
316  $qv =& $query->query_vars;
317 
318  // Prevent redirect when on the single entry endpoint
319  if( self::is_single_entry() ) {
320  add_filter( 'redirect_canonical', '__return_false' );
321  }
322 
323  $query->is_page = true;
324  $query->is_home = false;
325  $qv['page_id'] = $front_page_id;
326 
327  // Correct <!--nextpage--> for page_on_front
328  if ( ! empty( $qv['paged'] ) ) {
329  $qv['page'] = $qv['paged'];
330  unset( $qv['paged'] );
331  }
332  }
333 
334  // reset the is_singular flag after our updated code above
335  $query->is_singular = $query->is_single || $query->is_page || $query->is_attachment;
336  }
337  }
338 
339  /**
340  * Read the $post and process the View data inside
341  * @param array $wp Passed in the `wp` hook. Not used.
342  * @return void
343  */
344  public function parse_content( $wp = array() ) {
345  global $post;
346 
347  // If in admin and NOT AJAX request, get outta here.
348  if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) && gravityview()->request->is_admin() ) {
349  return;
350  /** Deprecated in favor of gravityview()->request->is_admin(). */
351  } else if ( GravityView_Plugin::is_admin() ) {
352  return;
353  }
354 
355  // Calculate requested Views
357 
358  // !important: we need to run this before getting single entry (to kick the advanced filter)
359  $this->set_context_view_id();
360 
361  $this->setIsGravityviewPostType( get_post_type( $post ) === 'gravityview' );
362 
363  $post_id = $this->getPostId() ? $this->getPostId() : (isset( $post ) ? $post->ID : null );
364  $this->setPostId( $post_id );
365  $post_has_shortcode = ! empty( $post->post_content ) ? gravityview_has_shortcode_r( $post->post_content, 'gravityview' ) : false;
366  $this->setPostHasShortcode( $this->isGravityviewPostType() ? null : ! empty( $post_has_shortcode ) );
367 
368  // check if the View is showing search results (only for multiple entries View)
369  $this->setIsSearch( $this->is_searching() );
370 
371  unset( $entry, $post_id, $post_has_shortcode );
372  }
373 
374  /**
375  * Set the entry
376  */
377  function set_entry_data() {
378  $entry_id = self::is_single_entry();
379  $this->setSingleEntry( $entry_id );
380  $this->setEntry( $entry_id );
381  }
382 
383  /**
384  * Checks if the current View is presenting search results
385  *
386  * @since 1.5.4
387  *
388  * @return boolean True: Yes, it's a search; False: No, not a search.
389  */
390  function is_searching() {
391 
392  // It's a single entry, not search
393  if ( $this->getSingleEntry() ) {
394  return false;
395  }
396 
398 
399  if( 'post' === $search_method ) {
400  $get = $_POST;
401  } else {
402  $get = $_GET;
403  }
404 
405  // No $_GET parameters
406  if ( empty( $get ) || ! is_array( $get ) ) {
407  return false;
408  }
409 
410  // Remove empty values
411  $get = array_filter( $get );
412 
413  // If the $_GET parameters are empty, it's no search.
414  if ( empty( $get ) ) {
415  return false;
416  }
417 
418  $search_keys = array_keys( $get );
419 
420  $search_match = implode( '|', self::$search_parameters );
421 
422  foreach ( $search_keys as $search_key ) {
423 
424  // Analyze the search key $_GET parameter and see if it matches known GV args
425  if ( preg_match( '/(' . $search_match . ')/i', $search_key ) ) {
426  return true;
427  }
428  }
429 
430  return false;
431  }
432 
433  /**
434  * Filter the title for the single entry view
435  *
436  *
437  * @param string $title current title
438  * @param int $passed_post_id Post ID
439  * @return string (modified) title
440  */
441  public function single_entry_title( $title, $passed_post_id = null ) {
442  global $post;
443 
444  // If this is the directory view, return.
445  if ( ! $this->getSingleEntry() ) {
446  return $title;
447  }
448 
449  $entry = $this->getEntry();
450 
451  /**
452  * @filter `gravityview/single/title/out_loop` Apply the Single Entry Title filter outside the WordPress loop?
453  * @param boolean $in_the_loop Whether to apply the filter to the menu title and the meta tag <title> - outside the loop
454  * @param array $entry Current entry
455  */
456  $apply_outside_loop = apply_filters( 'gravityview/single/title/out_loop' , in_the_loop(), $entry );
457 
458  if ( ! $apply_outside_loop ) {
459  return $title;
460  }
461 
462  // User reported WooCommerce doesn't pass two args.
463  if ( empty( $passed_post_id ) ) {
464  return $title;
465  }
466 
467  // Don't modify the title for anything other than the current view/post.
468  // This is true for embedded shortcodes and Views.
469  if ( is_object( $post ) && (int) $post->ID !== (int) $passed_post_id ) {
470  return $title;
471  }
472 
473  $context_view_id = $this->get_context_view_id();
474 
475  $multiple_views = defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ? gravityview()->views->count() > 1 : $this->getGvOutputData()->has_multiple_views();
476 
477  if ( $multiple_views && ! empty( $context_view_id ) ) {
478  if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ) {
479  $view = gravityview()->views->get( $context_view_id );
480  if ( ! $view ) {
481  /** Emulate the weird behavior of \GravityView_View_Data::get_view adding a view which wasn't there to begin with. */
482  gravityview()->views->add( \GV\View::by_id( $context_view_id ) );
483  $view = gravityview()->views->get( $context_view_id );
484  }
485  } else {
486  /** Deprecated. Use gravityview()->views->get() or gravityview()->request->get() */
487  $view_meta = $this->getGvOutputData()->get_view( $context_view_id );
488  }
489  } else {
490  if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ) {
491  foreach ( gravityview()->views->all() as $_view ) {
492  if ( intval( $_view->form->ID ) === intval( $entry['form_id'] ) ) {
493  $view = $_view;
494  break;
495  }
496  }
497 
498  /** No matching form sources were found, happens when requesting an entry from a different form . */
499  if ( ! isset( $view ) )
500  return $title;
501  } else {
502  /** Deprecated. Use gravityview()->views->all() or gravityview()->request->all() */
503  foreach ( $this->getGvOutputData()->get_views() as $view_id => $view_data ) {
504  if ( intval( $view_data['form_id'] ) === intval( $entry['form_id'] ) ) {
505  $view_meta = $view_data;
506  break;
507  }
508  }
509  }
510  }
511 
512  if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ) {
513  if ( $title = $view->settings->get( 'single_title' ) ) {
514  $title = GravityView_API::replace_variables( $title, $view->form->form, $entry );
515  $title = do_shortcode( $title );
516  }
517  } else {
518  /** Deprecated stuff in the future. See the branch above. */
519  if ( ! empty( $view_meta['atts']['single_title'] ) ) {
520 
521  $title = $view_meta['atts']['single_title'];
522 
523  // We are allowing HTML in the fields, so no escaping the output
524  $title = GravityView_API::replace_variables( $title, $view_meta['form'], $entry );
525 
526  $title = do_shortcode( $title );
527  }
528  }
529 
530 
531  return $title;
532  }
533 
534 
535  /**
536  * In case View post is called directly, insert the view in the post content
537  *
538  * @access public
539  * @static
540  * @param mixed $content
541  * @return string Add the View output into View CPT content
542  */
543  public function insert_view_in_content( $content ) {
544 
545  // Plugins may run through the content in the header. WP SEO does this for its OpenGraph functionality.
546  if ( ! did_action( 'loop_start' ) ) {
547 
548  do_action( 'gravityview_log_debug', '[insert_view_in_content] Not processing yet: loop_start hasn\'t run yet. Current action:', current_filter() );
549 
550  return $content;
551  }
552 
553  // We don't want this filter to run infinite loop on any post content fields
554  remove_filter( 'the_content', array( $this, 'insert_view_in_content' ) );
555 
556  // Otherwise, this is called on the Views page when in Excerpt mode.
557  if ( is_admin() ) {
558  return $content;
559  }
560 
561  // Only render in the loop. Fixes issues with the_content filter being applied in places like the sidebar
562  if( ! in_the_loop() ) {
563  return $content;
564  }
565 
566  if ( $this->isGravityviewPostType() ) {
567 
568  /** @since 1.7.4 */
569  if ( is_preview() && ! gravityview_get_form_id( $this->post_id ) ) {
570  $content .= __( 'When using a preset template, you must save the View before a Preview is available.', 'gravityview' );
571  } else {
572  if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ) {
573  foreach ( gravityview()->views->all() as $view ) {
574  $content .= $this->render_view( array( 'id' => $view->ID ) );
575  }
576  } else {
577  /** The \GravityView_View_Data::get_views method is depreacted. */
578  foreach ( $this->getGvOutputData()->get_views() as $view_id => $data ) {
579  $content .= $this->render_view( array( 'id' => $view_id ) );
580  }
581  }
582  }
583  }
584 
585  // Add the filter back in
586  add_filter( 'the_content', array( $this, 'insert_view_in_content' ) );
587 
588  return $content;
589  }
590 
591  /**
592  * Disable comments on GravityView post types
593  * @param boolean $open existing status
594  * @param int $post_id Post ID
595  * @return boolean
596  */
597  public function comments_open( $open, $post_id ) {
598 
599  if ( $this->isGravityviewPostType() ) {
600  $open = false;
601  }
602 
603  /**
604  * @filter `gravityview/comments_open` Whether to set comments to open or closed.
605  * @since 1.5.4
606  * @param boolean $open Open or closed status
607  * @param int $post_id Post ID to set comment status for
608  */
609  $open = apply_filters( 'gravityview/comments_open', $open, $post_id );
610 
611  return $open;
612  }
613 
614  /**
615  * Display a warning when a View has not been configured
616  *
617  * @since 1.19.2
618  *
619  * @param int $view_id The ID of the View currently being displayed
620  *
621  * @return void
622  */
623  public function context_not_configured_warning( $view_id = 0 ) {
624 
625  if ( ! class_exists( 'GravityView_View' ) ) {
626  return;
627  }
628 
629  $fields = GravityView_View::getInstance()->getContextFields();
630 
631  if ( ! empty( $fields ) ) {
632  return;
633  }
634 
635  $context = GravityView_View::getInstance()->getContext();
636 
637  switch( $context ) {
638  case 'directory':
639  $tab = __( 'Multiple Entries', 'gravityview' );
640  break;
641  case 'edit':
642  $tab = __( 'Edit Entry', 'gravityview' );
643  break;
644  case 'single':
645  default:
646  $tab = __( 'Single Entry', 'gravityview' );
647  break;
648  }
649 
650 
651  $title = sprintf( esc_html_x('The %s layout has not been configured.', 'Displayed when a View is not configured. %s is replaced by the tab label', 'gravityview' ), $tab );
652  $edit_link = admin_url( sprintf( 'post.php?post=%d&action=edit#%s-view', $view_id, $context ) );
653  $action_text = sprintf( esc_html__('Add fields to %s', 'gravityview' ), $tab );
654  $message = esc_html__( 'You can only see this message because you are able to edit this View.', 'gravityview' );
655 
656  $image = sprintf( '<img alt="%s" src="%s" style="margin-top: 10px;" />', $tab, esc_url(plugins_url( sprintf( 'assets/images/tab-%s.png', $context ), GRAVITYVIEW_FILE ) ) );
657  $output = sprintf( '<h3>%s <strong><a href="%s">%s</a></strong></h3><p>%s</p>', $title, esc_url( $edit_link ), $action_text, $message );
658 
659  echo GVCommon::generate_notice( $output . $image, 'gv-error error', 'edit_gravityview', $view_id );
660  }
661 
662 
663  /**
664  * Core function to render a View based on a set of arguments
665  *
666  * @access public
667  * @static
668  * @param array $passed_args {
669  *
670  * Settings for rendering the View
671  *
672  * @type int $id View id
673  * @type int $page_size Number of entries to show per page
674  * @type string $sort_field Form field id to sort
675  * @type string $sort_direction Sorting direction ('ASC', 'DESC', or 'RAND')
676  * @type string $start_date - Ymd
677  * @type string $end_date - Ymd
678  * @type string $class - assign a html class to the view
679  * @type string $offset (optional) - This is the start point in the current data set (0 index based).
680  * }
681  *
682  * @return string|null HTML output of a View, NULL if View isn't found
683  */
684  public function render_view( $passed_args ) {
685 
686  // validate attributes
687  if ( empty( $passed_args['id'] ) ) {
688  do_action( 'gravityview_log_error', '[render_view] Returning; no ID defined.', $passed_args );
689  return null;
690  }
691 
692  // Solve problem when loading content via admin-ajax.php
693  // @hack
694  if ( ! $this->getGvOutputData() ) {
695 
696  do_action( 'gravityview_log_error', '[render_view] gv_output_data not defined; parsing content.', $passed_args );
697 
698  $this->parse_content();
699  }
700 
701  // Make 100% sure that we're dealing with a properly called situation
702  if ( ! is_object( $this->getGvOutputData() ) || ! is_callable( array( $this->getGvOutputData(), 'get_view' ) ) ) {
703 
704  do_action( 'gravityview_log_error', '[render_view] gv_output_data not an object or get_view not callable.', $this->getGvOutputData() );
705 
706  return null;
707  }
708 
709  $view_id = $passed_args['id'];
710 
711  if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ) {
712  $view = gravityview()->views->get( $view_id );
713  if ( ! $view ) {
714  /** Emulate the weird behavior of \GravityView_View_Data::get_view adding a view which wasn't there to begin with. */
715  gravityview()->views->add( \GV\View::by_id( $view_id ) );
716  $view = gravityview()->views->get( $view_id );
717 
718  if ( ! $view ) {
719  do_action( 'gravityview_log_debug', sprintf( 'GravityView_frontend[render_view] Returning; View #%s does not have a form tied to it.', $view_id ) );
720  return null;
721  }
722 
723  }
724 
725  /** Update the view settings with the requested arguments. */
726  $view->settings->update( $passed_args );
727 
728  /** Form is not valid. */
729  if ( ! $view->form ) {
730  do_action( 'gravityview_log_debug', sprintf( 'GravityView_frontend[render_view] Returning; View #%s does not exist.', $view_id ) );
731  return null;
732  }
733  } else {
734  /** \GravityView_View_Data::get_view is deprecated. */
735  $view_data = $this->getGvOutputData()->get_view( $view_id, $passed_args );
736 
737  do_action( 'gravityview_log_debug', '[render_view] View Data: ', $view_data );
738 
739  do_action( 'gravityview_log_debug', '[render_view] Init View. Arguments: ', $passed_args );
740 
741  // The passed args were always winning, even if they were NULL.
742  // This prevents that. Filters NULL, FALSE, and empty strings.
743  $passed_args = array_filter( $passed_args, 'strlen' );
744 
745  //Override shortcode args over View template settings
746  $atts = wp_parse_args( $passed_args, $view_data['atts'] );
747 
748  do_action( 'gravityview_log_debug', '[render_view] Arguments after merging with View settings: ', $atts );
749  }
750 
751  // It's password protected and you need to log in.
752  if ( post_password_required( $view_id ) ) {
753 
754  do_action( 'gravityview_log_error', sprintf( '[render_view] Returning: View %d is password protected.', $view_id ) );
755 
756  // If we're in an embed or on an archive page, show the password form
757  if ( get_the_ID() !== $view_id ) {
758  return get_the_password_form();
759  }
760 
761  // Otherwise, just get outta here
762  return null;
763  }
764 
765  /**
766  * Don't render View if user isn't allowed to see it
767  * @since 1.15
768  * @since 1.17.2 Added check for if a user has no caps but is logged in (member of multisite, but not any site). Treat as if logged-out.
769  */
770  if( is_user_logged_in() && ! ( empty( wp_get_current_user()->caps ) && empty( wp_get_current_user()->roles ) ) && false === GVCommon::has_cap( 'read_gravityview', $view_id ) ) {
771 
772  do_action( 'gravityview_log_debug', sprintf( '%s Returning: View %d is not visible by current user.', __METHOD__, $view_id ) );
773 
774  return null;
775  }
776 
777  if( $this->isGravityviewPostType() ) {
778 
779  /**
780  * @filter `gravityview_direct_access` Should Views be directly accessible, or only visible using the shortcode?
781  * @see https://codex.wordpress.org/Function_Reference/register_post_type#public
782  * @see \GV\Entry::get_endpoint_name
783  * @since 1.15.2
784  * @param[in,out] boolean `true`: allow Views to be accessible directly. `false`: Only allow Views to be embedded via shortcode. Default: `true`
785  * @param int $view_id The ID of the View currently being requested. `0` for general setting
786  */
787  $direct_access = apply_filters( 'gravityview_direct_access', true, $view_id );
788 
789  if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ) {
790  $embed_only = $view->settings->get( 'embed_only' );
791  } else {
792  /** Deprecated. View attributes moved to \GV\View::$settings. */
793  $embed_only = ! empty( $atts['embed_only'] );
794  }
795 
796  if( ! $direct_access || ( $embed_only && ! GVCommon::has_cap( 'read_private_gravityviews' ) ) ) {
797  return __( 'You are not allowed to view this content.', 'gravityview' );
798  }
799  }
800 
801  ob_start();
802 
803  /**
804  * Set globals for templating
805  * @deprecated 1.6.2
806  */
807  global $gravityview_view;
808 
809  if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ) {
810  $view_data = $view->as_data();
811  $gravityview_view = new GravityView_View( $view_data );
812  $post_id = intval( $view->settings->get( 'post_id' ) ? : get_the_ID() );
813  $template_id = $view->template ? $view->template->ID : null;
814  } else {
815  /** These constructs are deprecated. Use the new gravityview() wrapper. */
816  $gravityview_view = new GravityView_View( $view_data );
817  $post_id = ! empty( $atts['post_id'] ) ? intval( $atts['post_id'] ) : get_the_ID();
818  $template_id = $view_data['template_id'];
819  }
820 
821  $gravityview_view->setPostId( $post_id );
822 
823  if ( ! $this->getSingleEntry() ) {
824 
825  // user requested Directory View
826  do_action( 'gravityview_log_debug', '[render_view] Executing Directory View' );
827 
828  //fetch template and slug
829  $view_slug = apply_filters( 'gravityview_template_slug_'. $template_id, 'table', 'directory' );
830 
831  do_action( 'gravityview_log_debug', '[render_view] View template slug: ', $view_slug );
832 
833  /**
834  * Disable fetching initial entries for views that don't need it (DataTables)
835  */
836  $get_entries = apply_filters( 'gravityview_get_view_entries_'.$view_slug, true );
837 
838  if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ) {
839  $hide_until_searched = $view->settings->get( 'hide_until_searched' );
840  } else {
841  /** $atts is deprecated, use \GV\View:$settings */
842  $hide_until_searched = ! empty( $atts['hide_until_searched'] );
843  }
844 
845  /**
846  * Hide View data until search is performed
847  * @since 1.5.4
848  */
849  if ( $hide_until_searched && ! $this->isSearch() ) {
850  $gravityview_view->setHideUntilSearched( true );
851  $get_entries = false;
852  }
853 
854  if ( $get_entries ) {
855 
856  if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ) {
857  $sort_columns = $view->settings->get( 'sort_columns' );
858  } else {
859  /** $atts is deprecated, use \GV\View:$settings */
860  $sort_columns = ! empty( $atts['sort_columns'] );
861  }
862 
863  if ( $sort_columns ) {
864  // add filter to enable column sorting
865  add_filter( 'gravityview/template/field_label', array( $this, 'add_columns_sort_links' ) , 100, 3 );
866  }
867 
868  if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ) {
869  $view_entries = self::get_view_entries( $view->settings->as_atts(), $view->form->ID );
870  } else {
871  /** $atts is deprecated, use \GV\View:$settings */
872  /** $view_data is deprecated, use \GV\View properties */
873  $view_entries = self::get_view_entries( $atts, $view_data['form_id'] );
874  }
875 
876  do_action( 'gravityview_log_debug', sprintf( '[render_view] Get Entries. Found %s entries total, showing %d entries', $view_entries['count'], sizeof( $view_entries['entries'] ) ) );
877 
878  } else {
879 
880  $view_entries = array( 'count' => null, 'entries' => null, 'paging' => null );
881 
882  do_action( 'gravityview_log_debug', '[render_view] Not fetching entries because `gravityview_get_view_entries_'.$view_slug.'` is false' );
883  }
884 
885  $gravityview_view->setPaging( $view_entries['paging'] );
886  $gravityview_view->setContext( 'directory' );
887  $sections = array( 'header', 'body', 'footer' );
888 
889  } else {
890 
891  // user requested Single Entry View
892  do_action( 'gravityview_log_debug', '[render_view] Executing Single View' );
893 
894  /**
895  * @action `gravityview_render_entry_{View ID}` Before rendering a single entry for a specific View ID
896  * @since 1.17
897  */
898  if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ) {
899  do_action( 'gravityview_render_entry_' . $view->ID );
900  } else {
901  /** $view_data is depreacted, use \GV\View properties */
902  do_action( 'gravityview_render_entry_'.$view_data['id'] );
903  }
904 
905  $entry = $this->getEntry();
906 
907  // You are not permitted to view this entry.
908  if ( empty( $entry ) || ! self::is_entry_approved( $entry, defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ? $view->settings->as_atts() : $atts ) ) {
909 
910  do_action( 'gravityview_log_debug', '[render_view] Entry does not exist. This may be because of View filters limiting access.' );
911 
912  // Only display warning once when multiple Views are embedded
914  ob_end_clean();
915  return null;
916  }
917 
918  /**
919  * @filter `gravityview/render/entry/not_visible` Modify the message shown to users when the entry doesn't exist or they aren't allowed to view it.
920  * @since 1.6
921  * @param string $message Default: "You have attempted to view an entry that is not visible or may not exist."
922  */
923  $message = apply_filters( 'gravityview/render/entry/not_visible', __( 'You have attempted to view an entry that is not visible or may not exist.', 'gravityview' ) );
924 
925  /**
926  * @since 1.6
927  */
928  echo esc_attr( $message );
929 
930  ob_end_clean();
931  return null;
932  }
933 
934  // We're in single view, but the view being processed is not the same view the single entry belongs to.
935  // important: do not remove this as it prevents fake attempts of displaying entries from other views/forms
936  $multiple_views = defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ? gravityview()->views->count() > 1 : $this->getGvOutputData()->has_multiple_views();
937  if ( $multiple_views && $view_id != $this->get_context_view_id() ) {
938  do_action( 'gravityview_log_debug', '[render_view] In single entry view, but the entry does not belong to this View. Perhaps there are multiple views on the page. View ID: '. $view_id );
939  ob_end_clean();
940  return null;
941  }
942 
943  //fetch template and slug
944  $view_slug = apply_filters( 'gravityview_template_slug_' . $template_id, 'table', 'single' );
945  do_action( 'gravityview_log_debug', '[render_view] View single template slug: ', $view_slug );
946 
947  //fetch entry detail
948  $view_entries['count'] = 1;
949  $view_entries['entries'][] = $entry;
950  do_action( 'gravityview_log_debug', '[render_view] Get single entry: ', $view_entries['entries'] );
951 
952  if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ) {
953  $back_link_label = $view->settings->get( 'back_link_label', null );
954  } else {
955  $back_link_label = isset( $atts['back_link_label'] ) ? $atts['back_link_label'] : null;
956  }
957 
958  // set back link label
959  $gravityview_view->setBackLinkLabel( $back_link_label );
960  $gravityview_view->setContext( 'single' );
961  $sections = array( 'single' );
962 
963  }
964 
965  // add template style
966  self::add_style( $template_id );
967 
968  // Prepare to render view and set vars
969  $gravityview_view->setEntries( $view_entries['entries'] );
970  $gravityview_view->setTotalEntries( $view_entries['count'] );
971 
972  // If Edit
973  if ( 'edit' === gravityview_get_context() ) {
974 
975  do_action( 'gravityview_log_debug', '[render_view] Edit Entry ' );
976 
977  do_action( 'gravityview_edit_entry', $this->getGvOutputData() );
978 
979  return ob_get_clean();
980 
981  } else {
982  // finaly we'll render some html
983  $sections = apply_filters( 'gravityview_render_view_sections', $sections, $template_id );
984 
985  do_action( 'gravityview_log_debug', '[render_view] Sections to render: ', $sections );
986  foreach ( $sections as $section ) {
987  do_action( 'gravityview_log_debug', '[render_view] Rendering '. $section . ' section.' );
988  $gravityview_view->render( $view_slug, $section, false );
989  }
990  }
991 
992  //@todo: check why we need the IF statement vs. print the view id always.
993  if ( $this->isGravityviewPostType() || $this->isPostHasShortcode() ) {
994  // Print the View ID to enable proper cookie pagination
995  echo '<input type="hidden" class="gravityview-view-id" value="' . esc_attr( $view_id ) . '">';
996  }
997  $output = ob_get_clean();
998 
999  return $output;
1000  }
1001 
1002  /**
1003  * Process the start and end dates for a view - overrides values defined in shortcode (if needed)
1004  *
1005  * The `start_date` and `end_date` keys need to be in a format processable by GFFormsModel::get_date_range_where(),
1006  * which uses \DateTime() format.
1007  *
1008  * You can set the `start_date` or `end_date` to any value allowed by {@link http://www.php.net//manual/en/function.strtotime.php strtotime()},
1009  * including strings like "now" or "-1 year" or "-3 days".
1010  *
1011  * @see GFFormsModel::get_date_range_where
1012  *
1013  * @param array $args View settings
1014  * @param array $search_criteria Search being performed, if any
1015  * @return array Modified `$search_criteria` array
1016  */
1017  public static function process_search_dates( $args, $search_criteria = array() ) {
1018 
1019  $return_search_criteria = $search_criteria;
1020 
1021  foreach ( array( 'start_date', 'end_date' ) as $key ) {
1022 
1023 
1024  // Is the start date or end date set in the view or shortcode?
1025  // If so, we want to make sure that the search doesn't go outside the bounds defined.
1026  if ( ! empty( $args[ $key ] ) ) {
1027 
1028  // Get a timestamp and see if it's a valid date format
1029  $date = strtotime( $args[ $key ] );
1030 
1031  // The date was invalid
1032  if ( empty( $date ) ) {
1033  do_action( 'gravityview_log_error', __METHOD__ . ' Invalid ' . $key . ' date format: ' . $args[ $key ] );
1034  continue;
1035  }
1036 
1037  // The format that Gravity Forms expects for start_date and day-specific (not hour/second-specific) end_date
1038  $datetime_format = 'Y-m-d H:i:s';
1039  $search_is_outside_view_bounds = false;
1040 
1041  if( ! empty( $search_criteria[ $key ] ) ) {
1042 
1043  $search_date = strtotime( $search_criteria[ $key ] );
1044 
1045  // The search is for entries before the start date defined by the settings
1046  switch ( $key ) {
1047  case 'end_date':
1048  /**
1049  * If the end date is formatted as 'Y-m-d', it should be formatted without hours and seconds
1050  * so that Gravity Forms can convert the day to 23:59:59 the previous day.
1051  *
1052  * If it's a relative date ("now" or "-1 day"), then it should use the precise date format
1053  *
1054  * @see GFFormsModel::get_date_range_where
1055  */
1056  $datetime_format = gravityview_is_valid_datetime( $args[ $key ] ) ? 'Y-m-d' : 'Y-m-d H:i:s';
1057  $search_is_outside_view_bounds = ( $search_date > $date );
1058  break;
1059  case 'start_date':
1060  $search_is_outside_view_bounds = ( $search_date < $date );
1061  break;
1062  }
1063  }
1064 
1065  // If there is no search being performed, or if there is a search being performed that's outside the bounds
1066  if ( empty( $search_criteria[ $key ] ) || $search_is_outside_view_bounds ) {
1067 
1068  // Then we override the search and re-set the start date
1069  $return_search_criteria[ $key ] = date_i18n( $datetime_format , $date, true );
1070  }
1071  }
1072  }
1073 
1074  if( isset( $return_search_criteria['start_date'] ) && isset( $return_search_criteria['end_date'] ) ) {
1075  // The start date is AFTER the end date. This will result in no results, but let's not force the issue.
1076  if ( strtotime( $return_search_criteria['start_date'] ) > strtotime( $return_search_criteria['end_date'] ) ) {
1077  do_action( 'gravityview_log_error', __METHOD__ . ' Invalid search: the start date is after the end date.', $return_search_criteria );
1078  }
1079  }
1080 
1081  return $return_search_criteria;
1082  }
1083 
1084 
1085  /**
1086  * Process the approved only search criteria according to the View settings
1087  *
1088  * @param array $args View settings
1089  * @param array $search_criteria Search being performed, if any
1090  * @return array Modified `$search_criteria` array
1091  */
1092  public static function process_search_only_approved( $args, $search_criteria ) {
1093 
1094  /** @since 1.19 */
1095  if( ! empty( $args['admin_show_all_statuses'] ) && GVCommon::has_cap('gravityview_moderate_entries') ) {
1096  do_action( 'gravityview_log_debug', __METHOD__ . ': User can moderate entries; showing all approval statuses' );
1097  return $search_criteria;
1098  }
1099 
1100  if ( ! empty( $args['show_only_approved'] ) ) {
1101 
1102  $search_criteria['field_filters'][] = array(
1105  );
1106 
1107  $search_criteria['field_filters']['mode'] = 'all'; // force all the criterias to be met
1108 
1109  do_action( 'gravityview_log_debug', '[process_search_only_approved] Search Criteria if show only approved: ', $search_criteria );
1110  }
1111 
1112  return $search_criteria;
1113  }
1114 
1115 
1116  /**
1117  * Check if a certain entry is approved.
1118  *
1119  * If we pass the View settings ($args) it will check the 'show_only_approved' setting before
1120  * checking the entry approved field, returning true if show_only_approved = false.
1121  *
1122  * @since 1.7
1123  * @since 1.18 Converted check to use GravityView_Entry_Approval_Status::is_approved
1124  *
1125  * @uses GravityView_Entry_Approval_Status::is_approved
1126  *
1127  * @param array $entry Entry object
1128  * @param array $args View settings (optional)
1129  *
1130  * @return bool
1131  */
1132  public static function is_entry_approved( $entry, $args = array() ) {
1133 
1134  if ( empty( $entry['id'] ) || ( array_key_exists( 'show_only_approved', $args ) && ! $args['show_only_approved'] ) ) {
1135  // is implicitly approved if entry is null or View settings doesn't require to check for approval
1136  return true;
1137  }
1138 
1139  /** @since 1.19 */
1140  if( ! empty( $args['admin_show_all_statuses'] ) && GVCommon::has_cap('gravityview_moderate_entries') ) {
1141  do_action( 'gravityview_log_debug', __METHOD__ . ': User can moderate entries, so entry is approved for viewing' );
1142  return true;
1143  }
1144 
1145  $is_approved = gform_get_meta( $entry['id'], GravityView_Entry_Approval::meta_key );
1146 
1147  return GravityView_Entry_Approval_Status::is_approved( $is_approved );
1148  }
1149 
1150  /**
1151  * Parse search criteria for a entries search.
1152  *
1153  * array(
1154  * 'search_field' => 1, // ID of the field
1155  * 'search_value' => '', // Value of the field to search
1156  * 'search_operator' => 'contains', // 'is', 'isnot', '>', '<', 'contains'
1157  * 'show_only_approved' => 0 or 1 // Boolean
1158  * )
1159  *
1160  * @param array $args Array of args
1161  * @param int $form_id Gravity Forms form ID
1162  * @return array Array of search parameters, formatted in Gravity Forms mode, using `status` key set to "active" by default, `field_filters` array with `key`, `value` and `operator` keys.
1163  */
1164  public static function get_search_criteria( $args, $form_id ) {
1165 
1166  /**
1167  * @filter `gravityview_fe_search_criteria` Modify the search criteria
1168  * @see GravityView_Widget_Search::filter_entries Adds the default search criteria
1169  * @param array $search_criteria Empty `field_filters` key
1170  * @param int $form_id ID of the Gravity Forms form that is being searched
1171  */
1172  $search_criteria = apply_filters( 'gravityview_fe_search_criteria', array( 'field_filters' => array() ), $form_id );
1173 
1174  $original_search_criteria = $search_criteria;
1175 
1176  do_action( 'gravityview_log_debug', '[get_search_criteria] Search Criteria after hook gravityview_fe_search_criteria: ', $search_criteria );
1177 
1178  // implicity search
1179  if ( ! empty( $args['search_value'] ) ) {
1180 
1181  // Search operator options. Options: `is` or `contains`
1182  $operator = ! empty( $args['search_operator'] ) && in_array( $args['search_operator'], array( 'is', 'isnot', '>', '<', 'contains' ) ) ? $args['search_operator'] : 'contains';
1183 
1184  $search_criteria['field_filters'][] = array(
1185  'key' => rgget( 'search_field', $args ), // The field ID to search
1186  'value' => _wp_specialchars( $args['search_value'] ), // The value to search. Encode ampersands but not quotes.
1187  'operator' => $operator,
1188  );
1189 
1190  // Lock search mode to "all" with implicit presearch filter.
1191  $search_criteria['field_filters']['mode'] = 'all';
1192  }
1193 
1194  if( $search_criteria !== $original_search_criteria ) {
1195  do_action( 'gravityview_log_debug', '[get_search_criteria] Search Criteria after implicity search: ', $search_criteria );
1196  }
1197 
1198  // Handle setting date range
1199  $search_criteria = self::process_search_dates( $args, $search_criteria );
1200 
1201  if( $search_criteria !== $original_search_criteria ) {
1202  do_action( 'gravityview_log_debug', '[get_search_criteria] Search Criteria after date params: ', $search_criteria );
1203  }
1204 
1205  // remove not approved entries
1206  $search_criteria = self::process_search_only_approved( $args, $search_criteria );
1207 
1208  /**
1209  * @filter `gravityview_status` Modify entry status requirements to be included in search results.
1210  * @param string $status Default: `active`. Accepts all Gravity Forms entry statuses, including `spam` and `trash`
1211  */
1212  $search_criteria['status'] = apply_filters( 'gravityview_status', 'active', $args );
1213 
1214  return $search_criteria;
1215  }
1216 
1217 
1218 
1219  /**
1220  * Core function to calculate View multi entries (directory) based on a set of arguments ($args):
1221  * $id - View id
1222  * $page_size - Page
1223  * $sort_field - form field id to sort
1224  * $sort_direction - ASC / DESC
1225  * $start_date - Ymd
1226  * $end_date - Ymd
1227  * $class - assign a html class to the view
1228  * $offset (optional) - This is the start point in the current data set (0 index based).
1229  *
1230  *
1231  *
1232  * @uses gravityview_get_entries()
1233  * @access public
1234  * @param array $args\n
1235  * - $id - View id
1236  * - $page_size - Page
1237  * - $sort_field - form field id to sort
1238  * - $sort_direction - ASC / DESC
1239  * - $start_date - Ymd
1240  * - $end_date - Ymd
1241  * - $class - assign a html class to the view
1242  * - $offset (optional) - This is the start point in the current data set (0 index based).
1243  * @param int $form_id Gravity Forms Form ID
1244  * @return array Associative array with `count`, `entries`, and `paging` keys. `count` has the total entries count, `entries` is an array with Gravity Forms full entry data, `paging` is an array with `offset` and `page_size` keys
1245  */
1246  public static function get_view_entries( $args, $form_id ) {
1247 
1248  do_action( 'gravityview_log_debug', '[get_view_entries] init' );
1249  // start filters and sorting
1250 
1251  $parameters = self::get_view_entries_parameters( $args, $form_id );
1252 
1253  $count = 0; // Must be defined so that gravityview_get_entries can use by reference
1254 
1255  // fetch entries
1256  if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ) {
1257  list( $entries, $paging, $count ) =
1258  \GV\Mocks\GravityView_frontend_get_view_entries( $args, $form_id, $parameters, $count );
1259  } else {
1260  /** Deprecated, use $form->entries instead. */
1261  $entries = gravityview_get_entries( $form_id, $parameters, $count );
1262 
1263  /** Set paging. */
1264  $paging = rgar( $parameters, 'paging' );
1265 
1266  /** Adjust count by defined offset. */
1267  $count = max( 0, ( $count - rgar( $args, 'offset', 0 ) ) );
1268  }
1269 
1270  do_action( 'gravityview_log_debug', sprintf( '%s: Get Entries. Found: %s entries', __METHOD__, $count ), $entries );
1271 
1272  /**
1273  * @filter `gravityview_view_entries` Filter the entries output to the View
1274  * @deprecated since 1.5.2
1275  * @param array $args View settings associative array
1276  * @var array
1277  */
1278  $entries = apply_filters( 'gravityview_view_entries', $entries, $args );
1279 
1280  $return = array(
1281  'count' => $count,
1282  'entries' => $entries,
1283  'paging' => $paging,
1284  );
1285 
1286  /**
1287  * @filter `gravityview/view/entries` Filter the entries output to the View
1288  * @param array $criteria associative array containing count, entries & paging
1289  * @param array $args View settings associative array
1290  * @since 1.5.2
1291  */
1292  return apply_filters( 'gravityview/view/entries', $return, $args );
1293  }
1294 
1295  /**
1296  * Get an array of search parameters formatted as Gravity Forms requires
1297  *
1298  * Results are filtered by `gravityview_get_entries` and `gravityview_get_entries_{View ID}` filters
1299  *
1300  * @uses GravityView_frontend::get_search_criteria
1301  * @uses GravityView_frontend::get_search_criteria_paging
1302  *
1303  * @since 1.20
1304  *
1305  * @see \GV\View_Settings::defaults For $args options
1306  *
1307  * @param array $args Array of View settings, as structured in \GV\View_Settings::defaults
1308  * @param int $form_id Gravity Forms form ID to search
1309  *
1310  * @return array With `search_criteria`, `sorting`, `paging`, `cache` keys
1311  */
1312  public static function get_view_entries_parameters( $args = array(), $form_id = 0 ) {
1313 
1314 
1315  if ( ! is_array( $args ) || ! is_numeric( $form_id ) ) {
1316 
1317  do_action( 'gravityview_log_error', __METHOD__ . ': Passed args are not an array or the form ID is not numeric' );
1318 
1319  return array();
1320  }
1321 
1322  $form_id = intval( $form_id );
1323 
1324  /**
1325  * Process search parameters
1326  * @var array
1327  */
1328  $search_criteria = self::get_search_criteria( $args, $form_id );
1329 
1330  $paging = self::get_search_criteria_paging( $args );
1331 
1332  $parameters = array(
1333  'search_criteria' => $search_criteria,
1334  'sorting' => self::updateViewSorting( $args, $form_id ),
1335  'paging' => $paging,
1336  'cache' => isset( $args['cache'] ) ? $args['cache'] : true,
1337  );
1338 
1339  /**
1340  * @filter `gravityview_get_entries` Filter get entries criteria
1341  * @param array $parameters Array with `search_criteria`, `sorting` and `paging` keys.
1342  * @param array $args View configuration args. {
1343  * @type int $id View id
1344  * @type int $page_size Number of entries to show per page
1345  * @type string $sort_field Form field id to sort
1346  * @type string $sort_direction Sorting direction ('ASC', 'DESC', or 'RAND')
1347  * @type string $start_date - Ymd
1348  * @type string $end_date - Ymd
1349  * @type string $class - assign a html class to the view
1350  * @type string $offset (optional) - This is the start point in the current data set (0 index based).
1351  * }
1352  * @param int $form_id ID of Gravity Forms form
1353  */
1354  $parameters = apply_filters( 'gravityview_get_entries', $parameters, $args, $form_id );
1355 
1356  /**
1357  * @filter `gravityview_get_entries_{View ID}` Filter get entries criteria
1358  * @param array $parameters Array with `search_criteria`, `sorting` and `paging` keys.
1359  * @param array $args View configuration args.
1360  */
1361  $parameters = apply_filters( 'gravityview_get_entries_'.$args['id'], $parameters, $args, $form_id );
1362 
1363  do_action( 'gravityview_log_debug', __METHOD__ . ': $parameters passed to gravityview_get_entries(): ', $parameters );
1364 
1365  return $parameters;
1366  }
1367 
1368  /**
1369  * Get the paging array for the View
1370  *
1371  * @since 1.19.5
1372  *
1373  * @param $args
1374  * @param int $form_id
1375  */
1376  public static function get_search_criteria_paging( $args ) {
1377 
1378  /**
1379  * @filter `gravityview_default_page_size` The default number of entries displayed in a View
1380  * @since 1.1.6
1381  * @param int $default_page_size Default: 25
1382  */
1383  $default_page_size = apply_filters( 'gravityview_default_page_size', 25 );
1384 
1385  // Paging & offset
1386  $page_size = ! empty( $args['page_size'] ) ? intval( $args['page_size'] ) : $default_page_size;
1387 
1388  if ( -1 === $page_size ) {
1389  $page_size = PHP_INT_MAX;
1390  }
1391 
1392  $curr_page = empty( $_GET['pagenum'] ) ? 1 : intval( $_GET['pagenum'] );
1393  $offset = ( $curr_page - 1 ) * $page_size;
1394 
1395  if ( ! empty( $args['offset'] ) ) {
1396  $offset += intval( $args['offset'] );
1397  }
1398 
1399  $paging = array(
1400  'offset' => $offset,
1401  'page_size' => $page_size,
1402  );
1403 
1404  do_action( 'gravityview_log_debug', __METHOD__ . ': Paging: ', $paging );
1405 
1406  return $paging;
1407  }
1408 
1409  /**
1410  * Updates the View sorting criteria
1411  *
1412  * @since 1.7
1413  *
1414  * @param array $args View settings. Required to have `sort_field` and `sort_direction` keys
1415  * @param int $form_id The ID of the form used to sort
1416  * @return array $sorting Array with `key`, `direction` and `is_numeric` keys
1417  */
1418  public static function updateViewSorting( $args, $form_id ) {
1419  $sorting = array();
1420  $sort_field_id = isset( $_GET['sort'] ) ? $_GET['sort'] : rgar( $args, 'sort_field' );
1421  $sort_direction = isset( $_GET['dir'] ) ? $_GET['dir'] : rgar( $args, 'sort_direction' );
1422 
1423  $sort_field_id = self::_override_sorting_id_by_field_type( $sort_field_id, $form_id );
1424 
1425  if ( ! empty( $sort_field_id ) ) {
1426  $sorting = array(
1427  'key' => $sort_field_id,
1428  'direction' => strtolower( $sort_direction ),
1429  'is_numeric' => GVCommon::is_field_numeric( $form_id, $sort_field_id )
1430  );
1431  }
1432 
1433  if ( 'RAND' === $sort_direction ) {
1434 
1435  $form = GFAPI::get_form( $form_id );
1436 
1437  // Get the first GF_Field field ID, set as the key for entry randomization
1438  if( ! empty( $form['fields'] ) ) {
1439 
1440  /** @var GF_Field $field */
1441  foreach ( $form['fields'] as $field ) {
1442 
1443  if( ! is_a( $field, 'GF_Field' ) ) {
1444  continue;
1445  }
1446 
1447  $sorting = array(
1448  'key' => $field->id,
1449  'is_numeric' => false,
1450  'direction' => 'RAND',
1451  );
1452 
1453  break;
1454  }
1455  }
1456  }
1457 
1458  GravityView_View::getInstance()->setSorting( $sorting );
1459 
1460  do_action( 'gravityview_log_debug', '[updateViewSorting] Sort Criteria : ', $sorting );
1461 
1462  return $sorting;
1463 
1464  }
1465 
1466  /**
1467  * Override sorting per field
1468  *
1469  * Currently only modifies sorting ID when sorting by the full name. Sorts by first name.
1470  * Use the `gravityview/sorting/full-name` filter to override.
1471  *
1472  * @todo Filter from GravityView_Field
1473  * @since 1.7.4
1474  *
1475  * @param int|string $sort_field_id Field used for sorting (`id` or `1.2`)
1476  * @param int $form_id GF Form ID
1477  *
1478  * @return string Possibly modified sorting ID
1479  */
1480  private static function _override_sorting_id_by_field_type( $sort_field_id, $form_id ) {
1481 
1483 
1484  $sort_field = GFFormsModel::get_field( $form, $sort_field_id );
1485 
1486  if( ! $sort_field ) {
1487  return $sort_field_id;
1488  }
1489 
1490  switch ( $sort_field['type'] ) {
1491 
1492  case 'address':
1493  // Sorting by full address
1494  if ( floatval( $sort_field_id ) === floor( $sort_field_id ) ) {
1495 
1496  /**
1497  * Override how to sort when sorting address
1498  *
1499  * @since 1.8
1500  *
1501  * @param string $address_part `street`, `street2`, `city`, `state`, `zip`, or `country` (default: `city`)
1502  * @param string $sort_field_id Field used for sorting
1503  * @param int $form_id GF Form ID
1504  */
1505  $address_part = apply_filters( 'gravityview/sorting/address', 'city', $sort_field_id, $form_id );
1506 
1507  switch( strtolower( $address_part ) ){
1508  case 'street':
1509  $sort_field_id .= '.1';
1510  break;
1511  case 'street2':
1512  $sort_field_id .= '.2';
1513  break;
1514  default:
1515  case 'city':
1516  $sort_field_id .= '.3';
1517  break;
1518  case 'state':
1519  $sort_field_id .= '.4';
1520  break;
1521  case 'zip':
1522  $sort_field_id .= '.5';
1523  break;
1524  case 'country':
1525  $sort_field_id .= '.6';
1526  break;
1527  }
1528 
1529  }
1530  break;
1531  case 'name':
1532  // Sorting by full name, not first, last, etc.
1533  if ( floatval( $sort_field_id ) === floor( $sort_field_id ) ) {
1534  /**
1535  * @filter `gravityview/sorting/full-name` Override how to sort when sorting full name.
1536  * @since 1.7.4
1537  * @param[in,out] string $name_part Sort by `first` or `last` (default: `first`)
1538  * @param[in] string $sort_field_id Field used for sorting
1539  * @param[in] int $form_id GF Form ID
1540  */
1541  $name_part = apply_filters( 'gravityview/sorting/full-name', 'first', $sort_field_id, $form_id );
1542 
1543  if ( 'last' === strtolower( $name_part ) ) {
1544  $sort_field_id .= '.6';
1545  } else {
1546  $sort_field_id .= '.3';
1547  }
1548  }
1549  break;
1550  case 'list':
1551  $sort_field_id = false;
1552  break;
1553  case 'time':
1554 
1555  /**
1556  * @filter `gravityview/sorting/time` Override how to sort when sorting time
1557  * @see GravityView_Field_Time
1558  * @since 1.14
1559  * @param[in,out] string $name_part Field used for sorting
1560  * @param[in] int $form_id GF Form ID
1561  */
1562  $sort_field_id = apply_filters( 'gravityview/sorting/time', $sort_field_id, $form_id );
1563  break;
1564  }
1565 
1566  return $sort_field_id;
1567  }
1568 
1569  /**
1570  * Verify if user requested a single entry view
1571  * @return boolean|string false if not, single entry slug if true
1572  */
1573  public static function is_single_entry() {
1574 
1575  if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ) {
1576  $var_name = \GV\Entry::get_endpoint_name();
1577  } else {
1578  /** Deprecated. Use \GV\Entry::get_endpoint_name instead. */
1580  }
1581 
1582  $single_entry = get_query_var( $var_name );
1583 
1584  /**
1585  * Modify the entry that is being displayed.
1586  *
1587  * @internal Should only be used by things like the oEmbed functionality.
1588  * @since 1.6
1589  */
1590  $single_entry = apply_filters( 'gravityview/is_single_entry', $single_entry );
1591 
1592  if ( empty( $single_entry ) ){
1593  return false;
1594  } else {
1595  return $single_entry;
1596  }
1597  }
1598 
1599 
1600  /**
1601  * Register styles and scripts
1602  *
1603  * @access public
1604  * @return void
1605  */
1606  public function add_scripts_and_styles() {
1607  global $post, $posts;
1608  // enqueue template specific styles
1609  if ( $this->getGvOutputData() ) {
1610 
1611  if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ) {
1612  $views = gravityview()->views->all();
1613  } else {
1614  /** \GravityView_View_Data::get_view is no more... */
1615  $views = $this->getGvOutputData()->get_views();
1616  }
1617 
1618  foreach ( $views as $view_id => $data ) {
1619  if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ) {
1620  $view = $data;
1621  $view_id = $view->ID;
1622  $template_id = $view->template ? $view->template->ID : null;
1623  $data = $view->as_data();
1624  } else {
1625  $template_id = $data['template_id'];
1626  }
1627 
1628  /**
1629  * Don't enqueue the scripts or styles if it's not going to be displayed.
1630  * @since 1.15
1631  */
1632  if( is_user_logged_in() && false === GVCommon::has_cap( 'read_gravityview', $view_id ) ) {
1633  continue;
1634  }
1635 
1636  // By default, no thickbox
1637  $js_dependencies = array( 'jquery', 'gravityview-jquery-cookie' );
1638  $css_dependencies = array();
1639 
1640  if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) ) {
1641  $lightbox = $view->settings->get( 'lightbox' );
1642  } else {
1643  /** View data attributes are now stored in \GV\View::$settings */
1644  $lightbox = ! empty( $data['atts']['lightbox'] );
1645  }
1646 
1647  // If the thickbox is enqueued, add dependencies
1648  if ( $lightbox ) {
1649 
1650  /**
1651  * @filter `gravity_view_lightbox_script` Override the lightbox script to enqueue. Default: `thickbox`
1652  * @param string $script_slug If you want to use a different lightbox script, return the name of it here.
1653  */
1654  $js_dependencies[] = apply_filters( 'gravity_view_lightbox_script', 'thickbox' );
1655 
1656  /**
1657  * @filter `gravity_view_lightbox_style` Modify the lightbox CSS slug. Default: `thickbox`
1658  * @param string $script_slug If you want to use a different lightbox script, return the name of its CSS file here.
1659  */
1660  $css_dependencies[] = apply_filters( 'gravity_view_lightbox_style', 'thickbox' );
1661  }
1662 
1663  /**
1664  * If the form has checkbox fields, enqueue dashicons
1665  * @see https://github.com/katzwebservices/GravityView/issues/536
1666  * @since 1.15
1667  */
1668  if( gravityview_view_has_single_checkbox_or_radio( $data['form'], $data['fields'] ) ) {
1669  $css_dependencies[] = 'dashicons';
1670  }
1671 
1672  wp_register_script( 'gravityview-jquery-cookie', plugins_url( 'assets/lib/jquery.cookie/jquery.cookie.min.js', GRAVITYVIEW_FILE ), array( 'jquery' ), GravityView_Plugin::version, true );
1673 
1674  $script_debug = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
1675 
1676  wp_register_script( 'gravityview-fe-view', plugins_url( 'assets/js/fe-views' . $script_debug . '.js', GRAVITYVIEW_FILE ), apply_filters( 'gravityview_js_dependencies', $js_dependencies ) , GravityView_Plugin::version, true );
1677 
1678  wp_enqueue_script( 'gravityview-fe-view' );
1679 
1680  if ( ! empty( $data['atts']['sort_columns'] ) ) {
1681  wp_enqueue_style( 'gravityview_font', plugins_url( 'assets/css/font.css', GRAVITYVIEW_FILE ), $css_dependencies, GravityView_Plugin::version, 'all' );
1682  }
1683 
1684  $this->enqueue_default_style( $css_dependencies );
1685 
1686  self::add_style( $template_id );
1687  }
1688 
1689  if ( 'wp_print_footer_scripts' === current_filter() ) {
1690 
1691  $js_localization = array(
1692  'cookiepath' => COOKIEPATH,
1693  'clear' => _x( 'Clear', 'Clear all data from the form', 'gravityview' ),
1694  'reset' => _x( 'Reset', 'Reset the search form to the state that existed on page load', 'gravityview' ),
1695  );
1696 
1697  /**
1698  * @filter `gravityview_js_localization` Modify the array passed to wp_localize_script()
1699  * @param array $js_localization The data padded to the Javascript file
1700  * @param array $views Array of View data arrays with View settings
1701  */
1702  $js_localization = apply_filters( 'gravityview_js_localization', $js_localization, $views );
1703 
1704  wp_localize_script( 'gravityview-fe-view', 'gvGlobals', $js_localization );
1705  }
1706  }
1707  }
1708 
1709  /**
1710  * Handle enqueuing the `gravityview_default_style` stylesheet
1711  *
1712  * @since 1.17
1713  *
1714  * @param array $css_dependencies Dependencies for the `gravityview_default_style` stylesheet
1715  *
1716  * @return void
1717  */
1718  private function enqueue_default_style( $css_dependencies = array() ) {
1719 
1720  /**
1721  * @filter `gravityview_use_legacy_search_css` Should GravityView use the legacy Search Bar stylesheet (from before Version 1.17)?
1722  * @since 1.17
1723  * @param bool $use_legacy_search_style If true, loads `gv-legacy-search(-rtl).css`. If false, loads `gv-default-styles(-rtl).css`. `-rtl` is added on RTL websites. Default: `false`
1724  */
1725  $use_legacy_search_style = apply_filters( 'gravityview_use_legacy_search_style', false );
1726 
1727  $rtl = is_rtl() ? '-rtl' : '';
1728 
1729  $css_file_base = $use_legacy_search_style ? 'gv-legacy-search' : 'gv-default-styles';
1730 
1731  $path = gravityview_css_url( $css_file_base . $rtl . '.css' );
1732 
1733  wp_enqueue_style( 'gravityview_default_style', $path, $css_dependencies, GravityView_Plugin::version, 'all' );
1734  }
1735 
1736  /**
1737  * Add template extra style if exists
1738  * @param string $template_id
1739  */
1740  public static function add_style( $template_id ) {
1741 
1742  if ( ! empty( $template_id ) && wp_style_is( 'gravityview_style_' . $template_id, 'registered' ) ) {
1743  do_action( 'gravityview_log_debug', sprintf( '[add_style] Adding extra template style for %s', $template_id ) );
1744  wp_enqueue_style( 'gravityview_style_' . $template_id );
1745  } elseif ( empty( $template_id ) ) {
1746  do_action( 'gravityview_log_error', '[add_style] Cannot add template style; template_id is empty' );
1747  }
1748 
1749  }
1750 
1751 
1752  /**
1753  * Inject the sorting links on the table columns
1754  *
1755  * Callback function for hook 'gravityview/template/field_label'
1756  * @see GravityView_API::field_label() (in includes/class-api.php)
1757  *
1758  * @since 1.7
1759  *
1760  * @param string $label Field label
1761  * @param array $field Field settings
1762  * @param array $form Form object
1763  *
1764  * @return string Field Label
1765  */
1766  public function add_columns_sort_links( $label = '', $field, $form ) {
1767 
1768  /**
1769  * Not a table-based template; don't add sort icons
1770  * @since 1.12
1771  */
1772  if( ! preg_match( '/table/ism', GravityView_View::getInstance()->getTemplatePartSlug() ) ) {
1773  return $label;
1774  }
1775 
1776  if ( ! $this->is_field_sortable( $field['id'], $form ) ) {
1777  return $label;
1778  }
1779 
1780  $sorting = GravityView_View::getInstance()->getSorting();
1781 
1782  $class = 'gv-sort';
1783 
1784  $sort_field_id = self::_override_sorting_id_by_field_type( $field['id'], $form['id'] );
1785 
1786  $sort_args = array(
1787  'sort' => $field['id'],
1788  'dir' => 'asc',
1789  );
1790 
1791  if ( ! empty( $sorting['key'] ) && (string) $sort_field_id === (string) $sorting['key'] ) {
1792  //toggle sorting direction.
1793  if ( 'asc' === $sorting['direction'] ) {
1794  $sort_args['dir'] = 'desc';
1795  $class .= ' gv-icon-sort-desc';
1796  } else {
1797  $sort_args['dir'] = 'asc';
1798  $class .= ' gv-icon-sort-asc';
1799  }
1800  } else {
1801  $class .= ' gv-icon-caret-up-down';
1802  }
1803 
1804  $url = add_query_arg( $sort_args, remove_query_arg( array('pagenum') ) );
1805 
1806  return '<a href="'. esc_url_raw( $url ) .'" class="'. $class .'" ></a>&nbsp;'. $label;
1807 
1808  }
1809 
1810  /**
1811  * Checks if field (column) is sortable
1812  *
1813  * @param string $field Field settings
1814  * @param array $form Gravity Forms form array
1815  *
1816  * @since 1.7
1817  *
1818  * @return bool True: Yes, field is sortable; False: not sortable
1819  */
1820  public function is_field_sortable( $field_id = '', $form = array() ) {
1821 
1822  $field_type = $field_id;
1823 
1824  if( is_numeric( $field_id ) ) {
1825  $field = GFFormsModel::get_field( $form, $field_id );
1826  $field_type = $field->type;
1827  }
1828 
1829  $not_sortable = array(
1830  'edit_link',
1831  'delete_link',
1832  );
1833 
1834  /**
1835  * @filter `gravityview/sortable/field_blacklist` Modify what fields should never be sortable.
1836  * @since 1.7
1837  * @param[in,out] array $not_sortable Array of field types that aren't sortable
1838  * @param string $field_type Field type to check whether the field is sortable
1839  * @param array $form Gravity Forms form
1840  */
1841  $not_sortable = apply_filters( 'gravityview/sortable/field_blacklist', $not_sortable, $field_type, $form );
1842 
1843  if ( in_array( $field_type, $not_sortable ) ) {
1844  return false;
1845  }
1846 
1847  return apply_filters( "gravityview/sortable/formfield_{$form['id']}_{$field_id}", apply_filters( "gravityview/sortable/field_{$field_id}", true, $form ) );
1848 
1849  }
1850 
1851 }
1852 
1854 
1855 
1856 
$url
Definition: post_image.php:25
$image
Definition: post_image.php:98
context_not_configured_warning( $view_id=0)
Display a warning when a View has not been configured.
static getInstance( $passed_post=NULL)
static _override_sorting_id_by_field_type( $sort_field_id, $form_id)
Override sorting per field.
static get_entry_var_name()
Return the query var / end point name for the entry.
static getInstance( $passed_post=NULL)
Definition: class-data.php:164
setIsGravityviewPostType( $is_gravityview_post_type)
single_entry_title( $title, $passed_post_id=null)
Filter the title for the single entry view.
__construct()
Class constructor, enforce Singleton pattern.
set_context_view_id( $view_id=null)
Set the context view ID used when page contains multiple embedded views or displaying the single entr...
static get_entry( $entry_slug, $force_allow_ids=false, $check_entry_display=true)
Return a single entry object.
static process_search_only_approved( $args, $search_criteria)
Process the approved only search criteria according to the View settings.
gravityview_get_form( $form_id)
Returns the form object for a given Form ID.
$class
static generate_notice( $notice, $class='', $cap='', $object_id=null)
Display updated/error notice.
render_view( $passed_args)
Core function to render a View based on a set of arguments.
$entries
$search_method
static is_entry_approved( $entry, $args=array())
Check if a certain entry is approved.
gravityview_css_url( $css_file='', $dir_path='')
Functions that don&#39;t require GravityView or Gravity Forms API access but are used in the plugin to ex...
setSingleEntry( $single_entry)
Sets the single entry ID and also the entry.
GravityView_frontend_get_view_entries( $args, $form_id, $parameters, $count)
Definition: _mocks.php:69
comments_open( $open, $post_id)
Disable comments on GravityView post types.
if(empty( $field_settings['content'])) $content
Definition: custom.php:37
is_field_sortable( $field_id='', $form=array())
Checks if field (column) is sortable.
static get_search_criteria( $args, $form_id)
Parse search criteria for a entries search.
static get_endpoint_name()
Return the endpoint name for a single Entry.
const APPROVED
static get_search_criteria_paging( $args)
Get the paging array for the View.
static is_field_numeric( $form=null, $field='')
Checks if the field type is a &#39;numeric&#39; field type (e.g.
setEntry( $entry)
Set the current entry.
enqueue_default_style( $css_dependencies=array())
Handle enqueuing the gravityview_default_style stylesheet.
gravityview_get_form_id( $view_id)
Get the connected form ID from a View ID.
gravityview_has_shortcode_r( $content, $tag='gravityview')
Placeholder until the recursive has_shortcode() patch is merged.
setPostHasShortcode( $post_has_shortcode)
gravityview_get_entries( $form_ids=null, $passed_criteria=null, &$total=null)
Retrieve entries given search, sort, paging criteria.
insert_view_in_content( $content)
In case View post is called directly, insert the view in the post content.
setGvOutputData( $gv_output_data)
static is_single_entry()
Verify if user requested a single entry view.
$field_id
Definition: time.php:17
static process_search_dates( $args, $search_criteria=array())
Process the start and end dates for a view - overrides values defined in shortcode (if needed) ...
static replace_variables( $text, $form=array(), $entry=array(), $url_encode=false, $esc_html=true, $nl2br=true, $format='html', $aux_data=array())
Alias for GravityView_Merge_Tags::replace_variables()
Definition: class-api.php:108
static is_admin()
Check if is_admin(), and make sure not DOING_AJAX.
If this file is called directly, abort.
if(empty( $created_by)) $form_id
is_searching()
Checks if the current View is presenting search results.
add_scripts_and_styles()
Register styles and scripts.
global $post
gravityview()
The main GravityView wrapper function.
parse_query_fix_frontpage(&$query)
Allow GravityView entry endpoints on the front page of a site.
gravityview_get_context()
GravityView_View $gravityview_view
Definition: class-api.php:1097
static is_approved( $status)
static add_style( $template_id)
Add template extra style if exists.
static has_cap( $caps='', $object_id=null, $user_id=null)
Alias of GravityView_Roles_Capabilities::has_cap()
const GRAVITYVIEW_FILE(! defined( 'ABSPATH'))
Plugin Name: GravityView Plugin URI: https://gravityview.co Description: The best, easiest way to display Gravity Forms entries on your website.
Definition: gravityview.php:26
parse_content( $wp=array())
Read the $post and process the View data inside.
const meta_key
get_context_view_id()
Returns the the view_id context when page contains multiple embedded views or displaying single entry...
$field
Definition: gquiz_grade.php:11
add_columns_sort_links( $label='', $field, $form)
Inject the sorting links on the table columns.
set_entry_data()
Set the entry.
$title
static getInstance()
Get the one true instantiated self.