Completed
Push — develop ( d510c0...8b9f5f )
by Gennady
08:20 queued 04:08
created

Views_Route::get_sub_item()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 16
ccs 3
cts 3
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
	 * @param string $class The value renderer. Default: null (raw value)
107
	 *
108 4
	 * @since 2.1 Add value renderer override $class parameter.
109 4
	 *
110
	 * @return mixed The data that is sent.
111
	 */
112 4
	public function prepare_entry_for_response( $view, $entry, \WP_REST_Request $request, $context, $class = null ) {
113 4
		$return = $entry->as_entry();
114 4
115
		// Only output the fields that should be displayed.
116
		$allowed = array();
117
		foreach ( $view->fields->by_position( "{$context}_*" )->by_visible()->all() as $field ) {
118
			$allowed[] = $field->ID;
119
		}
120
121
		/**
122
		 * @filter `gravityview/rest/entry/fields` Whitelist more entry fields that are output in regular REST requests.
123
		 * @param[in,out] array $allowed The allowed ones, default by_visible, by_position( "context_*" ), i.e. as set in the view.
124
		 * @param \GV\View $view The view.
125 4
		 * @param \GV\Entry $entry WordPress representation of the item.
126
		 * @param \WP_REST_Request $request Request object.
127 4
		 * @param string $context The context (directory, single)
128 4
		 */
129 4
		$allowed = apply_filters( 'gravityview/rest/entry/fields', $allowed, $view, $entry, $request, $context );
130
131
		foreach ( $return as $key => $value ) {
132
			if ( ! in_array( $key, $allowed ) ) {
133 4
				unset( $return[ $key ] );
134
			}
135 4
		}
136 4
137 4
		$r = new Request( $request );
138
139 4
		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...
140
			$renderer = new \GV\Field_Renderer();
141
		}
142
143
		foreach ( $allowed as $field ) {
144 4
			$source = is_numeric( $field ) ? $view->form : new \GV\Internal_Source();
145
			$field  = is_numeric( $field ) ? \GV\GF_Field::by_id( $view->form, $field ) : \GV\Internal_Field::by_id( $field );
146
147
			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...
148
				$return[ $field->ID ] = $renderer->render( $field, $view, $source, $entry, $r, $class );
0 ignored issues
show
Bug introduced by
The variable $renderer does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
149
			} else {
150
				$return[ $field->ID ] = $field->get_value( $view, $source, $entry, $r );
151
			}
152
		}
153
154
		// @todo Set the labels!
155
156 3
		return $return;
157
	}
158 3
159 3
	/**
160 3
	 * Get entries from a view
161
	 *
162 3
	 * Callback for /v1/views/{id}/entries/
163
	 *
164
	 * @since 2.0
165
	 * @param \WP_REST_Request $request Full data about the request.
166
	 * @return \WP_Error|\WP_REST_Response
167
	 */
168
	public function get_sub_items( $request ) {
169
170
		$url     = $request->get_url_params();
171
		$view_id = intval( $url['id'] );
172
		$format  = \GV\Utils::get( $url, 'format', 'json' );
173
174
		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...
175
			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...
176
177
			$post = get_post( $post_id );
0 ignored issues
show
introduced by
Overridding WordPress globals is prohibited
Loading history...
178 3
179
			if ( ! $post || is_wp_error( $post ) ) {
180 3
				return new \WP_Error( 'gravityview-post-not-found', sprintf( 'A post with ID #%d was not found.', $post_id ) );
181
			}
182 1
183 1
			$collection = \GV\View_Collection::from_post( $post );
184
185
			if ( ! $collection->contains( $view_id ) ) {
186 1
				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 ) );
187 1
			}
188 1
		}
189 1
190
		$view = \GV\View::by_id( $view_id );
191 1
192
		if ( 'html' === $format ) {
193
194
			$renderer = new \GV\View_Renderer();
195
			$count = $total = 0;
196
197
			/** @var \GV\Template_Context $context */
198
			add_action( 'gravityview/template/view/render', function( $context ) use ( &$count, &$total ) {
199
				$count = $context->entries->count();
200
				$total = $context->entries->total();
201
			} );
202 1
203
			$output = $renderer->render( $view, new Request( $request ) );
204 1
205 1
			/**
206 1
			 * @filter `gravityview/rest/entries/html/insert_meta` Whether to include `http-equiv` meta tags in the HTML output describing the data
207
			 * @since 2.0
208
			 * @param bool $insert_meta Add <meta> tags? [Default: true]
209 1
			 * @param int $count The number of entries being rendered
210 1
			 * @param \GV\View $view The view.
211 1
			 * @param \WP_REST_Request $request Request object.
212
			 * @param int $total The number of total entries for the request
213 1
			 */
214
			$insert_meta = apply_filters( 'gravityview/rest/entries/html/insert_meta', true, $count, $view, $request, $total );
215
216 3
			if ( $insert_meta ) {
217
				$output = '<meta http-equiv="X-Item-Count" content="' . $count . '" />' . $output;
218 3
				$output = '<meta http-equiv="X-Item-Total" content="' . $total . '" />' . $output;
219
			}
220
221
			$response = new \WP_REST_Response( $output, 200 );
222 3
			$response->header( 'X-Item-Count', $count );
223 1
			$response->header( 'X-Item-Total', $total );
224
225 1
			return $response;
226
		}
227
228 1
		$entries = $view->get_entries( new Request( $request ) );
229 1
230
		if ( ! $entries->all() ) {
231
			return new \WP_Error( 'gravityview-no-entries', __( 'No Entries found.', 'gravityview' ) );
232 1
		}
233
234 1
		if ( 'csv' === $format ) {
235 1
			ob_start();
236
237 1
			$csv = fopen( 'php://output', 'w' );
238 1
239
			/** Da' BOM :) */
240
			if ( apply_filters( 'gform_include_bom_export_entries', true, $view->form ? $view->form->form : null ) ) {
241 1
				fputs( $csv, "\xef\xbb\xbf" );
0 ignored issues
show
introduced by
Filesystem writes are forbidden, you should not be using fputs()
Loading history...
242
			}
243
244 1
			$headers_done = false;
245 1
246 1
			foreach ( $entries->all() as $entry ) {
247
				$entry = $this->prepare_entry_for_response( $view, $entry, $request, 'directory', '\GV\Field_CSV_Template' );
248 1
249
				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...
250
					$headers_done = fputcsv( $csv, array_map( array( '\GV\Utils', 'strip_excel_formulas' ), array_keys( $entry ) ) );
0 ignored issues
show
introduced by
Filesystem writes are forbidden, you should not be using fputcsv()
Loading history...
251 3
				}
252
253 3
				fputcsv( $csv, array_map( array( '\GV\Utils', 'strip_excel_formulas' ), $entry ) );
0 ignored issues
show
introduced by
Filesystem writes are forbidden, you should not be using fputcsv()
Loading history...
254 3
			}
255
256
			$response = new \WP_REST_Response( rtrim( ob_get_clean() ), 200 );
257 3
			$response->header( 'X-Item-Count', $entries->count() );
258
			$response->header( 'X-Item-Total', $entries->total() );
259
260
			return $response;
261
		}
262
263
		$data = array( 'entries' => $entries->all(), 'total' => $entries->total() );
264
265
		foreach ( $data['entries'] as &$entry ) {
266
			$entry = $this->prepare_entry_for_response( $view, $entry, $request, 'directory' );
267
		}
268
269
		return new \WP_REST_Response( $data, 200 );
270 2
	}
271 2
272 2
	/**
273 2
	 * Get one entry from view
274 2
	 *
275
	 * Callback for /v1/views/{id}/entries/{id}/
276 2
	 *
277 2
	 * @uses GVCommon::get_entry
278
	 * @since 2.0
279 2
	 * @param \WP_REST_Request $request Full data about the request.
280 1
	 * @return \WP_Error|\WP_REST_Response
281 1
	 */
282
	public function get_sub_item( $request ) {
283
		$url      = $request->get_url_params();
284 2
		$view_id  = intval( $url['id'] );
285
		$entry_id = intval( $url['s_id'] );
286
		$format   = \GV\Utils::get( $url, 'format', 'json' );
287
288
		$view  = \GV\View::by_id( $view_id );
289
		$entry = \GV\GF_Entry::by_id( $entry_id );
290
291
		if ( $format === 'html' ) {
0 ignored issues
show
introduced by
Found "=== '". Use Yoda Condition checks, you must
Loading history...
292
			$renderer = new \GV\Entry_Renderer();
293
			return $renderer->render( $entry, $view, new Request( $request ) );
294
		}
295 4
296 4
		return $this->prepare_entry_for_response( $view, $entry, $request, 'single' );
297
	}
298 1
299
	/**
300
	 * Prepare the item for the REST response
301 4
	 *
302
	 * @since 2.0
303 4
	 * @param \WP_Post $view_post WordPress representation of the item.
304
	 * @param \WP_REST_Request $request Request object.
305
	 * @return mixed
306 4
	 */
307
	public function prepare_view_for_response( $view_post, \WP_REST_Request $request ) {
308 4
		if ( is_wp_error( $this->get_item_permissions_check( $request, $view_post->ID ) ) ) {
309
			// Redacted out view.
310 4
			return array( 'ID' => $view_post->ID, 'post_content' => __( 'You are not allowed to access this content.', 'gravityview' ) );
311
		}
312 4
313
		$view = \GV\View::from_post( $view_post );
314 4
315 4
		$item = $view->as_data();
316
317 4
		// Add all the WP_Post data
318 4
		$view_post = $view_post->to_array();
319 4
320 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'] );
321 4
322
		$return = wp_parse_args( $item, $view_post );
323
324 4
		$return['title'] = $return['post_title'];
325
326
		$return['settings'] = isset( $return['atts'] ) ? $return['atts'] : array();
327 4
		unset( $return['atts'], $return['view_id'] );
328 4
329 4
		$return['search_criteria'] = array(
330
			'page_size' => rgars( $return, 'settings/page_size' ),
331
			'sort_field' => rgars( $return, 'settings/sort_field' ),
332 4
			'sort_direction' => rgars( $return, 'settings/sort_direction' ),
333 4
			'offset' => rgars( $return, 'settings/offset' ),
334
		);
335
336 4
		unset( $return['settings']['page_size'], $return['settings']['sort_field'], $return['settings']['sort_direction'] );
337
338
		// Redact for non-logged ins
339
		if ( ! \GVCommon::has_cap( 'edit_others_gravityviews' ) ) {
340
			unset( $return['settings'] );
341
			unset( $return['search_criteria'] );
342
		}
343
		
344 5
		if ( ! \GFCommon::current_user_can_any( 'gravityforms_edit_forms' ) ) {
345 5
			unset( $return['form'] );
346 4
		}
347
348 4
		return $return;
349 4
	}
350
351
	/**
352 5
	 * @param \WP_REST_Request $request
353
	 *
354
	 * @return bool|\WP_Error
355
	 */
356 5
	public function get_item_permissions_check( $request ) {
357
		if ( func_num_args() === 2 ) {
0 ignored issues
show
introduced by
Found "=== 2". Use Yoda Condition checks, you must
Loading history...
358 5
			$view_id = func_get_arg( 1 ); // $view_id override
359 5
		} else {
360
			$url     = $request->get_url_params();
361
			$view_id = intval( $url['id'] );
362 1
		}
363 1
364 1
		if ( ! $view = \GV\View::by_id( $view_id ) ) {
365 1
			return new \WP_Error( 'rest_forbidden', __( 'You are not allowed to access this content.', 'gravityview' ) );
366
		}
367
368 1
		while ( $error = $view->can_render( array( 'rest' ), $request ) ) {
369
370
			if ( ! is_wp_error( $error ) ) {
371
				break;
372
			}
373
374
			switch ( str_replace( 'gravityview/', '', $error->get_error_code() ) ) {
375
				case 'rest_disabled':
376
				case 'post_password_required':
377
				case 'not_public':
378
				case 'embed_only':
379 5
				case 'no_direct_access':
380 1
					return new \WP_Error( 'rest_forbidden', __( 'You are not allowed to access this content.', 'gravityview' ) );
381
				case 'no_form_attached':
382
					return new \WP_Error( 'rest_forbidden', __( 'This View is not configured properly.', 'gravityview' ) );
383 5
			}
384
		}
385
386 2
		/**
387
		 * @filter `gravityview/view/output/rest` Disable rest output. Final chance.
388 2
		 * @param[in,out] bool Enable or not.
389
		 * @param \GV\View $view The view.
390
		 */
391
		if ( ! apply_filters( 'gravityview/view/output/rest', true, $view ) ) {
392 2
			return new \WP_Error( 'rest_forbidden', __( 'You are not allowed to access this content.', 'gravityview' ) );
393 2
		}
394 2
395
		return true;
396 2
	}
397
398 2
	public function get_sub_item_permissions_check( $request ) {
399
		// Accessing a single entry needs the View access permissions.
400
		if ( is_wp_error( $error = $this->get_items_permissions_check( $request ) ) ) {
401
			return $error;
402 2
		}
403
404
		$url     = $request->get_url_params();
405
		$view_id = intval( $url['id'] );
406 2
		$entry_id = intval( $url['s_id'] );
407
408
		$view = \GV\View::by_id( $view_id );
409
410 2
		if ( ! $entry = \GV\GF_Entry::by_id( $entry_id ) ) {
411
			return new \WP_Error( 'rest_forbidden', 'You are not allowed to view this content.', 'gravityview' );
412
		}
413
414 2
		if ( $entry['form_id'] != $view->form->ID ) {
415
			return new \WP_Error( 'rest_forbidden', 'You are not allowed to view this content.', 'gravityview' );
416 2
		}
417 1
418 1
		if ( $entry['status'] != 'active' ) {
0 ignored issues
show
introduced by
Found "!= '". Use Yoda Condition checks, you must
Loading history...
419
			return new \WP_Error( 'rest_forbidden', 'You are not allowed to view this content.', 'gravityview' );
420
		}
421
422 2
		if ( apply_filters( 'gravityview_custom_entry_slug', false ) && $entry->slug != get_query_var( \GV\Entry::get_endpoint_name() ) ) {
423
			return new \WP_Error( 'rest_forbidden', 'You are not allowed to view this content.', 'gravityview' );
424
		}
425 4
426
		$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...
427 4
428
		if ( $view->settings->get( 'show_only_approved' ) && ! $is_admin_and_can_view ) {
429
			if ( ! \GravityView_Entry_Approval_Status::is_approved( gform_get_meta( $entry->ID, \GravityView_Entry_Approval::meta_key ) )  ) {
430 3
				return new \WP_Error( 'rest_forbidden', 'You are not allowed to view this content.', 'gravityview' );
431
			}
432 3
		}
433
434
		return true;
435
	}
436
437
	public function get_items_permissions_check( $request ) {
438
		// Getting a list of all Views is always possible.
439
		return true;
440
	}
441
442
	public function get_sub_items_permissions_check( $request ) {
443
		// Accessing all entries of a View needs the same permissions as accessing the View.
444
		return $this->get_item_permissions_check( $request );
445
	}
446
}
447