PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.1.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.1.0
2.1.0 2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 1.41 All 42 releases
← All changes | vendor/wpfluent/framework/src/WPFluent/Foundation/Application.php +264 -13 1.212.1.0 View file →
@@ -1,25 +1,58 @@
1 1 <?php
2 +/**
3 + * @package WPFluent
4 + * @author Sheikh Heera <heera.sheikh77@gmail.com> (https://heera.it)
5 + * @author Sheikh Heera <mail@heera.it>
6 + * @author Sheikh Heera <heera@authlab.io>
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 FluentBoards\Framework\Foundation;
4 13
5 14 use InvalidArgumentException;
6 15 use FluentBoards\Framework\Support\Arr;
16 +use FluentBoards\Framework\Support\Env;
17 +use FluentBoards\Framework\Http\Client;
7 18 use FluentBoards\Framework\Foundation\Config;
8 19 use FluentBoards\Framework\Container\Container;
9 20 use FluentBoards\Framework\Foundation\ComponentBinder;
10 -use FluentBoards\Framework\Foundation\FoundationTrait;
11 -use FluentBoards\Framework\Foundation\AsyncRequestTrait;
12 -use FluentBoards\Framework\Foundation\CronTaskSchedulerTrait;
21 +use FluentBoards\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 \FluentBoards\Framework\Foundation\Config $config
32 + * @property \FluentBoards\Framework\View\View $view
33 + * @property \FluentBoards\Framework\Cache\Cache $cache
34 + * @property \FluentBoards\Framework\Http\Router\Router $router
35 + * @property \FluentBoards\Framework\Http\Request\Request $request
36 + * @property \FluentBoards\Framework\Http\Response\Response $response
37 + * @property \FluentBoards\Framework\Validator\Validator $validator
38 + * @property \FluentBoards\Framework\Events\Dispatcher $events
39 + * @property \FluentBoards\Framework\Encryption\Encrypter $encrypter
40 + * @property \FluentBoards\Framework\Encryption\Encrypter $crypt
41 + * @property \FluentBoards\Framework\Database\DatabaseManager $db
42 + * @property \FluentBoards\Framework\Http\URL $url
43 + * @property \FluentBoards\Framework\Support\Mail $mail
44 + * @property \FluentBoards\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.
@@ -137,9 +207,8 @@
137 207 $this->loadConfigIfExists();
138 208 $this->registerTextdomain();
139 209 $this->bindCoreComponents();
140 210 $this->requireCommonFiles($this);
141 - $this->registerAsyncActions();
142 211 $this->addRestApiInitAction($this);
143 212 }
144 213
145 214 /**
@@ -151,8 +220,9 @@
151 220 {
152 221 App::setInstance($this);
153 222 $this->instance('app', $this);
154 223 $this->instance(__CLASS__, $this);
224 + $this->instance('endpoints', []);
155 225 }
156 226
157 227 /**
158 228 * Bind the paths and urls
@@ -212,8 +282,30 @@
212 282 $this->instance('config', new Config($data));
213 283 }
214 284
215 285 /**
286 + * Resolve the given type from the container.
287 + * This method is required for unit testing.
288 + *
289 + * @param string $abstract
290 + * @param array $parameters
291 + * @return mixed
292 + */
293 + public function make($abstract, $parameters = [])
294 + {
295 + if (str_starts_with($abstract, '_NS')) {
296 +
297 + $namespace = $this->getComposer(
298 + 'extra.wpfluent.namespace.current'
299 + );
300 +
301 + $abstract = str_replace('_NS', $namespace, $abstract);
302 + }
303 +
304 + return parent::make($abstract, $parameters);
305 + }
306 +
307 + /**
216 308 * Register plugin's text domain
217 309 *
218 310 * @return null
219 311 */
@@ -220,9 +312,11 @@
220 312 protected function registerTextdomain()
221 313 {
222 314 $this->addAction('init', function() {
223 315 load_plugin_textdomain(
224 - $this->config->get('app.text_domain'), false, $this->textDomainPath()
316 + $this->config->get(
317 + 'app.text_domain'
318 + ), false, $this->textDomainPath()
225 319 );
226 320 });
227 321 }
228 322
@@ -243,9 +337,12 @@
243 337 * @return null
244 338 */
245 339 protected function bindCoreComponents()
246 340 {
247 - (new ComponentBinder($this))->bindComponents();
341 + if (!$this->componentsBound) {
342 + $this->componentsBound = true;
343 + (new ComponentBinder($this))->bindComponents();
344 + }
248 345 }
249 346
250 347 /**
251 348 * Load (include) the files where hooks are registered.
@@ -255,17 +352,145 @@
255 352 * @return null
256 353 */
257 354 protected function requireCommonFiles($app)
258 355 {
356 + $this->addFilter(
357 + 'rest_pre_serve_request', [$this, 'preServeRequest'], 10, 4
358 + );
359 +
259 360 require_once $this->basePath . 'app/Hooks/actions.php';
260 361 require_once $this->basePath . 'app/Hooks/filters.php';
362 +
363 + if (file_exists($f = $this->basePath . 'app/Hooks/includes.php')) {
364 + require_once $f;
365 + }
366 + }
261 367
262 - if (file_exists($includes = $this->basePath . 'app/Hooks/includes.php')) {
263 - require_once $includes;
368 + /**
369 + * Handler for rest_pre_serve_request filter.
370 + *
371 + * @param bool $served (default: false)
372 + * @param \WP_REST_Response $result
373 + * @param \WP_REST_Request $request
374 + * @param \WP_REST_Server $server
375 + * @return bool (false to intercept, otherwise true)
376 + */
377 + public function preServeRequest($served, $result, $request, $server)
378 + {
379 + if ($result->get_status() === 404) {
380 + $route = $request->get_route();
381 + $slug = $this->config->get('app.slug');
382 +
383 + if ($this->isRequestOfPlugin($route, $slug)) {
384 + if ($this->isRequestForEndpoints($route)) {
385 + status_header(200);
386 + $result->set_status(200);
387 + // @phpstan-ignore-next-line
388 + $result->set_data($this->endpoints);
389 + } else {
390 + $result->set_data(
391 + $this->customizeNotFoundResponse(
392 + $result, $request
393 + )
394 + );
395 + }
396 + }
264 397 }
398 +
399 + return $served;
265 400 }
266 401
267 402 /**
403 + * Determines whether the request is madse by plugin.
404 + *
405 + * @param string $route (Rest route|Full URL)
406 + * @param string $slug (Plugin's slug)
407 + * @return bool
408 + */
409 + public function isRequestOfPlugin($route = '', $slug = '')
410 + {
411 + $slug = $slug ?: $this->config->get('app.slug');
412 +
413 + if (!$route) {
414 + if (get_option('permalink_structure')) {
415 + $route = $this->request->url();
416 + } else {
417 + $route = $this->request->query('rest_route');
418 + }
419 + }
420 +
421 + // For web routing (If web-routing is installed)
422 + if (!$route && !$this->request->isRest()) {
423 + $route = $this->request->url();
424 + }
425 +
426 + $parsedUrl = parse_url($route ?? '');
427 +
428 + $path = str_replace('/wp-json', '', $parsedUrl['path'] ?? '');
429 +
430 + if (is_admin()) {
431 + $page = $this->request->query('page');
432 + if ($slug === $page) {
433 + $path = $page;
434 + }
435 + }
436 +
437 + return str_starts_with(ltrim($path, '/'), $slug);
438 + }
439 +
440 + /**
441 + * Determines if the request is made for endpoints.
442 + *
443 + * @param string $route (Rest route|Full URL)
444 +
445 + * @return bool
446 + */
447 + protected function isRequestForEndpoints($route)
448 + {
449 + return str_ends_with($route, '__endpoints');
450 + }
451 +
452 + /**
453 + * Prepare a custom not found response.
454 + *
455 + * @param \WP_REST_Response $result
456 + * @param \WP_REST_Request $request
457 + *
458 + * @return array
459 + */
460 + public function customizeNotFoundResponse($result, $request)
461 + {
462 + $response = $result->get_data();
463 +
464 + if ($this->env() === 'dev') {
465 + $response['data']['wpfluent'] = [
466 + 'env' => $this->env(),
467 + 'method' => $request->get_method(),
468 + 'request_url' => $this->request->url(),
469 + 'route_params' => $request->get_url_params(),
470 + 'query_params' => $request->get_query_params(),
471 + 'body_params' => $request->get_body_params(),
472 + ];
473 + } else {
474 + $response['data']['wpfluent'] = [
475 + 'env' => $this->env()
476 + ];
477 + }
478 +
479 + return $response;
480 + }
481 +
482 + /**
483 + * Check if running unit test.
484 + *
485 + * @return boolean
486 + */
487 + public function isUnitTesting()
488 + {
489 + return getenv('ENV') === 'testing';
490 + }
491 +
492 + /**
268 493 * Register the rest api init actions and routes
269 494 *
270 495 * @param self $app
271 496 */
@@ -286,12 +511,13 @@
286 511 * Register rest routes.
287 512 *
288 513 * @param \FluentBoards\Framework\Http\Router $router
289 514 *
290 - * @return null
515 + * @return void
291 516 */
292 517 protected function registerRestRoutes($router)
293 518 {
519 + // @phpstan-ignore-next-line
294 520 $router->registerRoutes(
295 521 $this->requireRouteFile($router)
296 522 );
297 523 }
@@ -304,6 +530,31 @@
304 530 */
305 531 protected function requireRouteFile($router)
306 532 {
307 533 require_once $this['path.http'] . 'Routes/routes.php';
534 + }
535 +
536 + /**
537 + * Register plugin ready (booted) callbacks.
538 + *
539 + * @param callable $callback
540 + * @return void
541 + */
542 + public function ready(callable $callback)
543 + {
544 + $this->onReady[] = $callback;
545 + }
546 +
547 + /**
548 + * Execute plugin booted callbacks.
549 + *
550 + * @return void
551 + */
552 + protected function callPluginReadyCallbacks()
553 + {
554 + $this->addAction('init', function() {
555 + while ($callback = array_pop($this->onReady)) {
556 + $callback($this);
557 + }
558 + });
308 559 }
309 560 }