Skip to content
delegator.module 26.7 KiB
Newer Older
/**
 * @file
 *
 * The delegator handles two plugins and provides a UI to link them together.
 * The 'task' plugin defines a particular task, and it provides menu access
 * to those tasks so that they can respond to page requests.
 *
 * 'task_handlers' are then chosen to handle these tasks, arranged in
 * order of priority in the UI. Task handlers can choose whether or
 * not to handle a task. The first one that does handles it.
 *
 * This system was initially designed to provide a nice way to override
 * existing menu items and provide alternate paths based on the data,
 * particularly for node views, but tasks are not limited to this
 * kind of task. The UI is modular and can be embedded within another
 * module's UI to allow specific tasks to have chosen handlers.
 */

/**
 * Implementation of hook_perm().
 */
function delegator_perm() {
  return array('administer delegator');
}

/**
 * Implementation of hook_theme().
 */
    'delegator_admin_list_form' => array(
      'arguments' => array('form' => NULL),
      'file' => 'delegator.admin.inc',
    ),
    'delegator_admin_lock' => array(
      'arguments' => array('lock' => array(), 'task_name' => NULL),
      'file' => 'delegator.admin.inc',
    ),
    'delegator_admin_changed' => array(
      'arguments' => array(),
      'file' => 'delegator.admin.inc',
    ),

  // Allow task plugins to have theme registrations by passing through:
  $tasks = delegator_get_tasks();

  // Provide menu items for each task.
  foreach ($tasks as $task_id => $task) {
    if ($function = ctools_plugin_get_function($task, 'hook theme')) {
      $function($items, $task);
    }
  }

  return $items;
Earl Miles's avatar
Earl Miles committed
/**
 * Implementation of hook_ctools_plugin_dierctory() to let the system know
 * we implement task and task_handler plugins.
 */
function delegator_ctools_plugin_directory($module, $plugin) {
  if ($module == 'delegator') {
    return 'plugins/' . $plugin;
  }
}

/**
 * Implementation of hook_menu.
 *
 * Get a list of all tasks and delegate to them.
 */
function delegator_menu() {
  $items = array();

  // Task types get menu entries so they can set up their own administrative
  // areas.
  $task_types = delegator_get_task_types();

  foreach ($task_types as $id => $task_type) {
    if ($function = ctools_plugin_get_function($task_type, 'hook menu')) {
      $function($items, $task_type);
    }
  }

Earl Miles's avatar
Earl Miles committed
  // Provide menu items for each task.
  foreach ($tasks as $task_id => $task) {
    $handlers = delegator_get_task_handler_plugins($task);
Earl Miles's avatar
Earl Miles committed
    delegator_menu_task_items($items, $task_id, $task, $handlers);

    // And for those that provide subtasks, provide menu items for them, as well.
    foreach (delegator_get_task_subtasks($task) as $subtask_id => $subtask) {
      delegator_menu_task_items($items, $task_id, $subtask, $handlers, $subtask_id);
    }
  }

  return $items;
}

/**
 * Implementation of hook_menu_alter.
 *
 * Get a list of all tasks and delegate to them.
 */
function delegator_menu_alter(&$items) {
  $tasks = delegator_get_tasks();

  foreach ($tasks as $task) {
    if ($function = ctools_plugin_get_function($task, 'hook menu alter')) {
Earl Miles's avatar
Earl Miles committed
    // let the subtasks alter the menu items too.
    foreach (delegator_get_task_subtasks($task) as $subtask_id => $subtask) {
      if ($function = ctools_plugin_get_function($subtask, 'hook menu alter')) {
        $function($items, $subtask);
      }
    }
Earl Miles's avatar
Earl Miles committed
/**
 * Generate a menu item for a given task and subtask combination.
 */
function delegator_menu_task_items(&$items, $task_id, $task, $handlers, $subtask_id = NULL) {
  // If using a subtask, attach the subtask id to the name we use.
  $task_name = $task_id . ($subtask_id ? '-' . $subtask_id : '');

  // Allow the task to add its own menu items.
  if ($function = ctools_plugin_get_function($task, 'hook menu')) {
    $function($items, $task);
  }

  // Set up access permissions.
  $access_callback = isset($task['admin access callback']) ? $task['admin access callback'] : 'user_access';
  $access_arguments = isset($task['admin access arguments']) ? $task['admin access arguments'] : array('administer delegator');

  $base = array(
    'access callback' => $access_callback,
    'access arguments' => $access_arguments,
    'file' => 'delegator.admin.inc',
  );

  if (isset($task['admin title'])) {
    $items['admin/build/delegator/' . $task_name] = array(
      'title' => $task['admin title'],
      'description' => isset($task['admin description']) ? $task['admin description'] : '',
Earl Miles's avatar
Earl Miles committed
      'page callback' => 'delegator_administer_task',
      'page arguments' => array($task_name),
      'type' => MENU_CALLBACK,
Earl Miles's avatar
Earl Miles committed
    ) + $base;
  }

  // Form to add new items to this task.
  $items['admin/build/delegator/' . $task_name . '/add/%/%'] = array(
    'page callback' => 'delegator_administer_task_handler_add',
    'page arguments' => array($task_name, 5, 6),
  ) + $base;

  // Form to add export a handler
  $items['admin/build/delegator/' . $task_name . '/export/%'] = array(
    'page callback' => 'delegator_administer_task_handler_export',
    'page arguments' => array($task_name, 5),
  ) + $base;

  // Form to break a lock on this task.
  $items['admin/build/delegator/' . $task_name . '/break-lock'] = array(
    'title' => 'Break lock',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('delegator_administer_break_lock', $task_name),
    'type' => MENU_CALLBACK,
  ) + $base;

  // Form to import a task handler for this task.
  $items['admin/build/delegator/' . $task_name . '/import'] = array(
    'title' => 'Import',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('delegator_admin_import_task_handler', $task_name),
    'type' => MENU_CALLBACK,
  ) + $base;

Earl Miles's avatar
Earl Miles committed
  foreach ($handlers as $handler_id => $handler) {
    if (isset($handler['edit forms'])) {
      $weight = 0;
Earl Miles's avatar
Earl Miles committed
      foreach ($handler['edit forms'] as $form_id => $form_title) {
        if (!$default_task) {
          $default_task = TRUE;
          // The first edit form is the default for tabs, so it gets a bit
          // of special treatment here. It gets the parent menu declaration
          // which is just a callback, and it gets its main entry declared
          // as the default local task.
Earl Miles's avatar
Earl Miles committed
          $items["admin/build/delegator/$task_name/$handler_id/%"] = array(
            'title' => t('Edit'),
            'page callback' => 'delegator_administer_task_handler_edit',
            'page arguments' => array($task_name, $handler_id, 5, $form_id),
Earl Miles's avatar
Earl Miles committed
          ) + $base;
Earl Miles's avatar
Earl Miles committed
        }
        else {
          $type = $form_title ? MENU_LOCAL_TASK : MENU_CALLBACK;
Earl Miles's avatar
Earl Miles committed
        }
        // Handler to edit delegator task handlers. May exist in its own UI.
        $items["admin/build/delegator/$task_name/$handler_id/%/$form_id"] = array(
          'title' => $form_title,
          'page callback' => 'delegator_administer_task_handler_edit',
          'page arguments' => array($task_name, $handler_id, 5, $form_id),
          // This allows an empty form title to provide a hidden form
          // which is useful for doing more branch-like multi-step
          // functionality.
          'type' => $type,
          'weight' => $weight++,
        ) + $base;
// --------------------------------------------------------------------------
// Database routines

/**
 * 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($task, $subtask_id, $name) {
  ctools_include('export');
  $result = ctools_export_load_object('delegator_handlers', 'names', array($name));
  $handlers = delegator_get_default_task_handlers($task, $subtask_id);
  return delegator_compare_task_handlers($result, $handlers, $name);
}

/**
 * Load all task handlers for a given task/subtask.
 */
function delegator_load_task_handlers($task, $subtask_id = NULL, $default_handlers = NULL) {
  ctools_include('export');
  $conditions = array(
    'task' => $task['name'],
  );

  if (isset($subtask_id)) {
    $conditions['subtask'] = $subtask_id;
  }

  $handlers = ctools_export_load_object('delegator_handlers', 'conditions', $conditions);
  $defaults = isset($default_handlers) ? $default_handlers : 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;
      // Ensure task and subtask are correct, because it's easy to change task
      // names when editing a default and fail to do it on the associated handlers.
      $result->task = $task['name'];
      $result->subtask = $subtask_id;

  // Override weights from the weight table.
  if ($handlers) {
    $names = array();
    $placeholders = array();
    foreach ($handlers as $handler) {
      $names[] = $handler->name;
      $placeholders[] = "'%s'";
    }

    $result = db_query("SELECT name, weight FROM {delegator_weights} WHERE name IN (" . implode(', ', $placeholders) . ")", $names);
    while ($weight = db_fetch_object($result)) {
      $handlers[$weight->name]->weight = $weight->weight;
    }
  }

  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.
 */
function delegator_load_sorted_handlers($task, $subtask_id = NULL, $enabled = FALSE) {
  $handlers = delegator_load_task_handlers($task, $subtask_id);
  if ($enabled) {
    foreach ($handlers as $id => $handler) {
      if (!empty($handler->disabled)) {
        unset($handlers[$id]);
      }
    }
  }
  uasort($handlers, '_delegator_sort_task_handlers');
  return $handlers;
}

/**
 * Callback for uasort to sort task handlers.
 *
 * Task handlers are sorted by weight then by name.
 */
function _delegator_sort_task_handlers($a, $b) {
  if ($a->weight < $b->weight) {
    return -1;
  }
  elseif ($a->weight > $b->weight) {
    return 1;
  }
  elseif ($a->name < $b->name) {
    return -1;
  }
  elseif ($a->name > $b->name) {
    return 1;
  }

  return 0;
}

function delegator_save_task_handler(&$handler) {
  $update = (isset($handler->did)) ? array('did') : array();
  // Let the task handler respond to saves:
  if ($function = ctools_plugin_load_function('delegator', 'task_handlers', $handler->handler, 'save')) {
    $function($handler, $update);
  }

  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) {
    // @todo wtf was I going to do here?
  return $handler;
}

/**
 * Remove a task handler.
 */
function delegator_delete_task_handler($handler) {
  // Let the task handler respond to saves:
  if ($function = ctools_plugin_load_function('delegator', 'task_handlers', $handler->handler, 'delete')) {
    $function($handler);
  }
  db_query("DELETE FROM {delegator_handlers} WHERE name = '%s'", $handler->name);
  db_query("DELETE FROM {delegator_weights} WHERE name = '%s'", $handler->name);
/**
 * Export a task handler into code suitable for import or use as a default
 * task handler.
 */
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, $indent);
  $output .= $append;

  return $output;
}

/**
 * Create a new task handler object.
 *
 * @param $task
 *   The task this task handler is for.
 * @param $subtask_id
 *   The subtask this task handler is for.
 * @param $plugin
 *   The plugin this task handler is created from.
 * @param $weight
 *   The weight to give this new task handler.
 * @param $cache
 *   The task cache if the task is currently being edited. This must be used
 *   if task handlers already exist as it is used to determine a unique name
 *   and without this naming collisions could occur.
 */
function delegator_new_task_handler($task, $subtask_id, $plugin, $weight = 0, $cache = NULL) {
  // 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 = $task['name'];
  if ($subtask_id) {
    $base .= '_' . $subtask_id;
  }
  $base .= '_' . $plugin['name'];

  // 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($cache->handlers[$name])) {
    $name = $base . '_' . ++$count;
  }

  // Create a new, empty handler object.
  $handler          = new stdClass;
  $handler->title   = '';
  $handler->task    = $task['name'];
  $handler->name    = $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'];
    }
    else if (function_exists($plugin['default conf'])) {
      $handler->conf = $plugin['default conf']($handler, $task, $subtask_id);
    }
  }

  return $handler;
}

/**
 * Set an overidden weight for a task handler.
 *
 * We do this so that in-code task handlers don't need to get written
 * to the database just because they have their weight changed.
 */
function delegator_update_task_handler_weight($handler, $weight) {
  db_query("DELETE FROM {delegator_weights} WHERE name = '%s'", $handler->name);
  db_query("INSERT INTO {delegator_weights} (name, weight) VALUES ('%s', %d)", $handler->name, $weight);
}
/**
 * Shortcut function to get task type plugins.
 */
function delegator_get_task_types() {
  ctools_include('plugins');
  return ctools_get_plugins('delegator', 'task_types');
}

/**
 * Shortcut function to get a task type plugin.
 */
function delegator_get_task_type($id) {
  ctools_include('plugins');
  return ctools_get_plugins('delegator', 'task_types', $id);
}

/**
 * Shortcut function to get task plugins.
 */
function delegator_get_tasks() {
  return ctools_get_plugins('delegator', 'tasks');
}

/**
 * Shortcut function to get a task plugin.
 */
function delegator_get_task($id) {
  return ctools_get_plugins('delegator', 'tasks', $id);
}

/**
 * Get all tasks for a given type.
 */
function delegator_get_tasks_by_type($type) {
  ctools_include('plugins');
  $all_tasks = ctools_get_plugins('delegator', 'tasks');
  $tasks = array();
  foreach ($all_tasks as $id => $task) {
    if (isset($task['task type']) && $task['task type'] == $type) {
      $tasks[$id] = $task;
    }
  }

  return $tasks;
}

Earl Miles's avatar
Earl Miles committed
/**
 * Fetch all subtasks for a delegator task.
 *
 * @param $task
 *   A loaded $task plugin object.
 */
function delegator_get_task_subtasks($task) {
  if (empty($task['subtasks'])) {
    return array();
  }

  if ($function = ctools_plugin_get_function($task, 'subtasks callback')) {
    return $function($task);
  }
}

/**
 * Fetch all subtasks for a delegator task.
 *
 * @param $task
 *   A loaded $task plugin object.
 * @param $subtask_id
 *   The subtask ID to load.
 */
function delegator_get_task_subtask($task, $subtask_id) {
  if (empty($task['subtasks'])) {
    return;
  }

  if ($function = ctools_plugin_get_function($task, 'subtask callback')) {
    return $function($task, $subtask_id);
  }
}

/**
 * Shortcut function to get task handler plugins.
 */
function delegator_get_task_handlers() {
  return ctools_get_plugins('delegator', 'task_handlers');
}

/**
 * Shortcut function to get a task handler plugin.
 */
function delegator_get_task_handler($id) {
  return ctools_get_plugins('delegator', 'task_handlers', $id);
}

/**
 * Retrieve a list of all applicable task handlers for a given task.
 *
 * This looks at the $task['handler type'] and compares that to $task_handler['handler type'].
 * If the task has no type, the id of the task is used instead.
 */
function delegator_get_task_handler_plugins($task) {
  $type = isset($task['handler type']) ? $task['handler type'] : $task['name'];

  $handlers = array();
  $task_handlers = delegator_get_task_handlers();
  foreach ($task_handlers as $id => $handler) {
    $task_type = is_array($handler['handler type']) ? $handler['handler type'] : array($handler['handler type']);
    if (in_array($type, $task_type) || in_array($name, $task_type)) {
      $handlers[$id] = $handler;
    }
  }

  return $handlers;
}

/**
 * Get the title for a given handler.
 *
 * If the plugin has no 'admin title' function, the generic title of the
 * plugin is used instead.
 */
function delegator_get_handler_title($plugin, $handler, $task, $subtask_id) {
  $function = ctools_plugin_get_function($plugin, 'admin title');
  if ($function) {
    return $function($handler, $task, $subtask_id);
  }
  else {
    return $plugin['title'];
  }
}

/**
 * Get the admin summary (additional info) for a given handler.
 */
function delegator_get_handler_summary($plugin, $handler, $task, $subtask_id) {
  if ($function = ctools_plugin_get_function($plugin, 'admin summary')) {
    return $function($handler, $task, $subtask_id);
  }
}

Earl Miles's avatar
Earl Miles committed
 * Split a task name into a task id and subtask id, if applicable.
Earl Miles's avatar
Earl Miles committed
function delegator_get_task_id($task_name) {
  if (strpos($task_name, '-') !== FALSE) {
    return explode('-', $task_name, 2);
  }
  else {
    return array($task_name, NULL);
  }
}

/**
 * Turn a task id + subtask_id into a task name.
 */
function delegator_make_task_name($task_id, $subtask_id) {
  if ($subtask_id) {
    return $task_id . '-' . $subtask_id;
  }
  else {
    return $task_id;
/**
 * Delegator for arg load function because menu system will not load extra
 * files for these; they must be in a .module.
 */
function dp_arg_load($value, $subtask, $argument) {
  require_once './' . drupal_get_path('module', 'delegator') . '/plugins/tasks/page.inc';
  return _dp_arg_load($value, $subtask, $argument);
}

/**
 * Possibly provide a default value so that variable items can appear in visible menus.
 */
function dp_arg_to_arg($bit, $map, $index) {
/*
  Reference code.

  $start = microtime();
  $introspect = debug_backtrace();
  dsm(microtime() - $start);
  foreach ($introspect as $call) {
    if ($call['function'] == '_menu_link_translate') {
      dsm(array($map, $call['args']['0']));
    }
  }
*/
}


/**
 * Get the render function for a handler.
 */
function delegator_get_renderer($handler) {
  return ctools_plugin_load_function('delegator', 'task_handlers', $handler->handler, 'render');
}

/**
 * Callback for access control ajax form on behalf of page.inc task.
 *
 * Returns the cached access config and contexts used.
 */
function delegator_page_ctools_access_get($argument) {
  ctools_include('context');
  $task = delegator_get_task('page');

  require_once './' . drupal_get_path('module', 'delegator') . '/plugins/tasks/page.admin.inc';
  $page = delegator_page_get_page_cache($argument);

  $contexts = array();
  // Load contexts based on argument data:
  if ($arguments = _delegator_page_get_arguments($page)) {
    $contexts = ctools_context_get_placeholders_from_argument($arguments);
  }

  return array($page->access, $contexts);
}

/**
 * Callback for access control ajax form on behalf of page.inc task.
 *
 * Writes the changed access to the cache.
 */
function delegator_page_ctools_access_set($argument, $access) {
  $page = delegator_page_get_page_cache($argument);
  $page->access = $access;
  delegator_page_set_page_cache($page);
}

/**
 * Callback for access control ajax form on behalf of context task handler.
 *
 * Returns the cached access config and contexts used.
 */
function delegator_task_handler_ctools_access_get($argument) {
  ctools_include('context');
  require_once './' . drupal_get_path('module', 'delegator') . '/delegator.admin.inc';

  list($task_name, $name) = explode('*', $argument);
  list($task_id, $subtask_id) = delegator_get_task_id($task_name);
  $task = delegator_get_task($task_id);
  $cache = delegator_admin_get_task_cache($task, $subtask_id);

  $handler = delegator_admin_find_handler($name, $cache, $task_name);

  if (!isset($handler->conf['access'])) {
    $handler->conf['access'] = array();
  }

  ctools_include('context-task-handler');

  $contexts = ctools_context_handler_get_all_contexts($task, $subtask_id, $handler);

  return array($handler->conf['access'], $contexts);
}

/**
 * Callback for access control ajax form on behalf of context task handler.
 *
 * Writes the changed access to the cache.
 */
function delegator_task_handler_ctools_access_set($argument, $access) {
  list($task_name, $name) = explode('*', $argument);
  list($task_id, $subtask_id) = delegator_get_task_id($task_name);
  $task = delegator_get_task($task_id);
  $cache = delegator_admin_get_task_cache($task, $subtask_id);

  $handler = delegator_admin_find_handler($name, $cache, $task_name);

  $handler->conf['access'] = $access;
  delegator_admin_set_task_handler_cache($handler, TRUE);
  // Make sure the cache is aware that we're editing this item:
  if (!isset($cache->working) || $cache->working != $handler->name) {
    $cache->working = $handler->name;
    delegator_admin_set_task_cache($task, $subtask_id, $cache);
  }
}
/**
 * Load a context from an argument for a given page task.
 *
 * This is used as a menu callback to translate arguments and that is why it is
 * here in the .module file.
 *
 * @param $value
 *   The incoming argument value.
 * @param $subtask
 *   The subtask id.
 * @param $argument
 *   The numeric position of the argument in the path, counting from 0.
 *
 * @return
 *   A context item if one is configured, the argument if one is not, or
 *   FALSE if restricted or invalid.
 */
function _dp_arg_load($value, $subtask, $argument) {
  $page = delegator_page_load($subtask);
  if (!$page) {
    return FALSE;
  }

  $path = explode('/', $page->path);
  if (empty($path[$argument])) {
    return FALSE;
  }

  $keyword = substr($path[$argument], 1);
  if (empty($page->arguments[$keyword])) {
    return $value;
  }

  $page->arguments[$keyword]['keyword'] = $keyword;

  ctools_include('context');
  $context = ctools_context_get_context_from_argument($page->arguments[$keyword], $value);

  // convert false equivalents to false.
  return $context ? $context : FALSE;
}

/**
 * Reset the active menu trail to the trail specified by the task type.
 *
 * The task type can specify an 'admin path'. If it does,
 * 'admin/build/delegator' will be removed from the active trail and replaced
 * with whatever is in the admin path; that way task types can provide
 * their own administration.
 */
function delegator_set_trail($task, $task_name = NULL) {
  $task_type = delegator_get_task_type($task['task type']);
  if (empty($task_type['admin path'])) {
    return;
  }


  ctools_include('menu');
  $trail = menu_get_active_trail();
  $remove = ctools_get_menu_trail('admin/build/delegator');
  foreach ($remove as $info) {
    foreach ($trail as $id => $crumb) {
      if ($crumb['href'] == $info['href']) {
        unset($trail[$id]);
      }
    }
  }

  if ($task_name) {
    // Clean up broken 'Edit' link that Drupal leaves behind.
    $trail = array_values($trail);
    if (isset($trail[0]) && $trail[0]['title'] == t('Edit')) {
      unset($trail[0]);
    }

    array_unshift($trail, menu_get_item('admin/build/delegator/' . $task_name));
  }

  $trail = array_merge(ctools_get_menu_trail($task_type['admin path']), $trail);
  menu_set_active_trail($trail);
}