Contextual Administration follows the ctools plugin architecture for its "context_admin" plugins. A selectable list of these is displayed in the 'content' section of the form wizard and individual settings for the plugin on the 'content settings' section.

### Building a context_admin plugin

Context Admin plugins are fairly straight forward to build, but there can be some tricky bits depending on what you are attempting to accomplish.

#### Things to know:

Context Admin plugins have no processor, so you MUST fill out the portions of the plugin array you intend on using.

      $plugin = array(
        'title' => t('Create Node'),
        'description' => t('Allows the creation of a particular node type.  This circumvents the typical drupal access controls on node creation.'),
        'required context' => new ctools_context_optional(t('Node'), 'node'),
        'content form' => 'context_admin_node_create_menu_content_form',
        'content form submit' => 'context_admin_node_create_menu_content_form_submit',
        'render' => 'context_admin_node_create_menu_render_page',
        'node insert' => 'context_admin_node_create_menu_node_insert',
      );

This is a pretty typical context_admin plugin. This specifically is the node_create_menu plugin that allows for nodes of a particular type to be created.

#### Breaking it down

    'title' => t('Create Node'),
    'description' => t('Allows the creation of a particular node type.  This circumvents the typical drupal access controls on node creation.'),

The title will display in the context_admin 'content' display. Description currently doesn't display anywhere but it doesn't hurt to have it in the plugin for other developers to read.

    'required context' => new ctools_context_optional(t('Node'), 'node'),

Required context functions exactly the same as it would for any other ctools plugin that utilizes context. Plugins utilizing this will REQUIRE a context of a particular type to be present in the page (i.e. node) unless the context specifies that it is optional (as this one does). Optional contexts will show up as globally usable, required contexts will only be available when the contextual criteria for their presence is met.

    'content form' => 'context_admin_node_create_menu_content_form',
    'content form submit' => 'context_admin_node_create_menu_content_form_submit',

These callbacks provide our configuration form and a submission handler for it. A validate handler has not been added here but can be manually added to the content form itself. Patches welcome for adding a 'content form validate' handler.

    'render' => 'context_admin_node_create_menu_render_page',

This is the callback that renders a give plugin. Depending on your use case this can be quite convoluted (the user_create_menu.inc plugin for example) or very simple and straight forward (the admin_section.inc).

    'node insert' => 'context_admin_node_create_menu_node_insert',

This is a customized addition to the plugin. Context Admin supports adding new hooks to your plugins through a series of helper functions. By default context_admin supports form alterations and node insertions for its own core plugins. Adding new plugins you may find that you need to expose to additional hooks. This can be done as follows:

    /**
     * Implementation of hook_node_insert
     */
    function context_admin_node_insert($node) {
      context_admin_plugin_callback_invoke('node insert', 'hook', NULL, $node);
    }

The context_admin_plugin_callback_invoke() function does most of the setup for us. It understands the difference between regular hooks and drupal_alter calls. Additionally it can load up and execute the appropriate section of a plugin. In our case here we have told it:

1. Find the 'node insert' section of the active plugin.
2. You are dealing with a hook
3. Find the active page for yourself (we aren't overriding it)
4. Pass the remaining arguments to the callback to which 'node insert' returns

This can be done with an alter callback similarly:

    /**
     * Implementation of hook_form_alter
     */
    function context_admin_form_alter(&$form, &$form_state, $form_id) {
      // We never want to deal with node_form_validate directly, always utilize our
      // wrapper function instead.
      switch($form_id) {
        case 'context_admin_node_form_wrapper':
          global $user;
          $node_validate = array_search('node_form_validate', $form['#validate']);
          $ca_validate = array_search('context_admin_node_form_wrapper_validate', $form['#validate']);
          if ($node_validate !== FALSE && $ca_validate !== FALSE) {
            unset($form['#validate'][$ca_validate]);
            $form['#validate'][$node_validate] = 'context_admin_node_form_wrapper_validate';
          }
          elseif ($node_validate !== FALSE) {
            $form['#validate'][$node_validate] = 'context_admin_node_form_wrapper_validate';
          }
          break;
      }

      // Expose hook_form_alter() to context_admin plugins.
      $args = array(
        'form alter',
        'alter',
        NULL,
        &$form,
        &$form_state,
        $form_id
      );
      call_user_func_array('context_admin_plugin_callback_invoke', $args);
    }

For alterations we must approach this a little differently as we have to build up an argument array to pass back through call_user_func_array() and denote which variables are passed by reference. This will NOT work for variables that were not passed to your alter by reference.

The code of context_admin_plugin_callback_invoke().

    /**
     * Helper function for exposing new core/contrib hooks or alter functions to
     * context_admin plugins.
     *
     * $plugin_callback - the key within the $plugin array in the plugin for the
     * hook or alter function specific to the plugin.
     * $callback_type - either hook or alter, hooks will use func_get_args which
     * passes by value, and alters will use debug_backtrace to pass by reference.
     */
    function context_admin_plugin_callback_invoke($plugin_callback, $callback_type, $page = NULL) {
      // Checking to make sure that we're on a context_admin generated page.
      if (empty($page)) {
        $page = page_manager_get_current_page();
      }
      $plugin = '';
      if ($page) {
        if (isset($page['handler']->conf['context_admin_options'])) {
          $plugin = $page['handler']->conf['context_admin_options'];
        }

        ctools_include('plugins');
        if ($callback_type == 'alter') {
          $stack = debug_backtrace();
          if (isset($stack[0]["args"])) {
            $args = array();
            for($i=0; $i < count($stack[0]["args"]); $i++) {
              $args[$i] = &$stack[0]["args"][$i];
            }
          }
        }
        elseif ($callback_type == 'hook') {
          $args = func_get_args();
        }
        unset($args[0]);
        unset($args[1]);
        unset($args[2]);
        $args[] = $page;
        if ($callback = ctools_plugin_load_function('context_admin', 'context_admin', $plugin, $plugin_callback)) {
          call_user_func_array($callback, $args);
        }
      }
    }

Equipped with this, you should be able to expand on the plugin system for your own use. Look at a typical plugin to see what all is passed to every type of callback. Hooks exposed by context_admin_plugin_callback_invoke() are always passed the current page variable in addition to the variables normally passed to those hooks.