PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.2
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.2
6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 3.6.65 All 196 releases
fluentform / vendor / wpfluent / framework / src / WPFluent / Foundation / Application.php

Application.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.2, at vendor/wpfluent/framework/src/WPFluent/Foundation/Application.php

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