GravityView  2.9.2
The best, easiest way to display Gravity Forms entries on your website.
class-gv-rest-views-route.php
Go to the documentation of this file.
1 <?php
2 /**
3  * @package GravityView
4  * @license GPL2+
5  * @author Josh Pollock <josh@joshpress.net>
6  * @link http://gravityview.co
7  * @copyright Copyright 2015, Katz Web Services, Inc.
8  *
9  * @since 2.0
10  */
11 namespace GV\REST;
12 
13 /** If this file is called directly, abort. */
14 if ( ! defined( 'GRAVITYVIEW_DIR' ) ) {
15  die();
16 }
17 
18 class Views_Route extends Route {
19  /**
20  * Route Name
21  *
22  * @since 2.0
23  *
24  * @access protected
25  * @string
26  */
27  protected $route_name = 'views';
28 
29  /**
30  * Sub type, forms {$namespace}/route_name/{id}/sub_type type endpoints
31  *
32  * @since 2.0
33  * @access protected
34  * @var string
35  */
36  protected $sub_type = 'entries';
37 
38 
39  /**
40  * Get a collection of views
41  *
42  * Callback for GET /v1/views/
43  *
44  * @param \WP_REST_Request $request Full data about the request.
45  * @return \WP_Error|\WP_REST_Response
46  */
47  public function get_items( $request ) {
48 
49  $page = $request->get_param( 'page' );
50  $limit = $request->get_param( 'limit' );
51 
52  $items = \GVCommon::get_all_views( array(
53  'posts_per_page' => $limit,
54  'paged' => $page,
55  ) );
56 
57  if ( empty( $items ) ) {
58  return new \WP_Error( 'gravityview-no-views', __( 'No Views found.', 'gravityview' ) ); //@todo message
59  }
60 
61  $data = array(
62  'views' => array(),
63  'total' => wp_count_posts( 'gravityview' )->publish,
64  );
65  foreach ( $items as $item ) {
66  $data['views'][] = $this->prepare_view_for_response( $item, $request );
67  }
68 
69  return new \WP_REST_Response( $data, 200 );
70  }
71 
72  /**
73  * Get one view
74  *
75  * Callback for /v1/views/{id}/
76  *
77  * @since 2.0
78  * @param \WP_REST_Request $request Full data about the request.
79  * @return \WP_Error|\WP_REST_Response
80  */
81  public function get_item( $request ) {
82 
83  $url = $request->get_url_params();
84 
85  $view_id = intval( $url['id'] );
86 
87  $item = get_post( $view_id );
88 
89  //return a response or error based on some conditional
90  if ( $item && ! is_wp_error( $item ) ) {
91  $data = $this->prepare_view_for_response( $item, $request );
92  return new \WP_REST_Response( $data, 200 );
93  }
94 
95  return new \WP_Error( 'code', sprintf( 'A View with ID #%d was not found.', $view_id ) );
96  }
97 
98  /**
99  * Prepare the item for the REST response
100  *
101  * @since 2.0
102  * @param \GV\View $view The view.
103  * @param \GV\Entry $entry WordPress representation of the item.
104  * @param \WP_REST_Request $request Request object.
105  * @param string $context The context (directory, single)
106  * @param string $class The value renderer. Default: null (raw value)
107  *
108  * @since 2.1 Add value renderer override $class parameter.
109  *
110  * @return mixed The data that is sent.
111  */
112  public function prepare_entry_for_response( $view, $entry, \WP_REST_Request $request, $context, $class = null ) {
113 
114  // Only output the fields that should be displayed.
115  $allowed = array();
116  foreach ( $view->fields->by_position( "{$context}_*" )->by_visible( $view )->all() as $field ) {
117  $allowed[] = $field;
118  }
119 
120  /**
121  * @filter `gravityview/rest/entry/fields` Whitelist more entry fields that are output in regular REST requests.
122  * @param[in,out] array $allowed The allowed ones, default by_visible, by_position( "context_*" ), i.e. as set in the view.
123  * @param \GV\View $view The view.
124  * @param \GV\Entry $entry The entry.
125  * @param \WP_REST_Request $request Request object.
126  * @param string $context The context (directory, single)
127  */
128  $allowed_field_ids = apply_filters( 'gravityview/rest/entry/fields', wp_list_pluck( $allowed, 'ID' ), $view, $entry, $request, $context );
129 
130  $allowed = array_filter( $allowed, function( $field ) use ( $allowed_field_ids ) {
131  return in_array( $field->ID, $allowed_field_ids, true );
132  } );
133 
134  // Tack on additional fields if needed
135  foreach ( array_diff( $allowed_field_ids, wp_list_pluck( $allowed, 'ID' ) ) as $field_id ) {
136  $allowed[] = is_numeric( $field_id ) ? \GV\GF_Field::by_id( $view->form, $field_id ) : \GV\Internal_Field::by_id( $field_id );
137  }
138 
139  $r = new Request( $request );
140  $return = array();
141 
142  $renderer = new \GV\Field_Renderer();
143 
144  $used_ids = array();
145 
146  foreach ( $allowed as $field ) {
147  $source = is_numeric( $field->ID ) ? $view->form : new \GV\Internal_Source();
148 
149  $field_id = $field->ID;
150  $index = null;
151 
152  if ( ! isset( $used_ids[ $field_id ] ) ) {
153  $used_ids[ $field_id ] = 0;
154  } else {
155  $index = ++$used_ids[ $field_id ];
156  }
157 
158  if ( $index ) {
159  /**
160  * Modify non-unique IDs (custom, id, etc.) to be unique and not gobbled up.
161  */
162  $field_id = sprintf( '%s(%d)', $field_id, $index + 1 );
163  }
164 
165  /**
166  * @filter `gravityview/api/field/key` Filter the key name in the results for JSON output.
167  * @param[in,out] string $field_id The ID. Should be unique or keys will be gobbled up.
168  * @param \GV\View $view The view.
169  * @param \GV\Entry $entry The entry.
170  * @param \WP_REST_Request $request Request object.
171  * @param string $context The context (directory, single)
172  */
173  $field_id = apply_filters( 'gravityview/api/field/key', $field_id, $view, $entry, $request, $context );
174 
175  if ( ! $class && in_array( $field->ID, array( 'custom' ) ) ) {
176  /**
177  * Custom fields (and perhaps some others) will require rendering as they don't
178  * contain an intrinsic value (for custom their value is stored in the view and requires a renderer).
179  * We force the CSV template to take over in such cases, it's good enough for most cases.
180  */
181  $return[ $field_id ] = $renderer->render( $field, $view, $source, $entry, $r, '\GV\Field_CSV_Template' );
182  } else if ( $class ) {
183  $return[ $field_id ] = $renderer->render( $field, $view, $source, $entry, $r, $class );
184  } else {
185  switch ( $field->type ):
186  case 'list':
187  $return[ $field_id ] = unserialize( $field->get_value( $view, $source, $entry, $r ) );
188  break;
189  case 'fileupload':
190  case 'business_hours':
191  $return[ $field_id ] = json_decode( $field->get_value( $view, $source, $entry, $r ) );
192  break;
193  default;
194  $return[ $field_id ] = $field->get_value( $view, $source, $entry, $r );
195  endswitch;
196  }
197  }
198 
199  return $return;
200  }
201 
202  /**
203  * Get entries from a view
204  *
205  * Callback for /v1/views/{id}/entries/
206  *
207  * @since 2.0
208  * @param \WP_REST_Request $request Full data about the request.
209  * @return \WP_Error|\WP_REST_Response
210  */
211  public function get_sub_items( $request ) {
212 
213  $url = $request->get_url_params();
214  $view_id = intval( $url['id'] );
215  $format = \GV\Utils::get( $url, 'format', 'json' );
216 
217  if( $post_id = $request->get_param('post_id') ) {
218  global $post;
219 
220  $post = get_post( $post_id );
221 
222  if ( ! $post || is_wp_error( $post ) ) {
223  return new \WP_Error( 'gravityview-post-not-found', sprintf( 'A post with ID #%d was not found.', $post_id ) );
224  }
225 
226  $collection = \GV\View_Collection::from_post( $post );
227 
228  if ( ! $collection->contains( $view_id ) ) {
229  return new \WP_Error( 'gravityview-post-not-contains', sprintf( 'The post with ID #%d does not contain a View with ID #%d', $post_id, $view_id ) );
230  }
231  }
232 
233  $view = \GV\View::by_id( $view_id );
234 
235  if ( 'html' === $format ) {
236 
237  $renderer = new \GV\View_Renderer();
238  $count = $total = 0;
239 
240  /** @var \GV\Template_Context $context */
241  add_action( 'gravityview/template/view/render', function( $context ) use ( &$count, &$total ) {
242  $count = $context->entries->count();
243  $total = $context->entries->total();
244  } );
245 
246  $output = $renderer->render( $view, new Request( $request ) );
247 
248  /**
249  * @filter `gravityview/rest/entries/html/insert_meta` Whether to include `http-equiv` meta tags in the HTML output describing the data
250  * @since 2.0
251  * @param bool $insert_meta Add <meta> tags? [Default: true]
252  * @param int $count The number of entries being rendered
253  * @param \GV\View $view The view.
254  * @param \WP_REST_Request $request Request object.
255  * @param int $total The number of total entries for the request
256  */
257  $insert_meta = apply_filters( 'gravityview/rest/entries/html/insert_meta', true, $count, $view, $request, $total );
258 
259  if ( $insert_meta ) {
260  $output = '<meta http-equiv="X-Item-Count" content="' . $count . '" />' . $output;
261  $output = '<meta http-equiv="X-Item-Total" content="' . $total . '" />' . $output;
262  }
263 
264  $response = new \WP_REST_Response( $output, 200 );
265  $response->header( 'X-Item-Count', $count );
266  $response->header( 'X-Item-Total', $total );
267 
268  return $response;
269  }
270 
271  $entries = $view->get_entries( new Request( $request ) );
272 
273  if ( ! $entries->all() ) {
274  return new \WP_Error( 'gravityview-no-entries', __( 'No Entries found.', 'gravityview' ) );
275  }
276 
277  if ( 'csv' === $format ) {
278  ob_start();
279 
280  $csv = fopen( 'php://output', 'w' );
281 
282  /** Da' BOM :) */
283  if ( apply_filters( 'gform_include_bom_export_entries', true, $view->form ? $view->form->form : null ) ) {
284  fputs( $csv, "\xef\xbb\xbf" );
285  }
286 
287  $headers_done = false;
288 
289  foreach ( $entries->all() as $entry ) {
290  $entry = $this->prepare_entry_for_response( $view, $entry, $request, 'directory', '\GV\Field_CSV_Template' );
291 
292  if ( ! $headers_done ) {
293  $headers_done = fputcsv( $csv, array_map( array( '\GV\Utils', 'strip_excel_formulas' ), array_keys( $entry ) ) );
294  }
295 
296  fputcsv( $csv, array_map( array( '\GV\Utils', 'strip_excel_formulas' ), $entry ) );
297  }
298 
299  $response = new \WP_REST_Response( '', 200 );
300  $response->header( 'X-Item-Count', $entries->count() );
301  $response->header( 'X-Item-Total', $entries->total() );
302  $response->header( 'Content-Type', 'text/csv' );
303 
304  fflush( $csv );
305 
306  $data = rtrim( ob_get_clean() );
307 
308  add_filter( 'rest_pre_serve_request', function() use ( $data ) {
309  echo $data;
310  return true;
311  } );
312 
313  if ( defined( 'DOING_GRAVITYVIEW_TESTS' ) && DOING_GRAVITYVIEW_TESTS ) {
314  echo $data; // rest_pre_serve_request is not called in tests
315  }
316 
317  return $response;
318  }
319 
320  $data = array( 'entries' => $entries->all(), 'total' => $entries->total() );
321 
322  foreach ( $data['entries'] as &$entry ) {
323  $entry = $this->prepare_entry_for_response( $view, $entry, $request, 'directory' );
324  }
325 
326  return new \WP_REST_Response( $data, 200 );
327  }
328 
329  /**
330  * Get one entry from view
331  *
332  * Callback for /v1/views/{id}/entries/{id}/
333  *
334  * @uses GVCommon::get_entry
335  * @since 2.0
336  * @param \WP_REST_Request $request Full data about the request.
337  * @return \WP_Error|\WP_REST_Response
338  */
339  public function get_sub_item( $request ) {
340  $url = $request->get_url_params();
341  $view_id = intval( $url['id'] );
342  $entry_id = intval( $url['s_id'] );
343  $format = \GV\Utils::get( $url, 'format', 'json' );
344 
345  $view = \GV\View::by_id( $view_id );
346  $entry = \GV\GF_Entry::by_id( $entry_id );
347 
348  if ( $format === 'html' ) {
349  $renderer = new \GV\Entry_Renderer();
350  return $renderer->render( $entry, $view, new Request( $request ) );
351  }
352 
353  return $this->prepare_entry_for_response( $view, $entry, $request, 'single' );
354  }
355 
356  /**
357  * Prepare the item for the REST response
358  *
359  * @since 2.0
360  * @param \WP_Post $view_post WordPress representation of the item.
361  * @param \WP_REST_Request $request Request object.
362  * @return mixed
363  */
364  public function prepare_view_for_response( $view_post, \WP_REST_Request $request ) {
365  if ( is_wp_error( $this->get_item_permissions_check( $request, $view_post->ID ) ) ) {
366  // Redacted out view.
367  return array( 'ID' => $view_post->ID, 'post_content' => __( 'You are not allowed to access this content.', 'gravityview' ) );
368  }
369 
370  $view = \GV\View::from_post( $view_post );
371 
372  $item = $view->as_data();
373 
374  // Add all the WP_Post data
375  $view_post = $view_post->to_array();
376 
377  unset( $view_post['to_ping'], $view_post['ping_status'], $view_post['pinged'], $view_post['post_type'], $view_post['filter'], $view_post['post_category'], $view_post['tags_input'], $view_post['post_content'], $view_post['post_content_filtered'] );
378 
379  $return = wp_parse_args( $item, $view_post );
380 
381  $return['title'] = $return['post_title'];
382 
383  $return['settings'] = isset( $return['atts'] ) ? $return['atts'] : array();
384  unset( $return['atts'], $return['view_id'] );
385 
386  $return['search_criteria'] = array(
387  'page_size' => rgars( $return, 'settings/page_size' ),
388  'sort_field' => rgars( $return, 'settings/sort_field' ),
389  'sort_direction' => rgars( $return, 'settings/sort_direction' ),
390  'offset' => rgars( $return, 'settings/offset' ),
391  );
392 
393  unset( $return['settings']['page_size'], $return['settings']['sort_field'], $return['settings']['sort_direction'] );
394 
395  // Redact for non-logged ins
396  if ( ! \GVCommon::has_cap( 'edit_others_gravityviews' ) ) {
397  unset( $return['settings'] );
398  unset( $return['search_criteria'] );
399  }
400 
401  if ( ! \GFCommon::current_user_can_any( 'gravityforms_edit_forms' ) ) {
402  unset( $return['form'] );
403  }
404 
405  return $return;
406  }
407 
408  /**
409  * @param \WP_REST_Request $request
410  *
411  * @return bool|\WP_Error
412  */
413  public function get_item_permissions_check( $request ) {
414  if ( func_num_args() === 2 ) {
415  $view_id = func_get_arg( 1 ); // $view_id override
416  } else {
417  $url = $request->get_url_params();
418  $view_id = intval( $url['id'] );
419  }
420 
421  if ( ! $view = \GV\View::by_id( $view_id ) ) {
422  return new \WP_Error( 'rest_forbidden', __( 'You are not allowed to access this content.', 'gravityview' ) );
423  }
424 
425  while ( $error = $view->can_render( array( 'rest' ), $request ) ) {
426 
427  if ( ! is_wp_error( $error ) ) {
428  break;
429  }
430 
431  switch ( str_replace( 'gravityview/', '', $error->get_error_code() ) ) {
432  case 'rest_disabled':
433  case 'post_password_required':
434  case 'not_public':
435  case 'embed_only':
436  case 'no_direct_access':
437  return new \WP_Error( 'rest_forbidden', __( 'You are not allowed to access this content.', 'gravityview' ) );
438  case 'no_form_attached':
439  return new \WP_Error( 'rest_forbidden', __( 'This View is not configured properly.', 'gravityview' ) );
440  default:
441  return new \WP_Error( 'rest_forbidden', __( 'You are not allowed to access this content.', 'gravityview' ) );
442  }
443  }
444 
445  /**
446  * @filter `gravityview/view/output/rest` Disable rest output. Final chance.
447  * @param[in,out] bool Enable or not.
448  * @param \GV\View $view The view.
449  */
450  if ( ! apply_filters( 'gravityview/view/output/rest', true, $view ) ) {
451  return new \WP_Error( 'rest_forbidden', __( 'You are not allowed to access this content.', 'gravityview' ) );
452  }
453 
454  return true;
455  }
456 
457  public function get_sub_item_permissions_check( $request ) {
458  // Accessing a single entry needs the View access permissions.
459  if ( is_wp_error( $error = $this->get_items_permissions_check( $request ) ) ) {
460  return $error;
461  }
462 
463  $url = $request->get_url_params();
464  $view_id = intval( $url['id'] );
465  $entry_id = intval( $url['s_id'] );
466 
467  $view = \GV\View::by_id( $view_id );
468 
469  if ( ! $entry = \GV\GF_Entry::by_id( $entry_id ) ) {
470  return new \WP_Error( 'rest_forbidden', 'You are not allowed to view this content.', 'gravityview' );
471  }
472 
473  if ( $entry['form_id'] != $view->form->ID ) {
474  return new \WP_Error( 'rest_forbidden', 'You are not allowed to view this content.', 'gravityview' );
475  }
476 
477  if ( $entry['status'] != 'active' ) {
478  return new \WP_Error( 'rest_forbidden', 'You are not allowed to view this content.', 'gravityview' );
479  }
480 
481  if ( apply_filters( 'gravityview_custom_entry_slug', false ) && $entry->slug != get_query_var( \GV\Entry::get_endpoint_name() ) ) {
482  return new \WP_Error( 'rest_forbidden', 'You are not allowed to view this content.', 'gravityview' );
483  }
484 
485  $is_admin_and_can_view = $view->settings->get( 'admin_show_all_statuses' ) && \GVCommon::has_cap('gravityview_moderate_entries', $view->ID );
486 
487  if ( $view->settings->get( 'show_only_approved' ) && ! $is_admin_and_can_view ) {
489  return new \WP_Error( 'rest_forbidden', 'You are not allowed to view this content.', 'gravityview' );
490  }
491  }
492 
493  return true;
494  }
495 
496  public function get_items_permissions_check( $request ) {
497  // Getting a list of all Views is always possible.
498  return true;
499  }
500 
501  public function get_sub_items_permissions_check( $request ) {
502  // Accessing all entries of a View needs the same permissions as accessing the View.
503  return $this->get_item_permissions_check( $request );
504  }
505 }
$url
Definition: post_image.php:25
prepare_entry_for_response( $view, $entry, \WP_REST_Request $request, $context, $class=null)
Prepare the item for the REST response.
If this file is called directly, abort.
get_items( $request)
Get a collection of views.
global $post
Definition: edit-entry.php:7
if(gv_empty( $field['value'], false, false)) $format
$class
$entries
prepare_view_for_response( $view_post, \WP_REST_Request $request)
Prepare the item for the REST response.
If this file is called directly, abort.
static from_post(\WP_Post $post)
Get a list of objects inside the supplied .
static by_id( $form, $field_id)
Get a by and Field ID.
static get_endpoint_name()
Return the endpoint name for a single Entry.
get_item( $request)
Get one view.
If this file is called directly, abort.
static by_id( $post_id)
Construct a instance from a post ID.
get_sub_item( $request)
Get one entry from view.
$field_id
Definition: time.php:17
get_sub_items( $request)
Get a collection of items.
static by_id( $field_id)
Get a from an internal Gravity Forms field ID.
static get( $array, $key, $default=null)
Grab a value from an array or an object or default.
static by_id( $entry_id, $form_id=0)
Construct a instance by ID.
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
static get_all_views( $args=array())
Get all existing Views.
static from_post( $post)
Construct a instance from a .
const meta_key
$field
Definition: gquiz_grade.php:11