Completed
Push — develop ( f33cb6...e8781c )
by Gennady
15:41 queued 09:44
created

Views_Route::get_sub_item()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 16
ccs 11
cts 11
cp 1
crap 2
rs 9.7333
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 18 and the first side effect is on line 15.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * @package   GravityView
4
 * @license   GPL2+
5
 * @author    Josh Pollock <[email protected]>
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 1
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 3
	public function get_items( $request ) {
48
49 3
		$page = $request->get_param( 'page' );
50 3
		$limit = $request->get_param( 'limit' );
51
52 3
		$items = \GVCommon::get_all_views( array(
53 3
			'posts_per_page' => $limit,
54 3
			'paged' => $page,
55
		) );
56
57 3
		if ( empty( $items ) ) {
58
			return new \WP_Error( 'gravityview-no-views', __( 'No Views found.', 'gravityview' ) ); //@todo message
59
		}
60
61
		$data = array(
62 3
			'views' => array(),
63 3
			'total' => wp_count_posts( 'gravityview' )->publish,
64
		);
65 3
		foreach ( $items as $item ) {
66 3
			$data['views'][] = $this->prepare_view_for_response( $item, $request );
67
		}
68
69 3
		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 2
	public function get_item( $request ) {
82
83 2
		$url = $request->get_url_params();
84
85 2
		$view_id = intval( $url['id'] );
86
87 2
		$item = get_post( $view_id );
88
89
		//return a response or error based on some conditional
90 2
		if ( $item && ! is_wp_error( $item ) ) {
91 2
			$data = $this->prepare_view_for_response( $item, $request );
92 2
			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
	 * @return mixed The data that is sent.
107
	 */
108 4
	public function prepare_entry_for_response( $view, $entry, \WP_REST_Request $request, $context ) {
109 4
		$return = $entry->as_entry();
110
111
		// Only output the fields that should be displayed.
112 4
		$allowed = array();
113 4
		foreach ( $view->fields->by_position( "{$context}_*" )->by_visible()->all() as $field ) {
114 4
			$allowed[] = $field->ID;
115
		}
116
117
		/**
118
		 * @filter `gravityview/rest/entry/fields` Whitelist more entry fields that are output in regular REST requests.
119
		 * @param[in,out] array $allowed The allowed ones, default by_visible, by_position( "context_*" ), i.e. as set in the view.
120
		 * @param \GV\View $view The view.
121
		 * @param \GV\Entry $entry WordPress representation of the item.
122
		 * @param \WP_REST_Request $request Request object.
123
		 * @param string $context The context (directory, single)
124
		 */
125 4
		$allowed = apply_filters( 'gravityview/rest/entry/fields', $allowed, $view, $entry, $request, $context );
126
127 4
		foreach ( $return as $key => $value ) {
128 4
			if ( ! in_array( $key, $allowed ) ) {
129 4
				unset( $return[ $key ] );
130
			}
131
		}
132
133 4
		$r = new Request( $request );
134
135 4
		foreach ( $allowed as $field ) {
136 4
			$source = is_numeric( $field ) ? $view->form : new \GV\Internal_Source();
137 4
			$field  = is_numeric( $field ) ? \GV\GF_Field::by_id( $view->form, $field ) : \GV\Internal_Field::by_id( $field );
138
139 4
			$return[ $field->ID ] = $field->get_value( $view, $source, $entry, $r );
140
		}
141
142
		// @todo Set the labels!
143
144 4
		return $return;
145
	}
146
147
	/**
148
	 * Get entries from a view
149
	 *
150
	 * Callback for /v1/views/{id}/entries/
151
	 *
152
	 * @since 2.0
153
	 * @param \WP_REST_Request $request Full data about the request.
154
	 * @return \WP_Error|\WP_REST_Response
155
	 */
156 3
	public function get_sub_items( $request ) {
157
158 3
		$url     = $request->get_url_params();
159 3
		$view_id = intval( $url['id'] );
160 3
		$format  = \GV\Utils::get( $url, 'format', 'json' );
161
162 3
		if( $post_id = $request->get_param('post_id') ) {
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
163
			global $post;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
164
165
			$post = get_post( $post_id );
0 ignored issues
show
introduced by
Overridding WordPress globals is prohibited
Loading history...
166
167
			if ( ! $post || is_wp_error( $post ) ) {
168
				return new \WP_Error( 'gravityview-post-not-found', sprintf( 'A post with ID #%d was not found.', $post_id ) );
169
			}
170
171
			$collection = \GV\View_Collection::from_post( $post );
172
173
			if ( ! $collection->contains( $view_id ) ) {
174
				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 ) );
175
			}
176
		}
177
178 3
		$view = \GV\View::by_id( $view_id );
179
180 3
		if ( $format == 'html' ) {
0 ignored issues
show
introduced by
Found "== '". Use Yoda Condition checks, you must
Loading history...
181
182 1
			$renderer = new \GV\View_Renderer();
183 1
			$count = $total = 0;
184
185
			/** @var \GV\Template_Context $context */
186 1
			add_action( 'gravityview/template/view/render', function( $context ) use ( &$count, &$total ) {
187 1
				$count = $context->entries->count();
188 1
				$total = $context->entries->total();
189 1
			} );
190
191 1
			$output = $renderer->render( $view, new Request( $request ) );
192
193
			/**
194
			 * @filter `gravityview/rest/entries/html/insert_meta` Whether to include `http-equiv` meta tags in the HTML output describing the data
195
			 * @param bool $insert_meta Add <meta> tags? [Default: true]
196
			 * @param int $count The number of entries being rendered
197
			 * @param \GV\View $view The view.
198
			 * @param \WP_REST_Request $request Request object.
199
			 * @param int $total The number of total entries for the request
200
			 */
201 1
			$insert_meta = apply_filters( 'gravityview/rest/entries/html/insert_meta', true, $count, $view, $request, $total );
202
203 1
			if ( $insert_meta ) {
204 1
				$output = '<meta http-equiv="X-Item-Count" content="' . $count . '" />' . $output;
205 1
				$output = '<meta http-equiv="X-Item-Total" content="' . $total . '" />' . $output;
206
			}
207
208 1
			$response = new \WP_REST_Response( $output, 200 );
209 1
			$response->header( 'X-Item-Count', $count );
210 1
			$response->header( 'X-Item-Total', $total );
211
212 1
			return $response;
213
		}
214
215 3
		$entries = $view->get_entries( new Request( $request ) );
216
217 3
		if ( ! $entries->all() ) {
218
			return new \WP_Error( 'gravityview-no-entries', __( 'No Entries found.', 'gravityview' ) );
219
		}
220
221 3
		$data = array( 'entries' => $entries->all(), 'total' => $entries->total() );
222
223 3
		foreach ( $data['entries'] as &$entry ) {
224 3
			$entry = $this->prepare_entry_for_response( $view, $entry, $request, 'directory' );
225
		}
226
227 3
		return new \WP_REST_Response( $data, 200 );
228
	}
229
230
	/**
231
	 * Get one entry from view
232
	 *
233
	 * Callback for /v1/views/{id}/entries/{id}/
234
	 *
235
	 * @uses GVCommon::get_entry
236
	 * @since 2.0
237
	 * @param \WP_REST_Request $request Full data about the request.
238
	 * @return \WP_Error|\WP_REST_Response
239
	 */
240 2
	public function get_sub_item( $request ) {
241 2
		$url      = $request->get_url_params();
242 2
		$view_id  = intval( $url['id'] );
243 2
		$entry_id = intval( $url['s_id'] );
244 2
		$format   = \GV\Utils::get( $url, 'format', 'json' );
245
246 2
		$view  = \GV\View::by_id( $view_id );
247 2
		$entry = \GV\GF_Entry::by_id( $entry_id );
248
249 2
		if ( $format == 'html' ) {
0 ignored issues
show
introduced by
Found "== '". Use Yoda Condition checks, you must
Loading history...
250 1
			$renderer = new \GV\Entry_Renderer();
251 1
			return $renderer->render( $entry, $view, new Request( $request ) );
252
		}
253
254 2
		return $this->prepare_entry_for_response( $view, $entry, $request, 'single' );
255
	}
256
257
	/**
258
	 * Prepare the item for the REST response
259
	 *
260
	 * @since 2.0
261
	 * @param \WP_Post $view_post WordPress representation of the item.
262
	 * @param \WP_REST_Request $request Request object.
263
	 * @return mixed
264
	 */
265 4
	public function prepare_view_for_response( $view_post, \WP_REST_Request $request ) {
266 4
		if ( is_wp_error( $this->get_item_permissions_check( $request, $view_post->ID ) ) ) {
267
			// Redacted out view.
268 1
			return array( 'ID' => $view_post->ID, 'post_content' => __( 'You are not allowed to access this content.', 'gravityview' ) );
269
		}
270
271 4
		$view = \GV\View::from_post( $view_post );
272
273 4
		$item = $view->as_data();
274
275
		// Add all the WP_Post data
276 4
		$view_post = $view_post->to_array();
277
278 4
		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'] );
279
280 4
		$return = wp_parse_args( $item, $view_post );
281
282 4
		$return['title'] = $return['post_title'];
283
284 4
		$return['settings'] = isset( $return['atts'] ) ? $return['atts'] : array();
285 4
		unset( $return['atts'], $return['view_id'] );
286
287 4
		$return['search_criteria'] = array(
288 4
			'page_size' => rgars( $return, 'settings/page_size' ),
289 4
			'sort_field' => rgars( $return, 'settings/sort_field' ),
290 4
			'sort_direction' => rgars( $return, 'settings/sort_direction' ),
291 4
			'offset' => rgars( $return, 'settings/offset' ),
292
		);
293
294 4
		unset( $return['settings']['page_size'], $return['settings']['sort_field'], $return['settings']['sort_direction'] );
295
296
		// Redact for non-logged ins
297 4
		if ( ! \GVCommon::has_cap( 'edit_others_gravityviews' ) ) {
298 4
			unset( $return['settings'] );
299 4
			unset( $return['search_criteria'] );
300
		}
301
		
302 4
		if ( ! \GFCommon::current_user_can_any( 'gravityforms_edit_forms' ) ) {
303 4
			unset( $return['form'] );
304
		}
305
306 4
		return $return;
307
	}
308
309 5
	public function get_item_permissions_check( $request ) {
310 5
		if ( func_num_args() == 2 ) {
0 ignored issues
show
introduced by
Found "== 2". Use Yoda Condition checks, you must
Loading history...
311 4
			$view_id = func_get_arg( 1 ); // $view_id override
312
		} else {
313 4
			$url     = $request->get_url_params();
314 4
			$view_id = intval( $url['id'] );
315
		}
316
317 5
		if ( ! $view = \GV\View::by_id( $view_id ) ) {
318
			return new \WP_Error( 'rest_forbidden', __( 'You are not allowed to access this content.', 'gravityview' ) );
319
		}
320
321 5
		while ( $error = $view->can_render( array( 'rest' ), $request ) ) {
322 1
			if ( ! is_wp_error( $error ) )
0 ignored issues
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
323
				break;
324
325 1
			switch ( str_replace( 'gravityview/', '', $error->get_error_code() ) ) {
326 1
				case 'rest_disabled':
327 1
				case 'post_password_required':
328 1
				case 'not_public':
329
				case 'embed_only':
330
				case 'no_direct_access':
331 1
					return new \WP_Error( 'rest_forbidden', __( 'You are not allowed to access this content.', 'gravityview' ) );
332
				case 'no_form_attached':
333
					return new \WP_Error( 'rest_forbidden', __( 'This View is not configured properly.', 'gravityview' ) );
334
			}
335
336
			return $content;
0 ignored issues
show
Bug introduced by
The variable $content does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
337
		}
338
339
		/**
340
		 * @filter `gravityview/view/output/rest` Disable rest output. Final chance.
341
		 * @param[in,out] bool Enable or not.
342
		 * @param \GV\View $view The view.
343
		 */
344 5
		if ( ! apply_filters( 'gravityview/view/output/rest', true, $view ) ) {
345 1
			return new \WP_Error( 'rest_forbidden', __( 'You are not allowed to access this content.', 'gravityview' ) );
346
		}
347
348 5
		return true;
349
	}
350
351 2
	public function get_sub_item_permissions_check( $request ) {
352
		// Accessing a single entry needs the View access permissions.
353 2
		if ( is_wp_error( $error = $this->get_items_permissions_check( $request ) ) ) {
354
			return $error;
355
		}
356
357 2
		$url     = $request->get_url_params();
358 2
		$view_id = intval( $url['id'] );
359 2
		$entry_id = intval( $url['s_id'] );
360
361 2
		$view = \GV\View::by_id( $view_id );
362
363 2
		if ( ! $entry = \GV\GF_Entry::by_id( $entry_id ) ) {
364
			return new \WP_Error( 'rest_forbidden', 'You are not allowed to view this content.', 'gravityview' );
365
		}
366
367 2
		if ( $entry['form_id'] != $view->form->ID ) {
368
			return new \WP_Error( 'rest_forbidden', 'You are not allowed to view this content.', 'gravityview' );
369
		}
370
371 2
		if ( $entry['status'] != 'active' ) {
0 ignored issues
show
introduced by
Found "!= '". Use Yoda Condition checks, you must
Loading history...
372
			return new \WP_Error( 'rest_forbidden', 'You are not allowed to view this content.', 'gravityview' );
373
		}
374
375 2
		if ( apply_filters( 'gravityview_custom_entry_slug', false ) && $entry->slug != get_query_var( \GV\Entry::get_endpoint_name() ) ) {
376
			return new \WP_Error( 'rest_forbidden', 'You are not allowed to view this content.', 'gravityview' );
377
		}
378
379 2
		$is_admin_and_can_view = $view->settings->get( 'admin_show_all_statuses' ) && \GVCommon::has_cap('gravityview_moderate_entries', $view->ID );
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
380
381 2
		if ( $view->settings->get( 'show_only_approved' ) && ! $is_admin_and_can_view ) {
382 1
			if ( ! \GravityView_Entry_Approval_Status::is_approved( gform_get_meta( $entry->ID, \GravityView_Entry_Approval::meta_key ) )  ) {
383 1
				return new \WP_Error( 'rest_forbidden', 'You are not allowed to view this content.', 'gravityview' );
384
			}
385
		}
386
387 2
		return true;
388
	}
389
390 4
	public function get_items_permissions_check( $request ) {
391
		// Getting a list of all Views is always possible.
392 4
		return true;
393
	}
394
395 3
	public function get_sub_items_permissions_check( $request ) {
396
		// Accessing all entries of a View needs the same permissions as accessing the View.
397 3
		return $this->get_item_permissions_check( $request );
398
	}
399
}
400