PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.7.7
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.7.7
2.11.0 2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 All 78 releases
fluent-community / vendor / wpfluent / framework / src / WPFluent / Foundation / Application.php

Application.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 2.7.7, at vendor/wpfluent/framework/src/WPFluent/Foundation/Application.php

592 lines 15.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 */
11
12 namespace FluentCommunity\Framework\Foundation;
13
14 use InvalidArgumentException;
15 use FluentCommunity\Framework\Support\Arr;
16 use FluentCommunity\Framework\Support\Env;
17 use FluentCommunity\Framework\Http\Client;
18 use FluentCommunity\Framework\Foundation\Config;
19 use FluentCommunity\Framework\Container\Container;
20 use FluentCommunity\Framework\Foundation\ComponentBinder;
21 use FluentCommunity\Framework\Foundation\Concerns\FoundationTrait;
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 \FluentCommunity\Framework\Foundation\Config $config
32 * @property \FluentCommunity\Framework\View\View $view
33 * @property \FluentCommunity\Framework\Cache\Cache $cache
34 * @property \FluentCommunity\Framework\Http\Router\Router $router
35 * @property \FluentCommunity\Framework\Http\Request\Request $request
36 * @property \FluentCommunity\Framework\Http\Response\Response $response
37 * @property \FluentCommunity\Framework\Validator\Validator $validator
38 * @property \FluentCommunity\Framework\Events\Dispatcher $events
39 * @property \FluentCommunity\Framework\Encryption\Encrypter $encrypter
40 * @property \FluentCommunity\Framework\Encryption\Encrypter $crypt
41 * @property \FluentCommunity\Framework\Database\DatabaseManager $db
42 * @property \FluentCommunity\Framework\Http\URL $url
43 * @property \FluentCommunity\Framework\Support\Mail $mail
44 * @property \FluentCommunity\Framework\Support\Pipeline $pipeline
45 * @property string $__pluginfile__
46 * @property string $__namespace__
47 */
48 class Application extends Container
49 {
50 use FoundationTrait;
51
52 /**
53 * Main plugin file's absolute path
54 *
55 * @var string
56 */
57 protected $file = null;
58
59 /**
60 * Plugin's base url
61 *
62 * @var string
63 */
64 protected $baseUrl = null;
65
66 /**
67 * Plugin's base path
68 *
69 * @var string
70 */
71 protected $basePath = null;
72
73 /**
74 * Default namespace for hook's handlers
75 *
76 * @var string
77 */
78 protected $handlerNamespace = null;
79
80 /**
81 * Default namespace for controllers
82 *
83 * @var string
84 */
85 protected $controllerNamespace = null;
86
87 /**
88 * Default namespace for policy handlers
89 *
90 * @var string
91 */
92 protected $policyNamespace = null;
93
94 /**
95 * Composer JSON
96 *
97 * @var null|array
98 */
99 protected static $composer = null;
100
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 /**
116 * Construct the application instance
117 *
118 * @param string $file The main plugin file's absolute path
119 * @return null
120 */
121 public function __construct($file = null)
122 {
123 $this->init($file);
124 $this->loadEnvironmentVars();
125 $this->setAppLevelNamespace();
126 $this->bootstrapApplication();
127 $this->callPluginReadyCallbacks();
128 }
129
130 /**
131 * Init the application instance
132 *
133 * @param string $file The main plugin file's absolute path
134 *
135 * @return null
136 */
137 protected function init($file)
138 {
139 $this['__pluginfile__'] = $this->file = $file;
140 $this->basePath = plugin_dir_path($this->file);
141 $this->baseUrl = plugin_dir_url($this->file);
142 }
143
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 /**
158 * Set the default application level namespaces to resolve
159 * the controllers, policies and various hook handlers.
160 *
161 * @return null
162 */
163 protected function setAppLevelNamespace()
164 {
165 $composer = $this->getComposer();
166
167 $psr4 = array_flip($composer['autoload']['psr-4']);
168
169 $this->policyNamespace = $psr4['app/'] . 'Http\Policies';
170
171 $this->handlerNamespace = $psr4['app/'] . 'Hooks\Handlers';
172
173 $this->controllerNamespace = $psr4['app/'] . 'Http\Controllers';
174
175 $this['__namespace__'] = $composer['extra']['wpfluent']['namespace']['current'];
176 }
177
178 /**
179 * Get the composer data as an array
180 *
181 * @param string $section Specific key
182 *
183 * @return array partial or full composer data array
184 */
185 public function getComposer($section = null)
186 {
187 if (is_null(static::$composer)) {
188 static::$composer = json_decode(
189 file_get_contents($this->basePath . 'composer.json'), true
190 );
191 }
192
193 return $section ? Arr::get(
194 static::$composer, $section
195 ) : static::$composer;
196 }
197
198 /**
199 * Bootstrap the application.
200 *
201 * @return null
202 */
203 protected function bootstrapApplication()
204 {
205 $this->bindAppInstance();
206 $this->bindPathsAndUrls();
207 $this->loadConfigIfExists();
208 $this->registerMiddleware();
209 $this->registerTextdomain();
210 $this->bindCoreComponents();
211 $this->requireCommonFiles($this);
212 $this->addRestApiInitAction($this);
213 }
214
215 /**
216 * Bind application instance in the container.
217 *
218 * @return null
219 */
220 protected function bindAppInstance()
221 {
222 App::setInstance($this);
223 $this->instance('app', $this);
224 $this->instance(__CLASS__, $this);
225 $this->instance('endpoints', []);
226 }
227
228 /**
229 * Bind the paths and urls
230 *
231 * @return null
232 */
233 protected function bindPathsAndUrls()
234 {
235 $this->bindUrls();
236 $this->basePaths();
237 }
238
239 /**
240 * Bind urls
241 *
242 * @return null
243 */
244 protected function bindUrls()
245 {
246 $this['url.assets'] = $this->baseUrl . 'assets/';
247 }
248
249 /**
250 * Bind paths
251 *
252 * @return null
253 */
254 protected function basePaths()
255 {
256 $this['path'] = $this->basePath;
257 $this['path.app'] = $this->basePath . 'app/';
258 $this['path.hooks'] = $this['path.app'] . 'Hooks/';
259 $this['path.http'] = $this['path.app'] . 'Http/';
260 $this['path.controllers'] = $this['path.http'] . 'Controllers/';
261 $this['path.config'] = $this->basePath . 'config/';
262 $this['path.assets'] = $this->basePath . 'assets/';
263 $this['path.resources'] = $this->basePath . 'resources/';
264 $this['path.views'] = $this['path.app'] . 'Views/';
265 }
266
267 /**
268 * Load application's config and set
269 * the data in the Config instance.
270 *
271 * @return null
272 */
273 protected function loadConfigIfExists()
274 {
275 $files = [];
276
277 if (is_dir($this['path.config'])) {
278 foreach (glob($this['path.config'] . '*.php') as $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;
285 }
286 }
287
288 $this->instance('config', new Config([], $files));
289 }
290
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 /**
339 * Register plugin's text domain
340 *
341 * @return null
342 */
343 protected function registerTextdomain()
344 {
345 $this->addAction('init', function() {
346 load_plugin_textdomain(
347 $this->config->get(
348 'app.text_domain'
349 ), false, $this->textDomainPath()
350 );
351 });
352 }
353
354 /**
355 * Resolve the text domain path.
356 *
357 * @return null
358 */
359 protected function textDomainPath()
360 {
361 return basename($this['path']) . $this->config->get('app.domain_path');
362 }
363
364 /**
365 * Bind the components of the framework into the container so
366 * they'll be available throughout the application life cycle.
367 *
368 * @return null
369 */
370 protected function bindCoreComponents()
371 {
372 if (!$this->componentsBound) {
373 $this->componentsBound = true;
374 (new ComponentBinder($this))->bindComponents();
375 }
376 }
377
378 /**
379 * Load (include) the files where hooks are registered.
380 *
381 * @param self $app
382 *
383 * @return null
384 */
385 protected function requireCommonFiles($app)
386 {
387 $this->addFilter(
388 'rest_pre_serve_request', [$this, 'preServeRequest'], 10, 4
389 );
390
391 require_once $this->basePath . 'app/Hooks/actions.php';
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 }
398
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 }
428 }
429
430 return $served;
431 }
432
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 /**
524 * Register the rest api init actions and routes
525 *
526 * @param self $app
527 */
528 protected function addRestApiInitAction($app)
529 {
530 $this->addAction('rest_api_init', function($wpRestServer) use ($app) {
531 try {
532 $this->registerRestRoutes($app->router);
533 } catch (InvalidArgumentException $e) {
534 return $app->response->json([
535 'message' => $e->getMessage()
536 ], $e->getCode() ?: 500);
537 }
538 });
539 }
540
541 /**
542 * Register rest routes.
543 *
544 * @param \FluentCommunity\Framework\Http\Router $router
545 *
546 * @return void
547 */
548 protected function registerRestRoutes($router)
549 {
550 // @phpstan-ignore-next-line
551 $router->registerRoutes(
552 $this->requireRouteFile($router)
553 );
554 }
555
556 /**
557 * Load (include) routes
558 *
559 * @param \FluentCommunity\Framework\Http\Router $router
560 * @return null
561 */
562 protected function requireRouteFile($router)
563 {
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 });
590 }
591 }
592