From 25f972835dc259311c80a03c0189a6e8b17647fb Mon Sep 17 00:00:00 2001 From: Earl Miles <merlin@logrus.com> Date: Tue, 25 Nov 2008 02:24:19 +0000 Subject: [PATCH] Implement the whole export/import workflow. --- ctools.module | 20 ++ delegator/css/task-handlers.css | 13 + delegator/delegator.admin.inc | 470 ++++++++++++++++++++++----- delegator/delegator.install | 1 + delegator/delegator.module | 41 +++ delegator/help/api-task-handler.html | 3 + delegator/js/task-handlers.js | 20 ++ includes/export.inc | 101 +++++- 8 files changed, 583 insertions(+), 86 deletions(-) diff --git a/ctools.module b/ctools.module index 0db3b02b..76fffee9 100644 --- a/ctools.module +++ b/ctools.module @@ -33,3 +33,23 @@ function ctools_get_plugins($module, $type, $id = NULL) { ctools_include('plugins'); return _ctools_get_plugins($module, $type, $id); } + +/** + * Provide a form for displaying an export. + * + * This is a simple form that should be invoked like this: + * @code + * $output = drupal_get_form('ctools_export_form', $code, $object_title); + * @endcode + */ +function ctools_export_form(&$form_state, $code, $title = '') { + $lines = substr_count($code, "\n"); + $form['code'] = array( + '#type' => 'textarea', + '#title' => $title, + '#default_value' => $code, + '#rows' => $lines, + ); + + return $form; +} \ No newline at end of file diff --git a/delegator/css/task-handlers.css b/delegator/css/task-handlers.css index e63b5d88..f403a537 100644 --- a/delegator/css/task-handlers.css +++ b/delegator/css/task-handlers.css @@ -17,3 +17,16 @@ border: 1px solid red; padding: 1em; } + +.delegator-operations div { + display: inline; +} + +.delegator-disabled { + color: #ccc; +} + +.delegator-operations select { + width: 10em; +} + diff --git a/delegator/delegator.admin.inc b/delegator/delegator.admin.inc index 22228aaf..b59c53bb 100644 --- a/delegator/delegator.admin.inc +++ b/delegator/delegator.admin.inc @@ -21,6 +21,11 @@ define('DGA_CHANGED_CACHED', 0x02); */ define('DGA_CHANGED_DELETED', 0x04); +/** + * Bit flag on the 'changed' value to tell us if an item has had its disabled status changed. + */ +define('DGA_CHANGED_STATUS', 0x01); + /** * Page callback to administer a particular task. */ @@ -82,6 +87,7 @@ function delegator_admin_get_task_cache($task, $subtask_id, $task_handlers = NUL $cache->handlers[$id]['name'] = $id; $cache->handlers[$id]['weight'] = $handler->weight; $cache->handlers[$id]['changed'] = FALSE; + $cache->handlers[$id]['disabled'] = !empty($handler->disabled); } $cache->locked = ctools_object_cache_test('delegator_handlers', $key); } @@ -104,6 +110,20 @@ function delegator_admin_set_task_cache($task, $subtask_id, $cache) { return; } + // We only bother if something has been marked changed. This keeps us from + // locking when we should not. + $changed = FALSE; + foreach ($cache->handlers as $handler) { + if (!empty($handler['changed'])) { + $changed = TRUE; + break; + } + } + + if (!$changed) { + return; + } + // First, sort the cache object. uasort($cache->handlers, '_delegator_admin_task_cache_sort'); @@ -170,6 +190,35 @@ function _delegator_admin_task_cache_sort($a, $b) { return 0; } +/** + * Find the right handler to use for an id during the edit process. + * + * When editing, a handler may be stored in cache. It may also be + * reverted and unsaved, which can cause issues all their own. This + * function can be used to find the right handler to use in these cases. + */ +function delegator_admin_find_handler($id, $cache, $task_handlers = array()) { + // Use the one from the database or an updated one in cache? + if ($cache->handlers[$id]['changed'] & DGA_CHANGED_CACHED) { + $handler = delegator_admin_get_task_handler_cache($id); + } + else { + // Special case: Reverted handlers get their defaults back. + if ($cache->handlers[$id]['changed'] & DGA_CHANGED_DELETED) { + ctools_include('export'); + $handler = ctools_get_default_object('delegator_handlers', $id); + } + else if (!empty($task_handlers)) { + $handler = $task_handlers[$id]; + } + else { + $handler = delegator_load_task_handler($id); + } + } + + return $handler; +} + /** * Form to administer task handlers assigned to a task. */ @@ -198,7 +247,9 @@ function delegator_admin_list_form(&$form_state) { // Create data for a table for all of the task handlers. foreach ($cache->handlers as $id => $info) { // Skip deleted items. - if ($info['changed'] & DGA_CHANGED_DELETED) { + $handler = delegator_admin_find_handler($id, $form_state['cache'], $task_handlers); + + if ($info['changed'] & DGA_CHANGED_DELETED && !($handler->export_type & EXPORT_IN_CODE)) { $form['#changed'] = TRUE; continue; } @@ -207,14 +258,6 @@ function delegator_admin_list_form(&$form_state) { $form['#changed'] = TRUE; } - // Use the one from the database or an updated one in cache? - if ($info['changed'] & DGA_CHANGED_CACHED) { - $handler = delegator_admin_get_task_handler_cache($id); - } - else { - $handler = $task_handlers[$id]; - } - $plugin = $task_handler_plugins[$handler->handler]; $title = delegator_get_handler_title($plugin, $handler, $task, $form_state['subtask_id']); @@ -228,45 +271,92 @@ function delegator_admin_list_form(&$form_state) { '#default_value' => $info['weight'], ); - if (isset($plugin['edit forms'])) { - $form['handlers'][$id]['config'] = array( - '#value' => 'config button', - ); - } - $form['handlers'][$id]['changed'] = array( '#type' => 'value', '#value' => $info['changed'], ); - if (!empty($plugin['edit forms'])) { - $form['handlers'][$id]['config'] = array( - // '#value' must NOT be set on an image_button or it will always - // appear clicked. - '#type' => 'image_button', - '#src' => drupal_get_path('module', 'delegator') . '/images/configure.png', - '#handler' => $id, // so the submit handler can tell which one this is - '#submit' => array('delegator_admin_list_form_config'), - ); + // Make a list of possible actions. + $actions = array( + '' => t('Actions...'), + ); + + if ($info['disabled']) { + $actions['enable'] = t('Enable'); + } + // For enabled handlers. + else { + // Make all of the edit items under the Edit optgroup. + if (!empty($plugin['edit forms'])) { + foreach ($plugin['edit forms'] as $edit_id => $title) { + if ($title) { + $actions[t('Edit')]['edit-' . $edit_id] = $title; + } + } + } + + $actions['clone'] = t('Clone'); + $actions['export'] = t('Export'); + + if ($handler->export_type == (EXPORT_IN_CODE | EXPORT_IN_DATABASE)) { + $actions['delete'] = t('Revert'); + } + else if ($handler->export_type == EXPORT_IN_CODE) { + $actions['disable'] = t('Disable'); + } + else { + $actions['delete'] = t('Delete'); + } } - $form['handlers'][$id]['delete'] = array( - // '#value' must NOT be set on an image_button or it will always - // appear clicked. + $form['handlers'][$id]['action'] = array( + '#type' => 'select', + '#options' => $actions, + ); + + $form['handlers'][$id]['config'] = array( + // image buttons must not have a #value or they will not be properly detected. '#type' => 'image_button', - '#src' => drupal_get_path('module', 'delegator') . '/images/delete.png', + '#src' => drupal_get_path('module', 'delegator') . '/images/configure.png', '#handler' => $id, // so the submit handler can tell which one this is - '#submit' => array('delegator_admin_list_form_delete'), + '#submit' => array('delegator_admin_list_form_action'), ); + $type = $handler->type; + + // Adjust type for this scenario: They have reverted a handler to the in code + // version and have not modified it again. + if ($type == t('Overridden') && $info['changed'] &= DGA_CHANGED_DELETED && !($info['changed'] &= DGA_CHANGED_CACHED)) { + $type = t('Default'); + } + + $class = 'draggable'; + if ($type == t('Overridden')) { + $class .= ' delegator-overridden'; + } + else if ($type == t('Default')) { + $class .= ' delegator-default'; + if ($info['disabled']) { + $class .= ' delegator-disabled'; + } + } + $form['handlers'][$id]['class'] = array( - '#value' => 'draggable', + '#value' => $class, + ); + + if ($info['disabled']) { + $type .= ', ' . t('Disabled'); + } + + $form['handlers'][$id]['type'] = array( + '#value' => $type, ); // This sets the tabledrag last dragged class so that the most recently // touched row will show up yellow. This is a nice reminder after adding // or editing a row which one was touched. - if (isset($info['last touched'])) { + if (isset($cache->last_touched) && $cache->last_touched == $handler->name) { $form['handlers'][$id]['class']['#value'] .= ' delegator-changed'; } } @@ -297,6 +387,21 @@ function delegator_admin_list_form(&$form_state) { $form['#delegator-lock'] = $cache->locked; $form['#task-name'] = $form_state['task_name']; + // Set up a list of callbacks for our actions. This method allows + // clever form_alter uses to add more actions easily. + + // Bear in mind that any action will be split on a '-' so don't use it + // in your name. This is how 'edit' can edit multiple forms, i.e, + // edit-settings, edit-context, edit-foobarbaz. + $form['#actions'] = array( + 'edit' => 'delegator_admin_list_form_action_edit', + 'delete' => 'delegator_admin_list_form_action_delete', + 'enable' => 'delegator_admin_list_form_action_enable', + 'disable' => 'delegator_admin_list_form_action_disable', + 'clone' => 'delegator_admin_list_form_action_clone', + 'export' => 'delegator_admin_list_form_action_export', + ); + return $form; } @@ -312,7 +417,7 @@ function theme_delegator_admin_list_form($form) { $break = url('admin/build/delegator/' . $form['#task-name'] . '/break-lock'); $output .= '<div class="delegator-locked">'; - $output .= t('This view is being edited by user !user, and is therefore locked from editing by others. This lock is !age old. Click here to <a href="!break">break this lock</a>.', array('!user' => $name, '!age' => $lock_age, '!break' => $break)); + $output .= t('This task is being edited by user !user, and is therefore locked from editing by others. This lock is !age old. Click here to <a href="!break">break this lock</a>.', array('!user' => $name, '!age' => $lock_age, '!break' => $break)); $output .= '</div>'; } @@ -336,14 +441,17 @@ function theme_delegator_admin_list_form($form) { 'class' => 'delegator-handler', ); + $row[] = array( + 'data' => drupal_render($element['type']), + 'class' => 'delegator-type', + ); + $element['weight']['#attributes']['class'] = 'weight'; $row[] = drupal_render($element['weight']); $operations = ''; - if (isset($element['config'])) { - $operations .= drupal_render($element['config']); - } - $operations .= drupal_render($element['delete']); + $operations .= drupal_render($element['action']); + $operations .= drupal_render($element['config']); $row[] = array( 'data' => $operations, 'class' => 'delegator-operations', @@ -361,6 +469,7 @@ function theme_delegator_admin_list_form($form) { $header = array( array('data' => t('Task handler'), 'class' => 'delegator-handler'), + array('data' => t('Type'), 'class' => 'delegator-type'), t('Weight'), array('data' => t('Operations'), 'class' => 'delegator-operations') ); @@ -416,9 +525,7 @@ function delegator_admin_update_weights(&$form_state) { // Unset any 'last touched' flag and let whatever handler is updating the // weights do that if it wants to. - if (isset($handlers[$id]['last touched'])) { - unset($handlers[$id]['last touched']); - } + unset($form_state['cache']->last_touched); } // if weight stubbornly continues to not be set (meaning the cache was empty) @@ -468,6 +575,13 @@ function delegator_admin_list_form_add($form, &$form_state) { $handler->handler = $plugin['name']; $handler->weight = $weight; $handler->conf = array(); + + // These are provided by the core export API provided by ctools and we + // set defaults here so that we don't cause notices. Perhaps ctools should + // provide a way to do this for us so we don't have to muck with it. + $handler->export_type = EXPORT_IN_DATABASE; + $handler->type = t('Local'); + if (isset($plugin['default conf'])) { if (is_array($plugin['default conf'])) { $handler->conf = $plugin['default conf']; @@ -486,8 +600,9 @@ function delegator_admin_list_form_add($form, &$form_state) { 'name' => $handler->name, 'weight' => $handler->weight, 'changed' => DGA_CHANGED_CACHED, - 'last touched' => TRUE, + 'disabled' => FALSE, ); + $form_state['cache']->last_touched = $handler->name; // Store the changed task handler list. delegator_admin_set_task_cache($form_state['task'], $form_state['subtask_id'], $form_state['cache']); @@ -526,7 +641,7 @@ function delegator_admin_list_form_submit($form, &$form_state) { } } // If it has been somehow edited (or added), write the cached version - elseif ($info['changed'] & DGA_CHANGED_CACHED) { + if ($info['changed'] & DGA_CHANGED_CACHED) { // load and write the cached version. $handler = delegator_admin_get_task_handler_cache($id); // Make sure we get updated weight from the form for this. @@ -535,6 +650,8 @@ function delegator_admin_list_form_submit($form, &$form_state) { // Now that we've written it, remove it from cache. delegator_admin_clear_task_handler_cache($id); + + // @todo -- do we need to clear the handler weight here? } // Otherwise, check to see if it has moved and, if so, update the weight. elseif ($info['weight'] != $form_state['task_handlers'][$id]->weight) { @@ -542,6 +659,12 @@ function delegator_admin_list_form_submit($form, &$form_state) { // load mechanism checks for all, this is less database work. delegator_update_task_handler_weight($form_state['task_handlers'][$id], $info['weight']); } + + // Set enable/disabled status. + if ($info['changed'] & DGA_CHANGED_STATUS) { + ctools_include('export'); + ctools_export_set_status('delegator_handlers', $id, $info['disabled']); + } } drupal_set_message(t('All changes have been updated.')); @@ -566,63 +689,141 @@ function delegator_admin_list_form_cancel($form, &$form_state) { } /** - * Submit handler for item deletion. + * Submit handler for item action. * * This is attached to every delete button; it uses $form_state['clicked_value'] * to know which delete button was pressed. In the form, we set #handler => $id * to that this information could be easily retrieved. + * + * The actual action to call will be in the 'action' setting for the handler. */ -function delegator_admin_list_form_delete($form, &$form_state) { +function delegator_admin_list_form_action($form, &$form_state) { // Update the weights from the form. delegator_admin_update_weights($form_state); $id = $form_state['clicked_button']['#handler']; + $action = $form_state['values']['handlers'][$id]['action']; + + // Set this now, that way handlers can override it to go elsewhere if they + // want. + $form_state['redirect'] = $_GET['q']; + + // Break up our + if (strpos($action, '-') !== FALSE) { + list($action, $argument) = explode('-', $action, 2); + } + else { + $action = $action; + $argument = NULL; + } + + if (!empty($form['#actions'][$action]) && function_exists($form['#actions'][$action])) { + $form['#actions'][$action]($form, $form_state, $id, $action, $argument); + } + + delegator_admin_set_task_cache($form_state['task'], $form_state['subtask_id'], $form_state['cache']); + return; +} + + +/** + * Delegated submit handler to delete an item. + */ +function delegator_admin_list_form_action_delete($form, &$form_state, $id, $action, $argument) { // This overwrites 'moved' and 'cached' states. if ($form_state['cache']->handlers[$id]['changed'] & DGA_CHANGED_CACHED && !$form_state['cache']->locked) { // clear cached version. delegator_admin_clear_task_handler_cache($id); } $form_state['cache']->handlers[$id]['changed'] = DGA_CHANGED_DELETED; - - // Store the changed task handler list. - delegator_admin_set_task_cache($form_state['task'], $form_state['subtask_id'], $form_state['cache']); - $form_state['redirect'] = $_GET['q']; } /** - * Submit handler to configure an item. + * Delegated submit handler to edit an item. * - * This is attached to every configure button; it uses $form_state['clicked_value'] - * to know which delete button was pressed. In the form, we set #handler => $id - * to that this information could be easily retrieved. + * Which form to go to will be specified by $argument. */ -function delegator_admin_list_form_config($form, &$form_state) { - // Update the weights from the form. - delegator_admin_update_weights($form_state); - - // Store the changed task handler list. - delegator_admin_set_task_cache($form_state['task'], $form_state['subtask_id'], $form_state['cache']); +function delegator_admin_list_form_action_edit($form, &$form_state, $id, $action, $argument) { + // Use the one from the database or an updated one in cache? + $handler = delegator_admin_find_handler($id, $form_state['cache'], $form_state['task_handlers']); - $id = $form_state['clicked_button']['#handler']; + $name = $form_state['task']['name']; + // @todo: Allow an owner UI to control this URL. + // @todo: subtask ID + $form_state['redirect'] = "admin/build/delegator/$name/$handler->handler/$id/$argument"; +} +/** + * Clone an existing task handler into a new handler. + */ +function delegator_admin_list_form_action_clone($form, &$form_state, $id, $action, $argument) { // Use the one from the database or an updated one in cache? - if ($form_state['cache']->handlers[$id]['changed'] & DGA_CHANGED_CACHED) { - $handler = delegator_admin_get_task_handler_cache($id); + $handler = delegator_admin_find_handler($id, $form_state['cache'], $form_state['task_handlers']); + + // Get the next weight from the form + $handler->weight = delegator_admin_update_weights($form_state); + + // Generate a unique name. Unlike most named objects, we don't let people choose + // names for task handlers because they mostly don't make sense. + $base = $form_state['task']['name']; + if ($form_state['subtask_id']) { + $base .= '_' . $form_state['subtask_id']; } - else { - $handler = $form_state['task_handlers'][$id]; + $base .= '_' . $handler->handler; + + // Once we have a base, check to see if it is used. If it is, start counting up. + $name = $base; + $count = 1; + // If taken + while (isset($form_state['cache']->handlers[$name])) { + $name = $base . '_' . ++$count; } + $handler->name = $name; + + // Store the new handler. + if (!$form_state['cache']->locked) { + delegator_admin_set_task_handler_cache($handler); + } + + $form_state['cache']->handlers[$handler->name] = array( + 'name' => $handler->name, + 'weight' => $handler->weight, + 'changed' => DGA_CHANGED_CACHED, + 'disabled' => FALSE, + ); + $form_state['cache']->last_touched = $handler->name; +} + +/** + * Export a task handler. + */ +function delegator_admin_list_form_action_export($form, &$form_state, $id, $action, $argument) { + // Redirect to the export page. $name = $form_state['task']['name']; - // @todo: Allow an owner UI to control this URL. - // @todo: subtask ID - $form_state['redirect'] = "admin/build/delegator/$name/$handler->handler/$id"; + $form_state['redirect'] = "admin/build/delegator/$name/export/$id"; } /** - * Entry point to edit a task handler. + * Enable a task handler. */ -function delegator_administer_task_handler_edit($task_name, $handler_id, $name, $form_id) { +function delegator_admin_list_form_action_enable($form, &$form_state, $id, $action, $argument) { + $form_state['cache']->handlers[$id]['changed'] |= DGA_CHANGED_STATUS; + $form_state['cache']->handlers[$id]['disabled'] = FALSE; +} + +/** + * Enable a task handler. + */ +function delegator_admin_list_form_action_disable($form, &$form_state, $id, $action, $argument) { + $form_state['cache']->handlers[$id]['changed'] |= DGA_CHANGED_STATUS; + $form_state['cache']->handlers[$id]['disabled'] = TRUE; +} + +/** + * Entry point to export a task handler. + */ +function delegator_administer_task_handler_export($task_name, $name) { // Determine if the task id came in the form of TASK-SUBTASK or just TASK if (strpos($task_name, '-') !== FALSE) { list($task_id, $subtask_id) = explode('-', $task_name, 2); @@ -641,9 +842,39 @@ function delegator_administer_task_handler_edit($task_name, $handler_id, $name, return drupal_not_found(); } + $task = delegator_get_task($task_id); + $plugin = delegator_get_task_handler($handler->handler); + + $title = delegator_get_handler_title($plugin, $handler, $task, $subtask_id); + drupal_set_title(t('Export task handler "@title"', array('@title' => $title))); + + return drupal_get_form('ctools_export_form', delegator_export_task_handler($handler), $title); +} + +/** + * Entry point to edit a task handler. + */ +function delegator_administer_task_handler_edit($task_name, $handler_id, $name, $form_id) { + // Determine if the task id came in the form of TASK-SUBTASK or just TASK + if (strpos($task_name, '-') !== FALSE) { + list($task_id, $subtask_id) = explode('-', $task_name, 2); + } + else { + $task_id = $task_name; + $subtask_id = NULL; + } + $task = delegator_get_task($task_id); $plugin = delegator_get_task_handler($handler_id); + $cache = delegator_admin_get_task_cache($task, $subtask_id); + + $handler = delegator_admin_find_handler($name, $cache); + + if (!$handler) { + return drupal_not_found(); + } + // Prevent silliness of using some other handler type's tabs for this // particular handler, or of somehow having invalid tasks or task handlers. if ($handler_id != $handler->handler || @@ -679,6 +910,7 @@ function delegator_administer_task_handler_edit($task_name, $handler_id, $name, 'forms' => $plugin['edit forms'], 'type' => 'edit', 'task_name' => $task_name, + 'cache' => $cache, ); if (!empty($plugin['forms'][$form_id]['alternate next'])) { @@ -729,6 +961,8 @@ function delegator_administer_task_handler_add($task_name, $name, $form_id) { return drupal_not_found(); } + $cache = delegator_admin_get_task_cache($task, $subtask_id); + $title = delegator_get_handler_title($plugin, $handler, $task, $subtask_id); drupal_set_title(t('Add task handler "@title"', array('@title' => $title))); @@ -741,6 +975,7 @@ function delegator_administer_task_handler_add($task_name, $name, $form_id) { 'forms' => $plugin['add forms'], 'type' => 'add', 'task_name' => $task_name, + 'cache' => $cache, ); $output = ''; @@ -889,7 +1124,7 @@ function delegator_admin_edit_task_handler_submit($form, &$form_state) { // Update the task handler cache to let the system know this one has now // officially changed. - $cache = delegator_admin_get_task_cache($form_state['task'], $form_state['subtask_id']); + $cache = &$form_state['cache']; $form_state['redirect'] = $form_state['clicked_button']['#next']; if ($cache->locked) { @@ -899,17 +1134,11 @@ function delegator_admin_edit_task_handler_submit($form, &$form_state) { // Only bother updating the cache if we're going to change something, so if // our handler is not marked changed or is not the last touched handler, do so. - if (!($cache->handlers[$handler->name]['changed'] & DGA_CHANGED_CACHED) || empty($cache->handlers[$handler->name]['last touched'])) { - // Clear the last touched flag from all handlers - foreach ($cache->handlers as $id => $info) { - if (isset($info['last touched'])) { - unset($cache->handlers[$id]['last touched']); - } - } + if (!($cache->handlers[$handler->name]['changed'] & DGA_CHANGED_CACHED) || !isset($cache->last_touched) || $cache->last_touched != $handler->name) { // Set status of our handler $cache->handlers[$handler->name]['changed'] |= DGA_CHANGED_CACHED; - $cache->handlers[$handler->name]['last touched'] = TRUE; + $cache->last_touched = $handler->name; delegator_admin_set_task_cache($form_state['task'], $form_state['subtask_id'], $cache); } @@ -935,7 +1164,7 @@ function delegator_admin_edit_task_handler_cancel($form, &$form_state) { // Send an array() through as the list of $task_handlers -- because // if we're at this point there MUST be something in the cache. - $cache = delegator_admin_get_task_cache($form_state['task'], $form_state['subtask_id']); + $cache = &$form_state['cache']; if (isset($cache->handlers[$form_state['handler']->name])) { unset($cache->handlers[$form_state['handler']->name]); } @@ -996,3 +1225,90 @@ function delegator_administer_break_lock_submit(&$form, &$form_state) { drupal_set_message(t('The lock has been broken and you may now edit this task.')); $form_state['redirect'] = 'admin/build/delegator/' . $form_state['task_name']; } + +/** + * Import a task handler from cut & paste + */ +function delegator_admin_import_task_handler(&$form_state) { + drupal_set_title(t('Import task handler')); + $form['object'] = array( + '#type' => 'textarea', + '#title' => t('Paste task handler code here'), + '#rows' => 15, + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Import'), + ); + return $form; +} + +/** + * Ensure we got a valid task handler. + */ +function delegator_admin_import_task_handler_validate($form, &$form_state) { + ob_start(); + eval($form_state['values']['object']); + ob_end_clean(); + + if (!isset($handler) || !is_object($handler)) { + form_error($form['object'], t('Unable to interpret task handler code.')); + } + + // @todo support subtasks here + $task = delegator_get_task($handler->task); + if (!$task) { + form_error($form['object'], t('The task for that handler does not seem to exist.')); + } + + // See if the task is already locked. + $cache = delegator_admin_get_task_cache($task, $handler->subtask); + if ($cache->locked) { + $account = user_load($cache->locked->uid); + $name = theme('username', $account); + $lock_age = format_interval(time() - $cache->locked->updated); + $break = url('admin/build/delegator/' . $handler->task . '/break-lock', array('query' => array('destination' => $_GET['q'], 'cancel' => $_GET['q']))); + + form_error($form['object'], t('Unable to import task handler because the task is being edited by user !user, and is therefore locked from editing by others. This lock is !age old. Click here to <a href="!break">break this lock</a>.', array('!user' => $name, '!age' => $lock_age, '!break' => $break))); + } + + $handler->type = t('Normal'); + + ctools_include('export'); + $handler->export_type = EXPORT_IN_DATABASE; + + if (isset($cache->handlers[$handler->name])) { + drupal_set_message(t('Warning: The handler you are important already exists and is overwriting an existing handler. If this is not what you intend, you may Cancel this. You should then modify the <code>$handler->name</code> field of your import to have a unique name.'), 'warning'); + + $old_handler = delegator_admin_find_handler($handler->name, $cache); + $handler->export_type = $old_handler->export_type | EXPORT_IN_DATABASE; + } + + $form_state['task'] = $task; + $form_state['cache'] = $cache; + $form_state['handler'] = $handler; +} + +/** + * Clone an existing task handler into a new handler. + */ +function delegator_admin_import_task_handler_submit($form, &$form_state) { + // Use the one from the database or an updated one in cache? + $handler = &$form_state['handler']; + $cache = &$form_state['cache']; + + delegator_admin_set_task_handler_cache($handler); + + $cache->handlers[$handler->name] = array( + 'name' => $handler->name, + 'weight' => $handler->weight, + 'changed' => DGA_CHANGED_CACHED, + 'disabled' => FALSE, + ); + $cache->last_touched = $handler->name; + + delegator_admin_set_task_cache($form_state['task'], $handler->subtask, $cache); + // @todo: Support subtasks and tasks that have alternate administrative UIs. + $form_state['redirect'] = 'admin/build/delegator/' . $handler->task; +} diff --git a/delegator/delegator.install b/delegator/delegator.install index a32d6fcb..4b0ba30c 100644 --- a/delegator/delegator.install +++ b/delegator/delegator.install @@ -26,6 +26,7 @@ function delegator_schema_1() { 'type' => 'serial', 'not null' => TRUE, 'description' => t('Primary ID field for the table. Not used for anything except internal lookups.'), + 'no export' => TRUE, ), 'name' => array( 'type' => 'varchar', diff --git a/delegator/delegator.module b/delegator/delegator.module index 190dec44..6a23cc16 100644 --- a/delegator/delegator.module +++ b/delegator/delegator.module @@ -60,6 +60,22 @@ function delegator_menu() { 'file path' => drupal_get_path('module', 'system'), ); + $items['admin/build/delegator/list'] = array( + 'title' => 'Tasks', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10, + ); + + // Set up our own menu items here. + $items['admin/build/delegator/import'] = array( + 'title' => 'Import', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('delegator_admin_import_task_handler'), + 'access arguments' => array('administer delegator'), + 'file' => 'delegator.admin.inc', + 'type' => MENU_LOCAL_TASK, + ); + $tasks = delegator_get_tasks(); foreach ($tasks as $id => $task) { @@ -95,6 +111,15 @@ function delegator_menu() { 'file' => 'delegator.admin.inc', ); + // Form to add export a handler + $items['admin/build/delegator/' . $id . '/export/%'] = array( + 'page callback' => 'delegator_administer_task_handler_export', + 'page arguments' => array($id, 5), + 'access callback' => $access_callback, + 'access arguments' => $access_arguments, + 'file' => 'delegator.admin.inc', + ); + // Form to break a lock on this task. $items['admin/build/delegator/' . $id . '/break-lock'] = array( 'title' => 'Break lock', @@ -277,6 +302,22 @@ function delegator_delete_task_handler($handler) { db_query("DELETE FROM {delegator_weights} WHERE name = '%s'", $handler->name); } +function delegator_export_task_handler($handler, $indent = '') { + ctools_include('export'); + ctools_include('plugins'); + $handler = drupal_clone($handler); + + $append = ''; + if ($function = ctools_plugin_load_function('delegator', 'task_handlers', $handler->handler, 'export')) { + $append = $function($handler, $indent); + } + + $output = ctools_export_object('delegator_handlers', $handler, 'handler', $indent); + $output .= $append; + + return $output; +} + /** * Set an overidden weight for a task handler. * diff --git a/delegator/help/api-task-handler.html b/delegator/help/api-task-handler.html index 6904079b..72e6937b 100644 --- a/delegator/help/api-task-handler.html +++ b/delegator/help/api-task-handler.html @@ -9,6 +9,9 @@ task handler definition: default conf -- either an array() of default conf data or a callback that returns an array. params: $handler, $task, $subtask_id save -- callback to call just prior to the task handler being saved so it can adjust its data. + params: &$handler, $update (as drupal_write_record would receive) + export -- callback to call just prior to the task being exported. It should return text to append to the export if necessary. + params: &$handler, $indent forms => array( 'id' => array( diff --git a/delegator/js/task-handlers.js b/delegator/js/task-handlers.js index af6407e6..8460c226 100644 --- a/delegator/js/task-handlers.js +++ b/delegator/js/task-handlers.js @@ -18,4 +18,24 @@ Drupal.behaviors.zzGoLastDelegatorTaskList = function(context) { Drupal.tableDrag[id].oldRowElement = $row; } } + + $('.delegator-operations select:not(.delegator-processed)', context).each(function() { + var $next = $(this).parent().next('input'); + $next.hide(); + $(this).change(function() { + var val = $(this).val(); + // ignore empty + if (!val) { + return; + } + + // force confirm on delete + if ($(this).val() == 'delete' && !confirm(Drupal.t('Remove this task?'))) { + $(this).val(''); + return; + } + + $next.trigger('click'); + }); + }); } \ No newline at end of file diff --git a/includes/export.inc b/includes/export.inc index e4f27911..0d49f31e 100644 --- a/includes/export.inc +++ b/includes/export.inc @@ -152,30 +152,28 @@ function ctools_export_load_object($table, $type = 'all', $args = array()) { } } else if ($type == 'names') { - if (!in_array($names, $object->{$export['key']})) { + if (!in_array($object->{$export['key']}, $args)) { continue; } } - // Determine if default panel is enabled or disabled. + // Determine if default object is enabled or disabled. if (isset($status[$object->name])) { $object->disabled = $status[$object->name]; } if (!empty($cache[$table][$object->name])) { $cache[$table][$object->name]->type = t('Overridden'); - $cache[$table][$object->name]->export_status |= EXPORT_IN_CODE; + $cache[$table][$object->name]->export_type |= EXPORT_IN_CODE; if ($type == 'conditions') { - $return[$object->name] = $cache[$table][$object->name]->type; + $return[$object->name] = $cache[$table][$object->name]; } } else { $object->type = t('Default'); - $object->export_status = EXPORT_IN_CODE; + $object->export_type = EXPORT_IN_CODE; $object->in_code_only = TRUE; - // move the 'display' to the new 'primary' location. - $object->primary = $object->display; - unset($object->display); + $cache[$table][$object->name] = $object; if ($type == 'conditions') { $return[$object->name] = $object; @@ -212,6 +210,45 @@ function ctools_export_load_object($table, $type = 'all', $args = array()) { return $return; } +/** + * Get the default version of an object, if it exists. + * + * This function doesn't care if an object is in the database or not and + * does not check. This means that export_type could appear to be incorrect, + * because a version could exist in the database. However, it's not + * incorrect for this function as it is *only* used for the default + * in code version. + */ +function ctools_get_default_object($table, $name) { + $schema = ctools_export_get_schema($table); + $export = $schema['export']; + + if (!$export['default hook']) { + return; + } + + // @todo add a method to load .inc files for this. + $defaults = module_invoke_all($export['default hook']); + $status = variable_get($export['status'], array()); + + if (!isset($defaults[$name])) { + return; + } + + $object = $defaults[$name]; + + // Determine if default object is enabled or disabled. + if (isset($status[$object->name])) { + $object->disabled = $status[$object->name]; + } + + $object->type = t('Default'); + $object->export_type = EXPORT_IN_CODE; + $object->in_code_only = TRUE; + + return $object; +} + /** * Unpack data loaded from the database onto an object. * @@ -276,6 +313,51 @@ function ctools_var_export($var, $prefix = '') { return $output; } +/** + * Export an object into code. + */ +function ctools_export_object($table, $object, $identifier, $indent = '', $additions = array(), $additions2 = array()) { + $schema = drupal_get_schema($table); + + $output = $indent . '$' . $identifier . ' = new ' . get_class($object) . ";\n"; + + $output .= $indent . '$' . $identifier . '->disabled' . ' = FALSE; /* Edit this to true to make a default ' . $identifier . ' disabled initially */' . "\n"; + + // Put top additions here: + foreach ($additions as $field => $value) { + $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n"; + } + + // Go through our schema and build correlations. + foreach ($schema['fields'] as $field => $info) { + if (!empty($info['no export'])) { + continue; + } + if (!isset($object->$field)) { + if (isset($info['default'])) { + $object->$field = $info['default']; + } + else { + $object->$field = ''; + } + } + + $value = $object->$field; + if ($info['type'] == 'int') { + $value = (isset($info['size']) && $info['size'] == 'tiny') ? (bool) $value : (int) $value; + } + + $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n"; + } + + // And bottom additions here + foreach ($additions2 as $field => $value) { + $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n"; + } + + return $output; +} + /** * Get the schema for a given table. * @@ -303,7 +385,7 @@ function ctools_export_get_schema($table) { /** * Set the status of a default $object as a variable. * - * The status, in this case, is whether or not it is 'enabled' or 'disabled' + * The status, in this case, is whether or not it is 'disabled' * and is only valid for in-code objects that do not have a database * equivalent. This function does not check to make sure $object actually * exists. @@ -311,6 +393,7 @@ function ctools_export_get_schema($table) { function ctools_export_set_status($table, $name, $new_status = TRUE) { $schema = ctools_export_get_schema($table); $status = variable_get($schema['export']['status'], array()); + $status[$name] = $new_status; variable_set($schema['export']['status'], $status); } -- GitLab