From c23bbd6b7ff17c7ba1813bebdc2a56adcc9badc7 Mon Sep 17 00:00:00 2001
From: Earl Miles <merlin@logrus.com>
Date: Tue, 20 Jan 2009 18:30:59 +0000
Subject: [PATCH] Allow tasks to be imported and exported along with
 accompanying handlers.

---
 delegator/delegator.admin.inc          |   8 +-
 delegator/delegator.module             |  96 +++++++++++++++-
 delegator/help/api-task.html           |   2 +
 delegator/plugins/tasks/page.admin.inc | 149 +++++++++++++++++++++----
 delegator/plugins/tasks/page.inc       |  35 +++++-
 5 files changed, 260 insertions(+), 30 deletions(-)

diff --git a/delegator/delegator.admin.inc b/delegator/delegator.admin.inc
index 7fe6468d..54bbfbcb 100644
--- a/delegator/delegator.admin.inc
+++ b/delegator/delegator.admin.inc
@@ -254,7 +254,9 @@ function delegator_admin_find_handler($id, $cache, $task_name, $task_handlers =
       $handler = $task_handlers[$id];
     }
     else {
-      $handler = delegator_load_task_handler($id);
+      list($task_id, $subtask_id) = delegator_get_task_id($task_name);
+      $task = delegator_get_task($task_id);
+      $handler = delegator_load_task_handler($task, $subtask_id, $id);
     }
   }
 
@@ -933,17 +935,17 @@ function delegator_admin_list_form_action_disable($form, &$form_state, $id, $act
  */
 function delegator_administer_task_handler_export($task_name, $name) {
   list($task_id, $subtask_id) = delegator_get_task_id($task_name);
+  $task = delegator_get_task($task_id);
 
   $handler = delegator_admin_get_task_handler_cache($name);
   if (!$handler) {
-    $handler = delegator_load_task_handler($name);
+    $handler = delegator_load_task_handler($task, $subtask_id, $name);
   }
 
   if (!$handler) {
     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);
diff --git a/delegator/delegator.module b/delegator/delegator.module
index ab2b6f29..f061a9fb 100644
--- a/delegator/delegator.module
+++ b/delegator/delegator.module
@@ -239,13 +239,22 @@ function delegator_menu_task_items(&$items, $task_id, $task, $handlers, $subtask
 
 /**
  * Load a single task handler by name.
+ *
+ * Handlers can come from multiple sources; either the database or by normal
+ * export method, which is handled by the ctools library, but handlers can
+ * also be bundled with task/subtask. We have to check there and perform
+ * overrides as appropriate.
+ *
+ * Handlers bundled with the task are of a higher priority than default
+ * handlers provided by normal code, and are of a lower priority than
+ * the database, so we have to check the source of handlers when we have
+ * multiple to choose from.
  */
-function delegator_load_task_handler($name) {
+function delegator_load_task_handler($task, $subtask_id, $name) {
   ctools_include('export');
   $result = ctools_export_load_object('delegator_handlers', 'names', array($name));
-  if (isset($result[$name])) {
-    return $result[$name];
-  }
+  $handlers = delegator_get_default_task_handlers($task, $subtask_id);
+  return delegator_compare_task_handlers($result, $handlers, $name);
 }
 
 /**
@@ -262,6 +271,13 @@ function delegator_load_task_handlers($task, $subtask_id = NULL) {
   }
 
   $handlers = ctools_export_load_object('delegator_handlers', 'conditions', $conditions);
+  $defaults = delegator_get_default_task_handlers($task, $subtask_id);
+  foreach ($defaults as $name => $default) {
+    $result = delegator_compare_task_handlers($handlers, $defaults, $name);
+    if ($result) {
+      $handlers[$name] = $result;
+    }
+  }
 
   // Override weights from the weight table.
   if ($handlers) {
@@ -281,6 +297,73 @@ function delegator_load_task_handlers($task, $subtask_id = NULL) {
   return $handlers;
 }
 
+/**
+ * Get the default task handlers from a task, if they exist.
+ *
+ * Tasks can contain 'default' task handlers which are provided by the
+ * default task. Because these can come from either the task or the
+ * subtask, the logic is abstracted to reduce code duplication.
+ */
+function delegator_get_default_task_handlers($task, $subtask_id) {
+  // Load default handlers that are provied by the task/subtask itself.
+  $handlers = array();
+  if ($subtask_id) {
+    $subtask = delegator_get_task_subtask($task, $subtask_id);
+    if (isset($subtask['default handlers'])) {
+      $handlers = $subtask['default handlers'];
+    }
+  }
+  else if (isset($task['default handlers'])) {
+    $handlers = $task['default handlers'];
+  }
+
+  return $handlers;
+}
+
+/**
+ * Compare a single task handler from two lists and provide the correct one.
+ *
+ * Task handlers can be gotten from multiple sources. As exportable objects,
+ * they can be provided by default hooks and the database. But also, because
+ * they are tightly bound to tasks, they can also be provided by default
+ * tasks. This function reconciles where to pick up a task handler between
+ * the exportables list and the defaults provided by the task itself.
+ *
+ * @param $result
+ *   A list of handlers provided by export.inc
+ * @param $handlers
+ *   A list of handlers provided by the default task.
+ * @param $name
+ *   Which handler to compare.
+ * @return
+ *   Which handler to use, if any. May be NULL.
+ */
+function delegator_compare_task_handlers($result, $handlers, $name) {
+  // Compare our special default handler against the actual result, if
+  // any, and do the right thing.
+  if (!isset($result[$name]) && isset($handlers[$name])) {
+    $handlers[$name]->type = t('Default');
+    $handlers[$name]->export_type = EXPORT_IN_CODE;
+    return $handlers[$name];
+  }
+  else if (isset($result[$name]) && !isset($handlers[$name])) {
+    return $result[$name];
+  }
+  else if (isset($result[$name]) && isset($handlers[$name])) {
+    if ($result[$name]->export_type & EXPORT_IN_DATABASE) {
+      $result[$name]->type = t('Overridden');
+      $result[$name]->export_type = $result[$name]->export_type | EXPORT_IN_CODE;
+      return $result[$name];
+    }
+    else {
+      // In this case, our default is a higher priority than the standard default.
+      $handlers[$name]->type = t('Default');
+      $handlers[$name]->export_type = EXPORT_IN_CODE;
+      return $handlers[$name];
+    }
+  }
+}
+
 /**
  * Load all task handlers for a given task and subtask and sort them.
  */
@@ -331,6 +414,11 @@ function delegator_save_task_handler(&$handler) {
 
   drupal_write_record('delegator_handlers', $handler, $update);
   db_query("DELETE FROM {delegator_weights} WHERE name = '%s'", $handler->name);
+
+  // If this was previously a default handler, we may have to write task handlers.
+  if (!$update) {
+
+  }
   return $handler;
 }
 
diff --git a/delegator/help/api-task.html b/delegator/help/api-task.html
index 9eb7ed40..0cc3f06b 100644
--- a/delegator/help/api-task.html
+++ b/delegator/help/api-task.html
@@ -24,6 +24,8 @@ task definition:
   subtasks callback -- A callback which returns an array of all subtasks. 
     This MUST return an array, even if it's empty.Param: $task
 
+  default handlers -- If the task contains any default handlers, they can be included here.
+
 task names must not contain a - as that is used to separate the task name from the subtask ID.
 
 subtasks implement data very similar to their parent task. In particular, they
diff --git a/delegator/plugins/tasks/page.admin.inc b/delegator/plugins/tasks/page.admin.inc
index 6637eda7..8ce19c92 100644
--- a/delegator/plugins/tasks/page.admin.inc
+++ b/delegator/plugins/tasks/page.admin.inc
@@ -32,7 +32,8 @@ function delegator_page_menu(&$items, $task) {
   $items['admin/build/pages/import'] = array(
     'title' => 'Import page',
     'description' => 'Import a delegator page subtask.',
-    'page callback' => 'delegator_page_import_subtask',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('delegator_page_import_subtask'),
     'type' => MENU_LOCAL_TASK,
   ) + $base;
 
@@ -88,8 +89,8 @@ function delegator_page_menu(&$items, $task) {
 
   $items["admin/build/pages/clone/%"] = array(
     'title' => t('Clone'),
-    'page callback' => 'delegator_page_clone_subtask',
-    'page arguments' => array(4),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('delegator_page_clone_subtask', 4),
     'type' => MENU_CALLBACK,
   ) + $base;
 
@@ -436,7 +437,7 @@ function delegator_page_form_basic(&$form, &$form_state) {
     '#default_value' => $page->name,
   );
 
-  if (isset($page->pid)) {
+  if (isset($page->pid) || !empty($page->import)) {
     $form['name']['#disabled'] = TRUE;
   }
 
@@ -523,14 +524,13 @@ function delegator_page_form_basic_validate(&$form, &$form_state) {
       form_error($form['name'], t('That name is already used; the name must be unique.'));
     }
   }
-
 }
 
 /**
  * Store the values from the basic settings form.
  */
 function delegator_page_form_basic_submit(&$form, &$form_state) {
-  if (!isset($form_state['page']->pid)) {
+  if (!isset($form_state['page']->pid) && empty($form_state['page']->import)) {
     $form_state['page']->name = $form_state['values']['name'];
   }
 
@@ -1221,6 +1221,12 @@ function delegator_page_break_lock_subtask_submit(&$form, &$form_state) {
  */
 function delegator_page_import_subtask(&$form_state) {
   drupal_set_title(t('Import page'));
+  $form['name'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Page name'),
+    '#description' => t('Enter the name to use for this page if it is different from the source page. Leave blank to use the name of the page.'),
+  );
+
   $form['object'] = array(
     '#type' => 'textarea',
     '#title' => t('Paste page code here'),
@@ -1246,15 +1252,17 @@ function delegator_page_import_subtask_validate($form, &$form_state) {
     form_error($form['object'], t('Unable to interpret page code.'));
   }
 
+  $name = !empty($form_state['values']['name']) ? $form_state['values']['name'] : $page->name;
+
   // See if the task is already locked.
-  $cache = delegator_page_get_page_cache($page->name);
+  $cache = delegator_page_get_page_cache($name);
   if (!empty($cache->locked)) {
     $account = user_load($cache->locked->uid);
-    $name = theme('username', $account);
+    $username = theme('username', $account);
     $lock_age = format_interval(time() - $cache->locked->updated);
-    $break = url('admin/build/pages/break-lock/' . $page->name, array('query' => array('destination' => $_GET['q'], 'cancel' => $_GET['q'])));
+    $break = url('admin/build/pages/break-lock/' . $name, array('query' => array('destination' => $_GET['q'], 'cancel' => $_GET['q'])));
 
-    form_error($form['object'], t('Unable to import page because it 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)));
+    form_error($form['object'], t('Unable to import page because it 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' => $username, '!age' => $lock_age, '!break' => $break)));
   }
 
   $page->type = t('Normal');
@@ -1262,11 +1270,20 @@ function delegator_page_import_subtask_validate($form, &$form_state) {
   ctools_include('export');
   $page->export_type = EXPORT_IN_DATABASE;
 
-  if (isset($cache)) {
-    drupal_set_message(t('Warning: The page you are importing already exists and this operation will overwrite an existing page. If this is not what you intend, you may Cancel this. You should then modify the <code>$page-&gt;name</code> field of your import to have a unique name.'), 'warning');
+  if (isset($cache) && isset($cache->pid)) {
+    drupal_set_message(t('Warning: The page you are importing already exists and this operation will overwrite an existing page. If this is not what you intend, you may Cancel this. You should then choose a different page name to ensure you use a unique name.'), 'warning');
     $page->pid = $cache->pid;
   }
 
+  if (preg_match('/[^a-zA-Z0-9_]/', $name)) {
+    form_error($form['name'], t('Page name must be alphanumeric or underscores only.'));
+  }
+
+  if ($page->name != $name) {
+    $page->old_name = $page->name;
+    $page->name = $name;
+  }
+  $page->import = TRUE; // set a flag so that we can get special import behavior.
   $form_state['page'] = $page;
 }
 
@@ -1277,14 +1294,23 @@ function delegator_page_import_subtask_submit($form, &$form_state) {
   // Use the one from the database or an updated one in cache?
   $page = &$form_state['page'];
 
+  // If the name was changed, go through and rename any default handlers so
+  // that they'll actually work.
+  if (isset($page->old_name) && isset($page->default_handlers)) {
+    foreach ($page->default_handlers as $name => $handler) {
+      // replace the old name with the new name at the start of the name string.
+      $page->default_handlers[$name]->name = preg_replace('/^page_' . $page->old_name . '/', 'page_' . $page->name, $handler->name);
+    }
+  }
+
   delegator_page_set_page_cache($page);
-  $form_state['redirect'] = 'admin/build/pages/edit' . $form_state['page']->name;
+  $form_state['redirect'] = 'admin/build/pages/edit/' . $page->name;
 }
 
 /**
  * Entry point to export a page.
  */
-function delegator_page_export_subtask($name) {
+function delegator_page_export_subtask($name, $handlers = FALSE) {
   // This ensures the task .inc file is loaded
   $task = delegator_get_task('page');
   $page = delegator_page_load($name);
@@ -1294,20 +1320,101 @@ function delegator_page_export_subtask($name) {
   }
 
   $title = $page->admin_title;
-  drupal_set_title(t('Export task page "@title"', array('@title' => $page->admin_title)));
+  if ($handlers) {
+    drupal_set_title(t('Export task page "@title" with handlers', array('@title' => $page->admin_title)));
+    $export = delegator_page_export($page, TRUE);
+  }
+  else {
+    drupal_set_title(t('Export task page "@title"', array('@title' => $page->admin_title)));
+    $export = delegator_page_export($page);
+  }
 
-  return drupal_get_form('ctools_export_form', delegator_page_export($page), $title);
+  return drupal_get_form('ctools_export_form', $export, $title);
 }
 
 /**
- * Entry point to export a page.
+ * Entry point to clone a page.
+ */
+function delegator_page_clone_subtask(&$form_state, $name) {
+  // This ensures the task .inc file is loaded
+  $form_state['task'] = delegator_get_task('page');
+  $page = delegator_page_load($name);
+
+  if (!$page) {
+    drupal_not_found();
+    exit;
+  }
+
+  $form_state['page'] = $page;
+  $form = array();
+
+  $form['name'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Page name'),
+    '#description' => t('Enter the name to the new page It must be unique and contain only alphanumeric characters and underscores.'),
+  );
+
+  $form['handlers'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Clone handlers'),
+    '#description' => t('If checked all task handlers associated with the task will be cloned as well.'),
+  );
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Clone'),
+  );
+
+  return $form;
+}
+
+/**
+ * Validate clone page form.
  */
-function delegator_page_clone_subtask($name) {
-  return "Not yet implemented.";
+function delegator_page_clone_subtask_validate(&$form, &$form_state) {
+  // Ensure name fits the rules:
+  if (preg_match('/[^a-zA-Z0-9_]/', $form_state['values']['name'])) {
+    form_error($form['name'], t('Page name must be alphanumeric or underscores only.'));
+  }
+
+  // Ensure name is unique.
+  if (delegator_page_load($form_state['values']['name'])) {
+    form_error($form['name'], t('That name is already used; the name must be unique.'));
+  }
 }
 
 /**
- * Entry point to export a page.
+ * submit clone page form.
+ *
+ * Load the page, change the name(s) to protect the innocent, and if
+ * requested, load all the task handlers so that they get saved properly too.
+ */
+function delegator_page_clone_subtask_submit(&$form, &$form_state) {
+  $page = $form_state['page'];
+  $task = $form_state['task'];
+  $name = $form_state['values']['name'];
+
+  if (!empty($form_state['values']['handlers'])) {
+    $handlers = delegator_load_task_handlers($task, $page->name);
+    $page->default_handlers = array();
+
+    foreach ($handlers as $handler_name => $handler) {
+      // replace the old name with the new name at the start of the name string.
+      $handler->name = preg_replace('/^page_' . $page->name . '/', 'page_' . $name, $handler->name);
+      $handler->subtask = $name;
+      // Pretend these handlers are all default handlers so they get saved as new.
+      $handler->type = t('Default');
+      $handler->export_type = EXPORT_IN_CODE;
+      $page->default_handlers[$handler_name] = $handler;
+    }
+  }
+
+  $page->name = $name;
+  delegator_page_set_page_cache($page);
+  $form_state['redirect'] = 'admin/build/pages/edit/' . $page->name;
+}
+/**
+ * Entry point to enable a page.
  */
 function delegator_page_enable_subtask($name) {
   ctools_include('export');
@@ -1316,7 +1423,7 @@ function delegator_page_enable_subtask($name) {
 }
 
 /**
- * Entry point to export a page.
+ * Entry point to disable a page.
  */
 function delegator_page_disable_subtask($name) {
   ctools_include('export');
diff --git a/delegator/plugins/tasks/page.inc b/delegator/plugins/tasks/page.inc
index c6b6389f..5691f007 100644
--- a/delegator/plugins/tasks/page.inc
+++ b/delegator/plugins/tasks/page.inc
@@ -140,6 +140,10 @@ function delegator_page_build_subtask($task, $page) {
       'title' => t('Export'),
       'href' => "admin/build/pages/export/$name",
     );
+    $operations[] = array(
+      'title' => t('Export (with handlers)'),
+      'href' => "admin/build/pages/export/$name/handlers",
+    );
     if ($page->export_type == (EXPORT_IN_CODE | EXPORT_IN_DATABASE)) {
       $operations[] = array(
         'title' => t('Revert'),
@@ -165,7 +169,7 @@ function delegator_page_build_subtask($task, $page) {
       'href' => "admin/build/pages/enable/$name",
     );
   }
-  return array(
+  $subtask = array(
     'name' => $name,
     'admin title' => $page->admin_title,
     'admin path' => $page->path,
@@ -175,6 +179,12 @@ function delegator_page_build_subtask($task, $page) {
     'row class' => empty($page->disabled) ? 'delegator-enabled' : 'delegator-disabled',
     'storage' => $page->type,
   );
+
+  // default handlers may appear from a default subtask.
+  if (isset($page->default_handlers)) {
+    $subtask['default handlers'] = $page->default_handlers;
+  }
+  return $subtask;
 }
 
 /**
@@ -383,6 +393,19 @@ function delegator_page_load_all() {
 function delegator_page_save(&$subtask) {
   $update = (isset($subtask->pid)) ? array('pid') : array();
   drupal_write_record('delegator_pages', $subtask, $update);
+
+  // If this was a default page we may need to write default task
+  // handlers that we provided as well.
+  if (!$update || isset($subtask->default_handlers)) {
+    $handlers = delegator_load_task_handlers(delegator_get_task('page'), $subtask->name);
+    foreach ($subtask->default_handlers as $name => $handler) {
+      if (!isset($handlers[$name]) || !($handlers[$name]->export_type & EXPORT_IN_DATABASE)) {
+        // Make sure this is right, as exports can wander a bit.
+        $handler->subtask = $subtask->name;
+        delegator_save_task_handler($handler);
+      }
+    }
+  }
   return $subtask;
 }
 
@@ -396,9 +419,17 @@ function delegator_page_delete($subtask) {
 /**
  * Export a page subtask.
  */
-function delegator_page_export($subtask, $indent = '') {
+function delegator_page_export($subtask, $with_handlers = FALSE, $indent = '') {
   ctools_include('export');
   $output = ctools_export_object('delegator_pages', $subtask, 'page', $indent);
+  if ($with_handlers) {
+    $handlers = delegator_load_task_handlers(delegator_get_task('page'), $subtask->name);
+    $output .= $indent . '$page->default_handlers = array();' . "\n";
+    foreach ($handlers as $handler) {
+      $output .= delegator_export_task_handler($handler, $indent);
+      $output .= $indent . '$page->default_handlers[$handler->name] = $handler;' . "\n";
+    }
+  }
   return $output;
 }
 
-- 
GitLab