1 <?php
2 /**
3 * @package FrameworkOnFramework
4 * @subpackage platform
5 * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
6 * @license GNU General Public License version 2 or later; see LICENSE.txt
7 */
8 // Protect from unauthorized access
9 defined('FOF_INCLUDED') or die;
10
11 /**
12 * Part of the FOF Platform Abstraction Layer. It implements everything that
13 * depends on the platform FOF is running under, e.g. the Joomla! CMS front-end,
14 * the Joomla! CMS back-end, a CLI Joomla! Platform app, a bespoke Joomla!
15 * Platform / Framework web application and so on.
16 *
17 * This is the abstract class implementing some basic housekeeping functionality
18 * and provides the static interface to get the appropriate Platform object for
19 * use in the rest of the framework.
20 *
21 * @package FrameworkOnFramework
22 * @since 2.1
23 */
24 abstract class FOFPlatform implements FOFPlatformInterface
25 {
26 /**
27 * The ordering for this platform class. The lower this number is, the more
28 * important this class becomes. Most important enabled class ends up being
29 * used.
30 *
31 * @var integer
32 */
33 public $ordering = 100;
34
35 /**
36 * The internal name of this platform implementation. It must match the
37 * last part of the platform class name and be in all lowercase letters,
38 * e.g. "foobar" for FOFPlatformFoobar
39 *
40 * @var string
41 *
42 * @since 2.1.2
43 */
44 public $name = '';
45
46 /**
47 * The human readable platform name
48 *
49 * @var string
50 *
51 * @since 2.1.2
52 */
53 public $humanReadableName = 'Unknown Platform';
54
55 /**
56 * The platform version string
57 *
58 * @var string
59 *
60 * @since 2.1.2
61 */
62 public $version = '';
63
64 /**
65 * Caches the enabled status of this platform class.
66 *
67 * @var boolean
68 */
69 protected $isEnabled = null;
70
71 /**
72 * Filesystem integration objects cache
73 *
74 * @var object
75 *
76 * @since 2.1.2
77 */
78 protected $objectCache = array();
79
80 /**
81 * The list of paths where platform class files will be looked for
82 *
83 * @var array
84 */
85 protected static $paths = array();
86
87 /**
88 * The platform class instance which will be returned by getInstance
89 *
90 * @var FOFPlatformInterface
91 */
92 protected static $instance = null;
93
94 // ========================================================================
95 // Public API for platform integration handling
96 // ========================================================================
97
98 /**
99 * Register a path where platform files will be looked for. These take
100 * precedence over the built-in platform files.
101 *
102 * @param string $path The path to add
103 *
104 * @return void
105 */
106 public static function registerPlatformPath($path)
107 {
108 if (!in_array($path, self::$paths))
109 {
110 self::$paths[] = $path;
111 self::$instance = null;
112 }
113 }
114
115 /**
116 * Unregister a path where platform files will be looked for.
117 *
118 * @param string $path The path to remove
119 *
120 * @return void
121 */
122 public static function unregisterPlatformPath($path)
123 {
124 $pos = array_search($path, self::$paths);
125
126 if ($pos !== false)
127 {
128 unset(self::$paths[$pos]);
129 self::$instance = null;
130 }
131 }
132
133 /**
134 * Force a specific platform object to be used. If null, nukes the cache
135 *
136 * @param FOFPlatformInterface|null $instance The Platform object to be used
137 *
138 * @return void
139 */
140 public static function forceInstance($instance)
141 {
142 if ($instance instanceof FOFPlatformInterface || is_null($instance))
143 {
144 self::$instance = $instance;
145 }
146 }
147
148 /**
149 * Find and return the most relevant platform object
150 *
151 * @return FOFPlatformInterface
152 */
153 public static function getInstance()
154 {
155 if (!is_object(self::$instance))
156 {
157 // Where to look for platform integrations
158 $paths = array(__DIR__ . '/../integration');
159
160 if (is_array(self::$paths))
161 {
162 $paths = array_merge($paths, self::$paths);
163 }
164
165 // Get a list of folders inside this directory
166 $integrations = array();
167
168 foreach ($paths as $path)
169 {
170 if (!is_dir($path))
171 {
172 continue;
173 }
174
175 $di = new DirectoryIterator($path);
176 $temp = array();
177
178 foreach ($di as $fileSpec)
179 {
180 if (!$fileSpec->isDir())
181 {
182 continue;
183 }
184
185 $fileName = $fileSpec->getFilename();
186
187 if (substr($fileName, 0, 1) == '.')
188 {
189 continue;
190 }
191
192 $platformFilename = $path . '/' . $fileName . '/platform.php';
193
194 if (!file_exists($platformFilename))
195 {
196 continue;
197 }
198
199 $temp[] = array(
200 'classname' => 'FOFIntegration' . ucfirst($fileName) . 'Platform',
201 'fullpath' => $path . '/' . $fileName . '/platform.php',
202 );
203 }
204
205 $integrations = array_merge($integrations, $temp);
206 }
207
208 // Loop all paths
209 foreach ($integrations as $integration)
210 {
211 // Get the class name for this platform class
212 $class_name = $integration['classname'];
213
214 // Load the file if the class doesn't exist
215 if (!class_exists($class_name, false))
216 {
217 @include_once $integration['fullpath'];
218 }
219
220 // If the class still doesn't exist this file didn't
221 // actually contain a platform class; skip it
222 if (!class_exists($class_name, false))
223 {
224 continue;
225 }
226
227 // If it doesn't implement FOFPlatformInterface, skip it
228 if (!class_implements($class_name, 'FOFPlatformInterface'))
229 {
230 continue;
231 }
232
233 // Get an object of this platform
234 $o = new $class_name;
235
236 // If it's not enabled, skip it
237 if (!$o->isEnabled())
238 {
239 continue;
240 }
241
242 if (is_object(self::$instance))
243 {
244 // Replace self::$instance if this object has a
245 // lower order number
246 $current_order = self::$instance->getOrdering();
247 $new_order = $o->getOrdering();
248
249 if ($new_order < $current_order)
250 {
251 self::$instance = null;
252 self::$instance = $o;
253 }
254 }
255 else
256 {
257 // There is no self::$instance already, so use the
258 // object we just created.
259 self::$instance = $o;
260 }
261 }
262 }
263
264 return self::$instance;
265 }
266
267 /**
268 * Returns the ordering of the platform class.
269 *
270 * @see FOFPlatformInterface::getOrdering()
271 *
272 * @return integer
273 */
274 public function getOrdering()
275 {
276 return $this->ordering;
277 }
278
279 /**
280 * Is this platform enabled?
281 *
282 * @see FOFPlatformInterface::isEnabled()
283 *
284 * @return boolean
285 */
286 public function isEnabled()
287 {
288 if (is_null($this->isEnabled))
289 {
290 $this->isEnabled = false;
291 }
292
293 return $this->isEnabled;
294 }
295
296 /**
297 * Returns a platform integration object
298 *
299 * @param string $key The key name of the platform integration object, e.g. 'filesystem'
300 *
301 * @return object
302 *
303 * @since 2.1.2
304 */
305 public function getIntegrationObject($key)
306 {
307 $hasObject = false;
308
309 if (array_key_exists($key, $this->objectCache))
310 {
311 if (is_object($this->objectCache[$key]))
312 {
313 $hasObject = true;
314 }
315 }
316
317 if (!$hasObject)
318 {
319 // Instantiate a new platform integration object
320 $className = 'FOFIntegration' . ucfirst($this->getPlatformName()) . ucfirst($key);
321 $this->objectCache[$key] = new $className;
322 }
323
324 return $this->objectCache[$key];
325 }
326
327 /**
328 * Forces a platform integration object instance
329 *
330 * @param string $key The key name of the platform integration object, e.g. 'filesystem'
331 * @param object $object The object to force for this key
332 *
333 * @return object
334 *
335 * @since 2.1.2
336 */
337 public function setIntegrationObject($key, $object)
338 {
339 $this->objectCache[$key] = $object;
340 }
341
342 // ========================================================================
343 // Default implementation
344 // ========================================================================
345
346 /**
347 * Set the error Handling, if possible
348 *
349 * @param integer $level PHP error level (E_ALL)
350 * @param string $log_level What to do with the error (ignore, callback)
351 * @param array $options Options for the error handler
352 *
353 * @return void
354 */
355 public function setErrorHandling($level, $log_level, $options = array())
356 {
357 if (version_compare(JVERSION, '3.0', 'lt') )
358 {
359 return JError::setErrorHandling($level, $log_level, $options);
360 }
361 }
362
363 /**
364 * Returns the base (root) directories for a given component.
365 *
366 * @param string $component The name of the component. For Joomla! this
367 * is something like "com_example"
368 *
369 * @see FOFPlatformInterface::getComponentBaseDirs()
370 *
371 * @return array A hash array with keys main, alt, site and admin.
372 */
373 public function getComponentBaseDirs($component)
374 {
375 return array(
376 'main' => '',
377 'alt' => '',
378 'site' => '',
379 'admin' => '',
380 );
381 }
382
383 /**
384 * Return a list of the view template directories for this component.
385 *
386 * @param string $component The name of the component. For Joomla! this
387 * is something like "com_example"
388 * @param string $view The name of the view you're looking a
389 * template for
390 * @param string $layout The layout name to load, e.g. 'default'
391 * @param string $tpl The sub-template name to load (null by default)
392 * @param boolean $strict If true, only the specified layout will be
393 * searched for. Otherwise we'll fall back to
394 * the 'default' layout if the specified layout
395 * is not found.
396 *
397 * @see FOFPlatformInterface::getViewTemplateDirs()
398 *
399 * @return array
400 */
401 public function getViewTemplatePaths($component, $view, $layout = 'default', $tpl = null, $strict = false)
402 {
403 return array();
404 }
405
406 /**
407 * Get application-specific suffixes to use with template paths. This allows
408 * you to look for view template overrides based on the application version.
409 *
410 * @return array A plain array of suffixes to try in template names
411 */
412 public function getTemplateSuffixes()
413 {
414 return array();
415 }
416
417 /**
418 * Return the absolute path to the application's template overrides
419 * directory for a specific component. We will use it to look for template
420 * files instead of the regular component directorues. If the application
421 * does not have such a thing as template overrides return an empty string.
422 *
423 * @param string $component The name of the component for which to fetch the overrides
424 * @param boolean $absolute Should I return an absolute or relative path?
425 *
426 * @return string The path to the template overrides directory
427 */
428 public function getTemplateOverridePath($component, $absolute = true)
429 {
430 return '';
431 }
432
433 /**
434 * Load the translation files for a given component.
435 *
436 * @param string $component The name of the component. For Joomla! this
437 * is something like "com_example"
438 *
439 * @see FOFPlatformInterface::loadTranslations()
440 *
441 * @return void
442 */
443 public function loadTranslations($component)
444 {
445 return null;
446 }
447
448 /**
449 * Authorise access to the component in the back-end.
450 *
451 * @param string $component The name of the component.
452 *
453 * @see FOFPlatformInterface::authorizeAdmin()
454 *
455 * @return boolean True to allow loading the component, false to halt loading
456 */
457 public function authorizeAdmin($component)
458 {
459 return true;
460 }
461
462 /**
463 * Returns the JUser object for the current user
464 *
465 * @param integer $id The ID of the user to fetch
466 *
467 * @see FOFPlatformInterface::getUser()
468 *
469 * @return JDocument
470 */
471 public function getUser($id = null)
472 {
473 return null;
474 }
475
476 /**
477 * Returns the JDocument object which handles this component's response.
478 *
479 * @see FOFPlatformInterface::getDocument()
480 *
481 * @return JDocument
482 */
483 public function getDocument()
484 {
485 return null;
486 }
487
488 /**
489 * This method will try retrieving a variable from the request (input) data.
490 *
491 * @param string $key The user state key for the variable
492 * @param string $request The request variable name for the variable
493 * @param FOFInput $input The FOFInput object with the request (input) data
494 * @param mixed $default The default value. Default: null
495 * @param string $type The filter type for the variable data. Default: none (no filtering)
496 * @param boolean $setUserState Should I set the user state with the fetched value?
497 *
498 * @see FOFPlatformInterface::getUserStateFromRequest()
499 *
500 * @return mixed The value of the variable
501 */
502 public function getUserStateFromRequest($key, $request, $input, $default = null, $type = 'none', $setUserState = true)
503 {
504 return $input->get($request, $default, $type);
505 }
506
507 /**
508 * Load plugins of a specific type. Obviously this seems to only be required
509 * in the Joomla! CMS.
510 *
511 * @param string $type The type of the plugins to be loaded
512 *
513 * @see FOFPlatformInterface::importPlugin()
514 *
515 * @return void
516 */
517 public function importPlugin($type)
518 {
519 }
520
521 /**
522 * Execute plugins (system-level triggers) and fetch back an array with
523 * their return values.
524 *
525 * @param string $event The event (trigger) name, e.g. onBeforeScratchMyEar
526 * @param array $data A hash array of data sent to the plugins as part of the trigger
527 *
528 * @see FOFPlatformInterface::runPlugins()
529 *
530 * @return array A simple array containing the results of the plugins triggered
531 */
532 public function runPlugins($event, $data)
533 {
534 return array();
535 }
536
537 /**
538 * Perform an ACL check.
539 *
540 * @param string $action The ACL privilege to check, e.g. core.edit
541 * @param string $assetname The asset name to check, typically the component's name
542 *
543 * @see FOFPlatformInterface::authorise()
544 *
545 * @return boolean True if the user is allowed this action
546 */
547 public function authorise($action, $assetname)
548 {
549 return true;
550 }
551
552 /**
553 * Is this the administrative section of the component?
554 *
555 * @see FOFPlatformInterface::isBackend()
556 *
557 * @return boolean
558 */
559 public function isBackend()
560 {
561 return true;
562 }
563
564 /**
565 * Is this the public section of the component?
566 *
567 * @see FOFPlatformInterface::isFrontend()
568 *
569 * @return boolean
570 */
571 public function isFrontend()
572 {
573 return true;
574 }
575
576 /**
577 * Is this a component running in a CLI application?
578 *
579 * @see FOFPlatformInterface::isCli()
580 *
581 * @return boolean
582 */
583 public function isCli()
584 {
585 return true;
586 }
587
588 /**
589 * Is AJAX re-ordering supported? This is 100% Joomla!-CMS specific. All
590 * other platforms should return false and never ask why.
591 *
592 * @see FOFPlatformInterface::supportsAjaxOrdering()
593 *
594 * @return boolean
595 */
596 public function supportsAjaxOrdering()
597 {
598 return true;
599 }
600
601 /**
602 * Performs a check between two versions. Use this function instead of PHP version_compare
603 * so we can mock it while testing
604 *
605 * @param string $version1 First version number
606 * @param string $version2 Second version number
607 * @param string $operator Operator (see version_compare for valid operators)
608 *
609 * @return boolean
610 */
611 public function checkVersion($version1, $version2, $operator)
612 {
613 return version_compare($version1, $version2, $operator);
614 }
615
616 /**
617 * Saves something to the cache. This is supposed to be used for system-wide
618 * FOF data, not application data.
619 *
620 * @param string $key The key of the data to save
621 * @param string $content The actual data to save
622 *
623 * @return boolean True on success
624 */
625 public function setCache($key, $content)
626 {
627 return false;
628 }
629
630 /**
631 * Retrieves data from the cache. This is supposed to be used for system-side
632 * FOF data, not application data.
633 *
634 * @param string $key The key of the data to retrieve
635 * @param string $default The default value to return if the key is not found or the cache is not populated
636 *
637 * @return string The cached value
638 */
639 public function getCache($key, $default = null)
640 {
641 return false;
642 }
643
644 /**
645 * Is the global FOF cache enabled?
646 *
647 * @return boolean
648 */
649 public function isGlobalFOFCacheEnabled()
650 {
651 return true;
652 }
653
654 /**
655 * Clears the cache of system-wide FOF data. You are supposed to call this in
656 * your components' installation script post-installation and post-upgrade
657 * methods or whenever you are modifying the structure of database tables
658 * accessed by FOF. Please note that FOF's cache never expires and is not
659 * purged by Joomla!. You MUST use this method to manually purge the cache.
660 *
661 * @return boolean True on success
662 */
663 public function clearCache()
664 {
665 return false;
666 }
667
668 /**
669 * logs in a user
670 *
671 * @param array $authInfo authentification information
672 *
673 * @return boolean True on success
674 */
675 public function loginUser($authInfo)
676 {
677 return true;
678 }
679
680 /**
681 * logs out a user
682 *
683 * @return boolean True on success
684 */
685 public function logoutUser()
686 {
687 return true;
688 }
689
690 /**
691 * Logs a deprecated practice. In Joomla! this results in the $message being output in the
692 * deprecated log file, found in your site's log directory.
693 *
694 * @param $message The deprecated practice log message
695 *
696 * @return void
697 */
698 public function logDeprecated($message)
699 {
700 // The default implementation does nothing. Override this in your platform classes.
701 }
702
703 /**
704 * Returns the (internal) name of the platform implementation, e.g.
705 * "joomla", "foobar123" etc. This MUST be the last part of the platform
706 * class name. For example, if you have a plaform implementation class
707 * FOFPlatformFoobar you MUST return "foobar" (all lowercase).
708 *
709 * @return string
710 *
711 * @since 2.1.2
712 */
713 public function getPlatformName()
714 {
715 return $this->name;
716 }
717
718 /**
719 * Returns the version number string of the platform, e.g. "4.5.6". If
720 * implementation integrates with a CMS or a versioned foundation (e.g.
721 * a framework) it is advisable to return that version.
722 *
723 * @return string
724 *
725 * @since 2.1.2
726 */
727 public function getPlatformVersion()
728 {
729 return $this->version;
730 }
731
732 /**
733 * Returns the human readable platform name, e.g. "Joomla!", "Joomla!
734 * Framework", "Something Something Something Framework" etc.
735 *
736 * @return string
737 *
738 * @since 2.1.2
739 */
740 public function getPlatformHumanName()
741 {
742 return $this->humanReadableName;
743 }
744 }
745