Completed
Branch BUG-9464-payment-methods-myste... (4799f5)
by
unknown
328:53 queued 313:26
created

Base::send_response()   D

Complexity

Conditions 9
Paths 24

Size

Total Lines 32
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 2
Metric Value
c 4
b 1
f 2
dl 0
loc 32
rs 4.909
cc 9
eloc 23
nc 24
nop 1
1
<?php
2
namespace EventEspresso\core\libraries\rest_api\controllers;
3
use EventEspresso\core\libraries\rest_api\Rest_Exception;
4
if ( !defined( 'EVENT_ESPRESSO_VERSION' ) ) {
5
	exit( 'No direct script access allowed' );
6
}
7
8
/**
9
 *
10
 * Base
11
 *
12
 * Base controller for EE REST API
13
 *
14
 * @package			Event Espresso
15
 * @subpackage
16
 * @author				Mike Nelson
17
 *
18
 */
19
class Base {
20
21
	const header_prefix_for_ee = 'X-EE-';
22
23
	const header_prefix_for_wp = 'X-WP-';
24
25
	/**
26
	 * Contains debug info we'll send back in the response headers
27
	 * @var array
28
	 */
29
	protected $_debug_info = array();
30
31
	/**
32
	 * Indicates whether or not the API is in debug mode
33
	 * @var boolean
34
	 */
35
	protected $_debug_mode = false;
36
37
	/**
38
	 * Indicates the version that was requested
39
	 * @var string
40
	 */
41
	protected $_requested_version;
42
43
	/**
44
	 * flat array of headers to send in the response
45
	 * @var array
46
	 */
47
	protected $_response_headers = array();
48
49
50
51
	public function __construct() {
52
		$this->_debug_mode = defined( 'EE_REST_API_DEBUG_MODE' ) ? EE_REST_API_DEBUG_MODE : false;
53
	}
54
55
56
	/**
57
	 * Sets the version the user requested
58
	 * @param string $version eg '4.8'
59
	 */
60
	public function set_requested_version( $version ) {
61
		$this->_requested_version = $version;
62
	}
63
64
	/**
65
	 * Sets some debug info that we'll send back in headers
66
	 * @param string $key
67
	 * @param string|array $info
68
	 */
69
	protected function _set_debug_info( $key, $info ){
70
		$this->_debug_info[ $key ] = $info;
71
	}
72
73
	/**
74
	 * Sets headers for the response
75
	 *
76
	 * @param string       $header_key    , excluding the "X-EE-" part
77
	 * @param array|string $value         if an array, multiple headers will be added, one
78
	 *                                    for each key in the array
79
	 * @param boolean      $use_ee_prefix whether to use the EE prefix on the header, or fallback to
80
	 *                                    the standard WP one
81
	 */
82
	protected function _set_response_header( $header_key, $value, $use_ee_prefix = true ) {
83
		if( is_array( $value ) ) {
84
			foreach( $value as $value_key => $value_value ) {
85
				$this->_set_response_header(  $header_key . '[' . $value_key . ']', $value_value );
86
			}
87
		} else {
88
			$prefix = $use_ee_prefix ? Base::header_prefix_for_ee : Base::header_prefix_for_wp;
89
			$this->_response_headers[ $prefix . $header_key  ] = $value;
90
		}
91
	}
92
93
	/**
94
	 * Returns a flat array of headers to be added to the response
95
	 * @return array
96
	 */
97
	protected function _get_response_headers() {
98
		return apply_filters( 'FHEE__EventEspresso\core\libraries\rest_api\controllers\Base___get_response_headers',
99
			$this->_response_headers,
100
			$this,
101
			$this->_requested_version
102
		);
103
	}
104
105
	/**
106
	 * Adds error notices from EE_Error onto the provided \WP_Error
107
	 * @param \WP_Error $wp_error_response
108
	 * @return \WP_Error
109
	 */
110
	protected function _add_ee_errors_to_response( \WP_Error $wp_error_response ) {
111
		$notices_during_checkin = \EE_Error::get_raw_notices();
112
		if( ! empty( $notices_during_checkin[ 'errors' ] ) ) {
113
			foreach( $notices_during_checkin[ 'errors' ] as $error_code => $error_message ) {
114
				$wp_error_response->add(
115
					sanitize_key( $error_code ),
116
					strip_tags( $error_message ) );
117
			}
118
		}
119
		return $wp_error_response;
120
	}
121
122
123
124
	/**
125
	 * Sends a response, but also makes sure to attach headers that
126
	 * are handy for debugging.
127
	 * Specifically, we assume folks will want to know what exactly was the DB query that got run,
128
	 * what exactly was the Models query that got run, what capabilities came into play, what fields were omitted from
129
	 * the response, others?
130
	 *
131
	 * @param array|\WP_Error|\Exception $response
132
	 * @return \WP_REST_Response
133
	 */
134
	public function send_response( $response ) {
135
		if( $response instanceof Rest_Exception ) {
136
			$response = new \WP_Error( $response->get_string_code(), $response->getMessage(), $response->get_data() );
137
		}
138
		if( $response instanceof \Exception ) {
139
			$code = $response->getCode() ? $response->getCode() : 'error_occurred';
140
			$response = new \WP_Error( $code, $response->getMessage() );
141
		}
142
		if( $response instanceof \WP_Error ) {
0 ignored issues
show
Bug introduced by
The class WP_Error does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
143
			$response = $this->_add_ee_errors_to_response( $response );
144
			$rest_response = $this->_create_rest_response_from_wp_error( $response );
145
		}else{
146
			$rest_response = new \WP_REST_Response( $response, 200 );
147
		}
148
		$headers = array();
149
		if( $this->_debug_mode && is_array( $this->_debug_info ) ) {
150
			foreach( $this->_debug_info  as $debug_key => $debug_info ) {
151
				if( is_array( $debug_info ) ) {
152
					$debug_info = json_encode( $debug_info );
153
				}
154
				$headers[ 'X-EE4-Debug-' . ucwords( $debug_key ) ] = $debug_info;
155
			}
156
		}
157
		$headers = array_merge(
158
			$headers,
159
			$this->_get_response_headers(),
160
			$this->_get_headers_from_ee_notices()
161
		);
162
163
		$rest_response->set_headers( $headers );
164
		return $rest_response;
165
	}
166
167
	/**
168
	 * Converts the \WP_Error into `WP_REST_Response.
169
	 * Mostly this is just a copy-and-paste from \WP_REST_Server::error_to_response
170
	 * (which is protected)
171
	 * @param \WP_Error $wp_error
172
	 * @return \WP_REST_Response
173
	 */
174
	protected function _create_rest_response_from_wp_error( \WP_Error $wp_error ) {
175
		$error_data = $wp_error->get_error_data();
176
		if ( is_array( $error_data ) && isset( $error_data['status'] ) ) {
177
			$status = $error_data['status'];
178
		} else {
179
			$status = 500;
180
		}
181
182
		$errors = array();
183
		foreach ( (array) $wp_error->errors as $code => $messages ) {
184
			foreach ( (array) $messages as $message ) {
185
				$errors[] = array(
186
					'code'    => $code,
187
					'message' => $message,
188
					'data'    => $wp_error->get_error_data( $code )
189
				);
190
			}
191
		}
192
		$data = isset( $errors[0] ) ? $errors[0] : array();
193
		if ( count( $errors ) > 1 ) {
194
			// Remove the primary error.
195
			array_shift( $errors );
196
			$data['additional_errors'] = $errors;
197
		}
198
		return new \WP_REST_Response( $data, $status );
199
	}
200
201
	/**
202
	 * Array of headers derived from EE success, attention, and error messages
203
	 * @return array
204
	 */
205
	protected function _get_headers_from_ee_notices() {
206
		$headers = array();
207
		$notices = \EE_Error::get_raw_notices();
208
		foreach( $notices as $notice_type => $sub_notices ) {
0 ignored issues
show
Bug introduced by
The expression $notices of type boolean is not traversable.
Loading history...
209
			if( ! is_array( $sub_notices ) ) {
210
				continue;
211
			}
212
			foreach( $sub_notices as $notice_code => $sub_notice ) {
213
				$headers[ 'X-EE4-Notices-' . \EEH_Inflector::humanize( $notice_type ) . '[' . $notice_code . ']' ] = strip_tags( $sub_notice );
214
			}
215
		}
216
		return apply_filters(
217
			'FHEE__EventEspresso\core\libraries\rest_api\controllers\Base___get_headers_from_ee_notices__return',
218
			$headers,
219
			$this->_requested_version,
220
			$notices
221
		);
222
	}
223
224
	/**
225
	 * Finds which version of the API was requested given the route, and returns it.
226
	 * eg in a request to "mysite.com/wp-json/ee/v4.8.29/events/123" this would return
227
	 * "4.8.29"
228
	 * @param string $route
229
	 * @return string
230
	 */
231
	public function get_requested_version( $route = null ) {
232
		$matches = $this->parse_route(
233
			$route,
234
			'~' . \EED_Core_Rest_Api::ee_api_namespace_for_regex . '~',
235
			array( 'version' )
236
			);
237
		if( isset( $matches[ 'version' ] ) ) {
238
			return $matches[ 'version' ];
239
		} else {
240
			return \EED_Core_Rest_Api::latest_rest_api_version();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression \EED_Core_Rest_Api::latest_rest_api_version(); of type integer|string|false adds false to the return on line 240 which is incompatible with the return type documented by EventEspresso\core\libra...::get_requested_version of type string. It seems like you forgot to handle an error condition.
Loading history...
241
		}
242
243
	}
244
245
246
247
	/**
248
	 * Applies the regex to the route, then creates an array using the values of
249
	 * $match_keys as keys (but ignores the full pattern match). Returns the array of matches.
250
	 * For example, if you call
251
	 * parse_route( '/ee/v4.8/events', '~\/ee\/v([^/]*)\/(.*)~', array( 'version', 'model' ) )
252
	 * it will return array( 'version' => '4.8', 'model' => 'events' )
253
	 *
254
	 * @param string $route
255
	 * @param string $regex
256
	 * @param array  $match_keys EXCLUDING matching the entire regex
257
	 * @return array where  $match_keys are the keys (the first value of $match_keys
258
	 * becomes the first key of the return value, etc. Eg passing in $match_keys of
259
	 * array( 'model', 'id' ), will, if the regex is successful, will return
260
	 * array( 'model' => 'foo', 'id' => 'bar' )
261
	 * @throws \EE_Error if it couldn't be parsed
262
	 */
263
	public function parse_route( $route, $regex, $match_keys ) {
264
		$indexed_matches = array();
265
		$success = preg_match( $regex, $route, $matches );
266
		if(
267
			is_array( $matches ) ) {
268
			//skip the overall regex match. Who cares
269
			for( $i = 1; $i <= count( $match_keys ); $i++ ) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
270
				if( ! isset( $matches[ $i ] ) ) {
271
					$success = false;
272
				} else {
273
					$indexed_matches[ $match_keys[ $i - 1 ] ] = $matches[ $i ];
274
				}
275
			}
276
		}
277
		if( ! $success ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $success of type integer|false 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...
278
			throw new \EE_Error(
279
				__( 'We could not parse the URL. Please contact Event Espresso Support', 'event_espresso' ),
280
				'endpoint_parsing_error'
281
			);
282
		}
283
		return $indexed_matches;
284
	}
285
}
286
287
// End of file Base.php