$info) { $code = ''; if (!function_exists("{$info['module']}_features_api")) { /* @see \ctools_component_features_api() */ $code .= 'function ' . $info['module'] . '_features_api() { return ctools_component_features_api("' . $info['module'] . '"); }'; } // Ctools component with owner defined as "ctools". /* @see \hook_features_api() */ if (!function_exists("{$component}_features_api") && $info['module'] === 'ctools') { /* @see \ctools_component_features_api() */ $code .= 'function ' . $component . '_features_api() { return ctools_component_features_api("' . $component . '"); }'; } /* @see \hook_features_export() */ if (!function_exists("{$component}_features_export")) { /* @see \ctools_component_features_export() */ $code .= 'function ' . $component . '_features_export($data, &$export, $module_name = "") { return ctools_component_features_export("' . $component . '", $data, $export, $module_name); }'; } /* @see \hook_features_export_options() */ if (!function_exists("{$component}_features_export_options")) { /* @see \ctools_component_features_export_options() */ $code .= 'function ' . $component . '_features_export_options() { return ctools_component_features_export_options("' . $component . '"); }'; } /* @see \hook_features_export_render() */ if (!function_exists("{$component}_features_export_render")) { /* @see \ctools_component_features_export_render() */ $code .= 'function ' . $component . '_features_export_render($module, $data, $export = NULL) { return ctools_component_features_export_render("' . $component . '", $module, $data, $export); }'; } /* @see \hook_features_revert() */ if (!function_exists("{$component}_features_revert")) { /* @see \ctools_component_features_revert() */ $code .= 'function ' . $component . '_features_revert($module) { return ctools_component_features_revert("' . $component . '", $module); }'; } eval($code); } } } /** * Implements hook_features_api(). */ function ctools_features_api() { return array( 'ctools' => array( 'name' => 'CTools export API', 'feature_source' => TRUE, 'duplicates' => FEATURES_DUPLICATES_ALLOWED, // CTools API integration does not include a default hook declaration as // it is not a proper default hook. // 'default_hook' => 'ctools_plugin_api',. ), ); } /** * Implements hook_features_export(). * Adds references to the ctools mothership hook, ctools_plugin_api(). */ function ctools_features_export($data, &$export, $module_name = '') { // Add ctools dependency. $export['dependencies']['ctools'] = 'ctools'; // Add the actual ctools components which will need to be accounted for in // hook_ctools_plugin_api(). The components are actually identified by a // delimited list of values: `module_name:api:current_version`. foreach ($data as $component) { if ($info = _ctools_features_get_info($component)) { $identifier = "{$info['module']}:{$info['api']}:{$info['current_version']}"; $export['features']['ctools'][$identifier] = $identifier; } } return array(); } /** * Implements hook_features_export_render(). * Adds the ctools mothership hook, ctools_plugin_api(). */ function ctools_features_export_render($module, $data) { $component_exports = array(); foreach ($data as $component) { $code = array(); if ($info = _ctools_features_get_info($component)) { // For background on why we change the output for hook_views_api() // see http://drupal.org/node/1459120. if ($info['module'] == 'views') { $code[] = ' return array("api" => "3.0");'; } else { $code[] = ' if ($module == "' . $info['module'] . '" && $api == "' . $info['api'] . '") {'; $code[] = ' return array("version" => "' . $info['current_version'] . '");'; $code[] = ' }'; } } ctools_include('plugins'); $plugin_api_hook_name = ctools_plugin_api_get_hook($info['module'], $info['api']); if (array_key_exists($plugin_api_hook_name, $component_exports)) { $component_exports[$plugin_api_hook_name]['code'] .= "\n" . implode("\n", $code); } else { $component_exports[$plugin_api_hook_name] = array( 'code' => implode("\n", $code), 'args' => '$module = NULL, $api = NULL', ); } } return $component_exports; } /** * Master implementation of hook_features_api() for all ctools components. * * This is not registered as a hook implementation by itself. Instead, it gets * called from other implementations, especially those dynamically declared in * ctools_features_declare_functions(). * * Note that this master hook does not use $component like the others, but uses the * component module's namespace instead. * * @param string $module_name * The module on behalf of which ctools wants to declare components. * * @return array[] * Component definitions for the given module. * * @see \ctools_features_declare_functions() * @see \hook_features_api() */ function ctools_component_features_api($module_name) { $api = array(); foreach (_ctools_features_get_info() as $component => $info) { // If module owner is set to "ctools" we need to compare the component. if ($info['module'] == $module_name || ($info['module'] === 'ctools' && $component == $module_name)) { $api[$component] = $info; } } return $api; } /** * Master implementation of hook_features_export_options() for all ctools components. * * This is not registered as a hook implementation by itself. Instead, it gets * called from other implementations, especially those dynamically declared in * ctools_features_declare_functions(). * * @param string $component * Component name. * * @return string[] * Exportable options. * * @see \hook_features_export_options() */ function ctools_component_features_export_options($component) { $options = array(); ctools_include('export'); $schema = ctools_export_get_schema($component); if ($schema && $schema['export']['bulk export']) { if (!empty($schema['export']['list callback']) && function_exists($schema['export']['list callback'])) { $options = $schema['export']['list callback'](); } else { $options = _ctools_features_export_default_list($component, $schema); } } asort($options); return $options; } /** * Master implementation of hook_features_export() for all ctools components. * * This is not registered as a hook implementation by itself. Instead, it gets * called from other implementations, especially those dynamically declared in * ctools_features_declare_functions(). * * @param string $component * Component name. * @param string[] $data * Identifiers of objects to export. * @param array $export * Export array. * @param string $module_name * Feature module name. * * @return string[][] * The pipe array. * * @see \hook_features_export() */ function ctools_component_features_export($component, $data, &$export, $module_name = '') { global $features_ignore_conflicts; // Add the actual implementing module as a dependency. $info = _ctools_features_get_info(); if ($module_name !== $info[$component]['module']) { $export['dependencies'][$info[$component]['module']] = $info[$component]['module']; } $map = empty($features_ignore_conflicts) ? features_get_claimed_components($component) : array(); // Add the components. foreach ($data as $object_name) { if ($object = _ctools_features_export_crud_load($component, $object_name)) { // If this object is provided as a default by a different feature, don't // export and add that module as a dependency instead. $other_features = isset($map[$object_name]) ? $map[$object_name] : array(); if ($other_features && !in_array($module_name, $other_features)) { // Choose the first conflicting feature as dependency. $other_feature = reset($other_features); $export['dependencies'][$other_feature] = $other_feature; if (isset($export['features'][$component][$object_name])) { unset($export['features'][$component][$object_name]); } } // Otherwise, add the component. else { $export['features'][$component][$object_name] = $object_name; } } } // Let CTools handle API integration for this component. return array('ctools' => array($component)); } /** * Master implementation of hook_features_export_render() for all ctools components. * * This is not registered as a hook implementation by itself. Instead, it gets * called from other implementations, especially those dynamically declared in * ctools_features_declare_functions(). * * @param string $component * The component name. * @param string $module * Feature module name. * @param string[] $data * Identifiers of objects to be rendered. * * @return string[] * Format: $[$hook_name] = $function_body. * * @see \hook_features_export_render() */ function ctools_component_features_export_render($component, $module, $data) { // Reset the export display static to prevent clashes. drupal_static_reset('panels_export_display'); ctools_include('export'); $schema = ctools_export_get_schema($component); if (function_exists($schema['export']['to hook code callback'])) { $export = $schema['export']['to hook code callback']($data, $module); $code = explode("{\n", $export); array_shift($code); $code = explode('}', implode("{\n", $code)); array_pop($code); $code = implode('}', $code); } else { $code = ' $export = array();' . "\n\n"; foreach ($data as $object_name) { if ($object = _ctools_features_export_crud_load($component, $object_name)) { $identifier = $schema['export']['identifier']; $code .= _ctools_features_export_crud_export($component, $object, ' '); $code .= " \$export[" . ctools_var_export($object_name) . "] = \${$identifier};\n\n"; } } $code .= ' return $export;'; } return array($schema['export']['default hook'] => $code); } /** * Master implementation of hook_features_revert() for all ctools components. * * This is not registered as a hook implementation by itself. Instead, it gets * called from other implementations, especially those dynamically declared in * ctools_features_declare_functions(). * * @param string $component * Component name. * @param string $module * Feature module name. * * @see \hook_features_revert() */ function ctools_component_features_revert($component, $module) { if ($objects = features_get_default($component, $module)) { foreach ($objects as $name => $object) { // Some things (like views) do not use the machine name as key // and need to be loaded explicitly in order to be deleted. $object = ctools_export_crud_load($component, $name); if ($object && ($object->export_type & EXPORT_IN_DATABASE)) { _ctools_features_export_crud_delete($component, $object); } } } } /** * Helper function to return various ctools information for components. * * @param string|null $identifier * One of: * - A table name whose schema has ctools 'export' information. * - A string "$module:$api:$current_version". * - NULL, to return all components. * @param bool $reset * If TRUE, the static cache is reset. * * @return array[]|array|false * An array of components, or a specific component, or a stub array with * some component information, or FALSE if the component was not found. * * @see \hook_features_api() */ function _ctools_features_get_info($identifier = NULL, $reset = FALSE) { static $components; if (!isset($components) || $reset) { $components = array(); $modules = features_get_info(); ctools_include('export'); drupal_static('ctools_export_get_schemas', NULL, $reset); foreach (ctools_export_get_schemas_by_module() as $module => $schemas) { foreach ($schemas as $table => $schema) { if ($schema['export']['bulk export']) { // Let the API owner take precedence as the owning module. $api_module = isset($schema['export']['api']['owner']) ? $schema['export']['api']['owner'] : $module; $components[$table] = array( 'name' => isset($modules[$api_module]->info['name']) ? $modules[$api_module]->info['name'] : $api_module, 'default_hook' => $schema['export']['default hook'], 'default_file' => FEATURES_DEFAULTS_CUSTOM, 'module' => $api_module, 'feature_source' => TRUE, ); if (isset($schema['export']['api'])) { $components[$table] += array( 'api' => $schema['export']['api']['api'], 'default_filename' => $schema['export']['api']['api'], 'current_version' => $schema['export']['api']['current_version'], ); } } } } } // Return information specific to a particular component. if (isset($identifier)) { // Identified by the table name. if (isset($components[$identifier])) { return $components[$identifier]; } // New API identifier. Allows non-exportables related CTools APIs to be // supported by an explicit `module:api:current_version` key. elseif (substr_count($identifier, ':') === 2) { list($module, $api, $current_version) = explode(':', $identifier); // If a schema component matches the provided identifier, provide that // information. This also ensures that the version number is up to date. foreach ($components as $table => $info) { if ($info['module'] == $module && $info['api'] == $api && $info['current_version'] >= $current_version) { return $info; } } // Fallback to just giving back what was provided to us. return array('module' => $module, 'api' => $api, 'current_version' => $current_version); } return FALSE; } return $components; } /** * Wrapper around ctools_export_crud_export() for < 1.7 compatibility. * * @param string $table * Table name whose schema contains ctools 'export' information. * @param object $object * Object that was loaded from the table. * @param string $indent * Indentation to prepend to each line of code. * * @return string * PHP value expression. */ function _ctools_features_export_crud_export($table, $object, $indent = '') { return ctools_api_version('1.7') ? ctools_export_crud_export($table, $object, $indent) : ctools_export_object($table, $object, $indent); } /** * Wrapper around ctools_export_crud_load() for < 1.7 compatibility. * * @param string $table * Table name whose schema contains ctools 'export' information. * @param string $name * Identifier of the object to load. * * @return object|false|null * The loaded object, or FALSE or NULL if not found. */ function _ctools_features_export_crud_load($table, $name) { if (ctools_api_version('1.7')) { return ctools_export_crud_load($table, $name); } elseif ($objects = ctools_export_load_object($table, 'names', array($name))) { return array_shift($objects); } return FALSE; } /** * Wrapper around ctools_export_default_list() for < 1.7 compatibility. * * @param string $table * Table name whose schema contains ctools 'export' information. * @param array $schema * Schema array for the table. * * @return string[] * Format: $[$name] = $label * List of exportable object names. */ function _ctools_features_export_default_list($table, $schema) { if (ctools_api_version('1.7')) { return ctools_export_default_list($table, $schema); } elseif ($objects = ctools_export_load_object($table, 'all')) { return drupal_map_assoc(array_keys($objects)); } return array(); } /** * Wrapper around ctools_export_crud_delete() for < 1.7 compatibility. * * @param string $table * Table name whose schema contains ctools 'export' information. * @param object $object * The fully populated object to delete, or the export key. * * @see \ctools_export_crud_delete() */ function _ctools_features_export_crud_delete($table, $object) { if (ctools_api_version('1.7')) { ctools_export_crud_delete($table, $object); } else { $schema = ctools_export_get_schema($table); $export = $schema['export']; db_query("DELETE FROM {{$table}} WHERE {$export['key']} = '%s'", $object->{$export['key']}); } } /** * Implements hook_features_export_render() for page_manager. */ function page_manager_pages_features_export_render($module, $data) { // Reset the export display static to prevent clashes. drupal_static_reset('panels_export_display'); // Ensure that handlers have their code included before exporting. page_manager_get_tasks(); return ctools_component_features_export_render('page_manager_pages', $module, $data); } /** * Implements hook_features_revert() for page_manager. */ function page_manager_pages_features_revert($module) { if ($pages = features_get_default('page_manager_pages', $module)) { require_once drupal_get_path('module', 'ctools') . '/page_manager/plugins/tasks/page.inc'; foreach ($pages as $page) { // Skip menu rebuild for better performance. page_manager_page_delete($page, TRUE); } menu_rebuild(); } } /** * Implements hook_features_pipe_COMPONENT_alter() for views_view. */ function views_features_pipe_views_view_alter(&$pipe, $data, $export) { // @todo Remove this check before next stable release. if (!function_exists('views_plugin_list')) { return; } $map = array_flip($data); foreach (views_plugin_list() as $plugin) { foreach ($plugin['views'] as $view_name) { if (isset($map[$view_name])) { $pipe['dependencies'][$plugin['module']] = $plugin['module']; } } } }