danielebarchiesi@0: =') && version_compare($version, $current_version, '<=')) { danielebarchiesi@0: if (!isset($info['path'])) { danielebarchiesi@0: $info['path'] = drupal_get_path('module', $module); danielebarchiesi@0: } danielebarchiesi@0: $cache[$owner][$api][$module] = $info; danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // And allow themes to implement these as well. danielebarchiesi@0: $themes = _ctools_list_themes(); danielebarchiesi@0: foreach ($themes as $name => $theme) { danielebarchiesi@0: if (!empty($theme->info['api'][$owner][$api])) { danielebarchiesi@0: $info = $theme->info['api'][$owner][$api]; danielebarchiesi@0: if (!isset($info['version'])) { danielebarchiesi@0: continue; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // Only process if version is between minimum and current, inclusive. danielebarchiesi@0: if (version_compare($info['version'], $minimum_version, '>=') && version_compare($info['version'], $current_version, '<=')) { danielebarchiesi@0: if (!isset($info['path'])) { danielebarchiesi@0: $info['path'] = ''; danielebarchiesi@0: } danielebarchiesi@0: // Because themes can't easily specify full path, we add it here danielebarchiesi@0: // even though we do not for modules: danielebarchiesi@0: $info['path'] = drupal_get_path('theme', $name) . '/' . $info['path']; danielebarchiesi@0: $cache[$owner][$api][$name] = $info; danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // Allow other modules to hook in. danielebarchiesi@0: drupal_alter($hook, $cache[$owner][$api]); danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: return $cache[$owner][$api]; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Load a group of API files. danielebarchiesi@0: * danielebarchiesi@0: * This will ask each module if they support the given API, and if they do danielebarchiesi@0: * it will load the specified file name. The API and the file name danielebarchiesi@0: * coincide by design. danielebarchiesi@0: * danielebarchiesi@0: * @param $owner danielebarchiesi@0: * The name of the module that controls the API. danielebarchiesi@0: * @param $api danielebarchiesi@0: * The name of the api. The api name forms the file name: danielebarchiesi@0: * $module.$api.inc, though this can be overridden by the module's response. danielebarchiesi@0: * @param $minimum_version danielebarchiesi@0: * The lowest version API that is compatible with this one. If a module danielebarchiesi@0: * reports its API as older than this, its files will not be loaded. This danielebarchiesi@0: * should never change during operation. danielebarchiesi@0: * @param $current_version danielebarchiesi@0: * The current version of the api. If a module reports its minimum API as danielebarchiesi@0: * higher than this, its files will not be loaded. This should never change danielebarchiesi@0: * during operation. danielebarchiesi@0: * danielebarchiesi@0: * @return danielebarchiesi@0: * The API information, in case you need it. danielebarchiesi@0: */ danielebarchiesi@0: function ctools_plugin_api_include($owner, $api, $minimum_version, $current_version) { danielebarchiesi@0: static $already_done = array(); danielebarchiesi@0: danielebarchiesi@0: $info = ctools_plugin_api_info($owner, $api, $minimum_version, $current_version); danielebarchiesi@0: foreach ($info as $module => $plugin_info) { danielebarchiesi@0: if (!isset($already_done[$owner][$api][$module])) { danielebarchiesi@0: if (isset($plugin_info["$api file"])) { danielebarchiesi@0: $file = $plugin_info["$api file"]; danielebarchiesi@0: } danielebarchiesi@0: else if (isset($plugin_info['file'])) { danielebarchiesi@0: $file = $plugin_info['file']; danielebarchiesi@0: } danielebarchiesi@0: else { danielebarchiesi@0: $file = "$module.$api.inc"; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: if (file_exists(DRUPAL_ROOT . "/$plugin_info[path]/$file")) { danielebarchiesi@0: require_once DRUPAL_ROOT . "/$plugin_info[path]/$file"; danielebarchiesi@0: } danielebarchiesi@0: else if (file_exists(DRUPAL_ROOT . "/$file")) { danielebarchiesi@0: require_once DRUPAL_ROOT . "/$file"; danielebarchiesi@0: } danielebarchiesi@0: $already_done[$owner][$api][$module] = TRUE; danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: return $info; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Find out what hook to use to determine if modules support an API. danielebarchiesi@0: * danielebarchiesi@0: * By default, most APIs will use hook_ctools_plugin_api, but some modules danielebarchiesi@0: * want sole ownership. This technique lets modules define what hook danielebarchiesi@0: * to use. danielebarchiesi@0: */ danielebarchiesi@0: function ctools_plugin_api_get_hook($owner, $api) { danielebarchiesi@0: // Allow modules to use their own hook for this. The only easy way to do danielebarchiesi@0: // this right now is with a magically named function. danielebarchiesi@0: if (function_exists($function = $owner . '_' . $api . '_hook_name')) { danielebarchiesi@0: $hook = $function(); danielebarchiesi@0: } danielebarchiesi@0: else if (function_exists($function = $owner . '_ctools_plugin_api_hook_name')) { danielebarchiesi@0: $hook = $function(); danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // Do this last so that if the $function above failed to return, we have a danielebarchiesi@0: // sane default. danielebarchiesi@0: if (empty($hook)) { danielebarchiesi@0: $hook = 'ctools_plugin_api'; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: return $hook; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Fetch a group of plugins by name. danielebarchiesi@0: * danielebarchiesi@0: * @param $module danielebarchiesi@0: * The name of the module that utilizes this plugin system. It will be danielebarchiesi@0: * used to call hook_ctools_plugin_$plugin() to get more data about the plugin. danielebarchiesi@0: * @param $type danielebarchiesi@0: * The type identifier of the plugin. danielebarchiesi@0: * @param $id danielebarchiesi@0: * If specified, return only information about plugin with this identifier. danielebarchiesi@0: * The system will do its utmost to load only plugins with this id. danielebarchiesi@0: * danielebarchiesi@0: * @return danielebarchiesi@0: * An array of information arrays about the plugins received. The contents danielebarchiesi@0: * of the array are specific to the plugin. danielebarchiesi@0: */ danielebarchiesi@0: function ctools_get_plugins($module, $type, $id = NULL) { danielebarchiesi@0: // Store local caches of plugins and plugin info so we don't have to do full danielebarchiesi@0: // lookups everytime. danielebarchiesi@0: $plugins = &drupal_static('ctools_plugins', array()); danielebarchiesi@0: $info = ctools_plugin_get_plugin_type_info(); danielebarchiesi@0: danielebarchiesi@0: // Bail out noisily if an invalid module/type combination is requested. danielebarchiesi@0: if (!isset($info[$module][$type])) { danielebarchiesi@0: watchdog('ctools', 'Invalid plugin module/type combination requested: module @module and type @type', array('@module' => $module, '@type' => $type), WATCHDOG_ERROR); danielebarchiesi@0: return array(); danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // Make sure our plugins array is populated. danielebarchiesi@0: if (!isset($plugins[$module][$type])) { danielebarchiesi@0: $plugins[$module][$type] = array(); danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // Attempt to shortcut this whole piece of code if we already have danielebarchiesi@0: // the requested plugin: danielebarchiesi@0: if ($id && array_key_exists($id, $plugins[$module][$type])) { danielebarchiesi@0: return $plugins[$module][$type][$id]; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // Store the status of plugin loading. If a module plugin type pair is true, danielebarchiesi@0: // then it is fully loaded and no searching or setup needs to be done. danielebarchiesi@0: $setup = &drupal_static('ctools_plugin_setup', array()); danielebarchiesi@0: danielebarchiesi@0: // We assume we don't need to build a cache. danielebarchiesi@0: $build_cache = FALSE; danielebarchiesi@0: danielebarchiesi@0: // If the plugin info says this can be cached, check cache first. danielebarchiesi@0: if ($info[$module][$type]['cache'] && empty($setup[$module][$type])) { danielebarchiesi@0: $cache = cache_get("plugins:$module:$type", $info[$module][$type]['cache table']); danielebarchiesi@0: danielebarchiesi@0: if (!empty($cache->data)) { danielebarchiesi@0: // Cache load succeeded so use the cached plugin list. danielebarchiesi@0: $plugins[$module][$type] = $cache->data; danielebarchiesi@0: // Set $setup to true so we know things where loaded. danielebarchiesi@0: $setup[$module][$type] = TRUE; danielebarchiesi@0: } danielebarchiesi@0: else { danielebarchiesi@0: // Cache load failed so store that we need to build and write the cache. danielebarchiesi@0: $build_cache = TRUE; danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // Always load all hooks if we need them. Note we only need them now if the danielebarchiesi@0: // plugin asks for them. We can assume that if we have plugins we've already danielebarchiesi@0: // called the global hook. danielebarchiesi@0: if (!empty($info[$module][$type]['use hooks']) && empty($plugins[$module][$type])) { danielebarchiesi@0: $plugins[$module][$type] = ctools_plugin_load_hooks($info[$module][$type]); danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // Then see if we should load all files. We only do this if we danielebarchiesi@0: // want a list of all plugins or there was a cache miss. danielebarchiesi@0: if (empty($setup[$module][$type]) && ($build_cache || !$id)) { danielebarchiesi@0: $setup[$module][$type] = TRUE; danielebarchiesi@0: $plugins[$module][$type] = array_merge($plugins[$module][$type], ctools_plugin_load_includes($info[$module][$type])); danielebarchiesi@0: // If the plugin can have child plugins, and we're loading all plugins, danielebarchiesi@0: // go through the list of plugins we have and find child plugins. danielebarchiesi@0: if (!$id && !empty($info[$module][$type]['child plugins'])) { danielebarchiesi@0: // If a plugin supports children, go through each plugin and ask. danielebarchiesi@0: $temp = array(); danielebarchiesi@0: foreach ($plugins[$module][$type] as $name => $plugin) { danielebarchiesi@0: // The strpos ensures that we don't try to find children for plugins danielebarchiesi@0: // that are already children. danielebarchiesi@0: if (!empty($plugin['get children']) && function_exists($plugin['get children']) && strpos($name, ':') === FALSE) { danielebarchiesi@0: $temp = array_merge($plugin['get children']($plugin, $name), $temp); danielebarchiesi@0: } danielebarchiesi@0: else { danielebarchiesi@0: $temp[$name] = $plugin; danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: $plugins[$module][$type] = $temp; danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: danielebarchiesi@0: // If we were told earlier that this is cacheable and the cache was danielebarchiesi@0: // empty, give something back. danielebarchiesi@0: if ($build_cache) { danielebarchiesi@0: cache_set("plugins:$module:$type", $plugins[$module][$type], $info[$module][$type]['cache table']); danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // If no id was requested, we are finished here: danielebarchiesi@0: if (!$id) { danielebarchiesi@0: // Use array_filter because looking for unknown plugins could cause NULL danielebarchiesi@0: // entries to appear in the list later. danielebarchiesi@0: return array_filter($plugins[$module][$type]); danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // Check to see if we need to look for the file danielebarchiesi@0: if (!array_key_exists($id, $plugins[$module][$type])) { danielebarchiesi@0: // If we can have child plugins, check to see if the plugin name is in the danielebarchiesi@0: // format of parent:child and break it up if it is. danielebarchiesi@0: if (!empty($info[$module][$type]['child plugins']) && strpos($id, ':') !== FALSE) { danielebarchiesi@0: list($parent, $child) = explode(':', $id, 2); danielebarchiesi@0: } danielebarchiesi@0: else { danielebarchiesi@0: $parent = $id; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: if (!array_key_exists($parent, $plugins[$module][$type])) { danielebarchiesi@0: $result = ctools_plugin_load_includes($info[$module][$type], $parent); danielebarchiesi@0: // Set to either what was returned or NULL. danielebarchiesi@0: $plugins[$module][$type][$parent] = isset($result[$parent]) ? $result[$parent] : NULL; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // If we are looking for a child, and have the parent, ask the parent for the child. danielebarchiesi@0: if (!empty($child) && !empty($plugins[$module][$type][$parent]) && function_exists($plugins[$module][$type][$parent]['get child'])) { danielebarchiesi@0: $plugins[$module][$type][$id] = $plugins[$module][$type][$parent]['get child']($plugins[$module][$type][$parent], $parent, $child); danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // At this point we should either have the plugin, or a NULL. danielebarchiesi@0: return $plugins[$module][$type][$id]; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Return the full list of plugin type info for all plugin types registered in danielebarchiesi@0: * the current system. danielebarchiesi@0: * danielebarchiesi@0: * This function manages its own cache getting/setting, and should always be danielebarchiesi@0: * used as the way to initially populate the list of plugin types. Make sure you danielebarchiesi@0: * call this function to properly populate the ctools_plugin_type_info static danielebarchiesi@0: * variable. danielebarchiesi@0: * danielebarchiesi@0: * @return array danielebarchiesi@0: * A multilevel array of plugin type info, the outer array keyed on module danielebarchiesi@0: * name and each inner array keyed on plugin type name. danielebarchiesi@0: */ danielebarchiesi@0: function ctools_plugin_get_plugin_type_info($flush = FALSE) { danielebarchiesi@0: $info_loaded = &drupal_static('ctools_plugin_type_info_loaded', FALSE); danielebarchiesi@0: $all_type_info = &drupal_static('ctools_plugin_type_info', array()); danielebarchiesi@0: danielebarchiesi@0: // Only trigger info loading once. danielebarchiesi@0: if ($info_loaded && !$flush) { danielebarchiesi@0: return $all_type_info; danielebarchiesi@0: } danielebarchiesi@0: $info_loaded = TRUE; danielebarchiesi@0: danielebarchiesi@0: $cache = cache_get('ctools_plugin_type_info'); danielebarchiesi@0: if (!empty($cache->data) && !$flush) { danielebarchiesi@0: // Plugin type info cache is warm, use it. danielebarchiesi@0: $all_type_info = $cache->data; danielebarchiesi@0: } danielebarchiesi@0: else { danielebarchiesi@0: // Cache expired, refill it. danielebarchiesi@0: foreach (module_implements('ctools_plugin_type') as $module) { danielebarchiesi@0: $module_infos = array(); danielebarchiesi@0: $function = $module . '_ctools_plugin_type'; danielebarchiesi@0: $module_infos = $function(); danielebarchiesi@0: danielebarchiesi@0: foreach ($module_infos as $plugin_type_name => $plugin_type_info) { danielebarchiesi@0: // Apply defaults. Array addition will not overwrite pre-existing keys. danielebarchiesi@0: $plugin_type_info += array( danielebarchiesi@0: 'module' => $module, danielebarchiesi@0: 'type' => $plugin_type_name, danielebarchiesi@0: 'cache' => FALSE, danielebarchiesi@0: 'cache table' => 'cache', danielebarchiesi@0: 'classes' => array(), danielebarchiesi@0: 'use hooks' => FALSE, danielebarchiesi@0: 'defaults' => array(), danielebarchiesi@0: 'process' => '', danielebarchiesi@0: 'alterable' => TRUE, danielebarchiesi@0: 'extension' => 'inc', danielebarchiesi@0: 'info file' => FALSE, danielebarchiesi@0: 'hook' => $module . '_' . $plugin_type_name, danielebarchiesi@0: 'load themes' => FALSE, danielebarchiesi@0: ); danielebarchiesi@0: $all_type_info[$module][$plugin_type_name] = $plugin_type_info; danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: cache_set('ctools_plugin_type_info', $all_type_info); danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: return $all_type_info; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Reset all static caches that affect the result of ctools_get_plugins(). danielebarchiesi@0: */ danielebarchiesi@0: function ctools_get_plugins_reset() { danielebarchiesi@0: drupal_static_reset('ctools_plugins'); danielebarchiesi@0: drupal_static_reset('ctools_plugin_setup'); danielebarchiesi@0: drupal_static_reset('ctools_plugin_load_includes'); danielebarchiesi@0: drupal_static_reset('ctools_plugin_api_info'); danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Load plugins from a directory. danielebarchiesi@0: * danielebarchiesi@0: * @param $info danielebarchiesi@0: * The plugin info as returned by ctools_plugin_get_info() danielebarchiesi@0: * @param $file danielebarchiesi@0: * The file to load if we're looking for just one particular plugin. danielebarchiesi@0: * danielebarchiesi@0: * @return danielebarchiesi@0: * An array of information created for this plugin. danielebarchiesi@0: */ danielebarchiesi@0: function ctools_plugin_load_includes($info, $filename = NULL) { danielebarchiesi@0: // Keep a static array so we don't hit file_scan_directory more than necessary. danielebarchiesi@0: $all_files = &drupal_static(__FUNCTION__, array()); danielebarchiesi@0: danielebarchiesi@0: // store static of plugin arrays for reference because they can't be reincluded. danielebarchiesi@0: static $plugin_arrays = array(); danielebarchiesi@0: danielebarchiesi@0: // If we're being asked for all plugins of a type, skip any caching danielebarchiesi@0: // we may have done because this is an admin task and it's ok to danielebarchiesi@0: // spend the extra time. danielebarchiesi@0: if (!isset($filename)) { danielebarchiesi@0: $all_files[$info['module']][$info['type']] = NULL; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: if (!isset($all_files[$info['module']][$info['type']])) { danielebarchiesi@0: // If a filename was set, we will try to load our list of files from danielebarchiesi@0: // cache. This is considered normal operation and we try to reduce danielebarchiesi@0: // the time spent finding files. danielebarchiesi@0: if (isset($filename)) { danielebarchiesi@0: $cache = cache_get("ctools_plugin_files:$info[module]:$info[type]"); danielebarchiesi@0: if ($cache) { danielebarchiesi@0: $all_files[$info['module']][$info['type']] = $cache->data; danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: if (!isset($all_files[$info['module']][$info['type']])) { danielebarchiesi@0: $all_files[$info['module']][$info['type']] = array(); danielebarchiesi@0: // Load all our plugins. danielebarchiesi@0: $directories = ctools_plugin_get_directories($info); danielebarchiesi@0: $extension = (empty($info['info file']) || ($info['extension'] != 'inc')) ? $info['extension'] : 'info'; danielebarchiesi@0: danielebarchiesi@0: foreach ($directories as $module => $path) { danielebarchiesi@0: $all_files[$info['module']][$info['type']][$module] = file_scan_directory($path, '/\.' . $extension . '$/', array('key' => 'name')); danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: cache_set("ctools_plugin_files:$info[module]:$info[type]", $all_files[$info['module']][$info['type']]); danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: $file_list = $all_files[$info['module']][$info['type']]; danielebarchiesi@0: $plugins = array(); danielebarchiesi@0: danielebarchiesi@0: // Iterate through all the plugin .inc files, load them and process the hook danielebarchiesi@0: // that should now be available. danielebarchiesi@0: foreach (array_filter($file_list) as $module => $files) { danielebarchiesi@0: if ($filename) { danielebarchiesi@0: $files = isset($files[$filename]) ? array($filename => $files[$filename]) : array(); danielebarchiesi@0: } danielebarchiesi@0: foreach ($files as $file) { danielebarchiesi@0: if (!empty($info['info file'])) { danielebarchiesi@0: // Parse a .info file danielebarchiesi@0: $result = ctools_plugin_process_info($info, $module, $file); danielebarchiesi@0: } danielebarchiesi@0: else { danielebarchiesi@0: // Parse a hook. danielebarchiesi@0: $plugin = NULL; // ensure that we don't have something leftover from earlier. danielebarchiesi@0: danielebarchiesi@0: if (isset($plugin_arrays[$file->uri])) { danielebarchiesi@0: $identifier = $plugin_arrays[$file->uri]; danielebarchiesi@0: } danielebarchiesi@0: else { danielebarchiesi@0: danielebarchiesi@0: require_once DRUPAL_ROOT . '/' . $file->uri; danielebarchiesi@0: // .inc files have a special format for the hook identifier. danielebarchiesi@0: // For example, 'foo.inc' in the module 'mogul' using the plugin danielebarchiesi@0: // whose hook is named 'borg_type' should have a function named (deep breath) danielebarchiesi@0: // mogul_foo_borg_type() danielebarchiesi@0: danielebarchiesi@0: // If, however, the .inc file set the quasi-global $plugin array, we danielebarchiesi@0: // can use that and not even call a function. Set the $identifier danielebarchiesi@0: // appropriately and ctools_plugin_process() will handle it. danielebarchiesi@0: if (isset($plugin)) { danielebarchiesi@0: $plugin_arrays[$file->uri] = $plugin; danielebarchiesi@0: $identifier = $plugin; danielebarchiesi@0: } danielebarchiesi@0: else { danielebarchiesi@0: $identifier = $module . '_' . $file->name; danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: $result = ctools_plugin_process($info, $module, $identifier, dirname($file->uri), basename($file->uri), $file->name); danielebarchiesi@0: } danielebarchiesi@0: if (is_array($result)) { danielebarchiesi@0: $plugins = array_merge($plugins, $result); danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: return $plugins; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Get a list of directories to search for plugins of the given type. danielebarchiesi@0: * danielebarchiesi@0: * This utilizes hook_ctools_plugin_directory() to determine a complete list of danielebarchiesi@0: * directories. Only modules that implement this hook and return a string danielebarchiesi@0: * value will have their directories included. danielebarchiesi@0: * danielebarchiesi@0: * @param $info danielebarchiesi@0: * The $info array for the plugin as returned by ctools_plugin_get_info(). danielebarchiesi@0: * danielebarchiesi@0: * @return array $directories danielebarchiesi@0: * An array of directories to search. danielebarchiesi@0: */ danielebarchiesi@0: function ctools_plugin_get_directories($info) { danielebarchiesi@0: $directories = array(); danielebarchiesi@0: danielebarchiesi@0: foreach (module_implements('ctools_plugin_directory') as $module) { danielebarchiesi@0: $function = $module . '_ctools_plugin_directory'; danielebarchiesi@0: $result = $function($info['module'], $info['type']); danielebarchiesi@0: if ($result && is_string($result)) { danielebarchiesi@0: $directories[$module] = drupal_get_path('module', $module) . '/' . $result; danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: if (!empty($info['load themes'])) { danielebarchiesi@0: $themes = _ctools_list_themes(); danielebarchiesi@0: foreach ($themes as $name => $theme) { danielebarchiesi@0: if (!empty($theme->info['plugins'][$info['module']][$info['type']])) { danielebarchiesi@0: $directories[$name] = drupal_get_path('theme', $name) . '/' . $theme->info['plugins'][$info['module']][$info['type']]; danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: return $directories; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Helper function to build a ctools-friendly list of themes capable of danielebarchiesi@0: * providing plugins. danielebarchiesi@0: * danielebarchiesi@0: * @return array $themes danielebarchiesi@0: * A list of themes that can act as plugin providers, sorted parent-first with danielebarchiesi@0: * the active theme placed last. danielebarchiesi@0: */ danielebarchiesi@0: function _ctools_list_themes() { danielebarchiesi@0: static $themes; danielebarchiesi@0: if (is_null($themes)) { danielebarchiesi@0: $current = variable_get('theme_default', FALSE); danielebarchiesi@0: $themes = $active = array(); danielebarchiesi@0: $all_themes = list_themes(); danielebarchiesi@0: foreach ($all_themes as $name => $theme) { danielebarchiesi@0: // Only search from active themes danielebarchiesi@0: if (empty($theme->status) && $theme->name != $current) { danielebarchiesi@0: continue; danielebarchiesi@0: } danielebarchiesi@0: $active[$name] = $theme; danielebarchiesi@0: // Prior to drupal 6.14, $theme->base_themes does not exist. Build it. danielebarchiesi@0: if (!isset($theme->base_themes) && !empty($theme->base_theme)) { danielebarchiesi@0: $active[$name]->base_themes = ctools_find_base_themes($all_themes, $name); danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // Construct a parent-first list of all themes danielebarchiesi@0: foreach ($active as $name => $theme) { danielebarchiesi@0: $base_themes = isset($theme->base_themes) ? $theme->base_themes : array(); danielebarchiesi@0: $themes = array_merge($themes, $base_themes, array($name => $theme->info['name'])); danielebarchiesi@0: } danielebarchiesi@0: // Put the actual theme info objects into the array danielebarchiesi@0: foreach (array_keys($themes) as $name) { danielebarchiesi@0: if (isset($all_themes[$name])) { danielebarchiesi@0: $themes[$name] = $all_themes[$name]; danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // Make sure the current default theme always gets the last word danielebarchiesi@0: if ($current_key = array_search($current, array_keys($themes))) { danielebarchiesi@0: $themes += array_splice($themes, $current_key, 1); danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: return $themes; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Find all the base themes for the specified theme. danielebarchiesi@0: * danielebarchiesi@0: * Themes can inherit templates and function implementations from earlier themes. danielebarchiesi@0: * danielebarchiesi@0: * NOTE: this is a verbatim copy of system_find_base_themes(), which was not danielebarchiesi@0: * implemented until 6.14. It is included here only as a fallback for outdated danielebarchiesi@0: * versions of drupal core. danielebarchiesi@0: * danielebarchiesi@0: * @param $themes danielebarchiesi@0: * An array of available themes. danielebarchiesi@0: * @param $key danielebarchiesi@0: * The name of the theme whose base we are looking for. danielebarchiesi@0: * @param $used_keys danielebarchiesi@0: * A recursion parameter preventing endless loops. danielebarchiesi@0: * @return danielebarchiesi@0: * Returns an array of all of the theme's ancestors; the first element's value danielebarchiesi@0: * will be NULL if an error occurred. danielebarchiesi@0: */ danielebarchiesi@0: function ctools_find_base_themes($themes, $key, $used_keys = array()) { danielebarchiesi@0: $base_key = $themes[$key]->info['base theme']; danielebarchiesi@0: // Does the base theme exist? danielebarchiesi@0: if (!isset($themes[$base_key])) { danielebarchiesi@0: return array($base_key => NULL); danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: $current_base_theme = array($base_key => $themes[$base_key]->info['name']); danielebarchiesi@0: danielebarchiesi@0: // Is the base theme itself a child of another theme? danielebarchiesi@0: if (isset($themes[$base_key]->info['base theme'])) { danielebarchiesi@0: // Do we already know the base themes of this theme? danielebarchiesi@0: if (isset($themes[$base_key]->base_themes)) { danielebarchiesi@0: return $themes[$base_key]->base_themes + $current_base_theme; danielebarchiesi@0: } danielebarchiesi@0: // Prevent loops. danielebarchiesi@0: if (!empty($used_keys[$base_key])) { danielebarchiesi@0: return array($base_key => NULL); danielebarchiesi@0: } danielebarchiesi@0: $used_keys[$base_key] = TRUE; danielebarchiesi@0: return ctools_find_base_themes($themes, $base_key, $used_keys) + $current_base_theme; danielebarchiesi@0: } danielebarchiesi@0: // If we get here, then this is our parent theme. danielebarchiesi@0: return $current_base_theme; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Load plugin info for the provided hook; this is handled separately from danielebarchiesi@0: * plugins from files. danielebarchiesi@0: * danielebarchiesi@0: * @param $info danielebarchiesi@0: * The info array about the plugin as created by ctools_plugin_get_info() danielebarchiesi@0: * danielebarchiesi@0: * @return danielebarchiesi@0: * An array of info supplied by any hook implementations. danielebarchiesi@0: */ danielebarchiesi@0: function ctools_plugin_load_hooks($info) { danielebarchiesi@0: $hooks = array(); danielebarchiesi@0: foreach (module_implements($info['hook']) as $module) { danielebarchiesi@0: $result = ctools_plugin_process($info, $module, $module, drupal_get_path('module', $module)); danielebarchiesi@0: if (is_array($result)) { danielebarchiesi@0: $hooks = array_merge($hooks, $result); danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: return $hooks; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Process a single hook implementation of a ctools plugin. danielebarchiesi@0: * danielebarchiesi@0: * @param $info danielebarchiesi@0: * The $info array about the plugin as returned by ctools_plugin_get_info() danielebarchiesi@0: * @param $module danielebarchiesi@0: * The module that implements the plugin being processed. danielebarchiesi@0: * @param $identifier danielebarchiesi@0: * The plugin identifier, which is used to create the name of the hook danielebarchiesi@0: * function being called. danielebarchiesi@0: * @param $path danielebarchiesi@0: * The path where files utilized by this plugin will be found. danielebarchiesi@0: * @param $file danielebarchiesi@0: * The file that was loaded for this plugin, if it exists. danielebarchiesi@0: * @param $base danielebarchiesi@0: * The base plugin name to use. If a file was loaded for the plugin, this danielebarchiesi@0: * is the plugin to assume must be present. This is used to automatically danielebarchiesi@0: * translate the array to make the syntax more friendly to plugin danielebarchiesi@0: * implementors. danielebarchiesi@0: */ danielebarchiesi@0: function ctools_plugin_process($info, $module, $identifier, $path, $file = NULL, $base = NULL) { danielebarchiesi@0: if (is_array($identifier)) { danielebarchiesi@0: $result = $identifier; danielebarchiesi@0: } danielebarchiesi@0: else { danielebarchiesi@0: $function = $identifier . '_' . $info['hook']; danielebarchiesi@0: if (!function_exists($function)) { danielebarchiesi@0: return NULL; danielebarchiesi@0: } danielebarchiesi@0: $result = $function(); danielebarchiesi@0: if (!isset($result) || !is_array($result)) { danielebarchiesi@0: return NULL; danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // Automatically convert to the proper format that lets plugin implementations danielebarchiesi@0: // not nest arrays as deeply as they used to, but still support the older danielebarchiesi@0: // format where they do: danielebarchiesi@0: if ($base && (!isset($result[$base]) || !is_array($result[$base]))) { danielebarchiesi@0: $result = array($base => $result); danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: return _ctools_process_data($result, $info, $module, $path, $file); danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Fill in default values and run hooks for data loaded for one or danielebarchiesi@0: * more plugins. danielebarchiesi@0: */ danielebarchiesi@0: function _ctools_process_data($result, $plugin_type_info, $module, $path, $file) { danielebarchiesi@0: // Fill in global defaults. danielebarchiesi@0: foreach ($result as $name => $plugin) { danielebarchiesi@0: $result[$name] += array( danielebarchiesi@0: 'module' => $module, danielebarchiesi@0: 'name' => $name, danielebarchiesi@0: 'path' => $path, danielebarchiesi@0: 'file' => $file, danielebarchiesi@0: 'plugin module' => $plugin_type_info['module'], danielebarchiesi@0: 'plugin type' => $plugin_type_info['type'], danielebarchiesi@0: ); danielebarchiesi@0: danielebarchiesi@0: // Fill in plugin-specific defaults, if they exist. danielebarchiesi@0: if (!empty($plugin_type_info['defaults'])) { danielebarchiesi@0: if (is_array($plugin_type_info['defaults'])) { danielebarchiesi@0: $result[$name] += $plugin_type_info['defaults']; danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // Allow the plugin to be altered before processing. danielebarchiesi@0: if (!empty($plugin_type_info['alterable']) && $plugin_type_info['alterable']) { danielebarchiesi@0: drupal_alter('ctools_plugin_pre', $result[$name], $plugin_type_info); danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // Allow the plugin owner to do additional processing. danielebarchiesi@0: if (!empty($plugin_type_info['process']) && $function = ctools_plugin_get_function($plugin_type_info, 'process')) { danielebarchiesi@0: $function($result[$name], $plugin_type_info); danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // Allow the plugin to be altered after processing. danielebarchiesi@0: if (!empty($plugin_type_info['alterable']) && $plugin_type_info['alterable']) { danielebarchiesi@0: drupal_alter('ctools_plugin_post', $result[$name], $plugin_type_info); danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: return $result; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Process an info file for plugin information, rather than a hook. danielebarchiesi@0: */ danielebarchiesi@0: function ctools_plugin_process_info($info, $module, $file) { danielebarchiesi@0: $result = drupal_parse_info_file($file->uri); danielebarchiesi@0: if ($result) { danielebarchiesi@0: $result = array($file->name => $result); danielebarchiesi@0: return _ctools_process_data($result, $info, $module, dirname($file->uri), basename($file->uri)); danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Ask a module for info about a particular plugin type. danielebarchiesi@0: */ danielebarchiesi@0: function ctools_plugin_get_info($module, $type) { danielebarchiesi@0: $all_info = ctools_plugin_get_plugin_type_info(); danielebarchiesi@0: return isset($all_info[$module][$type]) ? $all_info[$module][$type] : array(); danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Get a function from a plugin, if it exists. If the plugin is not already danielebarchiesi@0: * loaded, try ctools_plugin_load_function() instead. danielebarchiesi@0: * danielebarchiesi@0: * @param $plugin_definition danielebarchiesi@0: * The loaded plugin type. danielebarchiesi@0: * @param $function_name danielebarchiesi@0: * The identifier of the function. For example, 'settings form'. danielebarchiesi@0: * danielebarchiesi@0: * @return danielebarchiesi@0: * The actual name of the function to call, or NULL if the function danielebarchiesi@0: * does not exist. danielebarchiesi@0: */ danielebarchiesi@0: function ctools_plugin_get_function($plugin_definition, $function_name) { danielebarchiesi@0: // If cached the .inc file may not have been loaded. require_once is quite safe danielebarchiesi@0: // and fast so it's okay to keep calling it. danielebarchiesi@0: if (isset($plugin_definition['file'])) { danielebarchiesi@0: // Plugins that are loaded from info files have the info file as danielebarchiesi@0: // $plugin['file']. Don't try to run those. danielebarchiesi@0: $info = ctools_plugin_get_info($plugin_definition['plugin module'], $plugin_definition['plugin type']); danielebarchiesi@0: if (empty($info['info file'])) { danielebarchiesi@0: require_once DRUPAL_ROOT . '/' . $plugin_definition['path'] . '/' . $plugin_definition['file']; danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: if (!isset($plugin_definition[$function_name])) { danielebarchiesi@0: return; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: if (is_array($plugin_definition[$function_name]) && isset($plugin_definition[$function_name]['function'])) { danielebarchiesi@0: $function = $plugin_definition[$function_name]['function']; danielebarchiesi@0: if (isset($plugin_definition[$function_name]['file'])) { danielebarchiesi@0: $file = $plugin_definition[$function_name]['file']; danielebarchiesi@0: if (isset($plugin_definition[$function_name]['path'])) { danielebarchiesi@0: $file = $plugin_definition[$function_name]['path'] . '/' . $file; danielebarchiesi@0: } danielebarchiesi@0: require_once DRUPAL_ROOT . '/' . $file; danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: else { danielebarchiesi@0: $function = $plugin_definition[$function_name]; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: if (function_exists($function)) { danielebarchiesi@0: return $function; danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Load a plugin and get a function name from it, returning success only danielebarchiesi@0: * if the function exists. danielebarchiesi@0: * danielebarchiesi@0: * @param $module danielebarchiesi@0: * The module that owns the plugin type. danielebarchiesi@0: * @param $type danielebarchiesi@0: * The type of plugin. danielebarchiesi@0: * @param $id danielebarchiesi@0: * The id of the specific plugin to load. danielebarchiesi@0: * @param $function_name danielebarchiesi@0: * The identifier of the function. For example, 'settings form'. danielebarchiesi@0: * danielebarchiesi@0: * @return danielebarchiesi@0: * The actual name of the function to call, or NULL if the function danielebarchiesi@0: * does not exist. danielebarchiesi@0: */ danielebarchiesi@0: function ctools_plugin_load_function($module, $type, $id, $function_name) { danielebarchiesi@0: $plugin = ctools_get_plugins($module, $type, $id); danielebarchiesi@0: return ctools_plugin_get_function($plugin, $function_name); danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Get a class from a plugin, if it exists. If the plugin is not already danielebarchiesi@0: * loaded, try ctools_plugin_load_class() instead. danielebarchiesi@0: * danielebarchiesi@0: * @param $plugin_definition danielebarchiesi@0: * The loaded plugin type. danielebarchiesi@0: * @param $class_name danielebarchiesi@0: * The identifier of the class. For example, 'handler'. danielebarchiesi@0: * danielebarchiesi@0: * @return danielebarchiesi@0: * The actual name of the class to call, or NULL if the class does not exist. danielebarchiesi@0: */ danielebarchiesi@0: function ctools_plugin_get_class($plugin_definition, $class_name) { danielebarchiesi@0: // If cached the .inc file may not have been loaded. require_once is quite safe danielebarchiesi@0: // and fast so it's okay to keep calling it. danielebarchiesi@0: if (isset($plugin_definition['file'])) { danielebarchiesi@0: // Plugins that are loaded from info files have the info file as danielebarchiesi@0: // $plugin['file']. Don't try to run those. danielebarchiesi@0: $info = ctools_plugin_get_info($plugin_definition['plugin module'], $plugin_definition['plugin type']); danielebarchiesi@0: if (empty($info['info file'])) { danielebarchiesi@0: require_once DRUPAL_ROOT . '/' . $plugin_definition['path'] . '/' . $plugin_definition['file']; danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: $return = FALSE; danielebarchiesi@0: if (!isset($plugin_definition[$class_name])) { danielebarchiesi@0: return; danielebarchiesi@0: } danielebarchiesi@0: else if (is_string($plugin_definition[$class_name])) { danielebarchiesi@0: // Plugin uses the string form shorthand. danielebarchiesi@0: $return = $plugin_definition[$class_name]; danielebarchiesi@0: } danielebarchiesi@0: else if (isset($plugin_definition[$class_name]['class'])) { danielebarchiesi@0: // Plugin uses the verbose array form. danielebarchiesi@0: $return = $plugin_definition[$class_name]['class']; danielebarchiesi@0: } danielebarchiesi@0: // @todo consider adding an else {watchdog(...)} here danielebarchiesi@0: danielebarchiesi@0: return ($return && class_exists($return)) ? $return : NULL; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Load a plugin and get a class name from it, returning success only if the danielebarchiesi@0: * class exists. danielebarchiesi@0: * danielebarchiesi@0: * @param $module danielebarchiesi@0: * The module that owns the plugin type. danielebarchiesi@0: * @param $type danielebarchiesi@0: * The type of plugin. danielebarchiesi@0: * @param $id danielebarchiesi@0: * The id of the specific plugin to load. danielebarchiesi@0: * @param $class_name danielebarchiesi@0: * The identifier of the class. For example, 'handler'. danielebarchiesi@0: * danielebarchiesi@0: * @return danielebarchiesi@0: * The actual name of the class to call, or NULL if the class does not exist. danielebarchiesi@0: */ danielebarchiesi@0: function ctools_plugin_load_class($module, $type, $id, $class_name) { danielebarchiesi@0: $plugin = ctools_get_plugins($module, $type, $id); danielebarchiesi@0: return ctools_plugin_get_class($plugin, $class_name); danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Sort callback for sorting plugins naturally. danielebarchiesi@0: * danielebarchiesi@0: * Sort first by weight, then by title. danielebarchiesi@0: */ danielebarchiesi@0: function ctools_plugin_sort($a, $b) { danielebarchiesi@0: if (is_object($a)) { danielebarchiesi@0: $a = (array) $a; danielebarchiesi@0: } danielebarchiesi@0: if (is_object($b)) { danielebarchiesi@0: $b = (array) $b; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: if (empty($a['weight'])) { danielebarchiesi@0: $a['weight'] = 0; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: if (empty($b['weight'])) { danielebarchiesi@0: $b['weight'] = 0; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: if ($a['weight'] == $b['weight']) { danielebarchiesi@0: return strnatcmp(strtolower($a['title']), strtolower($b['title'])); danielebarchiesi@0: } danielebarchiesi@0: return ($a['weight'] < $b['weight']) ? -1 : 1; danielebarchiesi@0: }