Completed
Branch BUG-10738-inconsistency-in-ses... (cda363)
by
unknown
13:38 queued 12s
created

Base::send_response()   D

Complexity

Conditions 9
Paths 24

Size

Total Lines 32
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 23
nc 24
nop 1
dl 0
loc 32
rs 4.909
c 0
b 0
f 0
1
<?php
2
namespace EventEspresso\core\libraries\rest_api\controllers;
3
4
use Exception;
5
use WP_Error;
6
use WP_REST_Response;
7
use EventEspresso\core\libraries\rest_api\RestException;
8
9
use EE_Error;
10
use EED_Core_Rest_Api;
11
use EEH_Inflector;
12
13
if (! defined('EVENT_ESPRESSO_VERSION')) {
14
    exit('No direct script access allowed');
15
}
16
17
18
19
/**
20
 * Base
21
 * Base controller for EE REST API
22
 *
23
 * @package               Event Espresso
24
 * @subpackage
25
 * @author                Mike Nelson
26
 */
27
class Base
28
{
29
30
    /**
31
     * @deprecated use all-caps version
32
     */
33
    // @codingStandardsIgnoreStart
34
    const header_prefix_for_ee = 'X-EE-';
35
    // @codingStandardsIgnoreEnd
36
37
    const HEADER_PREFIX_FOR_EE = 'X-EE-';
38
39
    /**
40
     * @deprecated use all-caps version instead
41
     */
42
    // @codingStandardsIgnoreStart
43
    const header_prefix_for_wp = 'X-WP-';
44
    // @codingStandardsIgnoreEnd
45
46
    const HEADER_PREFIX_FOR_WP = 'X-WP-';
47
48
    /**
49
     * Contains debug info we'll send back in the response headers
50
     *
51
     * @var array
52
     */
53
    protected $debug_info = array();
54
55
    /**
56
     * Indicates whether or not the API is in debug mode
57
     *
58
     * @var boolean
59
     */
60
    protected $debug_mode = false;
61
62
    /**
63
     * Indicates the version that was requested
64
     *
65
     * @var string
66
     */
67
    protected $requested_version;
68
69
    /**
70
     * flat array of headers to send in the response
71
     *
72
     * @var array
73
     */
74
    protected $response_headers = array();
75
76
77
78
    public function __construct()
79
    {
80
        $this->debug_mode = defined('EE_REST_API_DEBUG_MODE') ? EE_REST_API_DEBUG_MODE : false;
81
        //we are handling a REST request. Don't show a fancy HTML error message is any error comes up
82
        add_filter('FHEE__EE_Error__get_error__show_normal_exceptions', '__return_true');
83
    }
84
85
86
87
    /**
88
     * Sets the version the user requested
89
     *
90
     * @param string $version eg '4.8'
91
     */
92
    public function setRequestedVersion($version)
93
    {
94
        $this->requested_version = $version;
95
    }
96
97
98
99
    /**
100
     * Sets some debug info that we'll send back in headers
101
     *
102
     * @param string       $key
103
     * @param string|array $info
104
     */
105
    protected function setDebugInfo($key, $info)
106
    {
107
        $this->debug_info[$key] = $info;
108
    }
109
110
111
112
    /**
113
     * Sets headers for the response
114
     *
115
     * @param string       $header_key    , excluding the "X-EE-" part
116
     * @param array|string $value         if an array, multiple headers will be added, one
117
     *                                    for each key in the array
118
     * @param boolean      $use_ee_prefix whether to use the EE prefix on the header, or fallback to
119
     *                                    the standard WP one
120
     */
121
    protected function setResponseHeader($header_key, $value, $use_ee_prefix = true)
122
    {
123
        if (is_array($value)) {
124
            foreach ($value as $value_key => $value_value) {
125
                $this->setResponseHeader($header_key . '[' . $value_key . ']', $value_value);
126
            }
127
        } else {
128
            $prefix = $use_ee_prefix ? Base::HEADER_PREFIX_FOR_EE : Base::HEADER_PREFIX_FOR_WP;
129
            $this->response_headers[$prefix . $header_key] = $value;
130
        }
131
    }
132
133
134
135
    /**
136
     * Returns a flat array of headers to be added to the response
137
     *
138
     * @return array
139
     */
140
    protected function getResponseHeaders()
141
    {
142
        return apply_filters(
143
            'FHEE__EventEspresso\core\libraries\rest_api\controllers\Base___get_response_headers',
144
            $this->response_headers,
145
            $this,
146
            $this->requested_version
147
        );
148
    }
149
150
151
152
    /**
153
     * Adds error notices from EE_Error onto the provided \WP_Error
154
     *
155
     * @param WP_Error $wp_error_response
156
     * @return WP_Error
157
     */
158
    protected function addEeErrorsToResponse(WP_Error $wp_error_response)
159
    {
160
        $notices_during_checkin = EE_Error::get_raw_notices();
161
        if (! empty($notices_during_checkin['errors'])) {
162
            foreach ($notices_during_checkin['errors'] as $error_code => $error_message) {
163
                $wp_error_response->add(
164
                    sanitize_key($error_code),
165
                    strip_tags($error_message)
166
                );
167
            }
168
        }
169
        return $wp_error_response;
170
    }
171
172
173
174
    /**
175
     * Sends a response, but also makes sure to attach headers that
176
     * are handy for debugging.
177
     * Specifically, we assume folks will want to know what exactly was the DB query that got run,
178
     * what exactly was the Models query that got run, what capabilities came into play, what fields were omitted from
179
     * the response, others?
180
     *
181
     * @param array|WP_Error|Exception|RestException $response
182
     * @return WP_REST_Response
183
     */
184
    public function sendResponse($response)
185
    {
186
        if ($response instanceof RestException) {
187
            $response = new WP_Error($response->getStringCode(), $response->getMessage(), $response->getData());
188
        }
189
        if ($response instanceof Exception) {
190
            $code = $response->getCode() ? $response->getCode() : 'error_occurred';
191
            $response = new WP_Error($code, $response->getMessage());
192
        }
193
        if ($response instanceof WP_Error) {
0 ignored issues
show
Bug introduced by
The class WP_Error does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
194
            $response = $this->addEeErrorsToResponse($response);
195
            $rest_response = $this->createRestResponseFromWpError($response);
196
        } else {
197
            $rest_response = new WP_REST_Response($response, 200);
198
        }
199
        $headers = array();
200
        if ($this->debug_mode && is_array($this->debug_info)) {
201
            foreach ($this->debug_info as $debug_key => $debug_info) {
202
                if (is_array($debug_info)) {
203
                    $debug_info = wp_json_encode($debug_info);
204
                }
205
                $headers['X-EE4-Debug-' . ucwords($debug_key)] = $debug_info;
206
            }
207
        }
208
        $headers = array_merge(
209
            $headers,
210
            $this->getResponseHeaders(),
211
            $this->getHeadersFromEeNotices()
212
        );
213
        $rest_response->set_headers($headers);
214
        return $rest_response;
215
    }
216
217
218
219
    /**
220
     * Converts the \WP_Error into `WP_REST_Response.
221
     * Mostly this is just a copy-and-paste from \WP_REST_Server::error_to_response
222
     * (which is protected)
223
     *
224
     * @param WP_Error $wp_error
225
     * @return WP_REST_Response
226
     */
227
    protected function createRestResponseFromWpError(WP_Error $wp_error)
228
    {
229
        $error_data = $wp_error->get_error_data();
230
        if (is_array($error_data) && isset($error_data['status'])) {
231
            $status = $error_data['status'];
232
        } else {
233
            $status = 500;
234
        }
235
        $errors = array();
236
        foreach ((array)$wp_error->errors as $code => $messages) {
237
            foreach ((array)$messages as $message) {
238
                $errors[] = array(
239
                    'code'    => $code,
240
                    'message' => $message,
241
                    'data'    => $wp_error->get_error_data($code),
242
                );
243
            }
244
        }
245
        $data = isset($errors[0]) ? $errors[0] : array();
246
        if (count($errors) > 1) {
247
            // Remove the primary error.
248
            array_shift($errors);
249
            $data['additional_errors'] = $errors;
250
        }
251
        return new WP_REST_Response($data, $status);
252
    }
253
254
255
256
    /**
257
     * Array of headers derived from EE success, attention, and error messages
258
     *
259
     * @return array
260
     */
261
    protected function getHeadersFromEeNotices()
262
    {
263
        $headers = array();
264
        $notices = EE_Error::get_raw_notices();
265
        foreach ($notices as $notice_type => $sub_notices) {
266
            if (! is_array($sub_notices)) {
267
                continue;
268
            }
269
            foreach ($sub_notices as $notice_code => $sub_notice) {
270
                $headers['X-EE4-Notices-'
271
                         . EEH_Inflector::humanize($notice_type)
272
                         . '['
273
                         . $notice_code
274
                         . ']'] = strip_tags($sub_notice);
275
            }
276
        }
277
        return apply_filters(
278
            'FHEE__EventEspresso\core\libraries\rest_api\controllers\Base___get_headers_from_ee_notices__return',
279
            $headers,
280
            $this->requested_version,
281
            $notices
282
        );
283
    }
284
285
286
287
    /**
288
     * Finds which version of the API was requested given the route, and returns it.
289
     * eg in a request to "mysite.com/wp-json/ee/v4.8.29/events/123" this would return
290
     * "4.8.29".
291
     * We should know hte requested version in this model though, so if no route is
292
     * provided just use what we set earlier
293
     *
294
     * @param string $route
295
     * @return string
296
     */
297
    public function getRequestedVersion($route = null)
298
    {
299
        if ($route === null) {
300
            return $this->requested_version;
301
        }
302
        $matches = $this->parseRoute(
303
            $route,
304
            '~' . EED_Core_Rest_Api::ee_api_namespace_for_regex . '~',
305
            array('version')
306
        );
307
        if (isset($matches['version'])) {
308
            return $matches['version'];
309
        } else {
310
            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 310 which is incompatible with the return type documented by EventEspresso\core\libra...se::getRequestedVersion of type string. It seems like you forgot to handle an error condition.
Loading history...
311
        }
312
    }
313
314
315
316
    /**
317
     * Applies the regex to the route, then creates an array using the values of
318
     * $match_keys as keys (but ignores the full pattern match). Returns the array of matches.
319
     * For example, if you call
320
     * parse_route( '/ee/v4.8/events', '~\/ee\/v([^/]*)\/(.*)~', array( 'version', 'model' ) )
321
     * it will return array( 'version' => '4.8', 'model' => 'events' )
322
     *
323
     * @param string $route
324
     * @param string $regex
325
     * @param array  $match_keys EXCLUDING matching the entire regex
326
     * @return array where  $match_keys are the keys (the first value of $match_keys
327
     *                           becomes the first key of the return value, etc. Eg passing in $match_keys of
328
     *                           array( 'model', 'id' ), will, if the regex is successful, will return
329
     *                           array( 'model' => 'foo', 'id' => 'bar' )
330
     * @throws EE_Error if it couldn't be parsed
331
     */
332
    public function parseRoute($route, $regex, $match_keys)
333
    {
334
        $indexed_matches = array();
335
        $success = preg_match($regex, $route, $matches);
336
        if (is_array($matches)) {
337
            //skip the overall regex match. Who cares
338
            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...
339
                if (! isset($matches[$i])) {
340
                    $success = false;
341
                } else {
342
                    $indexed_matches[$match_keys[$i - 1]] = $matches[$i];
343
                }
344
            }
345
        }
346
        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...
347
            throw new EE_Error(
348
                __('We could not parse the URL. Please contact Event Espresso Support', 'event_espresso'),
349
                'endpoint_parsing_error'
350
            );
351
        }
352
        return $indexed_matches;
353
    }
354
355
356
357
    /**
358
     * Gets the body's params (either from JSON or parsed body), which EXCLUDES the GET params and URL params
359
     *
360
     * @param \WP_REST_Request $request
361
     * @return array
362
     */
363
    protected function getBodyParams(\WP_REST_Request $request)
364
    {
365
        //$request->get_params();
366
        return array_merge(
367
            (array)$request->get_body_params(),
368
            (array)$request->get_json_params()
369
        );
370
        // return array_diff_key(
371
        //    $request->get_params(),
372
        //     $request->get_url_params(),
373
        //     $request->get_query_params()
374
        // );
375
    }
376
}
377
378
// End of file Base.php
379