PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 1.91.6
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v1.91.6
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
fluent-boards / vendor / wpfluent / framework / src / WPFluent / Foundation / Application.php

Application.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 1.91.6, at vendor/wpfluent/framework/src/WPFluent/Foundation/Application.php

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