diff --git ctools.module ctools.module index 1255b04..6684e42 100644 --- ctools.module +++ ctools.module @@ -284,7 +284,7 @@ function _ctools_passthrough(&$items, $type = 'theme') { require_once './' . $file->filename; list($tool) = explode('.', $file->name, 2); - $function = 'ctools_' . $tool . '_' . $type; + $function = 'ctools_' . str_replace ('-', '_', $tool) . '_' . $type; if (function_exists($function)) { $function($items); } @@ -760,3 +760,60 @@ function ctools_file_check_directory(&$directory, $mode = 0, $form_item = NULL) return TRUE; } + +/** + * Menu loader; Load exportables when used with export-ui. + */ +function ctools_export_ui_load($export_name, $map_array) { + $return = &ctools_static(__FUNCTION__, FALSE); + + if (!$return) { + ctools_include('export'); + ctools_include('plugins'); + // TODO: We include export-ui for ctools_export_ui_defaults(). + // Maybe ctools_get_plugins() should do it for us. + ctools_include('export-ui'); + + $path = implode('/', $map_array); + + // Identify the plugin by the menu item path. + $plugin_item = array(); + foreach (ctools_get_plugins('ctools', 'export_ui') as $plugin) { + if ($plugin['has menu']) { + $prefix = $plugin['menu']['menu prefix'] .'/'. $plugin['menu']['menu item']; + if (strpos($path, $prefix) === 0) { + $plugin_item = $plugin; + break; + } + } + } + + if ($plugin_item) { + // Get the load callback. + $schema = ctools_export_get_schema($plugin_item['schema']); + $return = call_user_func($schema['export']['load callback'], $export_name); + } + } + + return $return; +} + +/** + * Menu access callback for various tasks of export-ui. + */ +function ctools_export_ui_task_access($export, $op) { + if (!user_access('administer site configuration')) { + return FALSE; + } + switch ($op) { + case 'revert': + return ($export->export_type & EXPORT_IN_DATABASE) && ($export->export_type & EXPORT_IN_CODE); + case 'delete': + return ($export->export_type & EXPORT_IN_DATABASE) && !($export->export_type & EXPORT_IN_CODE); + case 'disable': + return empty($export->disabled); + case 'enable': + return !empty($export->disabled); + } + return TRUE; +} diff --git includes/export-ui.admin.inc includes/export-ui.admin.inc new file mode 100644 index 0000000..718f115 --- /dev/null +++ includes/export-ui.admin.inc @@ -0,0 +1,365 @@ + 'ctools_export_ui_admin', + '#exports' => $exports, + '#plugin' => $plugin, + ); + + return $form; +} + + +/** + * Generates the omnibus export definition editing form. + * + * @param $op + * The type of form to build. Either "add", "view" or "edit" + * @param $export + * The export. + * + * @return + * A Drupal form array. + */ +function ctools_export_ui_form(&$form_state, $plugin_name, $op, $export = NULL) { + ctools_include('export'); + + $plugin = ctools_get_export_ui($plugin_name); + $export_key = $plugin['export']['key']; + + $export = !$export ? ctools_export_new_object($plugin['schema']) : $export; + + // Set title. + if ($op == 'edit') { + if ($export->export_type & EXPORT_IN_DATABASE) { + $title_op = $op; + } + else { + $title_op = 'view'; + } + } + else { + $title_op = $op; + } + + // Replace %title that might be there with the exportable title. + drupal_set_title(str_replace('%title', $export->{$export_key}, $plugin['form']['string']['title'][$title_op])); + + + $form['export'] = array( + '#type' => 'value', + '#value' => $export, + ); + + $form['plugin'] = array( + '#type' => 'value', + '#value' => $plugin, + ); + + $form['op'] = array( + '#type' => 'value', + '#value' => $op, + ); + + $form['info'] = array( + '#type' => 'fieldset', + ); + + if ($op == 'clone') { + $export->{$export_key} = 'clone_of_'. $export->{$export_key}; + } + + $form['info'][$export_key] = array( + // TODO: Add human readable name on key in export.inc? + '#title' => ucfirst($export_key), + '#type' => 'textfield', + '#default_value' => !empty($export->{$export_key}) ? $export->{$export_key} : '', + '#description' => t('The unique ID for this @export', array('@export' => $title)), + '#required' => TRUE, + '#maxlength' => 255, + ); + + if ($op === 'edit') { + $form['info'][$export_key]['#disabled'] = TRUE; + $form['info'][$export_key]['#value'] = $export->{$export_key}; + } + + $form['info']['tag'] = array( + '#title' => t('Tag'), + '#type' => 'textfield', + '#default_value' => !empty($export->tag) ? $export->tag : '', + '#description' => t('Tag for this @export', array('@export' => $title)), + ); + + // Add plugin's form definitions. + if (!empty($plugin['form']['settings']) && function_exists($plugin['form']['settings'])) { + // Pass $form by reference. + // TODO: validation and submit handlers should be declared by settings form + // callback? + $plugin['form']['settings']($form, $plugin_name, $op, $export); + } + + // Add buttons. + $form['buttons']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + ); + $form['buttons']['delete'] = array( + '#type' => 'submit', + '#value' => t('Delete'), + '#access' => $op === 'edit', + ); + + return $form; +} + +/** + * Provide a form to confirm one of the provided actions. + */ +function ctools_export_ui_confirm(&$form_state, $plugin_name, $op = 'delete', $export) { + $plugin = ctools_get_export_ui($plugin_name); + + $form = array(); + $form['export'] = array('#type' => 'value', '#value' => $export); + $form['action'] = array('#type' => 'value', '#value' => $op); + $form['plugin'] = array('#type' => 'value', '#value' => $plugin); + + $export_key = $plugin['export']['key']; + $question = str_replace('!action', $plugin['allowed operations'][$op], $plugin['form']['string']['confirmation']['question']); + $question = str_replace('%title', $export->{$export_key}, $plugin['form']['string']['confirmation']['question']); + + $form = confirm_form($form, + $question, + _ctools_export_ui_redirect($plugin['name']), + $plugin['form']['string']['confirmation'][$op], + drupal_ucfirst($plugin['allowed operations'][$op]), t('Cancel') + ); + return $form; +} + +/** + * Submit handler for the ctools_export_ui_confirm form. + */ +function ctools_export_ui_confirm_submit($form, &$form_state) { + ctools_include('export'); + $plugin = $form['plugin']['#value']; + + $export = $form_state['values']['export']; + switch ($form_state['values']['action']) { + case 'revert': + case 'delete': + $schema = ctools_export_get_schema($plugin['schema']); + call_user_func($schema['export']['delete callback'], $export); + break; + case 'disable': + ctools_export_set_object_status($export); + break; + case 'enable': + ctools_export_set_object_status($export, FALSE); + break; + } + $form_state['redirect'] = _ctools_export_ui_redirect($plugin['name']); +} + +/** + * Enable or disable an exportable. + */ +function ctools_export_ui_switcher(&$form_state, $plugin_name, $op = 'enable', $export) { + $plugin = ctools_get_export_ui($plugin_name); + $export_key = $plugin['export']['key']; + + ctools_export_set_object_status($export, $op != 'enable'); + drupal_set_message(str_replace('%title', $export->{$export_key}, $plugin['form']['string']['message'][$op])); + drupal_goto(_ctools_export_ui_redirect($plugin['name'])); +} + +/** + * Page callback for import form. Switches form output to export form + * if import submission has occurred. + */ +function ctools_export_ui_import_page($plugin_name) { + if (!empty($_POST) && $_POST['form_id'] == 'ctools_export_ui_form') { + return drupal_get_form('ctools_export_ui_form', $plugin_name, 'add'); + } + return drupal_get_form('ctools_export_ui_import', $plugin_name); +} + +/** + * Import form. Provides simple helptext instructions and textarea for + * pasting a export definition. + */ +function ctools_export_ui_import($form_state, $plugin_name) { + $plugin = ctools_get_export_ui($plugin_name); + + drupal_set_title($plugin['form']['string']['title']['import']); + + $form = array(); + $form['plugin'] = array( + '#type' => 'value', + '#value' => $plugin, + ); + + $form['help'] = array( + '#type' => 'item', + '#value' => $plugin['form']['string']['help']['import'], + ); + $form['import'] = array( + '#title' => t('@plugin object', array('@plugin' => $plugin['title'])), + '#type' => 'textarea', + '#rows' => 10, + '#required' => TRUE, + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Import'), + ); + return $form; +} + +/** + * Import form submit handler. Evaluates import code and transfers to + * export definition form. + */ +function ctools_export_ui_import_submit($form, &$form_state) { + $plugin = $form['plugin']['#value']; + $export_key = $plugin['export']['key']; + + $items = array(); + if ($import = $form_state['values']['import']) { + ob_start(); + $export = eval($import); + ob_end_clean(); + } + + if (is_object($export)) { + if (!empty($export->{$export_key})) { + + $schema = ctools_export_get_schema($plugin['schema']); + + // TODO: If $schema['export']['load callback'] == FALSE we should issue + // an error - but maybe it should happen in a more generic place. + if (call_user_func($schema['export']['load callback'], $export->{$export_key})) { + drupal_set_message(t('A @plugin with this name already exists. Please remove the existing export before importing this definition.', array('@plugin' => $plugin['title'])), 'error'); + } + else { + drupal_set_title($plugin['form']['string']['title']['export']); + $output = drupal_get_form('ctools_export_ui_form', $plugin['name'], 'add', (object) $export); + print theme('page', $output); + exit; + } + } + } + else { + drupal_set_message(t('An error occurred while importing. Please check your export definition.', 'error')); + + $form_state['redirect'] = _ctools_export_ui_redirect($plugin['name']); + } +} + +/** + * Provides a form with an exported export definition for use in modules. + * + * @param $cid + * A export id. + * + * @return + * A FormAPI array. + */ +function ctools_export_ui_export(&$form_state, $export, $plugin_name) { + ctools_include('export'); + $plugin = ctools_get_export_ui($plugin_name); + + $export_key = $plugin['export']['key']; + + drupal_set_title(t('Export %title', array('%title' => $export->{$export_key}))); + + return ctools_export_form($form_state, ctools_export_ui_export_object($plugin_name, $export), t('Export')); +} + +/** + * Validate export id values. + */ +function ctools_export_ui_form_name_validate($element, &$form_state) { + $plugin = $element['#plugin']; + // Check for string identifier sanity + if (!preg_match('!^[a-z0-9_-]+$!', $element['#value'])) { + form_set_error('name', t('The export id can only consist of lowercase letters, dashes, underscores, and numbers.')); + } + // Check for name collision + else if ($exists = ctools_export_load_object($plugin->name, 'names', array($element['#value']))) { + form_set_error('name', t('A @plugin with this name already exists. Please choose another name or delete the existing export before creating a new one.', array('@plugin' => $pluin->title))); + } +} + +/** + * Produces a export object from submitted form values. + * + * @param $form + * A form array with submitted values + * + * @return + * A export object + */ +function ctools_export_ui_form_process($form, $form_state) { + $plugin = $form['plugin']['#value']; + $export_key = $plugin['export']['key']; + + $values = $form_state['values']; + + $export = ctools_export_new_object($plugin['schema']); + + $schema = ctools_export_get_schema($plugin['schema']); + foreach (array_keys($schema['fields']) as $key) { + if(isset($values[$key])) { + $export->{$key} = $values[$key]; + } + } + + return $export; +} + +/** + * Submit handler for main ctools_export_ui form. + */ +function ctools_export_ui_form_submit($form, &$form_state) { + $plugin = $form['plugin']['#value']; + $export_key = $plugin['export']['key']; + + switch ($form_state['clicked_button']['#id']) { + // Send user to delete confirmation page + case 'edit-delete': + if (isset($form_state['values']['export']->{$export_key})) { + $menu_prefix = _ctools_export_ui_redirect($plugin['name']); + $form_state['redirect'] = "$menu_prefix/list/{$form_state['values']['export']->{$export_key}}/delete"; + } + return; + // Process form values and save and/or update the export in the db + case 'edit-submit': + $export = ctools_export_ui_form_process($form, $form_state); + + $schema = ctools_export_get_schema($plugin['schema']); + $result = call_user_func($schema['export']['save callback'], $export); + + if ($result) { + drupal_set_message(t('Saved @plugin %title.', array('@plugin' => $plugin['title'], '%title' => $export->{$export_key}))); + } + else { + drupal_set_message(t('Could not save @plugin %title.', array('@plugin' => $plugin['title'], '%title' => $export->{$export_key})), 'error'); + } + break; + } + $form_state['redirect'] = _ctools_export_ui_redirect($plugin['name']); +} \ No newline at end of file diff --git includes/export-ui.inc includes/export-ui.inc new file mode 100644 index 0000000..35850fe --- /dev/null +++ includes/export-ui.inc @@ -0,0 +1,318 @@ + 'ctools_export_ui_defaults', + ); +} + +/** + * Provide defaults for an export-ui plugin. + */ +function ctools_export_ui_defaults($info, &$plugin) { + ctools_include('export'); + + $plugin += array( + 'has menu' => TRUE, + 'title' => $plugin['name'], + 'export' => array(), + 'allowed operations' => array(), + 'menu' => array(), + 'form' => array(), + ); + + if (empty($plugin['schema'])) { + if ($plugin['has menu']) { + // We need to issue a warning as schema is a required key. + drupal_set_message(t('The plugin definition of @plugin is missing the "schema" key.', array('@plugin' => $plugin['name'])), 'error'); + } + } + else { + $schema = ctools_export_get_schema($plugin['schema']); + + $plugin['export'] += array( + // Add the identifier key from the schema so we don't have to call + // ctools_export_get_schema() just for that. + 'key' => $schema['export']['key'], + ); + } + + // Define allowed operations, and the name of the operations. + $plugin['allowed operations'] += array( + 'edit' => t('Edit'), + 'enable' => t('Enable'), + 'disable' => t('Disable'), + 'revert' => t('Revert'), + 'delete' => t('Delete'), + 'clone' => t('Clone'), + 'import' => t('Import'), + 'export' => t('Export'), + ); + + if ($plugin['has menu']) { + $plugin['menu'] += array( + 'menu item' => str_replace(' ', '-', $plugin['name']), + 'menu prefix' => 'admin/build', + ); + + $prefix_count = count(explode('/', $plugin['menu']['menu prefix'])); + + $plugin['menu'] += array( + // Default menu items that should be declared. + 'items' => array( + 'list callback' => array( + 'path' => '', + // Menu items are translated by the menu system. + 'title' => $plugin['title'], + 'description' => 'List '. $plugin['title'], + 'page callback' => 'drupal_get_form', + 'page arguments' => array('ctools_export_ui_admin', $plugin['name']), + 'type' => MENU_NORMAL_ITEM, + ), + 'list' => array( + 'path' => 'list', + 'title' => 'List', + 'description' => 'List '. $plugin['title'], + 'page callback' => 'drupal_get_form', + 'page arguments' => array('ctools_export_ui_admin', $plugin['name']), + 'type' => MENU_DEFAULT_LOCAL_TASK, + ), + 'add' => array( + 'path' => 'add', + 'title' => 'Add', + 'description' => 'Add a new '. $plugin['title'], + 'page callback' => 'drupal_get_form', + 'page arguments' => array('ctools_export_ui_form', $plugin['name'], 'add'), + 'type' => MENU_LOCAL_TASK, + ), + 'edit callback' => array( + 'path' => 'list/%ctools_export_ui', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('ctools_export_ui_form', $plugin['name'], 'edit', $prefix_count + 2), + 'type' => MENU_CALLBACK, + ), + 'edit' => array( + 'path' => 'list/%ctools_export_ui/edit', + 'title' => 'Edit', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('ctools_export_ui_form', $plugin['name'], 'edit', $prefix_count + 2), + 'type' => MENU_DEFAULT_LOCAL_TASK, + ), + ), + ); + + if ($plugin['allowed operations']['import']) { + $plugin['menu']['items'] += array( + 'import' => array( + 'path' => 'import', + 'title' => 'Import', + 'description' => 'Import '. $plugin['title'] .' to your site.', + // We allow permissions to import only to users that are allowed to + // execute php. + 'access callback' => 'ctools_access_multiperm', + 'access arguments' => array('use PHP for block visibility'), + 'page callback' => 'ctools_export_ui_import_page', + 'page arguments' => array($plugin['name']), + 'type' => MENU_LOCAL_TASK, + ), + ); + } + + if ($plugin['allowed operations']['export']) { + $plugin['menu']['items'] += array( + 'export' => array( + 'path' => 'list/%ctools_export_ui/export', + 'title' => 'Export', + 'description' => 'Export '. $plugin['title'], + 'page callback' => 'drupal_get_form', + 'page arguments' => array('ctools_export_ui_export', $prefix_count + 2, $plugin['name']), + 'type' => MENU_LOCAL_TASK, + ), + ); + } + + if ($plugin['allowed operations']['revert']) { + $plugin['menu']['items'] += array( + 'revert' => array( + 'path' => 'list/%ctools_export_ui/revert', + 'title' => 'Revert', + 'description' => 'Revert '. $plugin['title'], + 'page callback' => 'drupal_get_form', + 'page arguments' => array('ctools_export_ui_confirm', $plugin['name'], 'revert', $prefix_count + 2), + 'access callback' => 'ctools_export_ui_task_access', + 'access arguments' => array($prefix_count + 2, 'revert'), + 'type' => MENU_LOCAL_TASK, + ), + ); + } + + if ($plugin['allowed operations']['clone']) { + $plugin['menu']['items'] += array( + 'clone' => array( + 'path' => 'list/%ctools_export_ui/clone', + 'title' => 'Clone', + 'description' => 'Revert '. $plugin['title'], + 'page callback' => 'drupal_get_form', + 'page arguments' => array('ctools_export_ui_form', $plugin['name'], 'clone', $prefix_count + 2), + 'type' => MENU_LOCAL_TASK, + ), + ); + } + + if ($plugin['allowed operations']['delete']) { + $plugin['menu']['items'] += array( + 'delete' => array( + 'path' => 'list/%ctools_export_ui/delete', + 'title' => 'Delete', + 'description' => 'Delete '. $plugin['title'], + 'page callback' => 'drupal_get_form', + 'page arguments' => array('ctools_export_ui_confirm', $plugin['name'], 'delete', $prefix_count + 2), + 'access callback' => 'ctools_export_ui_task_access', + 'access arguments' => array($prefix_count + 2, 'delete'), + 'type' => MENU_LOCAL_TASK, + ), + ); + } + + if ($plugin['allowed operations']['enable']) { + $plugin['menu']['items'] += array( + 'enable' => array( + 'path' => 'list/%ctools_export_ui/enable', + 'title' => 'Enable', + 'description' => 'Enable '. $plugin['title'], + 'page callback' => 'drupal_get_form', + 'page arguments' => array('ctools_export_ui_switcher', $plugin['name'], 'enable', $prefix_count + 2), + 'access callback' => 'ctools_export_ui_task_access', + 'access arguments' => array($prefix_count + 2, 'enable'), + 'type' => MENU_LOCAL_TASK, + ), + ); + } + + if ($plugin['allowed operations']['disable']) { + $plugin['menu']['items'] += array( + 'disable' => array( + 'path' => 'list/%ctools_export_ui/disable', + 'title' => 'Disable', + 'description' => 'Disable '. $plugin['title'], + 'page callback' => 'drupal_get_form', + 'page arguments' => array('ctools_export_ui_switcher', $plugin['name'], 'disable', $prefix_count + 2), + 'access callback' => 'ctools_export_ui_task_access', + 'access arguments' => array($prefix_count + 2, 'disable'), + 'type' => MENU_LOCAL_TASK, + ), + ); + } + } + + // Define form elements. + $plugin['form'] += array( + 'settings' => $plugin['name'] . '_form', + 'string' => array( + // Strings used in drupal_set_title(). + 'title' => array( + 'add' => t('Add a new @plugin', array('@plugin' => $plugin['title'])), + // The "%title" will be replaced in ctools_export_ui_form(), as in this + // stage we dont have the specific exportable object. + 'edit' => t('Editing @plugin %title', array('@plugin' => $plugin['title'])), + 'view' => t('Viewing @plugin %title', array('@plugin' => $plugin['title'])), + 'clone' => t('Cloning @plugin %title', array('@plugin' => $plugin['title'])), + + 'import' => t('Import @plugin', array('@plugin' => $plugin['title'])), + 'export' => t('Export @plugin', array('@plugin' => $plugin['title'])), + ), + // Strings used in confirmation pages. + 'confirmation' => array( + // The "!action" and "%title" will be replaced in + // ctools_export_ui_form(). + 'question' => t('Are you sure you want to !action the @plugin %title?', array('@plugin' => $plugin['title'])), + 'revert' => t('This action will permanently remove any customizations made to this export.'), + 'delete' => t('This action will remove this export permanently from your site.'), + ), + // Strings used in $forms. + 'help' => array( + 'import' => t('You can import an exported definition by pasting the exported object code into the field below.'), + ), + // Strings used in drupal_set_message(). + 'message' => array( + 'enable' => t('@plugin %title was enabled.', array('@plugin' => $plugin['title'])), + 'disable' => t('@plugin %title was disabled.', array('@plugin' => $plugin['title'])), + ), + ), + ); +} + +/** + * Get a plugin handler. + */ +function ctools_export_ui_get_plugin($key, $info = array()) { + global $user; + $cache = &ctools_static(__FUNCTION__, array()); + if (empty($cache[$key])) { + ctools_include('plugins'); + $plugin = ctools_get_plugins('ctools', 'export_ui', $key); + + if ($class = ctools_plugin_get_class($plugin, 'handler')) { + $cache[$key] = new $class($plugin, $info); + } + } + return !empty($cache[$key]) ? $cache[$key] : FALSE; +} + + +/** + * CTools export function. + */ +function ctools_export_ui_export_object($plugin_name, $export, $indent = '') { + ctools_include('plugins'); + $plugin = ctools_get_plugins('ctools', 'export_ui', $plugin_name); + $schema = ctools_export_get_schema($plugin['schema']); + + // TODO: Add translatable fields. + + $extra ="\nreturn \$". $plugin['schema'] .";\n"; + + return ctools_export_object($plugin['schema'], $export, $indent) . $extra; +} + +/** + * Get redirection path from a plugin. + * + * @param $plguin_name + * The plugin name. + * + * @return + * The menu path to the plugin's list. + */ +function _ctools_export_ui_redirect($plugin_name) { + ctools_include('plugins'); + $plugin = ctools_get_plugins('ctools', 'export_ui', $plugin_name); + + return $plugin['menu']['menu prefix'] .'/'. $plugin['menu']['menu item']; +} + +/** + * Helper function to include CTools plugins and get an export-ui exportable. + * + * @param $plugin_name + * The plugin that should be laoded. + */ +function ctools_get_export_ui($plugin_name) { + ctools_include('plugins'); + return ctools_get_plugins('ctools', 'export_ui', $plugin_name); + +} + +/** + * Helper function to include CTools plugins and get all export-ui exportables. + */ +function ctools_get_export_uis() { + ctools_include('plugins'); + return ctools_get_plugins('ctools', 'export_ui', $plugin_name); +} \ No newline at end of file diff --git includes/export-ui.menu.inc includes/export-ui.menu.inc new file mode 100644 index 0000000..b2f8385 --- /dev/null +++ includes/export-ui.menu.inc @@ -0,0 +1,33 @@ + array('administer site configuration'), + 'file' => 'export-ui.admin.inc', + 'file path' => drupal_get_path('module', 'ctools') .'/includes', + // Add the map, so we can get the plugin in export_ui_load(). + 'load arguments' => array('%map'), + ); + + $path = !empty($item['path']) ? $prefix .'/'. $item['path'] : $prefix; + unset($item['path']); + $items[$path] = $item; + } + } + } +} \ No newline at end of file diff --git includes/export-ui.theme.inc includes/export-ui.theme.inc new file mode 100644 index 0000000..17cc3ea --- /dev/null +++ includes/export-ui.theme.inc @@ -0,0 +1,77 @@ + array('form' => array()), + 'file' => 'export-ui.theme.inc', + 'path' => drupal_get_path('module', 'ctools') .'/includes', + ); +} + +/** + * Generates the main export_ui admin page with a tiered export listing. + */ +function theme_ctools_export_ui_admin($form) { + ctools_include('export-ui'); + + $plugin = $form['#plugin']; + $export_key = $plugin['export']['key']; + + // Iterate once to group by tag. + $by_tag = array(); + foreach ($form['#exports'] as $export) { + if (!empty($export->tag)) { + $by_tag[$export->tag][$export->{$export_key}] = $export; + } + else { + $by_tag[''][$export->{$export_key}] = $export; + } + } + + // Generate listing of existing exports + ksort($by_tag); + $rows = array(); + foreach ($by_tag as $tag => $exports) { + if (!empty($tag)) { + $rows[] = array(array('data' => check_plain($tag), 'colspan' => 3, 'class' => 'tag')); + } + ksort($exports); + foreach ($exports as $export) { + $row = array('data' => array()); + + // Exportable info + $storage = t('Default'); + if ($export->export_type & EXPORT_IN_DATABASE) { + $storage = $export->export_type & EXPORT_IN_CODE ? t('Overridden') : t('Normal'); + } + $data = "{$export->{$export_key}} ({$storage})"; + $data .= !empty($export->description) ? '