Passed
Push — master ( c1f03e...423207 )
by Atanas
01:51
created

HttpKernel::makeMiddleware()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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