Skip to content
page.admin.inc 50.9 KiB
Newer Older
<?php
// $Id$

/**
 * @file
 * Administrative functions for the page subtasks.
 *
 * These are attached to the menu system in page.inc via the hook_menu
 * delegation. They are included here so that this code is loaded
 * only when needed.
 */

/**
 * Delegated implementation of hook_menu().
 */
function page_manager_page_menu(&$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 page manager');

  $base = array(
    'access callback' => $access_callback,
    'access arguments' => $access_arguments,
    'file' => 'plugins/tasks/page.admin.inc',
  );

  $items['admin/build/pages/add'] = array(
    'title' => 'Add custom page',
    'page callback' => 'page_manager_page_add_subtask',
    'page arguments' => array(),
    'type' => MENU_LOCAL_TASK,
  ) + $base;

  $items['admin/build/pages/import'] = array(
    'title' => 'Import page',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('page_manager_page_import_subtask', 'page'),
    'type' => MENU_LOCAL_TASK,
  ) + $base;

  // AJAX callbacks for argument modal.
  $items['admin/build/pages/argument'] = array(
    'page callback' => 'page_manager_page_subtask_argument_ajax',
    'type' => MENU_CALLBACK,
  ) + $base;

  // Add menu entries for each subtask
  foreach (page_manager_page_load_all() as $subtask_id => $subtask) {
    if (!empty($subtask->disabled)) {
      continue;
    }

    if (!isset($subtask->access['type'])) {
      $subtask->access['type'] = 'none';
    }
    if (!isset($subtask->access['settings'])) {
      $subtask->access['settings'] = NULL;
    }

    $path             = array();
    $page_arguments   = array($subtask_id);
    $access_arguments = array($subtask->access);
    $load_arguments   = array($subtask_id, '%index', '%map');

    // Replace named placeholders with our own placeholder to load contexts.
    $position = 0;
    foreach (explode('/', $subtask->path) as $bit) {
      // Remove things like double slashes completely.
      if (!isset($bit) || $bit === '') {
        continue;
      }

      if ($bit[0] == '%' && $bit != '%') {
        $placeholder = '%pm_arg';

        // Chop off that %.
        $name = substr($bit, 1);

        // Check to see if the argument plugin wants to use a different
        // placholder. This will allow to_args.
        if (!empty($subtask->arguments[$name])) {
          ctools_include('context');
          if (!empty($subtask->arguments[$name]['name'])) {
            $plugin = ctools_get_argument($subtask->arguments[$name]['name']);
            if (isset($plugin['path placeholder'])) {
              if (function_exists($plugin['path placeholder'])) {
                $placeholder = $plugin['path placeholder']($subtask->arguments[$name]);
              }
              else {
                $placeholder = $plugin['path placeholder'];
              }
        // If an argument, swap it out with our argument loader and make sure
        // the argument gets passed through to the page callback.
        $page_arguments[]   = $position;
        $access_arguments[] = $position;
      }
      else if ($bit[0] != '!') {
        $path[] = $bit;
      }

      // Increment position. We do it like this to skip empty items that
      // could happen from erroneous paths like: this///that
      $position++;
    }

    $menu_path = implode('/', $path);

    $items[$menu_path] = page_manager_page_menu_item($task, $subtask->menu, $access_arguments, $page_arguments, $load_arguments);

    // Add a parent menu item if one is configured.
    if (isset($subtask->menu['type']) && $subtask->menu['type'] == 'default tab' && $subtask->menu['parent']['type'] != 'none') {
      array_pop($path);
      $parent_path = implode('/', $path);
      $items[$parent_path] = page_manager_page_menu_item($task, $subtask->menu['parent'], $access_arguments, $page_arguments, $load_arguments);
    }
  }
}

/**
 * Create a menu item for page manager pages.
 *
 * @param $menu
 *   The configuration to use. It will contain a type, and depending on the
 *   type may also contain weight, title and name. These are presumed to have
 *   been configured from the UI.
 * @param $access_arguments
 *   Arguments that go with ctools_access_menu; it should be loaded with
 *   the access plugin type, settings, and positions of any arguments that
 *   may produce contexts.
 * @param $page_arguments
 *   This should be seeded with the subtask name for easy loading and like
 *   the access arguments above should contain positions of arguments so
 *   that the menu system passes contexts through.
 * @param $load_arguments
 *   Arguments to send to the arg loader; should be the subtask id and '%index'.
 */
function page_manager_page_menu_item($task, $menu, $access_arguments, $page_arguments, $load_arguments) {
  $item = array(
    'access callback' => 'ctools_access_menu',
    'access arguments' => $access_arguments,
    'page callback' => 'page_manager_page_execute',
    'page arguments' => $page_arguments,
    'load arguments' => $load_arguments,
    'file' => 'plugins/tasks/page.inc',
  );

  if (isset($menu['title'])) {
    $item['title'] = $menu['title'];
  }
  if (isset($menu['weight'])) {
    $item['weight'] = $menu['weight'];
  }

  if (empty($menu['type'])) {
    $menu['type'] = 'none';
  }

  switch ($menu['type']) {
    case 'none':
    default:
      $item['type'] = MENU_CALLBACK;
      break;

    case 'normal':
      $item['type'] = MENU_NORMAL_ITEM;
      // Insert item into the proper menu
      $item['menu_name'] = $menu['name'];
      break;

    case 'tab':
      $item['type'] = MENU_LOCAL_TASK;
      break;

    case 'default tab':
      $item['type'] = MENU_DEFAULT_LOCAL_TASK;
      break;
  }

  return $item;
}

/**
 * Page callback to add a subtask.
 */
function page_manager_page_add_subtask($task_name = NULL, $step = NULL) {
  $task = page_manager_get_task('page');
  $task_handler_plugins = page_manager_get_task_handler_plugins($task);
  if (empty($task_handler_plugins)) {
    drupal_set_message(t('There are currently no variants available and a page may not be added. Perhaps you need to install the Panels module to get a variant?'), 'error');
    return ' ';
  }

  $form_info = array(
    'id' => 'page_manager_add_page',
    'show trail' => TRUE,
    'show back' => TRUE,
    'show return' => FALSE,
    'next callback' => 'page_manager_page_add_subtask_next',
    'finish callback' => 'page_manager_page_add_subtask_finish',
    'return callback' => 'page_manager_page_add_subtask_finish',
    'cancel callback' => 'page_manager_page_add_subtask_cancel',
    'add order' => array(
      'basic' => t('Basic settings'),
      'argument' => t('Argument settings'),
      'access' => t('Access control'),
      'menu' => t('Menu settings'),
    ),
    'forms' => array(
      'basic' => array(
        'form id' => 'page_manager_page_form_basic',
      ),
      'access' => array(
        'form id' => 'page_manager_page_form_access',
      ),
      'menu' => array(
        'form id' => 'page_manager_page_form_menu',
      ),
      'argument' => array(
        'form id' => 'page_manager_page_form_argument',
      ),
    ),
  );

  if ($task_name) {
    $page = page_manager_get_page_cache($task_name);
    if (empty($page)) {
      return drupal_not_found();
    }

    $form_info['path'] = "admin/build/pages/add/$task_name/%step";
    $handler_plugin = page_manager_get_task_handler($page->handler);

    $form_info['forms'] += $handler_plugin['forms'];

    if (isset($page->forms)) {
      foreach ($page->forms as $id) {
        if (isset($form_info['add order'][$id])) {
          $form_info['order'][$id] = $form_info['add order'][$id];
        }
        else if (isset($handler_plugin['add features'][$id])) {
          $form_info['order'][$id] = $handler_plugin['add features'][$id];
        }
        else if (isset($handler_plugin['required forms'][$id])) {
          $form_info['order'][$id] = $handler_plugin['required forms'][$id];
        }
      }
    }
    else {
      $form_info['order'] = $form_info['add order'];
    }

    // This means we just submitted our form from the default list
    // of steps, which we've traded in for a newly generated list of
    // steps above. We need to translate this 'next' step into what
    // our questions determined would be next.
    if ($step == 'next') {
      $keys = array_keys($form_info['order']);
      // get rid of 'basic' from the list of forms.
      array_shift($keys);
      $step = array_shift($keys);

      // If $step == 'basic' at this point, we were not presented with any
      // additional forms at all. Let's just save and go!
      if ($step == 'basic') {
        page_manager_save_page_cache($page);
        // Redirect to the new page's task handler editor.
        drupal_goto(page_manager_edit_url($page->task_name));
      }
    }
  }
  else {
    $new_page = page_manager_page_new();
    $new_page->name = NULL;

    $page = new stdClass();
    page_manager_page_new_page_cache($new_page, $page);
    $form_info['path'] = 'admin/build/pages/add/%task_name/%step';
    $form_info['show trail'] = FALSE;
    $form_info['order'] = array(
      'basic' => t('Basic settings'),
      'next' => t('A meaningless second page'),
    );
  }

  ctools_include('wizard');
  $form_state = array(
    'task' => $task,
    'subtask' => $page->subtask,
    'page' => &$page,
    'type' => 'add',
    'task_id' => 'page',
    'task_name' => $page->task_name,
    'creating' => TRUE,
  );

  if (!empty($page->handlers)) {
    $keys = array_keys($page->handlers);
    $key = array_shift($keys);
    $form_state['handler'] = &$page->handlers[$key];
    $form_state['handler_id'] = $key;
  }

  $output = ctools_wizard_multistep_form($form_info, $step, $form_state);

  if (!$output) {
    // redirect.
    drupal_redirect_form(array(), $form_state['redirect']);
  }

  return $output;
}

/**
 * Callback generated when the add page process is finished.
 */
function page_manager_page_add_subtask_finish(&$form_state) {
  $page = &$form_state['page'];
  page_manager_set_page_cache($page);

  $handler = $form_state['handler'];
  $handler_plugin = page_manager_get_task_handler($handler->handler);
  // Redirect to the new page's task handler editor.
  if (isset($handler_plugin['add finish'])) {
    $form_state['redirect'] = page_manager_edit_url($page->task_name, array('handlers', $handler->name, $handler_plugin['add finish']));
  }
  else {
    $form_state['redirect'] = page_manager_edit_url($page->task_name);
  }
  return;
}

/**
 * Callback generated when the 'next' button is clicked.
 *
 * All we do here is store the cache.
 */
function page_manager_page_add_subtask_next(&$form_state) {
  if (empty($form_state['task_name']) || $form_state['task_name'] == 'page') {
    // We may not have known the path to go next, because we didn't yet know the
    // task name. This fixes that.
    $form_state['form_info']['path'] = str_replace('%task_name', $form_state['page']->task_name, $form_state['form_info']['path']);

    $form_state['redirect'] = ctools_wizard_get_path($form_state['form_info'], $form_state['clicked_button']['#next']);
  }

  // Update the cache with changes.
  page_manager_set_page_cache($form_state['page']);
}

/**
 * Callback generated when the 'cancel' button is clicked.
 *
 * All we do here is clear the cache.
 */
function page_manager_page_add_subtask_cancel(&$form_state) {
  // Wipe all our stored changes.
  if (isset($form_state['page']->task_name)) {
    page_manager_clear_page_cache($form_state['page']->task_name);
  }
}

/**
 * Basic settings form for a page manager page.
 */
function page_manager_page_form_basic(&$form, &$form_state) {
  $page = &$form_state['page']->subtask['subtask'];
  $task = $form_state['task'];

  $form['admin_title'] = array(
    '#type' => 'textfield',
    '#title' => t('Administrative title'),
    '#description' => t('The name of this page. This will appear in the administrative interface to easily identify it.'),
    '#default_value' => $page->admin_title,
  );

  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Machine name'),
    '#description' => t('The machine readable name of this page. It must be unique, and it must contain only alphanumeric characters and underscores. Once created, you will not be able to change this value!'),
    '#default_value' => $page->name,
  );

  if (isset($page->pid) || empty($form_state['creating'])) {
    $form['name']['#disabled'] = TRUE;
    $form['name']['#value'] = $page->name;
  }

  $form['admin_description'] = array(
    '#type' => 'textarea',
    '#title' => t('Administrative description'),
    '#description' => t('A description of what this page is, does or is for, for administrative use.'),
    '#default_value' => $page->admin_description,
  );

  // path
  $form['path'] = array(
    '#type' => 'textfield',
    '#title' => t('Path'),
    '#description' => t('The URL path to get to this page. You may create named placeholders for variable parts of the path by using %name for required elements and !name for optional elements. For example: "node/%node/foo", "forum/%forum" or "dashboard/!input". These named placeholders can be turned into contexts on the arguments form.'),
    '#default_value' => $page->path,
    '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='),
  $frontpage = variable_get('site_frontpage', 'node');

  $path = array();
  if ($page->path) {
    foreach (explode('/', $page->path) as $bit) {
      if ($bit[0] != '!') {
        $path[] = $bit;
      }
  $path = implode('/', $path);

  if (empty($path) || $path != $frontpage) {
    $form['frontpage'] = array(
      '#type' => 'checkbox',
      '#default_value' => !empty($page->make_frontpage),
      '#title' => t('Make this your site home page.'),
      '#description' => t('To set this panel as your home page you must create a unique path name with no % placeholders in the path. The site home page is currently set to %homepage on the !siteinfo configuration form.', array('!siteinfo' => l('Site Information', 'admin/settings/site-information'), '%homepage' => '/' . $frontpage)),
    );
  }
  else if ($path == $frontpage) {
    $form['frontpage_markup'] = array(
      '#value' => '<b>' . t('This page is currently set to be your site home page. This can be modified on the !siteinfo configuration form.', array('!siteinfo' => l('Site Information', 'admin/settings/site-information'))) . '</b>',
    );

    $form['frontpage'] = array(
      '#type' => 'value',
      '#value' => TRUE,
    );
  }

  if (!isset($page->pid) && !empty($form_state['creating'])) {
    $features['default'] = array(
      'access' => t('Access control'),
      'menu' => t('Visible menu item'),
    );

    module_load_include('inc', 'page_manager', 'page_manager.admin');
    page_manager_handler_add_form($form, $form_state, $features);
  }

}

function page_manager_page_form_basic_validate_filter($value) {
  return $value === -1;
}

/**
 * Validate the basic form.
 */
function page_manager_page_form_basic_validate(&$form, &$form_state) {
  // Ensure path is unused by other pages.
  $page = $form_state['page']->subtask['subtask'];
  $name = !empty($form_state['values']['name']) ? $form_state['values']['name'] : $page->name;
  if (empty($name)) {
    form_error($form['name'], t('Name is required.'));
  }

  // If this is new, make sure the name is unique:
  if (empty($page->name)) {
    $test = page_manager_page_load($name);
    if ($test) {
      form_error($form['name'], t('That name is used by another page: @page', array('@page' => $test->admin_title)));
    }

    // 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.'));
    }
  }

  $pages = page_manager_page_load_all();
  foreach ($pages as $test) {
    if ($test->name != $name && $test->path == $form_state['values']['path'] && empty($test->disabled)) {
      form_error($form['path'], t('That path is used by another page: @page', array('@page' => $test->admin_title)));
    }
  }

  // Ensure path is unused by things NOT pages. We do the double check because
  // we're checking against our page callback.
  $path = array();
  if (empty($form_state['values']['path'])) {
    form_error($form['path'], t('Path is required.'));
    // stop processing here if there is no path.
    return;
  }

  $found = FALSE;
  $error = FALSE;
  foreach (explode('/', $form_state['values']['path']) as $bit) {
    if (!isset($bit) || $bit === '') {
      continue;
    }

    if ($bit == '%' || $bit == '!') {
      form_error($form['path'], t('You cannot have an unnamed placeholder (% or ! by itself). Please name your placeholder by adding a short piece of descriptive text to the % or !, such as %user or %node.'));
    }

    if ($bit[0] == '%') {
      if ($found) {
        form_error($form['path'], t('You cannot have a dynamic path element after an optional path element.'));
      }
      $path[] = '%';
    }
    else if ($bit[0] == '!') {
      $found = TRUE;
    }
    else {
      if ($found) {
        form_error($form['path'], t('You cannot have a static path element after an optional path element.'));
      }
      $path[] = $bit;
    }
  }

  // Check to see if something that isn't a page manager page is using the path.
  $result = db_query('SELECT * FROM {menu_router} WHERE path = :path', array(':path' => $path));
  foreach ($result as $router) {
    if ($router->page_callback != 'page_manager_page_execute') {
      form_error($form['path'], t('That path is already in use. This system cannot override existing paths.'));
  // Ensure the path is not already an alias to something else.
  if (strpos($path, '%') === FALSE) {
    $alias = db_query('SELECT src, dst FROM {url_alias} WHERE dst = :path', array(':path' => $path))->fetchObject();
    if ($alias) {
      form_error($form['path'], t('That path is currently assigned to be an alias for @alias. This system cannot override existing aliases.', array('@alias' => $alias->src)));
    }
  }
  else {
    if (!empty($form_state['values']['frontpage'])) {
      form_error($form['path'], t('You cannot make this page your site home page if it uses % placeholders.'));
    }
  }

  // Ensure path is properly formed.
  $args = page_manager_page_get_named_arguments($form_state['values']['path']);
  if ($invalid_args = array_filter($args, 'page_manager_page_form_basic_validate_filter')) {
    foreach ($invalid_args as $arg => $position) {
      form_error($form['path'], t('Duplicated argument %arg', array('%arg' => $arg)));
    }
  }

  if (isset($args['%'])) {
    form_error($form['path'], t('Invalid arg <em>%</em>. All arguments must be named with keywords.'));
  }

  $form_state['arguments'] = $args;
}

/**
 * Store the values from the basic settings form.
 */
function page_manager_page_form_basic_submit(&$form, &$form_state) {
  $page = &$form_state['page']->subtask['subtask'];
  $cache = &$form_state['page'];

  // If this is a new thing, then we have to do a bunch of setup to create
  // the cache record with the right ID and some basic data that we could
  // not know until we asked the user some questions.
  if (!isset($page->pid) && !empty($form_state['creating'])) {
    // Update the data with our new name.
    $page->name = $form_state['values']['name'];
    $form_state['page']->task_name = page_manager_make_task_name($form_state['task_id'], $page->name);
    $cache->handler = $form_state['values']['handler'];
    $cache->subtask_id = $page->name;
    $plugin = page_manager_get_task_handler($cache->handler);

    // Create a new handler.
    $handler = page_manager_new_task_handler($plugin);
    $title = !empty($form_state['values']['title']) ? $form_state['values']['title'] : $plugin['title'];
    page_manager_handler_add_to_page($cache, $handler, $title);

    // Figure out which forms to present them with
    $cache->forms = array();
    $cache->forms[] = 'basic'; // This one is always there.
    if (!empty($form_state['arguments'])) {
      $cache->forms[] = 'argument';
    }

    $features = $form_state['values']['features'];
    $cache->forms = array_merge($cache->forms, array_keys(array_filter($features['default'])));
    if (isset($features[$form_state['values']['handler']])) {
      $cache->forms = array_merge($cache->forms, array_keys(array_filter($features[$form_state['values']['handler']])));
    }

    if (isset($plugin['required forms'])) {
      $cache->forms = array_merge($cache->forms, array_keys($plugin['required forms']));
    }
  }

  $page->admin_title = $form_state['values']['admin_title'];
  $cache->subtask['admin title'] = check_plain($form_state['values']['admin_title']);

  $page->admin_description = $form_state['values']['admin_description'];
  $cache->subtask['admin description'] = filter_xss_admin($form_state['values']['admin_description']);

  if ($page->path != $form_state['values']['path']) {
    $page->path = $form_state['values']['path'];
    page_manager_page_recalculate_arguments($page);

  $page->make_frontpage = !empty($form_state['values']['frontpage']);
}

/**
 * Form to handle menu item controls.
 */
function page_manager_page_form_menu(&$form, &$form_state) {
  ctools_include('dependent');
  $form['menu'] = array(
    '#prefix' => '<div class="clear-block">',
    '#suffix' => '</div>',
    '#tree' => TRUE,
  );
  $menu = $form_state['page']->subtask['subtask']->menu;
  if (empty($menu)) {
    $menu = array(
      'type' => 'none',
      'title' => '',
      'weight' => 0,
      'name' => 'navigation',
      'parent' => array(
        'type' => 'none',
        'title' => '',
        'weight' => 0,
        'name' => 'navigation',
      ),
    );
  }

  $form['menu']['type'] = array(
    '#title' => t('Type'),
    '#type' => 'radios',
    '#options' => array(
      'none' => t('No menu entry'),
      'normal' => t('Normal menu entry'),
      'tab' => t('Menu tab'),
      'default tab' => t('Default menu tab'),
    ),
    '#default_value' => $menu['type'],
  );

  $form['menu']['title'] = array(
    '#title' => t('Title'),
    '#type' => 'textfield',
    '#default_value' => $menu['title'],
    '#description' => t('If set to normal or tab, enter the text to use for the menu item.'),
    '#process' => array('ctools_dependent_process'),
    '#dependency' => array('radio:menu[type]' => array('normal', 'tab', 'default tab')),
  );

  list($major, $minor) = explode('.', VERSION, 2);

  $form['menu']['name-warning'] = array(
    '#type' => 'markup',
    '#prefix' => '<div class="warning">',
    '#value' => t("Warning: Changing this item's menu will not work reliably in Drupal 6.4 or earlier. Please upgrade your copy of Drupal at !url.", array('!url' => l('drupal.org', 'http://drupal.org/project/Drupal+project'))),
    '#suffix' => '</div>',
    '#process' => array('ctools_dependent_process'),
    '#dependency' => array('radio:menu[type]' => array('normal')),
    '#access' => ($minor < 5),
  );

  // Only display the menu selector if menu module is enabled.
  if (module_exists('menu')) {
    $form['menu']['name'] = array(
      '#title' => t('Menu'),
      '#type' => 'select',
      '#options' => menu_get_menus(),
      '#default_value' => $menu['name'],
      '#description' => t('Insert item into an available menu.'),
      '#process' => array('ctools_dependent_process'),
      '#dependency' => array('radio:menu[type]' => array('normal')),
    );
  }
  else {
    $form['menu']['name'] = array(
      '#type' => 'value',
      '#value' => $menu['name'],
    );
    $form['menu']['markup'] = array(
      '#value' => t('Menu selection requires the activation of menu module.'),
    );
  }
  $form['menu']['weight'] = array(
    '#title' => t('Weight'),
    '#type' => 'textfield',
    '#default_value' => isset($menu['weight']) ? $menu['weight'] : 0,
    '#description' => t('The lower the weight the higher/further left it will appear.'),
    '#process' => array('ctools_dependent_process'),
    '#dependency' => array('radio:menu[type]' => array('normal', 'tab', 'default tab')),
  );

  $form['menu']['parent']['type'] = array(
    '#prefix' => '<div id="edit-menu-parent-type-wrapper">',
    '#suffix' => '</div>',
    '#title' => t('Parent menu item'),
    '#type' => 'radios',
    '#options' => array('none' => t('Already exists'), 'normal' => t('Normal menu item'), 'tab' => t('Menu tab')),
    '#default_value' => $menu['parent']['type'],
    '#description' => t('When providing a menu item as a default tab, Drupal needs to know what the parent menu item of that tab will be. Sometimes the parent will already exist, but other times you will need to have one created. The path of a parent item will always be the same path with the last part left off. i.e, if the path to this view is <em>foo/bar/baz</em>, the parent path would be <em>foo/bar</em>.'),
    '#process' => array('expand_radios', 'ctools_dependent_process'),
    '#dependency' => array('radio:menu[type]' => array('default tab')),
  );
  $form['menu']['parent']['title'] = array(
    '#title' => t('Parent item title'),
    '#type' => 'textfield',
    '#default_value' => $menu['parent']['title'],
    '#description' => t('If creating a parent menu item, enter the title of the item.'),
    '#process' => array('ctools_dependent_process'),
    '#dependency' => array('radio:menu[type]' => array('default tab'), 'radio:menu[parent][type]' => array('normal', 'tab')),
    '#dependency_count' => 2,
  );
  // Only display the menu selector if menu module is enabled.
  if (module_exists('menu')) {
    $form['menu']['parent']['name'] = array(
      '#title' => t('Parent item menu'),
      '#type' => 'select',
      '#options' => menu_get_menus(),
      '#default_value' => $menu['parent']['name'],
      '#description' => t('Insert item into an available menu.'),
      '#process' => array('ctools_dependent_process'),
      '#dependency' => array('radio:menu[type]' => array('default tab'), 'radio:menu[parent][type]' => array('normal')),
      '#dependency_count' => 2,
    );
  }
  else {
    $form['menu']['parent']['name'] = array(
      '#type' => 'value',
      '#value' => $menu['parent']['name'],
    );
  }
  $form['menu']['parent']['weight'] = array(
    '#title' => t('Tab weight'),
    '#type' => 'textfield',
    '#default_value' => $menu['parent']['weight'],
    '#size' => 5,
    '#description' => t('If the parent menu item is a tab, enter the weight of the tab. The lower the number, the more to the left it will be.'),
    '#process' => array('ctools_dependent_process'),
    '#dependency' => array('radio:menu[type]' => array('default tab'), 'radio:menu[parent][type]' => array('tab')),
    '#dependency_count' => 2,
  );
}

/**
 * Validate handler for the menu form for add/edit page task.
 */
function page_manager_page_form_menu_validate(&$form, &$form_state) {
  // If setting a 'normal' menu entry, make sure that any placeholders
  // support the to_arg stuff.

  if ($form_state['values']['menu']['type'] == 'normal') {
    $page = $form_state['page']->subtask['subtask'];

    foreach (explode('/', $page->path) as $bit) {
      if (!isset($bit) || $bit === '') {
        continue;
      }

      if ($bit[0] == '%') {
        // Chop off that %.
        $name = substr($bit, 1);

        // Check to see if the argument plugin allows to arg:
        if (!empty($page->arguments[$name])) {
          ctools_include('context');
          $plugin = ctools_get_argument($page->arguments[$name]['name']);
          if (!empty($plugin['path placeholder to_arg'])) {
            continue;
          }
        }

        form_error($form['menu']['type'], t('Paths with non optional placeholders cannot be used as normal menu items unless the selected argument handler provides a default argument to use for the menu item.'));
        return;
      }
    }
  }
}

/**
 * Submit handler for the menu form for add/edit page task.
 */
function page_manager_page_form_menu_submit(&$form, &$form_state) {
  $form_state['page']->subtask['subtask']->menu = $form_state['values']['menu'];
  $form_state['page']->path_changed = TRUE;
}

/**
 * Form to handle menu item controls.
 */
function page_manager_page_form_access(&$form, &$form_state) {
  ctools_include('context');
  $form_state['module'] = 'page_manager_page';
  $form_state['callback argument'] = $form_state['page']->task_name;
  $form_state['access'] = $form_state['page']->subtask['subtask']->access;
  $form_state['no buttons'] = TRUE;
  $form_state['contexts'] = array();

  // Load contexts based on argument data:
  if ($arguments = _page_manager_page_get_arguments($form_state['page']->subtask['subtask'])) {
    $form_state['contexts'] = ctools_context_get_placeholders_from_argument($arguments);
  }

  ctools_include('context-access-admin');
  $form = array_merge($form, ctools_access_admin_form($form_state));
}

/**
 * Submit handler to deal with access control changes.
 */
function page_manager_page_form_access_submit(&$form, &$form_state) {
  $form_state['page']->subtask['subtask']->access['logic'] = $form_state['values']['logic'];
  $form_state['page']->path_changed = TRUE;
}

/**
 * Form to handle assigning argument handlers to named arguments.
 */
function page_manager_page_form_argument(&$form, &$form_state) {
  $page = &$form_state['page']->subtask['subtask'];
  $path = $page->path;

  $arguments = page_manager_page_get_named_arguments($path);

  $form['table'] = array(
    '#theme' => 'page_manager_page_form_argument_table',
    '#page-manager-path' => $path,
    'argument' => array(),
  );

  $task_name = $form_state['page']->task_name;
  foreach ($arguments as $keyword => $position) {
    $conf = array();

    if (isset($page->temporary_arguments[$keyword]) && !empty($form_state['allow temp'])) {
      $conf = $page->temporary_arguments[$keyword];
    }
    else if (isset($page->arguments[$keyword])) {
      $conf = $page->arguments[$keyword];
    }

    $context = t('No context assigned');

    $plugin = array();
    if ($conf && isset($conf['name'])) {
      ctools_include('context');
      $plugin = ctools_get_argument($conf['name']);

      if (isset($plugin['title'])) {
        $context = $plugin['title'];
      }
    }

    $form['table']['argument'][$keyword]['#keyword'] = $keyword;
    $form['table']['argument'][$keyword]['#position'] = $position;
    $form['table']['argument'][$keyword]['#context'] = $context;

    // The URL for this ajax button
    $form['table']['argument'][$keyword]['change-url'] = array(
      '#attributes' => array('class' => array("page-manager-context-$keyword-change-url")),
      '#type' => 'hidden',
      '#value' => url("admin/build/pages/argument/change/$task_name/$keyword", array('absolute' => TRUE)),
    );
    $form['table']['argument'][$keyword]['change'] = array(
      '#type' => 'submit',
      '#value' => t('Change'),
      '#attributes' => array('class' => array('ctools-use-modal')),
      '#id' => "page-manager-context-$keyword-change",
    );

    $form['table']['argument'][$keyword]['settings'] = array();

    // Only show the button if this has a settings form available:
    if (!empty($plugin)) {
      // The URL for this ajax button
      $form['table']['argument'][$keyword]['settings-url'] = array(
        '#attributes' => array('class' => array("page-manager-context-$keyword-settings-url")),
        '#type' => 'hidden',
        '#value' => url("admin/build/pages/argument/settings/$task_name/$keyword", array('absolute' => TRUE)),
      );
      $form['table']['argument'][$keyword]['settings'] = array(
        '#type' => 'submit',
        '#value' => t('Settings'),
        '#attributes' => array('class' => array('ctools-use-modal')),
        '#id' => "page-manager-context-$keyword-settings",
      );
    }
  }
}

/**
 * Theme the table for this form.
 */
function theme_page_manager_page_form_argument_table($form) {
  $header = array(
    array('data' => t('Argument'), 'class' => array('page-manager-argument')),
    array('data' => t('Position in path'), 'class' => array('page-manager-position')),
    array('data' => t('Context assigned'), 'class' => array('page-manager-context')),
    array('data' => t('Operations'), 'class' => array('page-manager-operations')),
  );

  $rows = array();

  ctools_include('modal');
  ctools_modal_add_js();
  foreach (element_children($form['argument']) as $key) {
    $row = array();
    $row[] = '%' . check_plain($form['argument'][$key]['#keyword']);
    $row[] = check_plain($form['argument'][$key]['#position']);
    $row[] = $form['argument'][$key]['#context'] . ' &nbsp; ' . drupal_render($form['argument'][$key]['change']);;
    $row[] = drupal_render($form['argument'][$key]['settings']) . drupal_render($form['argument'][$key]);

    $rows[] = array('data' => $row);
  }

  if (!$rows) {
    $rows[] = array(array('data' => t('The path %path has no arguments to configure.', array('%path' => $form['#page-manager-path'])), 'colspan' => 4));
  }

  $attributes = array(
    'id' => 'page-manager-argument-table',
  );

  $output = theme('table', $header, $rows, $attributes);
  return $output;
}

/**
 * Ajax entry point to edit an item
 */
function page_manager_page_subtask_argument_ajax($step = NULL, $task_name = NULL, $keyword = NULL) {
  ctools_include('ajax');
  ctools_include('modal');
  ctools_include('context');
  ctools_include('wizard');

  if (!$step) {
  }

  if (!$cache = page_manager_get_page_cache($task_name)) {
    return ajax_render_error(t('Invalid object name.'));
  }

  $page = &$cache->subtask['subtask'];
  $path = $page->path;
  $arguments = page_manager_page_get_named_arguments($path);

  // Load stored object from cache.
  if (!isset($arguments[$keyword])) {
    return ajax_render_error(t('Invalid keyword.'));
  }

  // Set up wizard info
  $form_info = array(
    'id' => 'page_manager_page_argument',
    'path' => "admin/build/pages/argument/%step/$task_name/$keyword",
    'show cancel' => TRUE,
    'next callback' => 'page_manager_page_argument_next',
    'finish callback' => 'page_manager_page_argument_finish',
    'cancel callback' => 'page_manager_page_argument_cancel',
    'order' => array(
      'change' => t('Change context type'),
      'settings' => t('Argument settings'),
    ),
    'forms' => array(
      'change' => array(
        'title' => t('Change argument'),
        'form id' => 'page_manager_page_argument_form_change',
      ),
      'settings' => array(
        'title' => t('Argument settings'),
        'form id' => 'page_manager_page_argument_form_settings',
      ),
    ),
  );

  $form_state = array(
    'page' => $cache,
    'keyword' => $keyword,
    'ajax' => TRUE,
    'modal' => TRUE,
    'commands' => array(),