Skip to content
panels.module 35.3 KiB
Newer Older
/**
 * @file panels.module
 *
 * Core functionality for the Panels engine.
 */

define('PANELS_REQUIRED_CTOOLS_API', '1.0.1');
 * Returns the API version of Panels. This didn't exist in 1.
 *
 * @return An array with the major and minor versions
/**
 * Implementation of hook_theme()
 */
  // Safety: go away if CTools is not at an appropriate version.
  if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
  $theme = array();
  $theme['panels_layout_link'] = array(
    'arguments' => array('title', 'id', 'image', 'link'),
  );
  $theme['panels_layout_icon'] = array(
    'arguments' => array('id', 'image', 'title' => NULL),
  );
  $theme['panels_edit_display_form'] = array(
    'arguments' => array('form'),
    'file' => 'includes/display-edit.inc',
  );
  $theme['panels_edit_layout_form_choose'] = array(
    'arguments' => array('form'),
    'file' => 'includes/display-edit.inc',
  );
  $theme['panels_pane'] = array(
    'arguments' => array('content', 'pane', 'display'),
    'file' => 'includes/display-render.inc',
  );
  $theme['panels_common_content_list'] = array(
    'arguments' => array('display'),
    'file' => 'includes/common.inc',
  );
  $theme['panels_dnd'] = array(
    'arguments' => array('content'),
    'file' => 'includes/display-edit.inc',
    'function' => 'theme_panels_dnd',
  );
  $theme['panels_panel_dnd'] = array(
    'arguments' => array('content', 'area', 'label', 'footer'),
    'file' => 'includes/display-edit.inc',
    'function' => 'theme_panels_panel_dnd',
  );
  $theme['panels_pane_dnd'] = array(
    'arguments' => array('block', 'id', 'label', 'left_buttons' => NULL, 'buttons' => NULL),
    'file' => 'includes/display-edit.inc',
  );
  $theme['panels_pane_collapsible'] = array(
    'arguments' => array('block'),
    'file' => 'includes/display-edit.inc',
  );
  $theme['panels_render_display_form'] = array(
    'arguments' => array('form' => NULL),
  );
  $theme['panels_dashboard'] = array(
    'arguments' => array(),
    'path' => drupal_get_path('module', 'panels') . '/templates',
    'file' => '../includes/callbacks.inc',
    'template' => 'panels-dashboard',
  );
  // Register layout and style themes on behalf of all of these items.
  panels_load_include('plugins');

  // No need to worry about files; the plugin has to already be loaded for us
  // to even know what the theme function is, so files will be auto included.
  $layouts = panels_get_layouts();
  foreach ($layouts as $name => $data) {
    foreach (array('theme', 'admin theme') as $callback) {
      if (!empty($data[$callback])) {
        $theme[$data[$callback]] = array(
          'arguments' => array('css_id' => NULL, 'content' => NULL, 'settings' => NULL, 'display' => NULL),
          'path' => $data['path'],
        );

        // if no theme function exists, assume template.
        if (!function_exists("theme_$data[theme]")) {
          $theme[$data[$callback]]['template'] = str_replace('_', '-', $data[$callback]);
          $theme[$data[$callback]]['file'] = $data['file']; // for preprocess.
        }

  $styles = panels_get_styles();
  foreach ($styles as $name => $data) {
    if (!empty($data['render pane'])) {
      $theme[$data['render pane']] = array(
        'arguments' => array('content' => NULL, 'pane' => NULL, 'display' => NULL),
      );
    }
    if (!empty($data['render panel'])) {
      $theme[$data['render panel']] = array(
        'arguments' => array('display' => NULL, 'panel_id' => NULL, 'panes' => NULL, 'settings' => NULL),
      );
    }

    if (!empty($data['hook theme'])) {
      if (is_array($data['hook theme'])) {
        $theme += $data['hook theme'];
      }
      else if (function_exists($data['hook theme'])) {
        $data['hook theme']($theme, $data);
      }
    }
  // Safety: go away if CTools is not at an appropriate version.
  if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
  $items = array();

  // Provide some common options to reduce code repetition.
  // By using array addition and making sure these are the rightmost
  // value, they won't override anything already set.
  $base = array(
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
    'file' => 'includes/display-edit.inc',
  );

  $items['panels/ajax/add-pane'] = array(
    'page callback' => 'panels_ajax_add_pane_choose',
  ) + $base;
  $items['panels/ajax/add-pane-config'] = array(
    'page callback' => 'panels_ajax_add_pane_config',
  ) + $base;
  $items['panels/ajax/configure'] = array(
    'page callback' => 'panels_ajax_configure_pane',
  ) + $base;
  $items['panels/ajax/show'] = array(
    'page callback' => 'panels_ajax_toggle_shown',
    'page arguments' => array('show'),
  ) + $base;
  $items['panels/ajax/hide'] = array(
    'page callback' => 'panels_ajax_toggle_shown',
    'page arguments' => array('hide'),
  ) + $base;
  $items['panels/ajax/cache-method'] = array(
    'page callback' => 'panels_ajax_cache_method',
  ) + $base;
  $items['panels/ajax/cache-settings'] = array(
    'page callback' => 'panels_ajax_cache_settings',
  ) + $base;
  $items['panels/ajax/display-settings'] = array(
    'page callback' => 'panels_ajax_display_settings',
  ) + $base;
  $items['panels/ajax/style-type'] = array(
    'page callback' => 'panels_ajax_style_type',
  ) + $base;
  $items['panels/ajax/style-settings'] = array(
    'page callback' => 'panels_ajax_style_settings',
  ) + $base;
  $items['panels/ajax/pane-css'] = array(
    'page callback' => 'panels_ajax_configure_pane_css',
  ) + $base;
  $items['panels/ajax/access-settings'] = array(
    'page callback' => 'panels_ajax_configure_access_settings',
  ) + $base;
  $items['panels/ajax/access-test'] = array(
    'page callback' => 'panels_ajax_configure_access_test',
  ) + $base;
  $items['panels/ajax/access-add'] = array(
    'page callback' => 'panels_ajax_add_access_test',
  ) + $base;
  $items['panels/ajax/preview'] = array(
    'page callback' => 'panels_ajax_preview',
  $admin_base = array(
    'file' => 'includes/callbacks.inc',
    'access arguments' => array('access administration pages'),

  );
  // Provide a nice location for a panels admin panel.
  $items['admin/build/panels'] = array(
    'page callback' => 'panels_admin_page',
    'description' => 'Administer items related to the Panels module.',
  $items['admin/build/panels/dashboard'] = array(
    'title' => 'Dashboard',
    'page callback' => 'panels_admin_page',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,

  $items['admin/build/panels/settings'] = array(
    'title' => 'Settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('panels_admin_settings_page'),
    'type' => MENU_LOCAL_TASK,

  $items['admin/build/panels/settings/general'] = array(
    'title' => 'General',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('panels_admin_settings_page'),
    'access arguments' => array('administer delegator'),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  ) + $admin_base;

  if (module_exists('delegator')) {
    $items['admin/build/panels/settings/panel-page'] = array(
      'title' => 'Panel pages',
      'page callback' => 'panels_admin_panel_context_page',
      'type' => MENU_LOCAL_TASK,
      'weight' => -10,
    ) + $admin_base;
  }
  panels_load_include('plugins');
  $layouts = panels_get_layouts();
  foreach ($layouts as $name => $data) {
    if (!empty($data['hook menu'])) {
      if (is_array($data['hook menu'])) {
        $items += $data['hook menu'];
      }
      else if (function_exists($data['hook menu'])) {
        $data['hook menu']($items, $data);
      }
    }
  }
  // Safety: go away if CTools is not at an appropriate version.
  if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
    return;
  }

  drupal_add_css(panels_get_path('css/panels.css'));
  drupal_add_js(panels_get_path('js/panels.js'));
function panels_load_include($include, $path = 'includes/') {
  static $loaded = array();
  if (empty($loaded["$path$include.inc"])) {
    require_once './' . panels_get_path("$path$include.inc");
    $loaded["$path$include.inc"] = TRUE;
  }
function panels_get_path($file, $base_path = FALSE, $module = 'panels') {
  $output = $base_path ? base_path() : '';
  return $output . drupal_get_path('module', $module) . '/' . $file;
}

/**
 * Implementation of hook_perm
 */
function panels_perm() {
  return array(
    'view all panes',
    'view pane admin links',
    'administer pane visibility',
    'administer pane access',
    'administer advanced pane settings',
    'use panels caching features'
  );
}
function panels_cache_get($obj, $did, $skip_cache = FALSE) {
  ctools_include('object-cache');
  // we often store contexts in cache, so let's just make sure we can load
  // them.
  ctools_include('context');
  return ctools_object_cache_get($obj, $did, $skip_cache);
 * Save the edited object into the cache.
function panels_cache_set($obj, $did, $cache) {
  ctools_include('object-cache');
  return ctools_object_cache_set($obj, $did, $cache);
/**
 * Clear a object from the cache; used if the editing is aborted.
 */
function panels_cache_clear($obj, $did) {
  ctools_include('object-cache');
  return ctools_object_cache_clear($obj, $did);

// ---------------------------------------------------------------------------
 * @defgroup mainapi Functions comprising the main panels API
 * @{
/**
 * Main API entry point to edit a panel display.
 *
 * Sample implementations utiltizing the the complex $destination behavior can be found
 * in panels_page_edit_content() and, in a separate contrib module, OG Blueprints
 * (http://drupal.org/project/og_blueprints), og_blueprints_blueprint_edit().
 *
 * @ingroup mainapi
 *
 * @param object $display instanceof panels_display \n
 *  A fully loaded panels $display object, as returned from panels_load_display().
 *  Merely passing a did is NOT sufficient. \n
 *  Note that 'fully loaded' means the $display must already be loaded with any contexts
 *  the caller wishes to have set for the display.
 * @param mixed $destination \n
 *  The redirect destination that the user should be taken to on form submission or
 *  cancellation. With panels_edit, $destination has complex effects on the return
 *  values of panels_edit() once the form has been submitted. See the explanation of
 *  the return value below to understand the different types of values returned by panels_edit()
 *  at different stages of FAPI. Under most circumstances, simply passing in
 *  drupal_get_destination() is all that's necessary.
 * @param array $content_types \n
 *  An associative array of allowed content types, typically as returned from
 *  panels_common_get_allowed_types(). Note that context partially governs available content types,
 *  so you will want to create any relevant contexts using panels_create_context() or
 *  panels_create_context_empty() to make sure all the appropriate content types are available.
 *
 * @return
 *  Because the functions called by panels_edit() invoke the form API, this function
 *  returns different values depending on the stage of form submission we're at. In Drupal 5,
 *  the phase of form submission is indicated by the contents of $_POST['op']. Here's what you'll
 *  get at different stages:
 *    -# If !$_POST['op']: then we're on on the initial passthrough and the form is being
 *       rendered, so it's the $form itself that's being returned. Because negative margins,
 *       a common CSS technique, bork the display editor's ajax drag-and-drop, it's important
 *       that the $output be printed, not returned. Use this syntax in the caller function: \n
 *          print theme('page', panels_edit($display, $destination, $content_types), FALSE); \n
 *    -# If $_POST['op'] == t('Cancel'): form submission has been cancelled. If empty($destination) == FALSE,
 *       then there is no return value and the panels API takes care of redirecting to $destination.
 *       If empty($destination) == TRUE, then there's still no return value, but the caller function
 *       has to take care of form redirection.
 *    -# If $_POST['op'] == ('Save'): the form has been submitted successfully and has run through
 *        panels_edit_display_submit(). $output depends on the value of $destination:
 *      - If empty($destination) == TRUE: $output contains the modified $display
 *        object, and no redirection will occur. This option is useful if the caller
 *        needs to perform additional operations on or with the modified $display before
 *        the page request is complete. Using hook_form_alter() to add an additional submit
 *        handler is typically the preferred method for something like this, but there
 *        are certain use cases where that is infeasible and $destination = NULL should
 *        be used instead. If this method is employed, the caller will need to handle form
 *        redirection. Note that having $_REQUEST['destination'] set, whether via
 *        drupal_get_destination() or some other method, will NOT interfere with this
 *        functionality; consequently, you can use drupal_get_destination() to safely store
 *        your desired redirect in the caller function, then simply use drupal_goto() once
 *        panels_edit() has done its business.
 *      - If empty($destination) == FALSE: the form will redirect to the URL string
 *        given in $destination and NO value will be returned.
function panels_edit($display, $destination = NULL, $content_types = NULL, $title = FALSE) {
  ctools_include('ajax');
  return _panels_edit($display, $destination, $content_types, $title);
/**
 * API entry point for selecting a layout for a given display.
 *
 * Layout selection is nothing more than a list of radio items encompassing the available
 * layouts for this display, as defined by .inc files in the panels/layouts subdirectory.
 * The only real complexity occurs when a user attempts to change the layout of a display
 * that has some content in it.
 *
 * @param object $display instanceof panels_display \n
 *  A fully loaded panels $display object, as returned from panels_load_display().
 *  Merely passing a did is NOT sufficient.
 * @param string $finish
 *  A string that will be used for the text of the form submission button. If no value is provided,
 *  then the form submission button will default to t('Save').
 * @param mixed $destination
 *  Basic usage is a string containing the URL that the form should redirect to upon submission.
 *  For a discussion of advanced usages, see panels_edit().
 * @param mixed $allowed_layouts
 *  Allowed layouts has three different behaviors that depend on which of three value types
 *  are passed in by the caller:
 *    #- if $allowed_layouts instanceof panels_allowed_layouts (includes subclasses): the most
 *       complex use of the API. The caller is passing in a loaded panels_allowed_layouts object
 *       that the client module previously created and stored somewhere using a custom storage
 *       mechanism.
 *    #- if is_string($allowed_layouts): the string will be used in a call to variable_get() which
 *       will call the $allowed_layouts . '_allowed_layouts' var. If the data was stored properly
 *       in the system var, the $allowed_layouts object will be unserialized and recreated.
 *       @see panels_common_set_allowed_layouts()
 *    #- if is_null($allowed_layouts): the default behavior, which also provides backwards
 *       compatibility for implementations of the Panels2 API written before beta4. In this case,
 *       a dummy panels_allowed_layouts object is created which does not restrict any layouts.
 *       Subsequent behavior is indistinguishable from pre-beta4 behavior.
 *
 * @return
 *  Can return nothing, or a modified $display object, or a redirection string; return values for the
 *  panels_edit* family of functions are quite complex. See panels_edit() for detailed discussion.
 * @see panels_edit()
 */
function panels_edit_layout($display, $finish, $destination = NULL, $allowed_layouts = NULL) {
  panels_load_include('display-layout');
  panels_load_include('plugins');
  return _panels_edit_layout($display, $finish, $destination, $allowed_layouts);
/**
 * API entry point for configuring the layout settings for a given display.
 *
 * For all layouts except Flexible, the layout settings form allows the user to select styles,
 * as defined by .inc files in the panels/styles subdirectory, for the panels in their display.
 * For the Flexible layout, the layout settings form allows the user to provide dimensions
 * for their flexible layout in addition to applying styles to panels.
 *
 * @param object $display instanceof panels_display \n
 *  A fully loaded panels $display object, as returned from panels_load_display().
 *  Merely passing a did is NOT sufficient.
 * @param string $finish
 *  A string that will be used for the text of (one of) the form submission button(s). Note that
 *  panels will NOT wrap $finish in t() for you, so your caller should make sure to do so. \n
 *  The submit behavior of the form is primarily governed by the value of $destination (see
 *  below), but is secondarily governed by $finish as follows:
 *    -# If $finish != t('Save'), then two #submit buttons will be present: one with the button
 *       text t('Save'), and the other with the button text $finish. .
 *      - Clicking the 'Save' button will save any changes on the form to the $display object and
 *        keep the user on the same editing page.
 *      - Clicking the $finish button will also save the $display object, but the user will be
 *        redirected to the URL specified in $destination.
 *    -# If $finish == t('Save'), then there is only one button, still called t('Save'), but it
 *       mimics the behavior of the $finish button above by redirecting the user away from the form.
 * @param mixed $destination
 *  Basic usage is a string containing the URL that the form should redirect to upon submission.
 *  For a discussion of advanced usages that rely on NULL values for $destination, see the
 *  panels_edit() documentation.
 * @param mixed $title
 *  The $title variable has three modes of operation:
 *    -# If $title == FALSE (the default), then no widget will appear on the panels_edit_layout_settings form
 *       allowing the user to select a title, and other means for setting page titles will take precedent. If
 *       no other means are used to provide a title, then the title will be hidden when rendering the $display.
 *    -# If $title == TRUE, then two widgets will appear on the panels_edit_layout_settings form allowing the
 *       user to input a title specific to this $display, as well as a checkbox enabling the user to disable
 *       page titles entirely for this $display object.
 *    -# If $title == (string), then the behavior is very similar to mode 2, but the widget description
 *       on the title textfield will indicate that the $title string will be used as the default page title
 *       if none is provided on this form. When utilizing this option, note that the panels API can only
 *       provide the data for these values; you must implement the appropriate conditionals to make it true.
 *
 * @return
 *  Can return nothing, or a modified $display object, or a redirection string; return values for the
 *  panels_edit* family of functions are quite complex. See panels_edit() for detailed discussion.
 * @see panels_edit()
function panels_edit_layout_settings($display, $finish, $destination = NULL, $title = FALSE) {
  panels_load_include('display-layout-settings');
  ctools_include('ajax');
  panels_load_include('plugins');
  return _panels_edit_layout_settings($display, $finish, $destination, $title);

// ---------------------------------------------------------------------------
// panels database functions

class panels_display {
  var $args = array();
  var $content = array();
  var $panels = array();
  var $incoming_content = NULL;
  var $css_id = NULL;
  var $context = array();

  function add_pane($pane, $location = FALSE) {
    $pane->pid = $this->next_new_pid();
    if (!$location || !isset($this->panels[$location])) {
      foreach ($this->panels as $panel_name => $panel) {
        if (array_key_exists($pane->pid, $panel)) {
          $this->panels[$panel_name][] = $pane->pid;
        }
    else {
      $this->panels[$location][] = $pane->pid;
    }
  function duplicate_pane($pid, $location = FALSE) {
    $pane = $this->clone_pane($pid);
    $this->add_pane($pane, $location);
  function clone_pane($pid) {
    $pane = drupal_clone($this->content[$pid]);
    foreach (array_keys($this->content) as $pidcheck) {
      // necessary?
      unset($pane->position);
    }
    return $pane;
  }
  function next_new_pid() {
    // necessary if/until we use this method and ONLY this method for adding temporary pids.
    // then we can do it with a nice static var.
    $id = array(0);
    foreach (array_keys($this->content) as $pid) {
      if (!is_numeric($pid)) {
        $id[] = substr($pid, 4);
      }
    }
    $next_id = max($id);
 * }@ End of 'defgroup mainapi', although other functions are specifically added later
 * Clean up a display object and add some required information, if missing.
 *
 * Currently a display object needs 'args', 'incoming content', 'context'
 * and a 'css_id'.
 *
 * @param &$display
 *   The display object to be sanitized.
 * @return
 *   The sanitized display object.
function panels_sanitize_display(&$display) {
  if (!isset($display->args)) {
    $display->args = array();
  }
  if (!isset($display->incoming_content)) {
    $display->incoming_content = NULL;
  }
  if (!isset($display->context)) {
    $display->context = array();
  }
  if (!isset($display->css_id)) {
    $display->css_id = NULL;
/**
 * Creates a new display, setting the ID to our magic new id.
 */
function panels_new_display() {
  ctools_include('export');
  $display = ctools_export_new_object('panels_display', FALSE);
/**
 * Create a new pane.
 *
 * @todo -- use schema API for some of this?
 */
function panels_new_pane($type, $subtype) {
  ctools_include('export');
  $pane = ctools_export_new_object('panels_pane', FALSE);
  $pane->pid = 'new';
  $pane->type = $type;
  $pane->subtype = $subtype;
  return $pane;
}
/**
 * Load and fill the requested $display object(s).
 *
 * Helper function primarily for for panels_load_display().
 *
 * @param array $dids
 *  An indexed array of dids to be loaded from the database.
 *
 * @return $displays
 *  An array of displays, keyed by their display dids.
 *
 * @todo schema API can drasticly simplify this code.
 */
function panels_load_displays($dids) {
  $displays = array();
  if (empty($dids) || !is_array($dids)) {
    return $displays;
  }
  $result = db_query("SELECT * FROM {panels_display} WHERE did IN (" .  db_placeholders($dids) . ")", $dids);
  ctools_include('export');
  while ($obj = db_fetch_object($result)) {
    $displays[$obj->did] = ctools_export_unpack_object('panels_display', $obj);
  // @TODO
  //
  // This code clearly never worked ($content, $layout and $settings are all unset)
  // where was this supposed to even have been?

//  foreach (module_implements('panels_layout_content_alter') as $module) {
//    $function = $module . '_panels_layout_content_alter';
//    $function($content, $layout, $settings);
//  }
  $result = db_query("SELECT * FROM {panels_pane} WHERE did IN (" . db_placeholders($dids) . ") ORDER BY did, panel, position", $dids);
  while ($obj = db_fetch_object($result)) {
    $pane = ctools_export_unpack_object('panels_pane', $obj);

    $displays[$pane->did]->panels[$pane->panel][] = $pane->pid;
    $displays[$pane->did]->content[$pane->pid] = $pane;
  }
 * Load a single display.
 *
 * @ingroup mainapi
 *
 * @param int $did
 *  The display id (did) of the display to be loaded.
 *
 * @return object $display instanceof panels_display \n
 *  Returns a partially-loaded panels_display object. $display objects returned from
 *  from this function have only the following data:
 *    - $display->did (the display id)
 *    - $display->name (the 'name' of the display, where applicable - it often isn't)
 *    - $display->layout (a string with the system name of the display's layout)
 *    - $display->panel_settings (custom layout style settings contained in an associative array; NULL if none)
 *    - $display->layout_settings (panel size and configuration settings for Flexible layouts; NULL if none)
 *    - $display->css_id (the special css_id that has been assigned to this display, if any; NULL if none)
 *    - $display->content (an array of pane objects, keyed by pane id (pid))
 *    - $display->panels (an associative array of panel regions, each an indexed array of pids in the order they appear in that region)
 *    - $display->cache (any relevant data from panels_simple_cache)
 *    - $display->args
 *    - $display->incoming_content
 *
 * While all of these members are defined, $display->context is NEVER defined in the returned $display;
 * it must be set using one of the ctools_context_create() functions.
function panels_load_display($did) {
  $displays = panels_load_displays(array($did));
  if (!empty($displays)) {
    return array_shift($displays);
  }
}
/**
 * Save a display object.
 *
 * @ingroup mainapi
 *
 * Note a new $display only receives a real did once it is run through this function.
 * Until then, it uses a string placeholder, 'new', in place of a real did. The same
 * applies to all new panes (whether on a new $display or not); in addition,
 * panes have sequential numbers appended, of the form 'new-1', 'new-2', etc.
 *
 * @param object $display instanceof panels_display \n
 *  The display object to be saved. Passed by reference so the caller need not use
 *  the return value for any reason except convenience.
 *
 * @return object $display instanceof panels_display \n
 */
function panels_save_display(&$display) {
  $update = (isset($display->did) && is_numeric($display->did)) ? array('did') : array();
  drupal_write_record('panels_display', $display, $update);

  $pids = array();
  if ($update) {
    // Get a list of all panes currently in the database for this display so we can know if there
    // are panes that need to be deleted. (i.e, aren't currently in our list of panes).
    $result = db_query("SELECT pid FROM {panels_pane} WHERE did = %d", $display->did);
    while ($pane = db_fetch_object($result)) {
      $pids[$pane->pid] = $pane->pid;
  // update all the panes
  panels_load_include('plugins');
  foreach ($display->panels as $id => $panes) {
    $position = 0;
    $new_panes = array();
    foreach ((array) $panes as $pid) {
      if (!isset($display->content[$pid])) {
        continue;
      }
      $type = ctools_get_content_type($pane->type);
      $pane->position = $position++;
      $pane->did = $display->did;
      $old_pid = $pane->pid;
      drupal_write_record('panels_pane', $pane, is_numeric($pid) ? array('pid') : array());
      if ($pane->pid != $old_pid) {
        // and put it back so our pids and positions can be used
        unset($display->content[$id]);
        $display->content[$pane->pid] = $pane;

      // re-add this to the list of content for this panel.

      // Remove this from the list of panes scheduled for deletion.
      if (isset($pids[$pane->pid])) {
        unset($pids[$pane->pid]);
    db_query("DELETE FROM {panels_pane} WHERE pid IN (" . db_placeholders($pids) . ")", $pids);
  // Clear any cached content for this display.
  panels_clear_cached_content($display);
  // to be nice, even tho we have a reference.
  return $display;
function panels_delete_display($display) {
  if (is_object($display)) {
    $did = $display->did;
  }
  else {
    $did = $display;
  }
  db_query("DELETE FROM {panels_display} WHERE did = %d", $did);
  db_query("DELETE FROM {panels_pane} WHERE did = %d", $did);
 * Exports the provided display into portable code.
 *
 * This function is primarily intended as a mechanism for cloning displays.
 * It generates an exact replica (in code) of the provided $display, with
 * the exception that it replaces all ids (dids and pids) with 'new-*' values.
 * Only once panels_save_display() is called on the code version of $display will
 * the exported display written to the database and permanently saved.
 *
 * @see panels_page_export() or _panels_page_fetch_display() for sample implementations.
 *
 * @ingroup mainapi
 *
 * @param object $display instanceof panels_display \n
 *  This export function does no loading of additional data about the provided
 *  display. Consequently, the caller should make sure that all the desired data
 *  has been loaded into the $display before calling this function.
 * @param string $prefix
 *  A string prefix that is prepended to each line of exported code. This is primarily
 *  used for prepending a double space when exporting so that the code indents and lines up nicely.
 *
 * @return string $output
 *  The passed-in $display expressed as code, ready to be imported. Import by running
 *  eval($output) in the caller function; doing so will create a new $display variable
 *  with all the exported values. Note that if you have already defined a $display variable in
 *  the same scope as where you eval(), your existing $display variable WILL be overwritten.
function panels_export_display($display, $prefix = '') {
  ctools_include('export');
  $output = ctools_export_object('panels_display', $display, $prefix);
  $output .= $prefix . '$display->content = array()' . ";\n";
  $output .= $prefix . '$display->panels = array()' . ";\n";
  $panels = array();

  if (!empty($display->content)) {
    $pid_counter = 0;
    $region_counters = array();
    foreach ($display->content as $pane) {
      $pane->pid = 'new-' . ++$pid_counter;
      $output .= ctools_export_object('panels_pane', $pane, $prefix . '  ');
      $output .= "$prefix  " . '$display->content[\'' . $pane->pid . '\'] = $pane' . ";\n";
      if (!isset($region_counters[$pane->panel])) {
        $region_counters[$pane->panel] = 0;
      $output .= "$prefix  " . '$display->panels[\'' . $pane->panel . '\'][' . $region_counters[$pane->panel]++ .'] = \'' . $pane->pid . "';\n";
/**
 * Render a display by loading the content into an appropriate
 * array and then passing through to panels_render_layout.
 *
 * if $incoming_content is NULL, default content will be applied. Use
 * an empty string to indicate no content.
 * @render
 * @ingroup hook_invocations
 */
function panels_render_display(&$display) {
  panels_load_include('display-render');
  panels_load_include('plugins');
  ctools_include('context');

  if (!empty($display->context)) {
    if ($form_context = ctools_context_get_form($display->context)) {
      $form_context->form['#theme'] = 'panels_render_display_form';
      $form_context->form['#display'] = &$display;
      $form_context->form['#form_context_id'] = $form_context->id;
      return drupal_render_form($form_context->form_id, $form_context->form);
    }
  }
  return _panels_render_display($display);
/**
 * Theme function to render our panel as a form.
 *
 * When rendering a display as a form, the entire display needs to be
 * inside the <form> tag so that the form can be spread across the
 * panes. This sets up the form system to be the main caller and we
 * then operate as a theme function of the form.
 */
function theme_panels_render_display_form($form) {
  $form['#children'] = _panels_render_display($form['#display']);
  drupal_render($form);
  return theme('form', $form);
}

/**
 * For external use: Given a layout ID and a $content array, return the
 * panel display. The content array is filled in based upon the content
 * available in the layout. If it's a two column with a content
 * array defined like array('left' => t('Left side'), 'right' =>
 * t('Right side')), then the $content array should be array('left' =>
 * $output_left, 'right' => $output_right)
 * @render
  panels_load_include('plugins');
  return _panels_print_layout($id, $content);
// @layout
function panels_print_layout_icon($id, $layout, $title = NULL) {
  drupal_add_css(panels_get_path('css/panels_admin.css'));
  $file = $layout['path'] . '/' . $layout['icon'];
  return theme('panels_layout_icon', $id, theme('image', $file), $title);
/**
 * Theme the layout icon image
 * @layout
 * @todo move to theme.inc
 */
function theme_panels_layout_icon($id, $image, $title = NULL) {
  $output = '<div class="layout-icon">';
  $output .= $image;
  if ($title) {
    $output .= '<div class="caption">' . $title . '</div>';
function theme_panels_layout_link($title, $id, $image, $link) {
  $output = '<div class="layout-link">';
  $output .= $image;
  $output .= '<div>' . $title . '</div>';
  $output .= '</div>';
  return $output;

/**
 * Print the layout link. Sends out to a theme function.
 * @layout
 */
function panels_print_layout_link($id, $layout, $link, $options = array()) {
  if (isset($options['query']['q'])) {
    unset($options['query']['q']);
  }

  drupal_add_css(panels_get_path('css/panels_admin.css'));
  $file = $layout['path'] . '/' . $layout['icon'];
  $image = l(theme('image', $file), $link, array('html' => true) + $options);
  $title = l($layout['title'], $link, $options);
  return theme('panels_layout_link', $title, $id, $image, $link);
}

/**
 * Implementation of hook_ctools_plugin_directory() to let the system know
 * we implement task and task_handler plugins.
 */
function panels_ctools_plugin_directory($module, $plugin) {
  // Safety: go away if CTools is not at an appropriate version.
  if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
    return;
  }
  if ($module == 'delegator' || $module == 'panels') {

/**
 * Inform CTools that the layout plugin can be loaded from themes.
 */
function panels_ctools_plugin_layouts() {
  return array(
    'load themes' => TRUE,
  );
}

/**
 * Inform CTools that the style plugin can be loaded from themes.
 */
function panels_ctools_plugin_styles() {
  return array(
    'load themes' => TRUE,
  );
}

/**
 * Get the display that is currently being rendered as a page.
 * Unlike in previous versions of this, this only returns the display,
 * not the page itself, because there are a number of different ways
 * to get to this point. It is hoped that the page data isn't needed
 * at this point. If it turns out there is, we will do something else to
 * get that functionality.
 */
function panels_get_current_page_display($change = NULL) {
  static $display = NULL;
  if ($change) {
    $display = $change;
  }

  return $display;
}