PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.10.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.10.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
fluent-booking / vendor / wpfluent / framework / src / WPFluent / Foundation / Application.php

Application.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 1.10.0, at vendor/wpfluent/framework/src/WPFluent/Foundation/Application.php

514 lines 12.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBooking\Framework\Foundation;
4
5 use InvalidArgumentException;
6 use FluentBooking\Framework\Support\Arr;
7 use FluentBooking\Framework\Support\Env;
8 use FluentBooking\Framework\Http\Client;
9 use FluentBooking\Framework\Foundation\Config;
10 use FluentBooking\Framework\Container\Container;
11 use FluentBooking\Framework\Foundation\ComponentBinder;
12 use FluentBooking\Framework\Foundation\Concerns\FoundationTrait;
13
14 class Application extends Container
15 {
16 use FoundationTrait;
17
18 /**
19 * Main plugin file's absolute path
20 *
21 * @var string
22 */
23 protected $file = null;
24
25 /**
26 * Plugin's base url
27 *
28 * @var string
29 */
30 protected $baseUrl = null;
31
32 /**
33 * Plugin's base path
34 *
35 * @var string
36 */
37 protected $basePath = null;
38
39 /**
40 * Default namespace for hook's handlers
41 *
42 * @var string
43 */
44 protected $handlerNamespace = null;
45
46 /**
47 * Default namespace for controllers
48 *
49 * @var string
50 */
51 protected $controllerNamespace = null;
52
53 /**
54 * Default namespace for policy handlers
55 *
56 * @var string
57 */
58 protected $permissionNamespace = null;
59
60 /**
61 * Composer JSON
62 *
63 * @var null|array
64 */
65 protected static $composer = null;
66
67 /**
68 * Ready event handlers
69 *
70 * @var array
71 */
72 protected $onReady = [];
73
74 /**
75 * Construct the application instance
76 *
77 * @param string $file The main plugin file's absolute path
78 * @return null
79 */
80 public function __construct($file = null)
81 {
82 $this->init($file);
83 $this->loadEnvironmentVars();
84 $this->setAppLevelNamespace();
85 $this->bootstrapApplication();
86 $this->callPluginReadyCallbacks();
87 }
88
89 /**
90 * Init the application instance
91 *
92 * @param string $file The main plugin file's absolute path
93 *
94 * @return null
95 */
96 protected function init($file)
97 {
98 $this['__pluginfile__'] = $this->file = $file;
99 $this->basePath = plugin_dir_path($this->file);
100 $this->baseUrl = plugin_dir_url($this->file);
101 }
102
103 /**
104 * Load the environment bariables from .env file.
105 *
106 * @param string $path
107 * @return void
108 */
109 protected function loadEnvironmentVars($path = null)
110 {
111 $path = $path ?: $this->basePath . '.env';
112
113 is_readable($path) && Env::load($path);
114 }
115
116 /**
117 * Set the default application level namespaces to resolve
118 * the controllers, policies and various hook handlers.
119 *
120 * @return null
121 */
122 protected function setAppLevelNamespace()
123 {
124 $composer = $this->getComposer();
125
126 $psr4 = array_flip($composer['autoload']['psr-4']);
127
128 $this->policyNamespace = $psr4['app/'] . 'Http\Policies';
129
130 $this->handlerNamespace = $psr4['app/'] . 'Hooks\Handlers';
131
132 $this->controllerNamespace = $psr4['app/'] . 'Http\Controllers';
133
134 $this['__namespace__'] = $composer['extra']['wpfluent']['namespace']['current'];
135 }
136
137 /**
138 * Get the composer data as an array
139 *
140 * @param string $section Specific key
141 *
142 * @return array partial or full composer data array
143 */
144 public function getComposer($section = null)
145 {
146 if (is_null(static::$composer)) {
147 static::$composer = json_decode(
148 file_get_contents($this->basePath . 'composer.json'), true
149 );
150 }
151
152 return $section ? Arr::get(
153 static::$composer, $section
154 ) : static::$composer;
155 }
156
157 /**
158 * Bootstrap the application.
159 *
160 * @return null
161 */
162 protected function bootstrapApplication()
163 {
164 $this->bindAppInstance();
165 $this->bindPathsAndUrls();
166 $this->loadConfigIfExists();
167 $this->registerTextdomain();
168 $this->bindCoreComponents();
169 $this->requireCommonFiles($this);
170 $this->addRestApiInitAction($this);
171 }
172
173 /**
174 * Bind application instance in the container.
175 *
176 * @return null
177 */
178 protected function bindAppInstance()
179 {
180 App::setInstance($this);
181 $this->instance('app', $this);
182 $this->instance(__CLASS__, $this);
183 $this->instance('endpoints', []);
184 }
185
186 /**
187 * Bind the paths and urls
188 *
189 * @return null
190 */
191 protected function bindPathsAndUrls()
192 {
193 $this->bindUrls();
194 $this->basePaths();
195 }
196
197 /**
198 * Bind urls
199 *
200 * @return null
201 */
202 protected function bindUrls()
203 {
204 $this['url.assets'] = $this->baseUrl . 'assets/';
205 }
206
207 /**
208 * Bind paths
209 *
210 * @return null
211 */
212 protected function basePaths()
213 {
214 $this['path'] = $this->basePath;
215 $this['path.app'] = $this->basePath . 'app/';
216 $this['path.hooks'] = $this['path.app'] . 'Hooks/';
217 $this['path.http'] = $this['path.app'] . 'Http/';
218 $this['path.controllers'] = $this['path.http'] . 'Controllers/';
219 $this['path.config'] = $this->basePath . 'config/';
220 $this['path.assets'] = $this->basePath . 'assets/';
221 $this['path.resources'] = $this->basePath . 'resources/';
222 $this['path.views'] = $this['path.app'] . 'Views/';
223 }
224
225 /**
226 * Load application's config and set
227 * the data in the Config instance.
228 *
229 * @return null
230 */
231 protected function loadConfigIfExists()
232 {
233 $data = [];
234
235 if (is_dir($this['path.config'])) {
236 foreach (glob($this['path.config'] . '*.php') as $file) {
237 $data[basename($file, '.php')] = require($file);
238 }
239 }
240
241 $this->instance('config', new Config($data));
242 }
243
244 /**
245 * Resolve the given type from the container.
246 * This method is required for unit testing.
247 *
248 * @param string $abstract
249 * @param array $parameters
250 * @return mixed
251 */
252 public function make($abstract, $parameters = [])
253 {
254 if (str_starts_with($abstract, '_NS')) {
255
256 $namespace = $this->getComposer(
257 'extra.wpfluent.namespace.current'
258 );
259
260 $abstract = str_replace('_NS', $namespace, $abstract);
261 }
262
263 return parent::make($abstract, $parameters);
264 }
265
266 /**
267 * Register plugin's text domain
268 *
269 * @return null
270 */
271 protected function registerTextdomain()
272 {
273 $this->addAction('init', function() {
274 load_plugin_textdomain(
275 $this->config->get(
276 'app.text_domain'
277 ), false, $this->textDomainPath()
278 );
279 });
280 }
281
282 /**
283 * Resolve the text domain path.
284 *
285 * @return null
286 */
287 protected function textDomainPath()
288 {
289 return basename($this['path']) . $this->config->get('app.domain_path');
290 }
291
292 /**
293 * Bind the components of the framework into the container so
294 * they'll be available throughout the application life cycle.
295 *
296 * @return null
297 */
298 protected function bindCoreComponents()
299 {
300 (new ComponentBinder($this))->bindComponents();
301 }
302
303 /**
304 * Load (include) the files where hooks are registered.
305 *
306 * @param self $app
307 *
308 * @return null
309 */
310 protected function requireCommonFiles($app)
311 {
312 $this->addFilter(
313 'rest_pre_serve_request', [$this, 'preServeRequest'], 10, 4
314 );
315
316 require_once $this->basePath . 'app/Hooks/actions.php';
317 require_once $this->basePath . 'app/Hooks/filters.php';
318
319 if (file_exists($includes = $this->basePath . 'app/Hooks/includes.php')) {
320 require_once $includes;
321 }
322 }
323
324 /**
325 * Handler for rest_pre_serve_request filter.
326 *
327 * @param bool $served (default: false)
328 * @param \WP_Rest_Response $result
329 * @param \WP_Rest_Request $request
330 * @param \WP_Rest_Server $server
331 * @return bool (false to intercept, otherwise true)
332 */
333 public function preServeRequest($served, $result, $request, $server)
334 {
335 if ($result->get_status() === 404) {
336 $route = $request->get_route();
337 $slug = $this->config->get('app.slug');
338
339 if ($this->isRequestOfPlugin($route, $slug)) {
340 if ($this->isRequestForEndpoints($route)) {
341 status_header(200);
342 $result->set_status(200);
343 $result->set_data($this->endpoints);
344 } else {
345 $result->set_data(
346 $this->customizeNotFoundResponse(
347 $result, $request
348 )
349 );
350 }
351 }
352 }
353
354 return $served;
355 }
356
357 /**
358 * Determines whether the request is madse by plugin.
359 *
360 * @param string $route (Rest route|Full URL)
361 * @param string $slug (Plugin's slug)
362 * @return bool
363 */
364 public function isRequestOfPlugin($route = '', $slug = '')
365 {
366 $slug = $slug ?: $this->config->get('app.slug');
367
368 if (!$route) {
369 if (get_option('permalink_structure')) {
370 $route = $this->request->url();
371 } else {
372 $route = $this->request->query('rest_route');
373 }
374 }
375
376 // For web routing (If web-routing is installed)
377 if (!$route && !$this->request->isRest()) {
378 $route = $this->request->url();
379 }
380
381 $parsedUrl = parse_url($route ?? '');
382
383 $path = str_replace('/wp-json', '', $parsedUrl['path'] ?? '');
384
385 if (is_admin()) {
386 $page = $this->request->query('page');
387 if ($slug === $page) {
388 $path = $page;
389 }
390 }
391
392 return str_starts_with(ltrim($path, '/'), $slug);
393 }
394
395 /**
396 * Determines if the request is made for endpoints.
397 *
398 * @param string $route (Rest route|Full URL)
399
400 * @return bool
401 */
402 protected function isRequestForEndpoints($route)
403 {
404 return str_ends_with($route, '__endpoints');
405 }
406
407 /**
408 * Prepare a custom not found response.
409 *
410 * @param \WP_Rest_Response $result
411 * @param \WP_Rest_Request $request
412 *
413 * @return array
414 */
415 public function customizeNotFoundResponse($result, $request)
416 {
417 $response = $result->get_data();
418
419 if ($this->env() === 'dev') {
420 $response['data']['wpfluent'] = [
421 'env' => $this->env(),
422 'method' => $request->get_method(),
423 'request_url' => $this->request->url(),
424 'route_params' => $request->get_url_params(),
425 'query_params' => $request->get_query_params(),
426 'body_params' => $request->get_body_params(),
427 ];
428 } else {
429 $response['data']['wpfluent'] = [
430 'env' => $this->env()
431 ];
432 }
433
434 return $response;
435 }
436
437 /**
438 * Check if running unit test.
439 *
440 * @return boolean
441 */
442 public function isUnitTesting()
443 {
444 return getenv('ENV') === 'testing';
445 }
446
447 /**
448 * Register the rest api init actions and routes
449 *
450 * @param self $app
451 */
452 protected function addRestApiInitAction($app)
453 {
454 $this->addAction('rest_api_init', function($wpRestServer) use ($app) {
455 try {
456 $this->registerRestRoutes($app->router);
457 } catch (InvalidArgumentException $e) {
458 return $app->response->json([
459 'message' => $e->getMessage()
460 ], $e->getCode() ?: 500);
461 }
462 });
463 }
464
465 /**
466 * Register rest routes.
467 *
468 * @param \FluentBooking\Framework\Http\Router $router
469 *
470 * @return null
471 */
472 protected function registerRestRoutes($router)
473 {
474 $router->registerRoutes(
475 $this->requireRouteFile($router)
476 );
477 }
478
479 /**
480 * Load (include) routes
481 *
482 * @param \FluentBooking\Framework\Http\Router $router
483 * @return null
484 */
485 protected function requireRouteFile($router)
486 {
487 require_once $this['path.http'] . 'Routes/routes.php';
488 }
489
490 /**
491 * Register plugin booted callbacks.
492 *
493 * @param callable $callback
494 * @return void
495 */
496 protected function ready(callable $callback)
497 {
498 $this->onReady[] = $callback;
499 }
500
501 /**
502 * Execute plugin booted callbacks.
503 *
504 * @param callable $callback
505 * @return void
506 */
507 protected function callPluginReadyCallbacks()
508 {
509 while ($callback = array_shift($this->onReady)) {
510 $callback($this);
511 }
512 }
513 }
514