PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.5.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.5.0
2.5.0 2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 All 34 releases
← All changes | vendor/wpfluent/framework/src/WPFluent/Foundation/Application.php +298 -16 1.7.0 → 2.5.0 View file →
@@ -1,25 +1,58 @@
1 1 <?php
2 +/**
3 + * @package WPFluent
4 + * @author Sheikh Heera <[email protected]> (https://heera.it)
5 + * @author Sheikh Heera <[email protected]>
6 + * @author Sheikh Heera <[email protected]>
7 + * @link https://github.com/wpfluent/framework2x
8 + * @license MIT https://opensource.org/licenses/MIT
9 + * @license GPL-2.0-or-later https://www.gnu.org/licenses/gpl-2.0.html
10 + */
2 11
3 12 namespace FluentBooking\Framework\Foundation;
4 13
5 14 use InvalidArgumentException;
6 15 use FluentBooking\Framework\Support\Arr;
16 +use FluentBooking\Framework\Support\Env;
17 +use FluentBooking\Framework\Http\Client;
7 18 use FluentBooking\Framework\Foundation\Config;
8 19 use FluentBooking\Framework\Container\Container;
9 20 use FluentBooking\Framework\Foundation\ComponentBinder;
10 -use FluentBooking\Framework\Foundation\FoundationTrait;
11 -use FluentBooking\Framework\Foundation\AsyncRequestTrait;
12 -use FluentBooking\Framework\Foundation\CronTaskSchedulerTrait;
21 +use FluentBooking\Framework\Foundation\Concerns\FoundationTrait;
13 22
23 +/**
24 + * Application — service container with magic property access via __get.
25 + *
26 + * Properties below are bound in {@see ComponentBinder::bindComponents()} and
27 + * {@see Application::init()}/{@see Application::setAppLevelNamespace()}. Each
28 + * `@property` declaration teaches PHPStan about a runtime container binding so
29 + * `$app->view->render(...)` and similar access can be type-checked.
30 + *
31 + * @property \FluentBooking\Framework\Foundation\Config $config
32 + * @property \FluentBooking\Framework\View\View $view
33 + * @property \FluentBooking\Framework\Cache\Cache $cache
34 + * @property \FluentBooking\Framework\Http\Router $router
35 + * @property \FluentBooking\Framework\Http\Request\Request $request
36 + * @property \FluentBooking\Framework\Http\Response\Response $response
37 + * @property \FluentBooking\Framework\Validator\Validator $validator
38 + * @property \FluentBooking\Framework\Events\Dispatcher $events
39 + * @property \FluentBooking\Framework\Encryption\Encrypter $encrypter
40 + * @property \FluentBooking\Framework\Encryption\Encrypter $crypt
41 + * @property \FluentBooking\Framework\Database\DatabaseManager $db
42 + * @property \FluentBooking\Framework\Http\URL $url
43 + * @property \FluentBooking\Framework\Support\Mail $mail
44 + * @property \FluentBooking\Framework\Support\Pipeline $pipeline
45 + * @property string $__pluginfile__
46 + * @property string $__namespace__
47 + */
14 48 class Application extends Container
15 49 {
16 50 use FoundationTrait;
17 - use AsyncRequestTrait;
18 - use CronTaskSchedulerTrait;
19 51
20 52 /**
21 53 * Main plugin file's absolute path
54 + *
22 55 * @var string
23 56 */
24 57 protected $file = null;
25 58
@@ -24,8 +57,9 @@
24 57 protected $file = null;
25 58
26 59 /**
27 60 * Plugin's base url
61 + *
28 62 * @var string
29 63 */
30 64 protected $baseUrl = null;
31 65
@@ -30,8 +64,9 @@
30 64 protected $baseUrl = null;
31 65
32 66 /**
33 67 * Plugin's base path
68 + *
34 69 * @var string
35 70 */
36 71 protected $basePath = null;
37 72
@@ -36,8 +71,9 @@
36 71 protected $basePath = null;
37 72
38 73 /**
39 74 * Default namespace for hook's handlers
75 + *
40 76 * @var string
41 77 */
42 78 protected $handlerNamespace = null;
43 79
@@ -42,8 +78,9 @@
42 78 protected $handlerNamespace = null;
43 79
44 80 /**
45 81 * Default namespace for controllers
82 + *
46 83 * @var string
47 84 */
48 85 protected $controllerNamespace = null;
49 86
@@ -48,19 +85,35 @@
48 85 protected $controllerNamespace = null;
49 86
50 87 /**
51 88 * Default namespace for policy handlers
89 + *
52 90 * @var string
53 91 */
54 - protected $permissionNamespace = null;
92 + protected $policyNamespace = null;
55 93
56 94 /**
57 95 * Composer JSON
96 + *
58 97 * @var null|array
59 98 */
60 99 protected static $composer = null;
61 100
62 101 /**
102 + * Ready event handlers
103 + *
104 + * @var array
105 + */
106 + protected $onReady = [];
107 +
108 + /**
109 + * Flag to check if components are bound.
110 + *
111 + * @var boolean
112 + */
113 + protected $componentsBound = false;
114 +
115 + /**
63 116 * Construct the application instance
64 117 *
65 118 * @param string $file The main plugin file's absolute path
66 119 * @return null
@@ -67,10 +120,12 @@
67 120 */
68 121 public function __construct($file = null)
69 122 {
70 123 $this->init($file);
124 + $this->loadEnvironmentVars();
71 125 $this->setAppLevelNamespace();
72 126 $this->bootstrapApplication();
127 + $this->callPluginReadyCallbacks();
73 128 }
74 129
75 130 /**
76 131 * Init the application instance
@@ -86,8 +141,21 @@
86 141 $this->baseUrl = plugin_dir_url($this->file);
87 142 }
88 143
89 144 /**
145 + * Load the environment bariables from .env file.
146 + *
147 + * @param string $path
148 + * @return void
149 + */
150 + protected function loadEnvironmentVars($path = null)
151 + {
152 + $path = $path ?: $this->basePath . '.env';
153 +
154 + is_readable($path) && Env::load($path);
155 + }
156 +
157 + /**
90 158 * Set the default application level namespaces to resolve
91 159 * the controllers, policies and various hook handlers.
92 160 *
93 161 * @return null
@@ -121,9 +189,11 @@
121 189 file_get_contents($this->basePath . 'composer.json'), true
122 190 );
123 191 }
124 192
125 - return $section ? Arr::get(static::$composer, $section) : static::$composer;
193 + return $section ? Arr::get(
194 + static::$composer, $section
195 + ) : static::$composer;
126 196 }
127 197
128 198 /**
129 199 * Bootstrap the application.
@@ -134,12 +204,12 @@
134 204 {
135 205 $this->bindAppInstance();
136 206 $this->bindPathsAndUrls();
137 207 $this->loadConfigIfExists();
208 + $this->registerMiddleware();
138 209 $this->registerTextdomain();
139 210 $this->bindCoreComponents();
140 211 $this->requireCommonFiles($this);
141 - $this->registerAsyncActions();
142 212 $this->addRestApiInitAction($this);
143 213 }
144 214
145 215 /**
@@ -151,8 +221,9 @@
151 221 {
152 222 App::setInstance($this);
153 223 $this->instance('app', $this);
154 224 $this->instance(__CLASS__, $this);
225 + $this->instance('endpoints', []);
155 226 }
156 227
157 228 /**
158 229 * Bind the paths and urls
@@ -200,20 +271,72 @@
200 271 * @return null
201 272 */
202 273 protected function loadConfigIfExists()
203 274 {
204 - $data = [];
275 + $files = [];
205 276
206 277 if (is_dir($this['path.config'])) {
207 278 foreach (glob($this['path.config'] . '*.php') as $file) {
208 - $data[basename($file, '.php')] = require($file);
279 + // middleware.php lives under app/Http/ now; it ships closures
280 + // that don't belong in Config storage.
281 + if (basename($file) === 'middleware.php') {
282 + continue;
283 + }
284 + $files[basename($file, '.php')] = $file;
209 285 }
210 286 }
211 287
212 - $this->instance('config', new Config($data));
288 + $this->instance('config', new Config([], $files));
213 289 }
214 290
215 291 /**
292 + * Register the HTTP middleware stack as a lazy container binding.
293 + *
294 + * Resolves from `app/Http/middleware.php` (canonical). Falls back to
295 + * `config/middleware.php` so un-migrated plugins keep working.
296 + *
297 + * @return void
298 + */
299 + protected function registerMiddleware()
300 + {
301 + $this->singleton('http.middleware', function ($app) {
302 + $new = $app['path.http'] . 'middleware.php';
303 + if (is_file($new)) {
304 + return require $new;
305 + }
306 +
307 + $legacy = $app['path.config'] . 'middleware.php';
308 + if (is_file($legacy)) {
309 + return require $legacy;
310 + }
311 +
312 + return [];
313 + });
314 + }
315 +
316 + /**
317 + * Resolve the given type from the container.
318 + * This method is required for unit testing.
319 + *
320 + * @param string $abstract
321 + * @param array $parameters
322 + * @return mixed
323 + */
324 + public function make($abstract, $parameters = [])
325 + {
326 + if (str_starts_with($abstract, '_NS')) {
327 +
328 + $namespace = $this->getComposer(
329 + 'extra.wpfluent.namespace.current'
330 + );
331 +
332 + $abstract = str_replace('_NS', $namespace, $abstract);
333 + }
334 +
335 + return parent::make($abstract, $parameters);
336 + }
337 +
338 + /**
216 339 * Register plugin's text domain
217 340 *
218 341 * @return null
219 342 */
@@ -220,9 +343,11 @@
220 343 protected function registerTextdomain()
221 344 {
222 345 $this->addAction('init', function() {
223 346 load_plugin_textdomain(
224 - $this->config->get('app.text_domain'), false, $this->textDomainPath()
347 + $this->config->get(
348 + 'app.text_domain'
349 + ), false, $this->textDomainPath()
225 350 );
226 351 });
227 352 }
228 353
@@ -243,9 +368,12 @@
243 368 * @return null
244 369 */
245 370 protected function bindCoreComponents()
246 371 {
247 - (new ComponentBinder($this))->bindComponents();
372 + if (!$this->componentsBound) {
373 + $this->componentsBound = true;
374 + (new ComponentBinder($this))->bindComponents();
375 + }
248 376 }
249 377
250 378 /**
251 379 * Load (include) the files where hooks are registered.
@@ -255,17 +383,145 @@
255 383 * @return null
256 384 */
257 385 protected function requireCommonFiles($app)
258 386 {
387 + $this->addFilter(
388 + 'rest_pre_serve_request', [$this, 'preServeRequest'], 10, 4
389 + );
390 +
259 391 require_once $this->basePath . 'app/Hooks/actions.php';
260 392 require_once $this->basePath . 'app/Hooks/filters.php';
393 +
394 + if (file_exists($f = $this->basePath . 'app/Hooks/includes.php')) {
395 + require_once $f;
396 + }
397 + }
261 398
262 - if (file_exists($includes = $this->basePath . 'app/Hooks/includes.php')) {
263 - require_once $includes;
399 + /**
400 + * Handler for rest_pre_serve_request filter.
401 + *
402 + * @param bool $served (default: false)
403 + * @param \WP_REST_Response $result
404 + * @param \WP_REST_Request $request
405 + * @param \WP_REST_Server $server
406 + * @return bool (false to intercept, otherwise true)
407 + */
408 + public function preServeRequest($served, $result, $request, $server)
409 + {
410 + if ($result->get_status() === 404) {
411 + $route = $request->get_route();
412 + $slug = $this->config->get('app.slug');
413 +
414 + if ($this->isRequestOfPlugin($route, $slug)) {
415 + if ($this->isRequestForEndpoints($route)) {
416 + status_header(200);
417 + $result->set_status(200);
418 + // @phpstan-ignore-next-line
419 + $result->set_data($this->endpoints);
420 + } else {
421 + $result->set_data(
422 + $this->customizeNotFoundResponse(
423 + $result, $request
424 + )
425 + );
426 + }
427 + }
264 428 }
429 +
430 + return $served;
265 431 }
266 432
267 433 /**
434 + * Determines whether the request is madse by plugin.
435 + *
436 + * @param string $route (Rest route|Full URL)
437 + * @param string $slug (Plugin's slug)
438 + * @return bool
439 + */
440 + public function isRequestOfPlugin($route = '', $slug = '')
441 + {
442 + $slug = $slug ?: $this->config->get('app.slug');
443 +
444 + if (!$route) {
445 + if (get_option('permalink_structure')) {
446 + $route = $this->request->url();
447 + } else {
448 + $route = $this->request->query('rest_route');
449 + }
450 + }
451 +
452 + // For web routing (If web-routing is installed)
453 + if (!$route && !$this->request->isRest()) {
454 + $route = $this->request->url();
455 + }
456 +
457 + $parsedUrl = parse_url($route ?? '');
458 +
459 + $path = str_replace('/wp-json', '', $parsedUrl['path'] ?? '');
460 +
461 + if (is_admin()) {
462 + $page = $this->request->query('page');
463 + if ($slug === $page) {
464 + $path = $page;
465 + }
466 + }
467 +
468 + return str_starts_with(ltrim($path, '/'), $slug);
469 + }
470 +
471 + /**
472 + * Determines if the request is made for endpoints.
473 + *
474 + * @param string $route (Rest route|Full URL)
475 +
476 + * @return bool
477 + */
478 + protected function isRequestForEndpoints($route)
479 + {
480 + return str_ends_with($route, '__endpoints');
481 + }
482 +
483 + /**
484 + * Prepare a custom not found response.
485 + *
486 + * @param \WP_REST_Response $result
487 + * @param \WP_REST_Request $request
488 + *
489 + * @return array
490 + */
491 + public function customizeNotFoundResponse($result, $request)
492 + {
493 + $response = $result->get_data();
494 +
495 + if ($this->env() === 'dev') {
496 + $response['data']['wpfluent'] = [
497 + 'env' => $this->env(),
498 + 'method' => $request->get_method(),
499 + 'request_url' => $this->request->url(),
500 + 'route_params' => $request->get_url_params(),
501 + 'query_params' => $request->get_query_params(),
502 + 'body_params' => $request->get_body_params(),
503 + ];
504 + } else {
505 + $response['data']['wpfluent'] = [
506 + 'env' => $this->env()
507 + ];
508 + }
509 +
510 + return $response;
511 + }
512 +
513 + /**
514 + * Check if running unit test.
515 + *
516 + * @return boolean
517 + */
518 + public function isUnitTesting()
519 + {
520 + return getenv('ENV') === 'testing';
521 + }
522 +
523 + /**
268 524 * Register the rest api init actions and routes
269 525 *
270 526 * @param self $app
271 527 */
@@ -286,12 +542,13 @@
286 542 * Register rest routes.
287 543 *
288 544 * @param \FluentBooking\Framework\Http\Router $router
289 545 *
290 - * @return null
546 + * @return void
291 547 */
292 548 protected function registerRestRoutes($router)
293 549 {
550 + // @phpstan-ignore-next-line
294 551 $router->registerRoutes(
295 552 $this->requireRouteFile($router)
296 553 );
297 554 }
@@ -304,6 +561,31 @@
304 561 */
305 562 protected function requireRouteFile($router)
306 563 {
307 564 require_once $this['path.http'] . 'Routes/routes.php';
565 + }
566 +
567 + /**
568 + * Register plugin ready (booted) callbacks.
569 + *
570 + * @param callable $callback
571 + * @return void
572 + */
573 + public function ready(callable $callback)
574 + {
575 + $this->onReady[] = $callback;
576 + }
577 +
578 + /**
579 + * Execute plugin booted callbacks.
580 + *
581 + * @return void
582 + */
583 + protected function callPluginReadyCallbacks()
584 + {
585 + $this->addAction('init', function() {
586 + while ($callback = array_pop($this->onReady)) {
587 + $callback($this);
588 + }
589 + });
308 590 }
309 591 }