/lib/class.component_registry.php
PHP | 908 lines | 536 code | 157 blank | 215 comment | 114 complexity | e8ded7bf041058a1fa4d1ffa774d8e12 MD5 | raw file
- <?php
- if (!defined('POPE_VERSION')) { die('Use autoload.php'); }
- /**
- * A registry of registered products, modules, adapters, and utilities.
- *
- *
- * How the registry gets initialized:
- * 1) Each product tells the registry where to find products and modules
- * 2) We load all products
- */
- class C_Component_Registry
- {
- static $_instance = NULL;
- var $_searched_paths = array();
- var $_blacklist = array();
- var $_meta_info = array();
- var $_default_path = NULL;
- var $_modules = array();
- var $_products = array();
- var $_adapters = array();
- var $_utilities = array();
- var $_module_type_cache = array();
- var $_module_type_cache_count = 0;
- /**
- * This is a singleton object
- */
- private function __construct()
- {
- // Create an autoloader
- spl_autoload_register(array($this, '_module_autoload'), TRUE);
- }
- /**
- * Returns a singleton
- * @return C_Component_Registry()
- */
- static function &get_instance()
- {
- if (is_null(self::$_instance)) {
- $klass = get_class();
- self::$_instance = new $klass();
- }
- return self::$_instance;
- }
- function require_module_file($module_file_abspath)
- {
- // We don't include (require) module files that have the same name. This
- // avoids loading module.autoupdate.php from two products
- static $already_required = array();
- $relpath = basename($module_file_abspath);
- if (!in_array($relpath, $already_required)) {
- @require_once($module_file_abspath);
- $already_required[] = $relpath;
- }
- }
- function has_searched_path_before($abspath)
- {
- return in_array($abspath, $this->_searched_paths);
- }
- function mark_as_searched_path($abspath)
- {
- $this->_searched_paths[] = $abspath;
- }
- /**
- * Adds a path in the search paths for loading modules
- * @param string $path
- * @param bool $recurse - TRUE, FALSE, or the number of levels to recurse
- * @param bool $load_all - loads all modules found in the path
- */
- function add_module_path($path, $recurse = false, $load_all = false)
- {
- if (!$recurse || (!$this->has_searched_path_before($path))) {
- // If no default module path has been set, then set one now
- if ($this->get_default_module_path() == null) {
- $this->set_default_module_path($path);
- }
- // We we've been passed a module file, then include it
- if (@file_exists($path) && is_file($path)) {
- $this->require_module_file($path);
- }
- // Recursively find product and module files in this path
- else foreach ($this->find_product_and_module_files($path, $recurse) as $file_abspath) {
- $this->require_module_file($file_abspath);
- }
- $this->mark_as_searched_path($path);
- }
- if ($load_all) $this->load_all_modules(NULL, $path);
- }
- /**
- * Retrieves the default module path (Note: this is just the generic root container path for modules)
- * @return string
- */
- function get_default_module_path()
- {
- return $this->_default_path;
- }
- /**
- * Sets the default module path (Note: this is just the generic root container path for modules)
- * @param string $path
- */
- function set_default_module_path($path)
- {
- $this->_default_path = $path;
- }
- /**
- * Retrieves the module path
- * @param string $module_id
- * @return string
- */
- function get_module_path($module_id)
- {
- if (isset($this->_meta_info[$module_id])) {
- $info = $this->_meta_info[$module_id];
- if (isset($info['path'])) {
- return $info['path'];
- }
- }
- return null;
- }
- /**
- * Retrieves the module installation directory
- * @param string $module_id
- * @return string
- */
- function get_module_dir($module_id)
- {
- $path = $this->get_module_path($module_id);
- if ($path != null) {
- return dirname($path);
- }
- return null;
- }
- function is_module_loaded($module_id)
- {
- return (isset($this->_meta_info[$module_id]) && isset($this->_meta_info[$module_id]['loaded']) && $this->_meta_info[$module_id]['loaded']);
- }
- /**
- * Loads a module's code according to its dependency list
- * @param string $module_id
- */
- function load_module($module_id)
- {
- $retval = FALSE;
- if (($module = $this->get_module($module_id)) && !$this->is_module_loaded($module_id) && !$this->is_blacklisted($module_id)) {
- $module->load();
- $retval = $this->_meta_info[$module_id]['loaded'] = TRUE;
- }
- return $retval;
- }
- function load_all_modules($type=NULL, $dir=NULL)
- {
- $modules = $this->get_known_module_list();
- $ret = true;
- foreach ($modules as $module_id)
- {
- if ($type == null || $this->get_module_meta($module_id, 'type') == $type) {
- if ($dir == NULL || strpos($this->get_module_dir($module_id), $dir) !== FALSE)
- $ret = $this->load_module($module_id) && $ret;
- }
- }
- return $ret;
- }
- /**
- * Initializes a previously loaded module
- * @param string $module_id
- */
- function initialize_module($module_id)
- {
- $retval = FALSE;
- if (isset($this->_modules[$module_id])) {
- $module = $this->_modules[$module_id];
- if ($this->is_module_loaded($module_id) && !$module->initialized) {
- if (method_exists($module, 'initialize'))
- $module->initialize();
- $module->initialized = true;
- }
- $retval = TRUE;
- }
- return $retval;
- }
- /**
- * Initializes an already loaded product
- * @param string $product_id
- * @return bool
- */
- function initialize_product($product_id)
- {
- return $this->initialize_module($product_id);
- }
- /**
- * Initializes all previously loaded modules
- */
- function initialize_all_modules()
- {
- $module_list = $this->get_loaded_module_list();
- foreach ($module_list as $module_id)
- {
- $this->initialize_module($module_id);
- }
- }
- /**
- * Adds an already loaded module to the registry
- * @param string $module_id
- * @param C_Base_Module $module_object
- */
- function add_module($module_id, $module_object)
- {
- if (!isset($this->_modules[$module_id])) {
- $this->_modules[$module_id] = $module_object;
- }
- if (!isset($this->_meta_info[$module_id])) {
- $klass = new ReflectionClass($module_object);
- $this->_meta_info[$module_id] = array(
- 'path' => $klass->getFileName(),
- 'type' => $klass->isSubclassOf('C_Base_Product') ? 'product' : 'module',
- 'loaded' => FALSE
- );
- }
- }
- /**
- * Deletes an already loaded module from the registry
- * @param string $module_id
- */
- function del_module($module_id)
- {
- if (isset($this->_modules[$module_id])) {
- unset($this->_modules[$module_id]);
- }
- }
- /**
- * Retrieves the instance of the registered module. Note: it's the instance of the module object, so the module needs to be loaded or this function won't return anything. For module info returned by scanning (with add_module_path), look at get_module_meta
- * @param string $module_id
- * @return C_Base_Module
- */
- function get_module($module_id)
- {
- if (isset($this->_modules[$module_id])) {
- return $this->_modules[$module_id];
- }
- return null;
- }
- function get_module_meta($module_id, $meta_name)
- {
- $meta = $this->get_module_meta_list($module_id);
- if (isset($meta[$meta_name])) {
- return $meta[$meta_name];
- }
- return null;
- }
- function get_module_meta_list($module_id)
- {
- if (isset($this->_meta_info[$module_id])) {
- return $this->_meta_info[$module_id];
- }
- return null;
- }
- /**
- * Retrieves a list of instantiated module ids, in their "loaded" order as defined by a product
- *
- * @return array
- */
- function get_module_list($for_product_id=FALSE)
- {
- $retval = $module_list = array();
- // As of May 1, 2015, there's a new standard. A product will provide get_provided_modules() and get_modules_to_load().
- // As of Feb 10, 2015, there's no standard way across Pope products to an "ordered" list of modules
- // that the product provides.
- //
- // The "standard" going forward will insist that all Product classes will provide either:
- // A) a static property called "modules"
- // B) an instance method called "define_modules", which returns a list of modules, and as well, sets
- // a static property called "modules'.
- //
- // IMPORTANT!
- // The Photocrati Theme, as of version 4.1.8, doesn't follow this standard. But both NextGEN Pro and Plus do.
- // Following the standard above, collect all modules provided by a product
- $problematic_product_id = FALSE;
- foreach ($this->get_product_list() as $product_id) {
- $modules = array();
- // Try getting the list of modules using the "standard" described above
- $obj = $this->get_product($product_id);
- try{
- $klass = new ReflectionClass($obj);
- if ($klass->hasMethod('get_modules_to_load')) {
- $modules = $obj->get_modules_provided();
- }
- elseif ($klass->hasProperty('modules')) {
- $modules = $klass->getStaticPropertyValue('modules');
- }
- if (!$modules && $klass->hasMethod('define_modules')) {
- $modules = $obj->define_modules();
- if ($klass->hasProperty('modules')) {
- $modules = $klass->getStaticPropertyValue('modules');
- }
- }
- }
- // We've encountered a product that doesn't follow the standard. For these exceptions, we'll have to
- // make an educated guess - if the module path is in the product's default module path, we know that
- // it belongs to the product
- catch (ReflectionException $ex) {
- $modules = array();
- }
- if (!$modules) {
- $product_path = $this->get_product_module_path($product_id);
- foreach ($this->_modules as $module_id => $module) {
- if (strpos($this->get_module_path($module_id), $product_path) !== FALSE) {
- $modules[] = $module_id;
- }
- }
- if (!$modules) $problematic_product_id = $product_id;
- }
- $module_list[$product_id] = $modules;
- }
- // If we have a problematic product, that is, one that we can't find it's ordered list of modules
- // that it provides, then we have one last fallback: get a list of modules that Pope is aware of, but hasn't
- // added to $module_list[$product_id] yet
- if ($problematic_product_id) {
- $modules = array();
- foreach (array_keys($this->_modules) as $module_id) {
- $assigned = FALSE;
- foreach (array_keys($module_list) as $product_id) {
- if (in_array($module_id, $module_list[$product_id])) {
- $assigned =TRUE;
- break;
- }
- }
- if (!$assigned) $modules[] = $module_id;
- }
- $module_list[$problematic_product_id] = $modules;
- }
- // Now that we know which products provide which modules, we can serve the request.
- if (!$for_product_id) {
- foreach (array_values($module_list) as $modules) {
- $retval = array_merge($retval, $modules);
- }
- }
- else $retval = $module_list[$for_product_id];
- // Final fallback...if all else fails, just return the list of all modules
- // that Pope is aware of
- if (!$retval) $retval = array_keys($this->_modules);
- return $retval;
- }
- function get_loaded_module_list()
- {
- $retval = array();
- foreach ($this->get_module_list() as $module_id) {
- if ($this->is_module_loaded($module_id)) $retval[] = $module_id;
- }
- return $retval;
- }
- /**
- * Retrieves a list of registered module ids, including those that aren't loaded (i.e. get_module() call with those unloaded ids will fail)
- * @return array
- */
- function get_known_module_list()
- {
- return array_keys($this->_meta_info);
- }
- function load_product($product_id)
- {
- return $this->load_module($product_id);
- }
- function load_all_products()
- {
- return $this->load_all_modules('product');
- }
- /**
- * Adds an already loaded product in the registry
- * @param string $product_id
- * @param C_Base_Module $product_object
- */
- function add_product($product_id, $product_object)
- {
- if (!isset($this->_products[$product_id])) {
- $this->_products[$product_id] = $product_object;
- }
- }
- /**
- * Deletes an already loaded product from the registry
- * @param string $product_id
- */
- function del_product($product_id)
- {
- if (isset($this->_products[$product_id])) {
- unset($this->_products[$product_id]);
- }
- }
- /**
- * Retrieves the instance of the registered product
- * @param string $product_id
- * @return C_Base_Module
- */
- function get_product($product_id)
- {
- if (isset($this->_products[$product_id])) {
- return $this->_products[$product_id];
- }
- return null;
- }
- function get_product_meta($product_id, $meta_name)
- {
- $meta = $this->get_product_meta_list($product_id);
- if (isset($meta[$meta_name])) {
- return $meta[$meta_name];
- }
- return null;
- }
- function get_product_meta_list($product_id)
- {
- if (isset($this->_meta_info[$product_id]) && $this->_meta_info[$product_id]['type'] == 'product') {
- return $this->_meta_info[$product_id];
- }
- return null;
- }
- /**
- * Retrieves the module installation path for a specific product (Note: this is just the generic root container path for modules of this product)
- * @param string $product_id
- * @return string
- */
- function get_product_module_path($product_id)
- {
- if (isset($this->_meta_info[$product_id])) {
- $info = $this->_meta_info[$product_id];
- if (isset($info['product-module-path'])) {
- return $info['product-module-path'];
- }
- }
- return null;
- }
- function blacklist_module_file($relpath)
- {
- if (!in_array($relpath, $this->_blacklist)) $this->_blacklist[] = $relpath;
- }
- function is_blacklisted($filename)
- {
- return in_array($filename, $this->_blacklist);
- }
- /**
- * Sets the module installation path for a specific product (Note: this is just the generic root container path for modules of this product)
- * @param string $product_id
- * @param string $module_path
- */
- function set_product_module_path($product_id, $module_path)
- {
- if (isset($this->_meta_info[$product_id])) {
- $this->_meta_info[$product_id]['product-module-path'] = $module_path;
- }
- }
- /**
- * Retrieves a list of instantiated product ids
- * @return array
- */
- function get_product_list()
- {
- return array_keys($this->_products);
- }
- /**
- * Retrieves a list of registered product ids, including those that aren't loaded (i.e. get_product() call with those unloaded ids will fail)
- * @return array
- */
- function get_known_product_list()
- {
- $list = array_keys($this->_meta_info);
- $return = array();
- foreach ($list as $module_id)
- {
- if ($this->get_product_meta_list($module_id) != null)
- {
- $return[] = $module_id;
- }
- }
- return $return;
- }
- /**
- * Registers an adapter for an interface with specific contexts
- * @param string $interface
- * @param string $class
- * @param array $contexts
- */
- function add_adapter($interface, $class, $contexts=FALSE)
- {
- // If no specific contexts are given, then we assume
- // that the adapter is to be applied in ALL contexts
- if (!$contexts) $contexts = array('all');
- if (!is_array($contexts)) $contexts = array($contexts);
- if (!isset($this->_adapters[$interface])) {
- $this->_adapters[$interface] = array();
- }
- // Iterate through each specific context
- foreach ($contexts as $context) {
- if (!isset($this->_adapters[$interface][$context])) {
- $this->_adapters[$interface][$context] = array();
- }
- $this->_adapters[$interface][$context][] = $class;
- }
- }
- /**
- * Removes an adapter for an interface. May optionally specifify what
- * contexts to remove the adapter from, leaving the rest intact
- * @param string $interface
- * @param string $class
- * @param array $contexts
- */
- function del_adapter($interface, $class, $contexts=FALSE)
- {
- // Ensure that contexts is an array of contexts
- if (!$contexts) $contexts = array('all');
- if (!is_array($contexts)) $contexts = array($contexts);
- // Iterate through each context for an adapter
- foreach ($this->_adapters[$interface] as $context => $classes) {
- if (!$context OR in_array($context, $contexts)) {
- $index = array_search($class, $classes);
- unset($this->_adapters[$interface][$context][$index]);
- }
- }
- }
- /**
- * Apply adapters registered for the component
- * @param C_Component $component
- * @return C_Component
- */
- function &apply_adapters(C_Component &$component)
- {
- // Iterate through each adapted interface. If the component implements
- // the interface, then apply the adapters
- foreach ($this->_adapters as $interface => $contexts) {
- if ($component->implements_interface($interface)) {
- // Determine what context apply to the current component
- $applied_contexts = array('all');
- if ($component->context) {
- $applied_contexts[] = $component->context;
- $applied_contexts = $this->_flatten_array($applied_contexts);
- }
- // Iterate through each of the components contexts and apply the
- // registered adapters
- foreach ($applied_contexts as $context) {
- if (isset($contexts[$context])) {
- foreach ($contexts[$context] as $adapter) {
- $component->add_mixin($adapter, FALSE);
- }
- }
- }
- }
- }
- return $component;
- }
- /**
- * Adds a utility for an interface, to be used in particular contexts
- * @param string $interface
- * @param string $class
- * @param array $contexts
- */
- function add_utility($interface, $class, $contexts=FALSE)
- {
- // If no specific contexts are given, then we assume
- // that the utility is for ALL contexts
- if (!$contexts) $contexts = array('all');
- if (!is_array($contexts)) $contexts = array($contexts);
- if (!isset($this->_utilities[$interface])) {
- $this->_utilities[$interface] = array();
- }
- // Add the utility for each appropriate context
- foreach ($contexts as $context) {
- $this->_utilities[$interface][$context] = $class;
- }
- }
- /**
- * Deletes a registered utility for a particular interface.
- * @param string $interface
- * @param array $contexts
- */
- function del_utility($interface, $contexts=FALSE)
- {
- if (!$contexts) $contexts = array('all');
- if (!is_array($contexts)) $contexts = array($contexts);
- // Iterate through each context for an interface
- foreach ($this->_utilities[$interface] as $context => $class) {
- if (!$context OR in_array($context, $contexts)) {
- unset($this->_utilities[$interface][$context]);
- }
- }
- }
- /**
- * Gets the class name of the component providing a utility implementation
- * @param string $interface
- * @param string|array $context
- * @return string
- */
- function get_utility_class_name($interface, $context=FALSE)
- {
- return $this->_retrieve_utility_class($interface, $context);
- }
- /**
- * Retrieves an instantiates the registered utility for the provided instance.
- * The instance is a singleton and must provide the get_instance() method
- * @param string $interface
- * @param string $context
- * @return C_Component
- */
- function get_utility($interface, $context=FALSE)
- {
- if (!$context) $context='all';
- $class = $this->_retrieve_utility_class($interface, $context);
- return call_user_func("{$class}::get_instance", $context);
- }
- /**
- * Flattens an array of arrays to a single array
- * @param array $array
- * @param array $parent (optional)
- * @param bool $exclude_duplicates (optional - defaults to TRUE)
- * @return array
- */
- function _flatten_array($array, $parent=NULL, $exclude_duplicates=TRUE)
- {
- if (is_array($array)) {
- // We're to add each element to the parent array
- if ($parent) {
- foreach ($array as $index => $element) {
- foreach ($this->_flatten_array($array) as $sub_element) {
- if ($exclude_duplicates) {
- if (!in_array($sub_element, $parent)) {
- $parent[] = $sub_element;
- }
- }
- else $parent[] = $sub_element;
- }
- }
- $array = $parent;
- }
- // We're starting the process..
- else {
- $index = 0;
- while (isset($array[$index])) {
- $element = $array[$index];
- if (is_array($element)) {
- $array = $this->_flatten_array($element, $array);
- unset($array[$index]);
- }
- $index += 1;
- }
- $array = array_values($array);
- }
- }
- else {
- $array = array($array);
- }
- return $array;
- }
- function find_product_and_module_files($abspath, $recursive=FALSE)
- {
- $retval = array();
- static $recursive_level = 0;
- $recursive_level++;
- $abspath = str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $abspath);
- $contents = @scandir($abspath);
- if ($contents) foreach ($contents as $filename) {
- if ($filename == '.' || $filename == '..' || $filename == 'error_log') continue;
- $filename_abspath = $abspath.DIRECTORY_SEPARATOR.$filename;
- // Is this a subdirectory?
- // We don't use is_dir(), as it's less efficient than just checking for a 'dot' in the filename.
- // The problem is that we're assuming that our directories won't contain a 'dot'.
- if ($recursive && strpos($filename, '.') === FALSE) {
- // The recursive parameter can either be set to TRUE or the number of levels to navigate
- // If we reach the max number of recursive levels we're supported to navigate, then we try
- // to guess if there's a module or product file under the directory with the same name as
- // the directory
- if ($recursive === TRUE || (is_int($recursive) && $recursive_level <= $recursive)) {
- $retval = array_merge($retval, $this->find_product_and_module_files($filename_abspath, $recursive));
- }
- elseif (@file_exists(($module_abspath = $filename_abspath.DIRECTORY_SEPARATOR.'module.'.$filename.'.php'))) {
- $filename = 'module.'.$filename.'.php';
- $filename_abspath = $module_abspath;
- }
- elseif (@file_exists(($product_abspath = $filename_abspath.DIRECTORY_SEPARATOR.'product.'.$filename.'.php'))) {
- $filename = 'product.'.$filename.'.php';
- $filename_abspath = $module_abspath;
- }
- }
- if ((strpos($filename, 'module.') === 0 OR strpos($filename, 'product.') === 0) AND !$this->is_blacklisted($filename)) {
- $retval[] = $filename_abspath;
- }
- }
- $this->mark_as_searched_path($abspath);
- $recursive_level--;
- return $retval;
- }
- /**
- * Private API method. Retrieves the class which currently provides the utility
- * @param string $interface
- * @param string $context
- */
- function _retrieve_utility_class($interface, $context='all')
- {
- $class = FALSE;
- if (!$context) $context = 'all';
- if (isset($this->_utilities[$interface])) {
- if (isset($this->_utilities[$interface][$context])) {
- $class = $this->_utilities[$interface][$context];
- }
- // No utility defined for the specified interface
- else {
- if ($context == 'all') $context = 'default';
- $class = $this->_retrieve_utility_class($interface, FALSE);
- if (!$class)
- throw new Exception("No utility registered for `{$interface}` with the `{$context}` context.");
- }
- }
- else throw new Exception("No utilities registered for `{$interface}`");
- return $class;
- }
- /**
- * Autoloads any classes, interfaces, or adapters needed by this module
- */
- function _module_autoload($name)
- {
- // Pope classes are always prefixed
- if (strpos($name, 'C_') !== 0 && strpos($name, 'A_') !== 0 && strpos($name, 'Mixin_') !== 0) {
- return;
- }
- if ($this->_module_type_cache == null || count($this->_modules) > $this->_module_type_cache_count)
- {
- $this->_module_type_cache_count = count($this->_modules);
- $modules = $this->_modules;
- $keys = array();
- foreach ($modules as $mod => $properties) $keys[$mod] = $properties->module_version;
- if (!($this->_module_type_cache = C_Pope_Cache::get($keys, array()))) {
- foreach ($modules as $module_id => $module)
- {
- $dir = $this->get_module_dir($module_id);
- $type_list = $module->get_type_list();
- foreach ($type_list as $type => $filename)
- {
- $this->_module_type_cache[strtolower($type)] = $dir . DIRECTORY_SEPARATOR . $filename;
- }
- }
- C_Pope_Cache::set($keys, $this->_module_type_cache);
- }
- elseif (is_object($this->_module_type_cache)) $this->_module_type_cache = get_object_vars($this->_module_type_cache);
- }
- $name = strtolower($name);
- if (isset($this->_module_type_cache[$name]))
- {
- $module_filename = $this->_module_type_cache[$name];
- if (file_exists($module_filename))
- {
- require_once($module_filename);
- }
- }
- }
- }