PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / trunk
Fluent Support – Helpdesk & Customer Support Ticket System vtrunk
2.4.0 2.3.2 2.3.1 2.3.0 2.2.1 2.2.0 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.4.0 1.4.1 1.4.2 1.4.5 1.4.6 1.4.7 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 All 68 releases
fluent-support / vendor / wpfluent / framework / src / WPFluent / Foundation / Application.php

Application.php in Fluent Support – Helpdesk & Customer Support Ticket System trunk, at vendor/wpfluent/framework/src/WPFluent/Foundation/Application.php

561 lines 14.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 FluentSupport\Framework\Foundation;
13
14 use InvalidArgumentException;
15 use FluentSupport\Framework\Support\Arr;
16 use FluentSupport\Framework\Support\Env;
17 use FluentSupport\Framework\Http\Client;
18 use FluentSupport\Framework\Foundation\Config;
19 use FluentSupport\Framework\Container\Container;
20 use FluentSupport\Framework\Foundation\ComponentBinder;
21 use FluentSupport\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 \FluentSupport\Framework\Foundation\Config $config
32 * @property \FluentSupport\Framework\View\View $view
33 * @property \FluentSupport\Framework\Cache\Cache $cache
34 * @property \FluentSupport\Framework\Http\Router\Router $router
35 * @property \FluentSupport\Framework\Http\Request\Request $request
36 * @property \FluentSupport\Framework\Http\Response\Response $response
37 * @property \FluentSupport\Framework\Validator\Validator $validator
38 * @property \FluentSupport\Framework\Events\Dispatcher $events
39 * @property \FluentSupport\Framework\Encryption\Encrypter $encrypter
40 * @property \FluentSupport\Framework\Encryption\Encrypter $crypt
41 * @property \FluentSupport\Framework\Database\DatabaseManager $db
42 * @property \FluentSupport\Framework\Http\URL $url
43 * @property \FluentSupport\Framework\Support\Mail $mail
44 * @property \FluentSupport\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->registerTextdomain();
209 $this->bindCoreComponents();
210 $this->requireCommonFiles($this);
211 $this->addRestApiInitAction($this);
212 }
213
214 /**
215 * Bind application instance in the container.
216 *
217 * @return null
218 */
219 protected function bindAppInstance()
220 {
221 App::setInstance($this);
222 $this->instance('app', $this);
223 $this->instance(__CLASS__, $this);
224 $this->instance('endpoints', []);
225 }
226
227 /**
228 * Bind the paths and urls
229 *
230 * @return null
231 */
232 protected function bindPathsAndUrls()
233 {
234 $this->bindUrls();
235 $this->basePaths();
236 }
237
238 /**
239 * Bind urls
240 *
241 * @return null
242 */
243 protected function bindUrls()
244 {
245 $this['url.assets'] = $this->baseUrl . 'assets/';
246 }
247
248 /**
249 * Bind paths
250 *
251 * @return null
252 */
253 protected function basePaths()
254 {
255 $this['path'] = $this->basePath;
256 $this['path.app'] = $this->basePath . 'app/';
257 $this['path.hooks'] = $this['path.app'] . 'Hooks/';
258 $this['path.http'] = $this['path.app'] . 'Http/';
259 $this['path.controllers'] = $this['path.http'] . 'Controllers/';
260 $this['path.config'] = $this->basePath . 'config/';
261 $this['path.assets'] = $this->basePath . 'assets/';
262 $this['path.resources'] = $this->basePath . 'resources/';
263 $this['path.views'] = $this['path.app'] . 'Views/';
264 }
265
266 /**
267 * Load application's config and set
268 * the data in the Config instance.
269 *
270 * @return null
271 */
272 protected function loadConfigIfExists()
273 {
274 $data = [];
275
276 if (is_dir($this['path.config'])) {
277 foreach (glob($this['path.config'] . '*.php') as $file) {
278 $data[basename($file, '.php')] = require($file);
279 }
280 }
281
282 $this->instance('config', new Config($data));
283 }
284
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 /**
308 * Register plugin's text domain
309 *
310 * @return null
311 */
312 protected function registerTextdomain()
313 {
314 $this->addAction('init', function() {
315 load_plugin_textdomain(
316 $this->config->get(
317 'app.text_domain'
318 ), false, $this->textDomainPath()
319 );
320 });
321 }
322
323 /**
324 * Resolve the text domain path.
325 *
326 * @return null
327 */
328 protected function textDomainPath()
329 {
330 return basename($this['path']) . $this->config->get('app.domain_path');
331 }
332
333 /**
334 * Bind the components of the framework into the container so
335 * they'll be available throughout the application life cycle.
336 *
337 * @return null
338 */
339 protected function bindCoreComponents()
340 {
341 if (!$this->componentsBound) {
342 $this->componentsBound = true;
343 (new ComponentBinder($this))->bindComponents();
344 }
345 }
346
347 /**
348 * Load (include) the files where hooks are registered.
349 *
350 * @param self $app
351 *
352 * @return null
353 */
354 protected function requireCommonFiles($app)
355 {
356 $this->addFilter(
357 'rest_pre_serve_request', [$this, 'preServeRequest'], 10, 4
358 );
359
360 require_once $this->basePath . 'app/Hooks/actions.php';
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 }
367
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 }
397 }
398
399 return $served;
400 }
401
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 /**
493 * Register the rest api init actions and routes
494 *
495 * @param self $app
496 */
497 protected function addRestApiInitAction($app)
498 {
499 $this->addAction('rest_api_init', function($wpRestServer) use ($app) {
500 try {
501 $this->registerRestRoutes($app->router);
502 } catch (InvalidArgumentException $e) {
503 return $app->response->json([
504 'message' => $e->getMessage()
505 ], $e->getCode() ?: 500);
506 }
507 });
508 }
509
510 /**
511 * Register rest routes.
512 *
513 * @param \FluentSupport\Framework\Http\Router $router
514 *
515 * @return void
516 */
517 protected function registerRestRoutes($router)
518 {
519 // @phpstan-ignore-next-line
520 $router->registerRoutes(
521 $this->requireRouteFile($router)
522 );
523 }
524
525 /**
526 * Load (include) routes
527 *
528 * @param \FluentSupport\Framework\Http\Router $router
529 * @return null
530 */
531 protected function requireRouteFile($router)
532 {
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 });
559 }
560 }
561