GravityView  2.17
The best, easiest way to display Gravity Forms entries on your website.
class-gv-shortcode-gravityview.php
Go to the documentation of this file.
1 <?php
2 namespace GV\Shortcodes;
3 
4 /** If this file is called directly, abort. */
5 if ( ! defined( 'GRAVITYVIEW_DIR' ) ) {
6  die();
7 }
8 
9 /**
10  * The [gravityview] shortcode.
11  */
12 class gravityview extends \GV\Shortcode {
13  /**
14  * {@inheritDoc}
15  */
16  public $name = 'gravityview';
17 
18  /**
19  * A stack of calls to track nested shortcodes.
20  */
21  public static $callstack = array();
22 
23  /**
24  * Process and output the [gravityview] shortcode.
25  *
26  * @param array $passed_atts The attributes passed.
27  * @param string $content The content inside the shortcode.
28  * @param string $tag The shortcode tag.
29  *
30  * @return string|null The output.
31  */
32  public function callback( $passed_atts, $content = '', $tag = '' ) {
33  $request = gravityview()->request;
34 
35  if ( $request->is_admin() ) {
36  return '';
37  }
38 
39  $atts = wp_parse_args( $passed_atts, array(
40  'id' => 0,
41  'view_id' => 0,
42  'detail' => null,
43  'class' => '',
44  ) );
45 
46  if ( ! $view_id = $atts['id'] ? : $atts['view_id'] ) {
47  if ( $atts['detail'] && $view = $request->is_view() ) {
48  $view_id = $view->ID;
49  }
50  }
51 
52  $view = \GV\View::by_id( $view_id );
53 
54  if ( ! $view ) {
55  gravityview()->log->error( 'View does not exist #{view_id}', array( 'view_id' => $view_id ) );
56  return '';
57  }
58 
59  $post = get_post( $view->ID );
60 
61  $gv_view_data = \GravityView_View_Data::getInstance();
62 
63  if ( ! $gv_view_data->views->contains( $view->ID ) ) {
64  $gv_view_data->views->add( $view );
65  }
66 
67  /**
68  * @action `gravityview/shortcode/before-processing` Runs before the GV shortcode is processed; can be used to load additional scripts/styles
69  *
70  * @since 2.13.4
71  *
72  * @param \GV\View $view GV View
73  * @param \WP_Post $post Associated WP post
74  */
75  do_action( 'gravityview/shortcode/before-processing', $view, $post );
76 
77  gravityview()->views->set( $view );
78 
79  /**
80  * When this shortcode is embedded inside a View we can only display it as a directory. There's no other way.
81  * Try to detect that we're not embedded to allow edit and single contexts.
82  */
83  $is_reembedded = false; // Assume not embedded unless detected otherwise.
84  if ( in_array( get_class( $request ), array( 'GV\Frontend_Request', 'GV\Mock_Request' ) ) ) {
85 
86  if ( ( $_view = $request->is_view() ) && $_view->ID !== $view->ID ) {
87  $is_reembedded = true;
88 
89  } elseif ( $request->is_entry( $view->form ? $view->form->ID : 0 ) && self::$callstack ) {
90  $is_reembedded = true;
91  }
92  }
93 
94  array_push( self::$callstack, true );
95 
96  /**
97  * Remove Widgets on a nested embedded View.
98  */
99  if ( $is_reembedded ) {
100  $view->widgets = new \GV\Widget_Collection();
101  }
102 
103  $atts = $this->parse_and_sanitize_atts( $atts );
104 
105  $view->settings->update( array( 'shortcode_atts' => $atts ) );
106  $view->settings->update( $atts );
107 
108  /**
109  * Check permissions.
110  */
111  while ( $error = $view->can_render( array( 'shortcode' ), $request ) ) {
112  if ( ! is_wp_error( $error ) )
113  break;
114 
115  switch ( str_replace( 'gravityview/', '', $error->get_error_code() ) ) {
116  case 'post_password_required':
117  return self::_return( get_the_password_form( $view->ID ) );
118  case 'no_form_attached':
119  /**
120  * This View has no data source. There's nothing to show really.
121  * ...apart from a nice message if the user can do anything about it.
122  */
123  if ( \GVCommon::has_cap( array( 'edit_gravityviews', 'edit_gravityview' ), $view->ID ) ) {
124  return self::_return( sprintf( __( 'This View is not configured properly. Start by <a href="%s">selecting a form</a>.', 'gk-gravityview' ), esc_url( get_edit_post_link( $view->ID, false ) ) ) );
125  }
126  break;
127  case 'no_direct_access':
128  case 'embed_only':
129  case 'not_public':
130  default:
131  return self::_return( __( 'You are not allowed to view this content.', 'gk-gravityview' ) );
132  }
133  }
134 
135  $is_admin_and_can_view = $view->settings->get( 'admin_show_all_statuses' ) && \GVCommon::has_cap('gravityview_moderate_entries', $view->ID );
136 
137  /**
138  * View details.
139  */
140  if ( $atts['detail'] ) {
141  $entries = $view->get_entries( $request );
142  return self::_return( $this->detail( $view, $entries, $atts ) );
143 
144  /**
145  * Editing a single entry.
146  */
147  } else if ( ! $is_reembedded && ( $entry = $request->is_edit_entry( $view->form ? $view->form->ID : 0 ) ) ) {
148 
149  /**
150  * When editing an entry don't render multiple views.
151  */
152  if ( ( $selected = \GV\Utils::_GET( 'gvid' ) ) && $view->ID != $selected ) {
153  gravityview()->log->notice( 'Entry ID #{entry_id} not rendered because another View ID was passed using `?gvid`: #{selected}', array( 'entry_id' => $entry->ID, 'selected' => $selected ) );
154  return self::_return( '' );
155  }
156 
157  if ( $entry['status'] != 'active' ) {
158  gravityview()->log->notice( 'Entry ID #{entry_id} is not active', array( 'entry_id' => $entry->ID ) );
159  return self::_return( __( 'You are not allowed to view this content.', 'gk-gravityview' ) );
160  }
161 
162  if ( apply_filters( 'gravityview_custom_entry_slug', false ) && $entry->slug != get_query_var( \GV\Entry::get_endpoint_name() ) ) {
163  gravityview()->log->error( 'Entry ID #{entry_id} was accessed by a bad slug', array( 'entry_id' => $entry->ID ) );
164  return self::_return( __( 'You are not allowed to view this content.', 'gk-gravityview' ) );
165  }
166 
167  if ( $view->settings->get( 'show_only_approved' ) && ! $is_admin_and_can_view ) {
169  gravityview()->log->error( 'Entry ID #{entry_id} is not approved for viewing', array( 'entry_id' => $entry->ID ) );
170  return self::_return( __( 'You are not allowed to view this content.', 'gk-gravityview' ) );
171  }
172  }
173 
174  $renderer = new \GV\Edit_Entry_Renderer();
175  return self::_return( $renderer->render( $entry, $view, $request ) );
176 
177  /**
178  * Viewing a single entry.
179  */
180  } else if ( ! $is_reembedded && ( $entry = $request->is_entry( $view->form ? $view->form->ID : 0 ) ) ) {
181  /**
182  * When viewing an entry don't render multiple views.
183  */
184  if ( ( $selected = \GV\Utils::_GET( 'gvid' ) ) && $view->ID != $selected ) {
185  return self::_return( '' );
186  }
187 
188  $entryset = $entry->is_multi() ? $entry->entries : array( $entry );
189 
190  foreach ( $entryset as $e ) {
191  if ( $e['status'] != 'active' ) {
192  gravityview()->log->notice( 'Entry ID #{entry_id} is not active', array( 'entry_id' => $e->ID ) );
193  return self::_return( __( 'You are not allowed to view this content.', 'gk-gravityview' ) );
194  }
195 
196  if ( apply_filters( 'gravityview_custom_entry_slug', false ) && $e->slug != get_query_var( \GV\Entry::get_endpoint_name() ) ) {
197  gravityview()->log->error( 'Entry ID #{entry_id} was accessed by a bad slug', array( 'entry_id' => $e->ID ) );
198  return self::_return( __( 'You are not allowed to view this content.', 'gk-gravityview' ) );
199  }
200 
201  if ( $view->settings->get( 'show_only_approved' ) && ! $is_admin_and_can_view ) {
203  gravityview()->log->error( 'Entry ID #{entry_id} is not approved for viewing', array( 'entry_id' => $e->ID ) );
204  return self::_return( __( 'You are not allowed to view this content.', 'gk-gravityview' ) );
205  }
206  }
207 
208  $error = \GVCommon::check_entry_display( $e->as_entry(), $view );
209 
210  if ( is_wp_error( $error ) ) {
211  gravityview()->log->error( 'Entry ID #{entry_id} is not approved for viewing: {message}', array( 'entry_id' => $e->ID, 'message' => $error->get_error_message() ) );
212  return self::_return( __( 'You are not allowed to view this content.', 'gk-gravityview' ) );
213  }
214  }
215 
216  $renderer = new \GV\Entry_Renderer();
217  return self::_return( $renderer->render( $entry, $view, $request ) );
218 
219  /**
220  * Just this view.
221  */
222  } else {
223  if ( $is_reembedded ) {
224 
225  // Mock the request with the actual View, not the global one
226  $mock_request = new \GV\Mock_Request();
227  $mock_request->returns['is_view'] = $view;
228  $mock_request->returns['is_entry'] = $request->is_entry( $view->form ? $view->form->ID : 0 );
229  $mock_request->returns['is_edit_entry'] = $request->is_edit_entry( $view->form ? $view->form->ID : 0 );
230  $mock_request->returns['is_search'] = $request->is_search();
231 
232  $request = $mock_request;
233  }
234 
235  $renderer = new \GV\View_Renderer();
236  return self::_return( $renderer->render( $view, $request ) );
237  }
238  }
239 
240  /**
241  * Validate attributes passed to the [gravityview] shortcode. Supports {get} Merge Tags values.
242  *
243  * Attributes passed to the shortcode are compared to registered attributes {@see \GV\View_Settings::defaults}
244  * Only attributes that are defined will be allowed through.
245  *
246  * Then, {get} merge tags are replaced with their $_GET values, if passed
247  *
248  * Then, attributes are sanitized based on the type of setting (number, checkbox, select, radio, text)
249  *
250  * @see \GV\View_Settings::defaults() Only attributes defined in default() are valid to be passed via the shortcode
251  *
252  * @param array $passed_atts Attribute pairs defined to render the View
253  *
254  * @return array Valid and sanitized attribute pairs
255  */
256  private function parse_and_sanitize_atts( $passed_atts ) {
257 
258  $defaults = \GV\View_Settings::defaults( true );
259 
260  $supported_atts = array_fill_keys( array_keys( $defaults ), '' );
261 
262  // Whittle down the attributes to only valid pairs
263  $filtered_atts = shortcode_atts( $supported_atts, $passed_atts, 'gravityview' );
264 
265  // Only keep the passed attributes after making sure that they're valid pairs
266  $filtered_atts = array_intersect_key( (array) $passed_atts, $filtered_atts );
267 
268  $atts = array();
269 
270  foreach( $filtered_atts as $key => $passed_value ) {
271 
272  // Allow using GravityView merge tags in shortcode attributes, like {get} and {created_by}
273  $passed_value = \GravityView_Merge_Tags::replace_variables( $passed_value );
274 
275  switch( $defaults[ $key ]['type'] ) {
276 
277  /**
278  * Make sure number fields are numeric.
279  * Also, convert mixed number strings to numbers
280  * @see http://php.net/manual/en/function.is-numeric.php#107326
281  */
282  case 'number':
283  if( is_numeric( $passed_value ) ) {
284  $atts[ $key ] = ( $passed_value + 0 );
285  }
286  break;
287 
288  // Checkboxes should be 1 or 0
289  case 'checkbox':
290  $atts[ $key ] = gv_empty( $passed_value, true, false ) ? 0 : 1;
291  break;
292 
293  /**
294  * Only allow values that are defined in the settings
295  */
296  case 'select':
297  case 'radio':
298  $options = isset( $defaults[ $key ]['choices'] ) ? $defaults[ $key ]['choices'] : $defaults[ $key ]['options'];
299  if( in_array( $passed_value, array_keys( $options ) ) ) {
300  $atts[ $key ] = $passed_value;
301  }
302  break;
303 
304  case 'text':
305  default:
306  $atts[ $key ] = $passed_value;
307  break;
308  }
309  }
310 
311  $atts['detail'] = \GV\Utils::get( $passed_atts, 'detail', null );
312 
313  return $atts;
314  }
315 
316  /**
317  * Output view details.
318  *
319  * @param \GV\View $view The View.
320  * @param \GV\Entry_Collection $entries The calculated entries.
321  * @param array $atts The shortcode attributes (with defaults).
322  *
323  * @return string The output.
324  */
325  private function detail( $view, $entries, $atts ) {
326  $output = '';
327 
328  switch ( $key = $atts['detail'] ):
329  case 'total_entries':
330  $output = number_format_i18n( $entries->total() );
331  break;
332  case 'first_entry':
333  $output = number_format_i18n( min( $entries->total(), $view->settings->get( 'offset' ) + 1 ) );
334  break;
335  case 'last_entry':
336  $output = number_format_i18n( $view->settings->get( 'page_size' ) + $view->settings->get( 'offset' ) );
337  break;
338  case 'page_size':
339  $output = number_format_i18n( $view->settings->get( $key ) );
340  break;
341  endswitch;
342 
343  /**
344  * @filter `gravityview/shortcode/detail/{$detail}` Filter the detail output returned from `[gravityview detail="$detail"]`
345  * @since 1.13
346  * @param string[in,out] $output Existing output
347  *
348  * @since 2.0.3
349  * @param \GV\View $view The view.
350  * @param \GV\Entry_Collection $entries The entries.
351  * @param array $atts The shortcode atts with defaults.
352  */
353  $output = apply_filters( "gravityview/shortcode/detail/$key", $output, $view );
354 
355  return $output;
356  }
357 
358  /**
359  * Pop the callstack and return the value.
360  */
361  private static function _return( $value ) {
362  array_pop( self::$callstack );
363  return $value;
364  }
365 }
static _GET( $name, $default=null)
Grab a value from the _GET superglobal or default.
If this file is called directly, abort.
callback( $passed_atts, $content='', $tag='')
Process and output the [gravityview] shortcode.
static getInstance( $passed_post=NULL)
Definition: class-data.php:122
static _return( $value)
Pop the callstack and return the value.
static replace_variables( $text, $form=array(), $entry=array(), $url_encode=false, $esc_html=true, $nl2br=true, $format='html', $aux_data=array())
Alias for GFCommon::replace_variables()
$entries
global $post
Definition: delete-entry.php:7
If this file is called directly, abort.
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_endpoint_name()
Return the endpoint name for a single Entry.
parse_and_sanitize_atts( $passed_atts)
Validate attributes passed to the [gravityview] shortcode.
static by_id( $post_id)
Construct a instance from a post ID.
static $callstack
A stack of calls to track nested shortcodes.
static get( $array, $key, $default=null)
Grab a value from an array or an object or default.
gravityview()
The main GravityView wrapper function.
detail( $view, $entries, $atts)
Output view details.
static defaults( $detailed=false, $group=null)
Retrieve the default View settings.
gv_empty( $value, $zero_is_empty=true, $allow_string_booleans=true)
Is the value empty?
static is_approved( $status)
static has_cap( $caps='', $object_id=null, $user_id=null)
Alias of GravityView_Roles_Capabilities::has_cap()
$entry
Definition: notes.php:27
const meta_key