Completed
Push — develop ( 87b48c...882b48 )
by Gennady
23:47 queued 03:49
created

Views_Route::get_item_permissions_check()   C

Complexity

Conditions 12
Paths 24

Size

Total Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 14.5573

Importance

Changes 0
Metric Value
cc 12
nc 24
nop 1
dl 0
loc 43
ccs 17
cts 23
cp 0.7391
crap 14.5573
rs 6.9666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
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
	 * @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 8
	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 8
		$allowed = array();
116 8
		foreach ( $view->fields->by_position( "{$context}_*" )->by_visible( $view )->all() as $field ) {
117 8
			$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 8
		$allowed_field_ids = apply_filters( 'gravityview/rest/entry/fields', wp_list_pluck( $allowed, 'ID' ), $view, $entry, $request, $context );
129
130 8
		$allowed = array_filter( $allowed, function( $field ) use ( $allowed_field_ids ) {
131 8
			return in_array( $field->ID, $allowed_field_ids, true );
132 8
		} );
133
134
		// Tack on additional fields if needed
135 8
		foreach ( array_diff( $allowed_field_ids, wp_list_pluck( $allowed, 'ID' ) ) as $field_id ) {
136 2
			$allowed[] = is_numeric( $field_id ) ? \GV\GF_Field::by_id( $view->form, $field_id ) : \GV\Internal_Field::by_id( $field_id );
137
		}
138
139 8
		$r = new Request( $request );
140 8
		$return = array();
141
142 8
		$renderer = new \GV\Field_Renderer();
143
		
144 8
		$used_ids = array();
145
146 8
		foreach ( $allowed as $field ) {
147 8
			$source = is_numeric( $field->ID ) ? $view->form : new \GV\Internal_Source();
148
149 8
			$field_id = $field->ID;
150 8
			$index = null;
151
152 8
			if ( ! isset( $used_ids[ $field_id ] ) ) {
153 8
				$used_ids[ $field_id ] = 0;
154
			} else {
155 1
				$index = ++$used_ids[ $field_id ];
156
			}
157
158 8
			if ( $index ) {
159
				/**
160
				 * Modify non-unique IDs (custom, id, etc.) to be unique and not gobbled up.
161
				 */
162 1
				$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 8
			$field_id = apply_filters( 'gravityview/api/field/key', $field_id, $view, $entry, $request, $context );
174
175 8
			if ( ! $class && in_array( $field->ID, array( 'custom' ) ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $class of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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 2
				$return[ $field_id ] = $renderer->render( $field, $view, $source, $entry, $r, '\GV\Field_CSV_Template' );
182 8
			} else if ( $class ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $class of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
183 4
				$return[ $field_id ] = $renderer->render( $field, $view, $source, $entry, $r, $class );
184
			} else {
185 6
				switch ( $field->type ):
186 6
					case 'list':
187 1
						$return[ $field_id ] = unserialize( $field->get_value( $view, $source, $entry, $r ) );
188 1
						break;
189 6
					case 'fileupload':
190 6
					case 'business_hours':
191 1
						$return[ $field_id ] = json_decode( $field->get_value( $view, $source, $entry, $r ) );
192 1
						break;
193
					default;
194 6
						$return[ $field_id ] = $field->get_value( $view, $source, $entry, $r );
195
				endswitch;
196
			}
197
		}
198
199 8
		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 6
	public function get_sub_items( $request ) {
212
213 6
		$url     = $request->get_url_params();
214 6
		$view_id = intval( $url['id'] );
215 6
		$format  = \GV\Utils::get( $url, 'format', 'json' );
216
217 6
		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 6
		$view = \GV\View::by_id( $view_id );
234
235 6
		if ( 'html' === $format ) {
236
237 1
			$renderer = new \GV\View_Renderer();
238 1
			$count = $total = 0;
239
240
			/** @var \GV\Template_Context $context */
241 1
			add_action( 'gravityview/template/view/render', function( $context ) use ( &$count, &$total ) {
242 1
				$count = $context->entries->count();
243 1
				$total = $context->entries->total();
244 1
			} );
245
246 1
			$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 1
			$insert_meta = apply_filters( 'gravityview/rest/entries/html/insert_meta', true, $count, $view, $request, $total );
258
259 1
			if ( $insert_meta ) {
260 1
				$output = '<meta http-equiv="X-Item-Count" content="' . $count . '" />' . $output;
261 1
				$output = '<meta http-equiv="X-Item-Total" content="' . $total . '" />' . $output;
262
			}
263
264 1
			$response = new \WP_REST_Response( $output, 200 );
265 1
			$response->header( 'X-Item-Count', $count );
266 1
			$response->header( 'X-Item-Total', $total );
267
268 1
			return $response;
269
		}
270
271 6
		$entries = $view->get_entries( new Request( $request ) );
272
273 6
		if ( ! $entries->all() ) {
274
			return new \WP_Error( 'gravityview-no-entries', __( 'No Entries found.', 'gravityview' ) );
275
		}
276
277 6
		if ( 'csv' === $format ) {
278 4
			ob_start();
279
280 4
			$csv = fopen( 'php://output', 'w' );
281
282
			/** Da' BOM :) */
283 4
			if ( apply_filters( 'gform_include_bom_export_entries', true, $view->form ? $view->form->form : null ) ) {
284 4
				fputs( $csv, "\xef\xbb\xbf" );
285
			}
286
287 4
			$headers_done = false;
288
289 4
			foreach ( $entries->all() as $entry ) {
290 4
				$entry = $this->prepare_entry_for_response( $view, $entry, $request, 'directory', '\GV\Field_CSV_Template' );
291
292 4
				if ( ! $headers_done ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $headers_done of type false|integer is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
293 4
					$headers_done = fputcsv( $csv, array_map( array( '\GV\Utils', 'strip_excel_formulas' ), array_keys( $entry ) ) );
294
				}
295
296 4
				fputcsv( $csv, array_map( array( '\GV\Utils', 'strip_excel_formulas' ), $entry ) );
297
			}
298
299 4
			$response = new \WP_REST_Response( '', 200 );
300 4
			$response->header( 'X-Item-Count', $entries->count() );
301 4
			$response->header( 'X-Item-Total', $entries->total() );
302 4
			$response->header( 'Content-Type', 'text/csv' );
303
304 4
			fflush( $csv );
305
306 4
			$data = rtrim( ob_get_clean() );
307
308 4
			add_filter( 'rest_pre_serve_request', function() use ( $data ) {
309
				echo $data;
310
				return true;
311 4
			} );
312
313 4
			if ( defined( 'DOING_GRAVITYVIEW_TESTS' ) && DOING_GRAVITYVIEW_TESTS ) {
314 4
				echo $data; // rest_pre_serve_request is not called in tests
315
			}
316
317 4
			return $response;
318
		}
319
320 4
		$data = array( 'entries' => $entries->all(), 'total' => $entries->total() );
321
322 4
		foreach ( $data['entries'] as &$entry ) {
323 4
			$entry = $this->prepare_entry_for_response( $view, $entry, $request, 'directory' );
324
		}
325
326 4
		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 4
	public function get_sub_item( $request ) {
340 4
		$url      = $request->get_url_params();
341 4
		$view_id  = intval( $url['id'] );
342 4
		$entry_id = intval( $url['s_id'] );
343 4
		$format   = \GV\Utils::get( $url, 'format', 'json' );
344
345 4
		$view  = \GV\View::by_id( $view_id );
346 4
		$entry = \GV\GF_Entry::by_id( $entry_id );
347
348 4
		if ( $format === 'html' ) {
349 1
			$renderer = new \GV\Entry_Renderer();
350 1
			return $renderer->render( $entry, $view, new Request( $request ) );
351
		}
352
353 4
		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 4
	public function prepare_view_for_response( $view_post, \WP_REST_Request $request ) {
365 4
		if ( is_wp_error( $this->get_item_permissions_check( $request, $view_post->ID ) ) ) {
366
			// Redacted out view.
367 1
			return array( 'ID' => $view_post->ID, 'post_content' => __( 'You are not allowed to access this content.', 'gravityview' ) );
368
		}
369
370 4
		$view = \GV\View::from_post( $view_post );
371
372 4
		$item = $view->as_data();
373
374
		// Add all the WP_Post data
375 4
		$view_post = $view_post->to_array();
376
377 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'] );
378
379 4
		$return = wp_parse_args( $item, $view_post );
380
381 4
		$return['title'] = $return['post_title'];
382
383 4
		$return['settings'] = isset( $return['atts'] ) ? $return['atts'] : array();
384 4
		unset( $return['atts'], $return['view_id'] );
385
386 4
		$return['search_criteria'] = array(
387 4
			'page_size' => rgars( $return, 'settings/page_size' ),
388 4
			'sort_field' => rgars( $return, 'settings/sort_field' ),
389 4
			'sort_direction' => rgars( $return, 'settings/sort_direction' ),
390 4
			'offset' => rgars( $return, 'settings/offset' ),
391
		);
392
393 4
		unset( $return['settings']['page_size'], $return['settings']['sort_field'], $return['settings']['sort_direction'] );
394
395
		// Redact for non-logged ins
396 4
		if ( ! \GVCommon::has_cap( 'edit_others_gravityviews' ) ) {
397 4
			unset( $return['settings'] );
398 4
			unset( $return['search_criteria'] );
399
		}
400
		
401 4
		if ( ! \GFCommon::current_user_can_any( 'gravityforms_edit_forms' ) ) {
402 4
			unset( $return['form'] );
403
		}
404
405 4
		return $return;
406
	}
407
408
	/**
409
	 * @param \WP_REST_Request $request
410
	 *
411
	 * @return bool|\WP_Error
412
	 */
413 8
	public function get_item_permissions_check( $request ) {
414 8
		if ( func_num_args() === 2 ) {
415 4
			$view_id = func_get_arg( 1 ); // $view_id override
416
		} else {
417 7
			$url     = $request->get_url_params();
418 7
			$view_id = intval( $url['id'] );
419
		}
420
421 8
		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 8
		while ( $error = $view->can_render( array( 'rest' ), $request ) ) {
426
427 8
			if ( ! is_wp_error( $error ) ) {
428 8
				break;
429
			}
430
431 1
			switch ( str_replace( 'gravityview/', '', $error->get_error_code() ) ) {
432 1
				case 'rest_disabled':
433 1
				case 'post_password_required':
434 1
				case 'not_public':
435
				case 'embed_only':
436
				case 'no_direct_access':
437 1
					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 8
		if ( ! apply_filters( 'gravityview/view/output/rest', true, $view ) ) {
451 1
			return new \WP_Error( 'rest_forbidden', __( 'You are not allowed to access this content.', 'gravityview' ) );
452
		}
453
454 8
		return true;
455
	}
456
457 4
	public function get_sub_item_permissions_check( $request ) {
458
		// Accessing a single entry needs the View access permissions.
459 4
		if ( is_wp_error( $error = $this->get_items_permissions_check( $request ) ) ) {
460
			return $error;
461
		}
462
463 4
		$url     = $request->get_url_params();
464 4
		$view_id = intval( $url['id'] );
465 4
		$entry_id = intval( $url['s_id'] );
466
467 4
		$view = \GV\View::by_id( $view_id );
468
469 4
		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 4
		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 4
		if ( $entry['status'] != 'active' ) {
478
			return new \WP_Error( 'rest_forbidden', 'You are not allowed to view this content.', 'gravityview' );
479
		}
480
481 4
		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 4
		$is_admin_and_can_view = $view->settings->get( 'admin_show_all_statuses' ) && \GVCommon::has_cap('gravityview_moderate_entries', $view->ID );
486
487 4
		if ( $view->settings->get( 'show_only_approved' ) && ! $is_admin_and_can_view ) {
488 1
			if ( ! \GravityView_Entry_Approval_Status::is_approved( gform_get_meta( $entry->ID, \GravityView_Entry_Approval::meta_key ) )  ) {
489 1
				return new \WP_Error( 'rest_forbidden', 'You are not allowed to view this content.', 'gravityview' );
490
			}
491
		}
492
493 4
		return true;
494
	}
495
496 6
	public function get_items_permissions_check( $request ) {
497
		// Getting a list of all Views is always possible.
498 6
		return true;
499
	}
500
501 6
	public function get_sub_items_permissions_check( $request ) {
502
		// Accessing all entries of a View needs the same permissions as accessing the View.
503 6
		return $this->get_item_permissions_check( $request );
504
	}
505
}
506