<?php /** * @package VikBooking * @subpackage core * @author E4J s.r.l. * @copyright Copyright (C) 2021 E4J s.r.l. All Rights Reserved. * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL * @link https://vikwp.com */ // No direct access defined('ABSPATH') or die('No script kiddies please!'); /** * Trait for VikBooking classes that may creates sub instances. * * @since 1.5.10 */ trait VBOFactoryAware { /** * A list of paths to folders containing all the available cron jobs. * * @var array */ private $includePaths = array(); /** * Defines the class prefix of the instances that should be created * through this factory. * * @var string */ protected $instanceClassPrefix = ''; /** * Defines the namespace separator of the class names, such as: * - foo_bar_baz * - foo.bar.baz * * @var string */ protected $instanceNamespaceSeparator = ''; /** * Gets a list of supported include paths. * * @return array */ final public function getIncludePaths() { return $this->includePaths; } /** * Adds one path to include in cron jobs search. * Proxy of addIncludePaths(). * * @param string $path The path to search for cron jobs. * * @return self This object to support chaining. */ final public function addIncludePath($path) { return $this->addIncludePaths($path); } /** * Adds one or more paths to include in cron jobs search. * * @param mixed $paths The path or array of paths to search for cron jobs. * * @return self This object to support chaining. */ final public function addIncludePaths($paths) { if (empty($paths)) { return $this; } $includePaths = $this->getIncludePaths(); // in case the path is an array, if (!is_array($paths)) { // always treat the given path as an array for a correct merging $paths = [$paths]; } // merge all the paths and make sure we have no duplicates $includePaths = array_unique(array_merge($includePaths, $paths)); // update include paths $this->setIncludePaths($includePaths); return $this; } /** * Sets the include paths to search for cron jobs. * * @param array $paths Array with paths to search in. * * @return self This object to support chaining. */ final public function setIncludePaths($paths) { $this->includePaths = (array) $paths; return $this; } /** * Return all the installed instances that matches the specified query. * * @return array A list of the found instances. */ final public function getInstances() { $paths = array(); foreach ($this->getIncludePaths() as $dir) { // recursively scan the directory in search of PHP files $files = JFolder::files($dir, '.php$', $recursive = true, $full = true); foreach ($files as $filePath) { $element = []; // register file path $element['path'] = $filePath; // register element name $element['name'] = ltrim(substr($filePath, strlen($dir)), DIRECTORY_SEPARATOR); if ($this->instanceNamespaceSeparator) { // replace directory separators with namespace $element['name'] = str_replace(DIRECTORY_SEPARATOR, $this->instanceNamespaceSeparator, $element['name']); } // get rid of PHP file extension $element['name'] = basename($element['name'], '.php'); // inject element within the list $paths[] = $element; } } $list = array(); foreach ($paths as $p) { // require element file require_once $p['path']; try { // try to create the element object $list[$p['name']] = $this->createInstance($p['name'], [], $autoload = false); } catch (Exception $e) { // unable to create the instance, go ahead } } // sort elements by name since they might be located on different folders $this->rearrangeInstances($list); return $list; } /** * Creates a new instance of the requested element. * * @param string $element The element name. * @param array $args The class constructor arguments. * @param boolean $autoload Whether to autoload the file or not. * * @return mixed * * @throws Exception */ final public function createInstance($element, $args = [], $autoload = true) { // get rid of the file extension $element = basename($element, '.php'); // convert the filename in classname $classname = preg_replace("/[^a-zA-Z0-9]+/", ' ', $element); $classname = str_replace(' ', '', ucwords($classname)); $classname = $this->instanceClassPrefix . $classname; if (!class_exists($classname)) { if ($autoload) { // detect file $file = $this->findPath($element); if (!$file) { // element file not found throw new Exception(sprintf('The instance [%s] does not exist.', $element), 404); } // possible match detected, require the file require_once $file; } if (!class_exists($classname)) { // unable to detect the class file throw new Exception(sprintf('Instance [%s] class not found.', $classname), 404); } } if (!is_array($args)) { // wrap arguments into an array for a correct instantiation $args = [$args]; } // create reflection of the element $class = new ReflectionClass($classname); // instantiate element class with the given arguments $obj = $class->newInstanceArgs($args); // make sure the object is a valid instance if (!$this->isInstanceValid($obj)) { throw new Exception(sprintf('The object [%s] is not a valid instance.', $classname), 406); } return $obj; } /** * Gets the path of the specified element. * * @param string $element The element name. * * @return mixed The element path if exists, false otherwise. */ final protected function findPath($element) { // convert element into a subfolder if ($this->instanceNamespaceSeparator) { $element = str_replace($this->instanceNamespaceSeparator, DIRECTORY_SEPARATOR, $element); } foreach ($this->getIncludePaths() as $dir) { // build full path $path = rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $element . '.php'; // check whether the file exists if (JFile::exists($path)) { // match found, return file path return $path; } } // no match found return false; } /** * Children classes can override this method to rearrange the ordering * of the elements created through this factory class. * * @param array &$list The list of instances. * * @return void */ protected function rearrangeInstances(&$list) { // not enough information to rearrange the list... } /** * Children classes can override this method to make sure that the * created instance is compliant with the factory requirements. * * @param mixed $object The object to validate. * * @return boolean True if valid, false otherwise. */ protected function isInstanceValid($object) { // the validation should be delegated to the class that // inherits this trait methods return true; } }