1 | <?php |
||||
2 | /** |
||||
3 | * @package WPEmerge |
||||
4 | * @author Atanas Angelov <[email protected]> |
||||
5 | * @copyright 2017-2019 Atanas Angelov |
||||
6 | * @license https://www.gnu.org/licenses/gpl-2.0.html GPL-2.0 |
||||
7 | * @link https://wpemerge.com/ |
||||
8 | */ |
||||
9 | |||||
10 | namespace WPEmerge\Kernels; |
||||
11 | |||||
12 | use Exception; |
||||
13 | use Pimple\Container; |
||||
14 | use Psr\Http\Message\ResponseInterface; |
||||
15 | use WP_Query; |
||||
16 | use WPEmerge\Application\GenericFactory; |
||||
17 | use WPEmerge\Exceptions\ConfigurationException; |
||||
18 | use WPEmerge\Exceptions\ErrorHandlerInterface; |
||||
19 | use WPEmerge\Helpers\Handler; |
||||
20 | use WPEmerge\Helpers\HandlerFactory; |
||||
21 | use WPEmerge\Middleware\ExecutesMiddlewareTrait; |
||||
22 | use WPEmerge\Middleware\HasMiddlewareDefinitionsTrait; |
||||
23 | use WPEmerge\Middleware\ReadsHandlerMiddlewareTrait; |
||||
24 | use WPEmerge\Requests\RequestInterface; |
||||
25 | use WPEmerge\Responses\ConvertsToResponseTrait; |
||||
26 | use WPEmerge\Responses\ResponseService; |
||||
27 | use WPEmerge\Routing\HasQueryFilterInterface; |
||||
28 | use WPEmerge\Routing\Router; |
||||
29 | use WPEmerge\Routing\SortsMiddlewareTrait; |
||||
30 | use WPEmerge\View\ViewService; |
||||
31 | |||||
32 | /** |
||||
33 | * Describes how a request is handled. |
||||
34 | */ |
||||
35 | class HttpKernel implements HttpKernelInterface { |
||||
36 | use HasMiddlewareDefinitionsTrait; |
||||
37 | use SortsMiddlewareTrait; |
||||
38 | use ConvertsToResponseTrait; |
||||
39 | use ReadsHandlerMiddlewareTrait; |
||||
40 | use ExecutesMiddlewareTrait; |
||||
41 | |||||
42 | /** |
||||
43 | * Container. |
||||
44 | * |
||||
45 | * @var Container |
||||
46 | */ |
||||
47 | protected $container = null; |
||||
48 | |||||
49 | /** |
||||
50 | * Injection factory. |
||||
51 | * |
||||
52 | * @var GenericFactory |
||||
53 | */ |
||||
54 | protected $factory = null; |
||||
55 | |||||
56 | /** |
||||
57 | * Handler factory. |
||||
58 | * |
||||
59 | * @var HandlerFactory |
||||
60 | */ |
||||
61 | protected $handler_factory = null; |
||||
62 | |||||
63 | /** |
||||
64 | * Response service. |
||||
65 | * |
||||
66 | * @var ResponseService |
||||
67 | */ |
||||
68 | protected $response_service = null; |
||||
69 | |||||
70 | /** |
||||
71 | * Request. |
||||
72 | * |
||||
73 | * @var RequestInterface |
||||
74 | */ |
||||
75 | protected $request = null; |
||||
76 | |||||
77 | /** |
||||
78 | * Router. |
||||
79 | * |
||||
80 | * @var Router |
||||
81 | */ |
||||
82 | protected $router = null; |
||||
83 | |||||
84 | /** |
||||
85 | * View Service. |
||||
86 | * |
||||
87 | * @var ViewService |
||||
88 | */ |
||||
89 | protected $view_service = null; |
||||
90 | |||||
91 | /** |
||||
92 | * Error handler. |
||||
93 | * |
||||
94 | * @var ErrorHandlerInterface |
||||
95 | */ |
||||
96 | protected $error_handler = null; |
||||
97 | |||||
98 | /** |
||||
99 | * Template WordPress attempted to load. |
||||
100 | * |
||||
101 | * @var string |
||||
102 | */ |
||||
103 | protected $template = ''; |
||||
104 | |||||
105 | /** |
||||
106 | * Constructor. |
||||
107 | * |
||||
108 | * @codeCoverageIgnore |
||||
109 | * @param Container $container |
||||
110 | * @param GenericFactory $factory |
||||
111 | * @param HandlerFactory $handler_factory |
||||
112 | * @param ResponseService $response_service |
||||
113 | * @param RequestInterface $request |
||||
114 | * @param Router $router |
||||
115 | * @param ViewService $view_service |
||||
116 | * @param ErrorHandlerInterface $error_handler |
||||
117 | */ |
||||
118 | public function __construct( |
||||
119 | Container $container, |
||||
120 | GenericFactory $factory, |
||||
121 | HandlerFactory $handler_factory, |
||||
122 | ResponseService $response_service, |
||||
123 | RequestInterface $request, |
||||
124 | Router $router, |
||||
125 | ViewService $view_service, |
||||
126 | ErrorHandlerInterface $error_handler |
||||
127 | ) { |
||||
128 | $this->container = $container; |
||||
129 | $this->factory = $factory; |
||||
130 | $this->handler_factory = $handler_factory; |
||||
131 | $this->response_service = $response_service; |
||||
132 | $this->request = $request; |
||||
133 | $this->router = $router; |
||||
134 | $this->view_service = $view_service; |
||||
135 | $this->error_handler = $error_handler; |
||||
136 | } |
||||
137 | |||||
138 | /** |
||||
139 | * Get the current response. |
||||
140 | * |
||||
141 | * @codeCoverageIgnore |
||||
142 | * @return ResponseInterface|null |
||||
143 | */ |
||||
144 | protected function getResponse() { |
||||
145 | return isset( $this->container[ WPEMERGE_RESPONSE_KEY ] ) ? $this->container[ WPEMERGE_RESPONSE_KEY ] : null; |
||||
146 | } |
||||
147 | |||||
148 | /** |
||||
149 | * Get a Response Service instance. |
||||
150 | * |
||||
151 | * @codeCoverageIgnore |
||||
152 | * @return ResponseService |
||||
153 | */ |
||||
154 | protected function getResponseService() { |
||||
155 | return $this->response_service; |
||||
156 | } |
||||
157 | |||||
158 | /** |
||||
159 | * Make a middleware class instance. |
||||
160 | * |
||||
161 | * @codeCoverageIgnore |
||||
162 | * @param string $class |
||||
163 | * @return object |
||||
164 | */ |
||||
165 | protected function makeMiddleware( $class ) { |
||||
166 | return $this->factory->make( $class ); |
||||
167 | } |
||||
168 | |||||
169 | /** |
||||
170 | * Execute a handler. |
||||
171 | * |
||||
172 | * @param Handler $handler |
||||
173 | * @param array $arguments |
||||
174 | * @return ResponseInterface |
||||
175 | */ |
||||
176 | 2 | protected function executeHandler( Handler $handler, $arguments = [] ) { |
|||
177 | 2 | $response = call_user_func_array( [$handler, 'execute'], array_values( $arguments ) ); |
|||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
178 | 2 | $response = $this->toResponse( $response ); |
|||
179 | |||||
180 | 2 | if ( ! $response instanceof ResponseInterface ) { |
|||
181 | 1 | throw new ConfigurationException( |
|||
182 | 'Response returned by controller is not valid ' . |
||||
183 | 1 | '(expected ' . ResponseInterface::class . '; received ' . gettype( $response ) . ').' |
|||
0 ignored issues
–
show
The function
gettype was not found. Maybe you did not declare it correctly or list all dependencies?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
184 | ); |
||||
185 | } |
||||
186 | |||||
187 | 1 | return $response; |
|||
188 | } |
||||
189 | |||||
190 | /** |
||||
191 | * {@inheritDoc} |
||||
192 | */ |
||||
193 | 2 | public function run( RequestInterface $request, $middleware, $handler, $arguments = [] ) { |
|||
194 | 2 | $this->error_handler->register(); |
|||
195 | |||||
196 | try { |
||||
197 | 2 | $handler = $handler instanceof Handler ? $handler : $this->handler_factory->make( $handler ); |
|||
198 | |||||
199 | 2 | $middleware = array_merge( $middleware, $this->getHandlerMiddleware( $handler ) ); |
|||
200 | 2 | $middleware = $this->expandMiddleware( $middleware ); |
|||
201 | 2 | $middleware = $this->uniqueMiddleware( $middleware ); |
|||
202 | 2 | $middleware = $this->sortMiddleware( $middleware ); |
|||
203 | |||||
204 | $response = $this->executeMiddleware( $middleware, $request, function () use ( $handler, $arguments ) { |
||||
205 | 2 | return $this->executeHandler( $handler, $arguments ); |
|||
206 | 2 | } ); |
|||
207 | 1 | } catch ( Exception $exception ) { |
|||
208 | 1 | $response = $this->error_handler->getResponse( $request, $exception ); |
|||
209 | } |
||||
210 | |||||
211 | 1 | $this->error_handler->unregister(); |
|||
212 | |||||
213 | 1 | return $response; |
|||
214 | } |
||||
215 | |||||
216 | /** |
||||
217 | * {@inheritDoc} |
||||
218 | */ |
||||
219 | 2 | public function handle( RequestInterface $request, $arguments = [] ) { |
|||
220 | 2 | $route = $this->router->execute( $request ); |
|||
221 | |||||
222 | 2 | if ( $route === null ) { |
|||
223 | 1 | return null; |
|||
224 | } |
||||
225 | |||||
226 | 1 | $route_arguments = $route->getArguments( $request ); |
|||
227 | |||||
228 | $request = $request |
||||
229 | 1 | ->withAttribute( 'route', $route ) |
|||
230 | 1 | ->withAttribute( 'route_arguments', $route_arguments ); |
|||
231 | |||||
232 | 1 | $response = $this->run( |
|||
233 | 1 | $request, |
|||
234 | 1 | $route->getAttribute( 'middleware', [] ), |
|||
235 | 1 | $route->getAttribute( 'handler' ), |
|||
236 | 1 | array_merge( |
|||
237 | 1 | [$request], |
|||
238 | $arguments, |
||||
239 | $route_arguments |
||||
240 | ) |
||||
241 | ); |
||||
242 | |||||
243 | 1 | $this->container[ WPEMERGE_RESPONSE_KEY ] = $response; |
|||
244 | |||||
245 | 1 | return $response; |
|||
246 | } |
||||
247 | |||||
248 | /** |
||||
249 | * Respond with the current response. |
||||
250 | * |
||||
251 | * @return void |
||||
252 | */ |
||||
253 | 2 | public function respond() { |
|||
254 | 2 | $response = $this->getResponse(); |
|||
255 | |||||
256 | 2 | if ( ! $response instanceof ResponseInterface ) { |
|||
257 | 1 | return; |
|||
258 | } |
||||
259 | |||||
260 | 1 | $this->response_service->respond( $response ); |
|||
261 | 1 | } |
|||
262 | |||||
263 | /** |
||||
264 | * Output the current view outside of the routing flow. |
||||
265 | * |
||||
266 | * @return void |
||||
267 | */ |
||||
268 | 1 | public function compose() { |
|||
269 | 1 | $view = $this->view_service->make( $this->template ); |
|||
270 | |||||
271 | 1 | echo $view->toString(); |
|||
272 | 1 | } |
|||
273 | |||||
274 | /** |
||||
275 | * {@inheritDoc} |
||||
276 | * @codeCoverageIgnore |
||||
277 | */ |
||||
278 | public function bootstrap() { |
||||
279 | // Web. Use 3100 so it's high enough and has uncommonly used numbers |
||||
280 | // before and after. For example, 1000 is too common and it would have 999 before it |
||||
281 | // which is too common as well.). |
||||
282 | add_action( 'request', [$this, 'filterRequest'], 3100 ); |
||||
283 | add_action( 'template_include', [$this, 'filterTemplateInclude'], 3100 ); |
||||
284 | |||||
285 | // Ajax. |
||||
286 | add_action( 'admin_init', [$this, 'registerAjaxAction'] ); |
||||
287 | |||||
288 | // Admin. |
||||
289 | add_action( 'admin_init', [$this, 'registerAdminAction'] ); |
||||
290 | } |
||||
291 | |||||
292 | /** |
||||
293 | * Filter the main query vars. |
||||
294 | * |
||||
295 | * @param array $query_vars |
||||
296 | * @return array |
||||
297 | */ |
||||
298 | 2 | public function filterRequest( $query_vars ) { |
|||
299 | 2 | $routes = $this->router->getRoutes(); |
|||
300 | |||||
301 | 2 | foreach ( $routes as $route ) { |
|||
302 | 2 | if ( ! $route instanceof HasQueryFilterInterface ) { |
|||
303 | 2 | continue; |
|||
304 | } |
||||
305 | |||||
306 | 2 | if ( ! $route->isSatisfied( $this->request ) ) { |
|||
307 | 1 | continue; |
|||
308 | } |
||||
309 | |||||
310 | 2 | $this->container[ WPEMERGE_APPLICATION_KEY ] |
|||
311 | ->renderConfigExceptions( function () use ( $route, &$query_vars ) { |
||||
312 | 2 | $query_vars = $route->applyQueryFilter( $this->request, $query_vars ); |
|||
313 | 2 | } ); |
|||
314 | 2 | break; |
|||
315 | } |
||||
316 | |||||
317 | 2 | return $query_vars; |
|||
318 | } |
||||
319 | |||||
320 | /** |
||||
321 | * Filter the main template file. |
||||
322 | * |
||||
323 | * @param string $template |
||||
324 | * @return string |
||||
325 | */ |
||||
326 | 4 | public function filterTemplateInclude( $template ) { |
|||
327 | /** @var WP_Query $wp_query */ |
||||
328 | 4 | global $wp_query; |
|||
329 | |||||
330 | 4 | $this->template = $template; |
|||
331 | |||||
332 | 4 | $response = $this->handle( $this->request, [$template] ); |
|||
333 | |||||
334 | // A route has matched so we use its response. |
||||
335 | 4 | if ( $response instanceof ResponseInterface ) { |
|||
336 | 2 | if ( $response->getStatusCode() === 404 ) { |
|||
337 | 1 | $wp_query->set_404(); |
|||
338 | } |
||||
339 | |||||
340 | 2 | add_action( 'wpemerge.kernels.http_kernel.respond', [$this, 'respond'] ); |
|||
341 | |||||
342 | 2 | return WPEMERGE_DIR . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'view.php'; |
|||
343 | } |
||||
344 | |||||
345 | // No route has matched, but we still want to compose views. |
||||
346 | 2 | $composers = $this->view_service->getComposersForView( $template ); |
|||
347 | |||||
348 | 2 | if ( ! empty( $composers ) ) { |
|||
349 | 1 | add_action( 'wpemerge.kernels.http_kernel.respond', [$this, 'compose'] ); |
|||
350 | |||||
351 | 1 | return WPEMERGE_DIR . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'view.php'; |
|||
352 | } |
||||
353 | |||||
354 | 1 | return $template; |
|||
355 | } |
||||
356 | |||||
357 | /** |
||||
358 | * Register ajax action to hook into current one. |
||||
359 | * |
||||
360 | * @return void |
||||
361 | */ |
||||
362 | public function registerAjaxAction() { |
||||
363 | if ( ! wp_doing_ajax() ) { |
||||
364 | return; |
||||
365 | } |
||||
366 | |||||
367 | $action = $this->request->body( 'action', $this->request->query( 'action' ) ); |
||||
368 | $action = sanitize_text_field( $action ); |
||||
369 | |||||
370 | add_action( "wp_ajax_{$action}", [$this, 'actionAjax'] ); |
||||
371 | add_action( "wp_ajax_nopriv_{$action}", [$this, 'actionAjax'] ); |
||||
372 | } |
||||
373 | |||||
374 | /** |
||||
375 | * Act on ajax action. |
||||
376 | * |
||||
377 | * @return void |
||||
378 | */ |
||||
379 | public function actionAjax() { |
||||
380 | $response = $this->handle( $this->request, [''] ); |
||||
381 | |||||
382 | if ( ! $response instanceof ResponseInterface ) { |
||||
383 | return; |
||||
384 | } |
||||
385 | |||||
386 | $this->response_service->respond( $response ); |
||||
387 | |||||
388 | wp_die( '', '', ['response' => null] ); |
||||
389 | } |
||||
390 | |||||
391 | /** |
||||
392 | * Get page hook. |
||||
393 | * Slightly modified version of code from wp-admin/admin.php. |
||||
394 | * |
||||
395 | * @return string |
||||
396 | */ |
||||
397 | protected function getAdminPageHook() { |
||||
398 | global $pagenow, $typenow, $plugin_page; |
||||
399 | |||||
400 | $page_hook = ''; |
||||
401 | |||||
402 | if ( isset( $plugin_page ) ) { |
||||
403 | $the_parent = $pagenow; |
||||
404 | |||||
405 | if ( ! empty( $typenow ) ) { |
||||
406 | $the_parent = $pagenow . '?post_type=' . $typenow; |
||||
407 | } |
||||
408 | |||||
409 | $page_hook = get_plugin_page_hook( $plugin_page, $the_parent ); |
||||
410 | } |
||||
411 | |||||
412 | return $page_hook; |
||||
413 | } |
||||
414 | |||||
415 | /** |
||||
416 | * Get admin page hook. |
||||
417 | * Slightly modified version of code from wp-admin/admin.php. |
||||
418 | * |
||||
419 | * @param string $page_hook |
||||
420 | * @return string |
||||
421 | */ |
||||
422 | protected function getAdminHook( $page_hook ) { |
||||
423 | global $pagenow, $plugin_page; |
||||
424 | |||||
425 | if ( ! empty( $page_hook ) ) { |
||||
426 | return $page_hook; |
||||
427 | } |
||||
428 | |||||
429 | if ( isset( $plugin_page ) ) { |
||||
430 | return $plugin_page; |
||||
431 | } |
||||
432 | |||||
433 | if ( isset( $pagenow ) ) { |
||||
434 | return $pagenow; |
||||
435 | } |
||||
436 | |||||
437 | return ''; |
||||
438 | } |
||||
439 | |||||
440 | /** |
||||
441 | * Register admin action to hook into current one. |
||||
442 | * |
||||
443 | * @return void |
||||
444 | */ |
||||
445 | public function registerAdminAction() { |
||||
446 | $page_hook = $this->getAdminPageHook(); |
||||
447 | $hook_suffix = $this->getAdminHook( $page_hook ); |
||||
448 | |||||
449 | add_action( "load-{$hook_suffix}", [$this, 'actionAdminLoad'] ); |
||||
450 | add_action( $hook_suffix, [$this, 'actionAdmin'] ); |
||||
451 | } |
||||
452 | |||||
453 | /** |
||||
454 | * Act on admin action load. |
||||
455 | * |
||||
456 | * @return void |
||||
457 | */ |
||||
458 | public function actionAdminLoad() { |
||||
459 | $response = $this->handle( $this->request, [''] ); |
||||
460 | |||||
461 | if ( ! $response instanceof ResponseInterface ) { |
||||
462 | return; |
||||
463 | } |
||||
464 | |||||
465 | if ( ! headers_sent() ) { |
||||
0 ignored issues
–
show
The function
headers_sent was not found. Maybe you did not declare it correctly or list all dependencies?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
466 | $this->response_service->sendHeaders( $response ); |
||||
467 | } |
||||
468 | } |
||||
469 | |||||
470 | /** |
||||
471 | * Act on admin action. |
||||
472 | * |
||||
473 | * @return void |
||||
474 | */ |
||||
475 | public function actionAdmin() { |
||||
476 | $response = $this->getResponse(); |
||||
477 | |||||
478 | if ( ! $response instanceof ResponseInterface ) { |
||||
479 | return; |
||||
480 | } |
||||
481 | |||||
482 | $this->response_service->sendBody( $response ); |
||||
483 | } |
||||
484 | } |
||||
485 |