PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / 1.2.21
VikAppointments Services Booking Calendar v1.2.21
1.2.21 1.2.20 trunk 1.2.17 1.2.18 1.2.19
vikappointments / libraries / adapter / mvc / controller.php
vikappointments / libraries / adapter / mvc Last commit date
controllers 3 days ago models 3 days ago controller.php 3 days ago model.php 3 days ago view.php 3 days ago
controller.php
564 lines
1 <?php
2 /**
3 * @package VikWP - Libraries
4 * @subpackage adapter.mvc
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2023 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 JLoader::import('adapter.mvc.view');
15 JLoader::import('adapter.mvc.model');
16
17 /**
18 * The main controller used by the MVC framework.
19 * This controller is used to dispatch the requested actions
20 * to the apposite views.
21 *
22 * @since 10.0
23 */
24 #[\AllowDynamicProperties]
25 abstract class JController
26 {
27 /**
28 * A list of controller instance.
29 *
30 * @var array
31 */
32 protected static $instances = array();
33
34 /**
35 * A list of excluded methods.
36 *
37 * @var array
38 */
39 protected $excludedMethods = array();
40
41 /**
42 * The controller prefix.
43 *
44 * @var string
45 */
46 protected $prefix;
47
48 /**
49 * The base path of the plugin.
50 *
51 * @var string
52 */
53 protected $basePath;
54
55 /**
56 * URL for redirection.
57 *
58 * @var string
59 * @since 10.1.30
60 */
61 protected $redirect;
62
63 /**
64 * Redirect message.
65 *
66 * @var string
67 * @since 10.1.30
68 */
69 protected $message;
70
71 /**
72 * Redirect message type.
73 *
74 * @var string
75 * @since 10.1.30
76 */
77 protected $messageType;
78
79 /**
80 * Array of class methods to call for a given task.
81 *
82 * @var array
83 * @since 10.1.30
84 */
85 protected $taskMap;
86
87 /**
88 * Class constructor.
89 *
90 * @param array $config An optional associative array of configuration settings.
91 */
92 public function __construct($config = array())
93 {
94 if (isset($config['prefix']))
95 {
96 $this->prefix = $config['prefix'];
97 }
98
99 if (isset($config['base']))
100 {
101 $this->basePath = $config['base'];
102 }
103
104 $reflect = new ReflectionClass('JController');
105
106 foreach ($reflect->getMethods() as $method)
107 {
108 $this->excludedMethods[] = $method->getName();
109 }
110
111 $this->registerTask('unpublish', 'publish');
112 }
113
114 /**
115 * Method to get a singleton controller instance.
116 *
117 * @param string $prefix The prefix for the controller.
118 * @param string $base The base path from which loading the controller.
119 *
120 * @return self A new controller instance.
121 */
122 public static function getInstance($prefix, $base)
123 {
124 if (!isset(static::$instances[$prefix]))
125 {
126 $app = JFactory::getApplication();
127 $input = $app->input;
128
129 $task = $input->get('task', '');
130 $cmd = $input->get('controller', '');
131
132 if (strpos($task, '.') !== false)
133 {
134 $split = explode('.', $task);
135
136 $cmd = array_shift($split);
137 $task = array_pop($split);
138 }
139
140 $folder = $app->isAdmin() ? 'admin' : 'site';
141
142 // load the main controller
143 if (!JLoader::import($folder . '.controller', $base))
144 {
145 wp_die(
146 '<h1>' . JText::translate('FATAL_ERROR') . '</h1>' .
147 '<p>' . JText::translate('CONTROLLER_FILE_NOT_FOUND_ERR') . '</p>',
148 404
149 );
150 }
151
152 $className = $prefix . 'Controller';
153
154 // check if the controller class exists
155 if (!class_exists($className))
156 {
157 wp_die(
158 '<h1>' . JText::translate('FATAL_ERROR') . '</h1>' .
159 '<p>' . JText::sprintf('CONTROLLER_CLASS_NOT_FOUND_ERR', $className) . '</p>',
160 404
161 );
162 }
163
164 // try to check if we should load a dedicated controller
165 if ($cmd)
166 {
167 /**
168 * Search also inside the libraries folder of the current plugin.
169 * Prioritize this folder to avoid conflicts with deprecated files.
170 *
171 * @since 10.1.35
172 */
173 if (JLoader::import('libraries.mvc.' . $folder . '.controllers.' . $cmd, $base) || JLoader::import($folder . '.controllers.' . $cmd, $base))
174 {
175 $childClass = $className . ucwords($cmd);
176
177 if (class_exists($childClass))
178 {
179 $className = $childClass;
180 }
181 }
182 }
183
184 // setup options array
185 $options = [
186 'prefix' => $prefix,
187 'base' => $base,
188 ];
189
190 // instantiate the controller
191 $controller = new $className($options);
192
193 // make sure the controller is a valid instance
194 if (!$controller instanceof JController)
195 {
196 wp_die(
197 '<h1>' . JText::translate('FATAL_ERROR') . '</h1>' .
198 '<p>' . JText::translate('CONTROLLER_INVALID_INSTANCE_ERR') . '</p>',
199 500
200 );
201 }
202
203 // cache the instance
204 static::$instances[$prefix] = $controller;
205 }
206
207 return static::$instances[$prefix];
208 }
209
210 /**
211 * Typical view method for MVC based architecture.
212 *
213 * This function is provided as a default implementation, in most cases
214 * you will need to override it in your own controllers.
215 *
216 * @return self This object to support chaining.
217 *
218 * @uses getView()
219 * @uses getModel()
220 */
221 public function display()
222 {
223 $input = JFactory::getApplication()->input;
224
225 // get view name
226 $action = $input->get('view', null);
227 $layout = $input->get('layout', null);
228
229 if ($action)
230 {
231 // try to obtain the view related to the specified action
232 $view = $this->getView($action);
233
234 if ($view)
235 {
236 // try to obtain the model related to the specified view
237 $model = $this->getModel($action);
238
239 if ($model)
240 {
241 // attach the model if exists
242 $view->setModel($model);
243 }
244
245 /**
246 * Inject the layout through the apposite setter instead of passing it
247 * as argument to the `JView::display()` method.
248 *
249 * @since 10.1.41
250 */
251 if ($layout)
252 {
253 $view->setLayout($layout);
254 }
255
256 /**
257 * Fires before the controller displays the view.
258 *
259 * @param JView $view The view instance.
260 *
261 * @since 10.1.16
262 */
263 do_action_ref_array(strtolower($this->prefix) . '_before_display_' . strtolower($action), array(&$view));
264
265 // display the view before to terminate
266 $view->display();
267
268 /**
269 * Fires after the controller displayed the view.
270 *
271 * @param JView $view The view instance.
272 *
273 * @since 10.1.16
274 */
275 do_action(strtolower($this->prefix) . '_after_display_' . strtolower($action), array($view));
276 }
277
278 }
279
280 return $this;
281 }
282
283 /**
284 * Execute a task by triggering a method in the derived class.
285 *
286 * @param string $task The task to perform. If no matching task is found,
287 * the default 'display' method is executed.
288 *
289 * @return mixed The value returned by the called method.
290 */
291 public function execute($task)
292 {
293 $task = (string) $task;
294
295 // get only the string after the dot, if any
296 if (strpos($task, '.') !== false)
297 {
298 $split = explode('.', $task);
299 $task = array_pop($split);
300
301 /**
302 * Reset the task without the controller context.
303 *
304 * @since 10.1.30
305 */
306 JFactory::getApplication()->input->set('task', $task);
307 }
308
309 // raise an error if we are trying to call reserved methods
310 if (in_array($task, $this->excludedMethods) && $task != 'display')
311 {
312 wp_die(
313 '<h1>' . JText::translate('FATAL_ERROR') . '</h1>' .
314 '<p>' . JText::translate('CONTROLLER_PROTECTED_METHOD_ERR') . '</p>',
315 403
316 );
317 }
318
319 $reflect = new ReflectionClass(get_class($this));
320
321 // check if we should use a different task linked to the specified one
322 if (isset($this->taskMap[$task]))
323 {
324 $task = $this->taskMap[$task];
325 }
326
327 // check if the $task method is callable
328 if (!$reflect->hasMethod($task) || !$reflect->getMethod($task)->isPublic())
329 {
330 // otherwise use default 'display' method
331 $task = 'display';
332 }
333
334 try
335 {
336 // dispatch callback
337 $result = call_user_func(array($this, $task));
338 }
339 catch (Exception $e)
340 {
341 // We need to terminate the buffer here to avoid displaying
342 // the output printed by the views into the error screen.
343
344 while (ob_get_status())
345 {
346 // repeat until the buffer is empty
347 ob_end_clean();
348 }
349
350 if (!wp_doing_ajax())
351 {
352 /**
353 * Included exception backtrace within the document in case the DEBUG is turned on.
354 *
355 * @since 10.1.35
356 */
357 if (WP_DEBUG)
358 {
359 $trace = '<pre style="white-space:pre-wrap;">' . $e->getTraceAsString() . '</pre>';
360 }
361 else
362 {
363 $trace = '';
364 }
365
366 // raise an error in case an exception has been thrown
367 wp_die(
368 '<h1>' . JText::translate('FATAL_ERROR') . '</h1>'
369 . '<p>' . $e->getMessage() . '</p>'
370 . $trace,
371 ($code = $e->getCode()) ? $code : 500
372 );
373 }
374 else
375 {
376 /**
377 * Raise a minified error for AJAX requests.
378 *
379 * @since 10.1.21
380 */
381 wp_die(
382 $e->getMessage(),
383 ($code = $e->getCode()) ? $code : 500
384 );
385 }
386 }
387
388 return $result;
389 }
390
391 /**
392 * Set a URL for browser redirection.
393 *
394 * @param string $url URL to redirect to.
395 * @param string $msg Message to display on redirect.
396 * @param string $type Message type.
397 *
398 * @return self This object to support chaining.
399 *
400 * @since 10.1.30
401 */
402 public function setRedirect($url, $msg = null, $type = null)
403 {
404 // register redirection URL
405 $this->redirect = $url;
406
407 if ($msg !== null)
408 {
409 // controller may have set this directly
410 $this->message = $msg;
411 }
412
413 // ensure the type is not overwritten by a previous call to setMessage
414 if (empty($type))
415 {
416 if (empty($this->messageType))
417 {
418 $this->messageType = 'message';
419 }
420 }
421 // if the type is explicitly set, set it
422 else
423 {
424 $this->messageType = $type;
425 }
426
427 return $this;
428 }
429
430 /**
431 * Redirects the browser or returns false if no redirect is set.
432 *
433 * @return boolean False if no redirect exists.
434 *
435 * @since 10.1.30
436 */
437 public function redirect()
438 {
439 if ($this->redirect)
440 {
441 $app = JFactory::getApplication();
442
443 if ($this->message)
444 {
445 // enqueue the redirect message
446 $app->enqueueMessage($this->message, $this->messageType);
447 }
448
449 // execute the redirect
450 $app->redirect($this->redirect);
451 }
452
453 return false;
454 }
455
456 /**
457 * Register (map) a task to a method in the class.
458 *
459 * @param string $task The task.
460 * @param string $method The name of the method in the derived class to perform for this task.
461 *
462 * @return self This object to support chaining.
463 *
464 * @since 10.1.30
465 */
466 public function registerTask($task, $method)
467 {
468 $this->taskMap[strtolower($task)] = $method;
469
470 return $this;
471 }
472
473 /**
474 * Returns the view object related to the specified name.
475 *
476 * @param string $view The view name.
477 *
478 * @return mixed The view object if exists, otherwise false.
479 */
480 protected function getView($view)
481 {
482 $app = JFactory::getApplication();
483
484 $folder = $app->isAdmin() ? 'admin' : 'site';
485
486 $paths = [];
487
488 /**
489 * Search also inside the libraries folder of the current plugin.
490 * Prioritize this folder to avoid conflicts with deprecated views.
491 *
492 * @since 10.1.35
493 */
494 $paths[] = $this->basePath . '/libraries/mvc/' . $folder . '/views';
495
496 // get default view path
497 $paths[] = $this->basePath . '/' . $folder . '/views';
498
499 // find the matching path
500 $path = JPath::find($paths, $view);
501
502 if (!$path)
503 {
504 return false;
505 }
506
507 // view found, make sure the entry point exists
508 $path = JPath::clean($path . '/view.html.php');
509
510 // make sure the file path exists
511 if (!is_file($path))
512 {
513 return false;
514 }
515
516 // include the view file
517 require_once $path;
518
519 $className = $this->prefix . 'View' . ucwords($view);
520
521 // make sure the view class exists
522 if (!class_exists($className))
523 {
524 return false;
525 }
526
527 $obj = new $className(dirname($path));
528
529 // make sure the view is a valid instance
530 if (!$obj instanceof JView)
531 {
532 return false;
533 }
534
535 return $obj;
536 }
537
538 /**
539 * Method to get a model object.
540 *
541 * @param string $name The model name.
542 * @param string $prefix The class prefix.
543 * @param array $config Configuration array for model.
544 *
545 * @return mixed Model object on success; otherwise false on failure.
546 */
547 public function getModel($name = '', $prefix = '', $config = array())
548 {
549 if (!$name)
550 {
551 // obtain the model name from the classname of this controller
552 $name = strtolower(str_replace($this->prefix . 'Controller', '', get_class($this)));
553 }
554
555 if (!$prefix)
556 {
557 $prefix = $this->prefix;
558 }
559
560 // invoke parent
561 return JModel::getInstance($name, $prefix, $config);
562 }
563 }
564