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