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