Passed
Push — master ( 3d3460...062c3f )
by Atanas
02:01
created

HttpKernel   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 406
Duplicated Lines 0 %

Test Coverage

Coverage 55.26%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 39
eloc 125
dl 0
loc 406
rs 9.28
c 2
b 0
f 0
ccs 63
cts 114
cp 0.5526

18 Methods

Rating   Name   Duplication   Size   Complexity  
A actionAjax() 0 10 2
A executeHandler() 0 12 2
A handle() 0 27 2
A registerAdminAction() 0 6 1
A actionAdminLoad() 0 9 3
A getAdminHook() 0 16 4
A respond() 0 8 2
A filterTemplateInclude() 0 17 3
A __construct() 0 16 1
A getResponseService() 0 2 1
A getResponse() 0 2 2
A filterRequest() 0 18 4
A makeMiddleware() 0 2 1
A actionAdmin() 0 8 2
A getAdminPageHook() 0 16 3
A bootstrap() 0 12 1
A registerAjaxAction() 0 10 2
A run() 0 21 3
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\RouteInterface;
29
use WPEmerge\Routing\Router;
30
use WPEmerge\Routing\SortsMiddlewareTrait;
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
	 * Error handler.
86
	 *
87
	 * @var ErrorHandlerInterface
88
	 */
89
	protected $error_handler = null;
90
91
	/**
92
	 * Constructor.
93
	 *
94
	 * @codeCoverageIgnore
95
	 * @param Container             $container
96
	 * @param GenericFactory        $factory
97
	 * @param HandlerFactory        $handler_factory
98
	 * @param ResponseService       $response_service
99
	 * @param RequestInterface      $request
100
	 * @param Router                $router
101
	 * @param ErrorHandlerInterface $error_handler
102
	 */
103
	public function __construct(
104
		Container $container,
105
		GenericFactory $factory,
106
		HandlerFactory $handler_factory,
107
		ResponseService $response_service,
108
		RequestInterface $request,
109
		Router $router,
110
		ErrorHandlerInterface $error_handler
111
	) {
112
		$this->container = $container;
113
		$this->factory = $factory;
114
		$this->handler_factory = $handler_factory;
115
		$this->response_service = $response_service;
116
		$this->request = $request;
117
		$this->router = $router;
118
		$this->error_handler = $error_handler;
119
	}
120
121
	/**
122
	 * Get the current response.
123
	 *
124
	 * @codeCoverageIgnore
125
	 * @return ResponseInterface|null
126
	 */
127
	protected function getResponse() {
128
		return isset( $this->container[ WPEMERGE_RESPONSE_KEY ] ) ? $this->container[ WPEMERGE_RESPONSE_KEY ] : null;
129
	}
130
131
	/**
132
	 * Get a Response Service instance.
133
	 *
134
	 * @codeCoverageIgnore
135
	 * @return ResponseService
136
	 */
137
	protected function getResponseService() {
138
		return $this->response_service;
139
	}
140
141
	/**
142
	 * Make a middleware class instance.
143
	 *
144
	 * @codeCoverageIgnore
145
	 * @param  string $class
146
	 * @return object
147
	 */
148
	protected function makeMiddleware( $class ) {
149
		return $this->factory->make( $class );
150
	}
151
152
	/**
153
	 * Execute a handler.
154
	 *
155
	 * @param  Handler           $handler
156
	 * @param  array             $arguments
157
	 * @return ResponseInterface
158
	 */
159 2
	protected function executeHandler( Handler $handler, $arguments = [] ) {
160 2
		$response = call_user_func_array( [$handler, 'execute'], $arguments );
161 2
		$response = $this->toResponse( $response );
162
163 2
		if ( ! $response instanceof ResponseInterface ) {
164 1
			throw new ConfigurationException(
165
				'Response returned by controller is not valid ' .
166 1
				'(expected ' . ResponseInterface::class . '; received ' . gettype( $response ) . ').'
167
			);
168
		}
169
170 1
		return $response;
171
	}
172
173
	/**
174
	 * {@inheritDoc}
175
	 */
176 2
	public function run( RequestInterface $request, $middleware, $handler, $arguments = [] ) {
177 2
		$this->error_handler->register();
178
179
		try {
180 2
			$handler = $handler instanceof Handler ? $handler : $this->handler_factory->make( $handler );
181
182 2
			$middleware = array_merge( $middleware, $this->getHandlerMiddleware( $handler ) );
183 2
			$middleware = $this->expandMiddleware( $middleware );
184 2
			$middleware = $this->uniqueMiddleware( $middleware );
185 2
			$middleware = $this->sortMiddleware( $middleware );
186
187 2
			$response = $this->executeMiddleware( $middleware, $request, function () use ( $handler, $arguments ) {
188 2
				return $this->executeHandler( $handler, $arguments );
189 2
			} );
190 1
		} catch ( Exception $exception ) {
191 1
			$response = $this->error_handler->getResponse( $request, $exception );
192
		}
193
194 1
		$this->error_handler->unregister();
195
196 1
		return $response;
197
	}
198
199
	/**
200
	 * {@inheritDoc}
201
	 */
202 2
	public function handle( RequestInterface $request, $arguments = [] ) {
203 2
		$route = $this->router->execute( $request );
204
205 2
		if ( $route === null ) {
206 1
			return null;
207
		}
208
209 1
		$route_arguments = $route->getArguments( $request );
210
211
		$request = $request
212 1
			->withAttribute( 'route', $route )
213 1
			->withAttribute( 'route_arguments', $route_arguments );
214
215 1
		$response = $this->run(
216 1
			$request,
217 1
			$route->getMiddleware(),
218 1
			$route->getHandler(),
219 1
			array_merge(
220 1
				[$request],
221 1
				$arguments,
222 1
				$route_arguments
223
			)
224
		);
225
226 1
		$this->container[ WPEMERGE_RESPONSE_KEY ] = $response;
227
228 1
		return $response;
229
	}
230
231
	/**
232
	 * Respond with the current response.
233
	 *
234
	 * @return void
235
	 */
236 2
	public function respond() {
237 2
		$response = $this->getResponse();
238
239 2
		if ( ! $response instanceof ResponseInterface ) {
240 1
			return;
241
		}
242
243 1
		$this->response_service->respond( $response );
244 1
	}
245
246
	/**
247
	 * {@inheritDoc}
248
	 * @codeCoverageIgnore
249
	 */
250
	public function bootstrap() {
251
		// Web. Use 3100 so it's high enough and has uncommonly used numbers
252
		// before and after. For example, 1000 is too common and it would have 999 before it
253
		// which is too common as well.).
254
		add_action( 'request', [$this, 'filterRequest'], 3100 );
255
		add_action( 'template_include', [$this, 'filterTemplateInclude'], 3100 );
256
257
		// Ajax.
258
		add_action( 'admin_init', [$this, 'registerAjaxAction'] );
259
260
		// Admin.
261
		add_action( 'admin_init', [$this, 'registerAdminAction'] );
262
	}
263
264
	/**
265
	 * Filter the main query vars.
266
	 *
267
	 * @param  array $query_vars
268
	 * @return array
269
	 */
270 2
	public function filterRequest( $query_vars ) {
271
		/** @var $routes RouteInterface[] */
272 2
		$routes = $this->router->getRoutes();
273
274 2
		foreach ( $routes as $route ) {
275 2
			if ( ! $route instanceof HasQueryFilterInterface ) {
276 2
				continue;
277
			}
278
279 2
			if ( ! $route->isSatisfied( $this->request ) ) {
280 1
				continue;
281
			}
282
283 2
			$query_vars = $route->applyQueryFilter( $this->request, $query_vars );
284 2
			break;
285
		}
286
287 2
		return $query_vars;
288
	}
289
290
	/**
291
	 * Filter the main template file.
292
	 *
293
	 * @param  string $view
294
	 * @return string
295
	 */
296 3
	public function filterTemplateInclude( $view ) {
297
		/** @var $wp_query WP_Query */
298 3
		global $wp_query;
299
300 3
		$response = $this->handle( $this->request, [$view] );
301
302 3
		if ( $response instanceof ResponseInterface ) {
303 2
			if ( $response->getStatusCode() === 404 ) {
304 1
				$wp_query->set_404();
305
			}
306
307 2
			add_action( 'wpemerge.respond', [$this, 'respond'] );
308
309 2
			return WPEMERGE_DIR . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'view.php';
310
		}
311
312 1
		return $view;
313
	}
314
315
	/**
316
	 * Register ajax action to hook into current one.
317
	 *
318
	 * @return void
319
	 */
320
	public function registerAjaxAction() {
321
		if ( ! wp_doing_ajax() ) {
322
			return;
323
		}
324
325
		$action = $this->request->body( 'action', $this->request->query( 'action' ) );
326
		$action = sanitize_text_field( $action );
327
328
		add_action( "wp_ajax_{$action}", [$this, 'actionAjax'] );
329
		add_action( "wp_ajax_nopriv_{$action}", [$this, 'actionAjax'] );
330
	}
331
332
	/**
333
	 * Act on ajax action.
334
	 *
335
	 * @return void
336
	 */
337
	public function actionAjax() {
338
		$response = $this->handle( $this->request, [''] );
339
340
		if ( ! $response instanceof ResponseInterface ) {
341
			return;
342
		}
343
344
		$this->response_service->respond( $response );
345
346
		wp_die( '', '', ['response' => null] );
347
	}
348
349
	/**
350
	 * Get page hook.
351
	 * Slightly modified version of code from wp-admin/admin.php.
352
	 *
353
	 * @return string
354
	 */
355
	protected function getAdminPageHook() {
356
		global $pagenow, $typenow, $plugin_page;
357
358
		$page_hook = '';
359
360
		if ( isset( $plugin_page ) ) {
361
			$the_parent = $pagenow;
362
363
			if ( ! empty( $typenow ) ) {
364
				$the_parent = $pagenow . '?post_type=' . $typenow;
365
			}
366
367
			$page_hook = get_plugin_page_hook( $plugin_page, $the_parent );
368
		}
369
370
		return $page_hook;
371
	}
372
373
	/**
374
	 * Get admin page hook.
375
	 * Slightly modified version of code from wp-admin/admin.php.
376
	 *
377
	 * @param  string $page_hook
378
	 * @return string
379
	 */
380
	protected function getAdminHook( $page_hook ) {
381
		global $pagenow, $plugin_page;
382
383
		if ( ! empty( $page_hook ) ) {
384
			return $page_hook;
385
		}
386
387
		if ( isset( $plugin_page ) ) {
388
			return $plugin_page;
389
		}
390
391
		if ( isset( $pagenow ) ) {
392
			return $pagenow;
393
		}
394
395
		return '';
396
	}
397
398
	/**
399
	 * Register admin action to hook into current one.
400
	 *
401
	 * @return void
402
	 */
403
	public function registerAdminAction() {
404
		$page_hook = $this->getAdminPageHook();
405
		$hook_suffix = $this->getAdminHook( $page_hook );
406
407
		add_action( "load-{$hook_suffix}", [$this, 'actionAdminLoad'] );
408
		add_action( $hook_suffix, [$this, 'actionAdmin'] );
409
	}
410
411
	/**
412
	 * Act on admin action load.
413
	 *
414
	 * @return void
415
	 */
416
	public function actionAdminLoad() {
417
		$response = $this->handle( $this->request, [''] );
418
419
		if ( ! $response instanceof ResponseInterface ) {
420
			return;
421
		}
422
423
		if ( ! headers_sent() ) {
424
			$this->response_service->sendHeaders( $response );
425
		}
426
	}
427
428
	/**
429
	 * Act on admin action.
430
	 *
431
	 * @return void
432
	 */
433
	public function actionAdmin() {
434
		$response = $this->getResponse();
435
436
		if ( ! $response instanceof ResponseInterface ) {
437
			return;
438
		}
439
440
		$this->response_service->sendBody( $response );
441
	}
442
}
443