PluginProbe
Metricool – Social media and site statistics / 2.1.1
Metricool – Social media and site statistics v2.1.1
2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.27 trunk
← All changes | app/Managers/EndpointManager.php +52 -18 2.0.2 → 2.1.1 View file →
@@ -4,8 +4,9 @@
4 4
5 5 namespace Metricool\Managers;
6 6
7 7 use Metricool\Bootstrap\App;
8 +use Metricool\Exceptions\RestDataException;
8 9 use Metricool\Interfaces\MiddlewareInterface;
9 10 use Metricool\Interfaces\MultiEndpointInterface;
10 11 use Metricool\Interfaces\SingleEndpointInterface;
11 12 use Metricool\Support\Helpers\Storages\EnvironmentConfig;
@@ -11,13 +12,16 @@
11 12 use Metricool\Support\Helpers\Storages\EnvironmentConfig;
12 13 use Metricool\Support\Helpers\Storages\MiddlewareConfig;
13 14 use Metricool\Traits\HasAllowlistControl;
14 15 use Metricool\Traits\HasNonces;
16 +use Metricool\Traits\HasRestAccess;
17 +use Throwable;
15 18
16 19 final class EndpointManager extends AbstractManager
17 20 {
18 21 use HasNonces;
19 22 use HasAllowlistControl;
23 + use HasRestAccess;
20 24
21 25 private EnvironmentConfig $env;
22 26
23 27 private array $routes = [];
@@ -105,9 +109,9 @@
105 109 * 'methods' => \WP_REST_Server::READABLE,
106 110 * 'callback' => [$this, 'callback'],
107 111 * 'permission_callback' => [$this, 'permissionCallback'],
108 112 * 'middleware' => [
109 - * 'user_can:administrator', // alias in config/middleware.php
113 + * 'metricool:auth', // alias in config/middleware.php
110 114 * ExampleMiddleware::class, // MiddlewareInterface class
111 115 * ],
112 116 * 'apply_default_middleware' => true, // optional, default is true
113 117 * 'version' => 'v1', // optional, default is the value config/env.php
@@ -125,9 +129,9 @@
125 129
126 130 foreach ($routes as $route => $data) {
127 131 $methods = ($data['methods'] ?? 'GET');
128 132 $callback = ($data['callback'] ?? null);
129 - $permissionCallback = ($data['permission_callback'] ?? '__return_true');
133 + $permissionCallback = ($data['permission_callback'] ?? $this->defaultPermissionCallback());
130 134 $middleware = ($data['middleware'] ?? []);
131 135 $applyDefaultMiddleware = ($data['apply_default_middleware'] ?? true);
132 136 $version = ($data['version'] ?? $this->env->getString('http.version'));
133 137 $args = ($data['args'] ?? null);
@@ -143,9 +147,9 @@
143 147 }
144 148
145 149 $arguments = [
146 150 'methods' => $this->normalizeMethods($methods),
147 - 'callback' => $this->callbackMiddleware($callback, $middleware),
151 + 'callback' => $this->applyMiddleware($callback, $middleware),
148 152 'permission_callback' => $permissionCallback,
149 153 ];
150 154
151 155 if ($args !== null) {
@@ -156,8 +160,19 @@
156 160 }
157 161 }
158 162
159 163 /**
164 + * The default permission callback applied to routes that do not define
165 + * their own. Default is ```metricool_manage``` capability check.
166 + */
167 + private function defaultPermissionCallback(): callable
168 + {
169 + return function (): bool {
170 + return $this->userCanManage();
171 + };
172 + }
173 +
174 + /**
160 175 * Get the plugins REST routes
161 176 * @uses apply_filters metricool_rest_routes
162 177 */
163 178 private function getPluginRestRoutes(): array
@@ -180,30 +195,49 @@
180 195 return apply_filters('metricool_rest_routes', $this->routes);
181 196 }
182 197
183 198 /**
184 - * Wrap the endpoint callback with the default middleware and any named middleware from the pipeline.
185 - * Each entry can be either a registered alias (e.g. 'auth:metricool') or a fully qualified class name (e.g. MetricoolAuthenticated::class).
199 + * Wrap the endpoint callback with middleware. Provided middleware can either be an alias ```auth:metricool``` or
200 + * a FQCN```MetricoolAuthenticated::class```
201 + *
202 + * @param callable $callback The endpoint's callback
203 + * @param array $middlewares The middleware to apply
204 + * @return callable The wrapped callback
205 + *
186 206 * @throws \ReflectionException
187 207 */
188 - public function callbackMiddleware(callable $callback, array $middlewares = []): callable
208 + public function applyMiddleware(callable $callback, array $middlewares = []): callable
189 209 {
190 - $middlewareInstances = $this->resolveMiddleware($middlewares);
210 + $instances = $this->resolveMiddleware($middlewares);
211 + $pipeline = $this->buildPipeline($callback, $instances);
191 212
192 - return static function (\WP_REST_Request $request) use ($callback, $middlewareInstances) {
213 + return function (\WP_REST_Request $request) use ($pipeline) {
193 214 try {
194 - foreach ($middlewareInstances as $middleware) {
195 - $response = $middleware->handle($request);
196 - if ($response instanceof \WP_REST_Response) {
197 - return $response;
198 - }
199 - }
200 - } catch (\Exception $e) {
201 - return new \WP_REST_Response(['message' => $e->getMessage()], 500);
215 + return $pipeline($request);
216 + } catch (RestDataException $e) {
217 + return $this->sendHttpErrorResponse($e->getMessage(), $e->getData(), $e->getResponseCode());
218 + } catch (Throwable $e) {
219 + return $this->sendHttpErrorResponse($e->getMessage(), null, $e->getCode() ?: 500);
202 220 }
221 + };
222 + }
203 223
204 - return $callback($request);
205 - };
224 + /**
225 + * Return a pipeline of middleware into a single callback.
226 + */
227 + private function buildPipeline(callable $callback, array $middleware): callable
228 + {
229 + return array_reduce(
230 + array_reverse($middleware),
231 + static function (callable $next, MiddlewareInterface $middleware): callable {
232 + return static function (\WP_REST_Request $request) use ($middleware, $next) {
233 + return $middleware->handle($request, $next);
234 + };
235 + },
236 + static function (\WP_REST_Request $request) use ($callback) {
237 + return $callback($request);
238 + }
239 + );
206 240 }
207 241
208 242 /**
209 243 * Resolve middleware entries to MiddlewareInterface instances.