Skip to content
panels.module 34.2 KiB
Newer Older
 * Returns the API version of Panels. This didn't exist in 1.
 *
 * @return An array with the major and minor versions
function panels_api_version() {
  return array(2, 0);
}

function panels_theme() {
  $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_imagebutton'] = array(
    'arguments' => array('element'),
  );
  $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_common_context_list'] = array(
    'arguments' => array('object'),
    'file' => 'includes/common.inc',
  );
  $theme['panels_common_context_item_form'] = array(
    'arguments' => array('form'),
    'file' => 'includes/common.inc',
  );
  $theme['panels_common_content_item_row'] = array(
    'arguments' => array('type', 'form', 'position', 'count', 'with_tr' => TRUE),
    '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',
  );

  // 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) {
    if (!empty($data['theme'])) {
      $theme[$data['theme']] = array(
        'arguments' => array('css_id' => NULL, 'content' => NULL, 'settings' => NULL),

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

  $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),
      );
    }

  }

  return $theme;
function panels_menu() {
  $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;

  // For panel settings on the edit layout settings page
  $items['panels/ajax/style-settings'] = array(
    'page callback' => 'panels_ajax_style_settings',
    'file' => 'includes/display-layout-settings.inc',
  ) + $base;

  // Non-display editor callbacks
  $items['panels/node/autocomplete'] = array(
    'title' => t('Autocomplete node'),
    'page callback' => 'panels_node_autocomplete',
    'file' => 'includes/callbacks.inc',
  ) + $base;

  // For context add/configure calls in common-context.inc
  $items['panels/ajax/context-add'] = array(
    'page callback' => 'panels_ajax_context_item_add',
    'file' => 'includes/common-context.inc',
  ) + $base;

  // Provide a nice location for a panels admin panel.
  //
  // TODO: Provide a cute little panel-base administrative panel
  // that provides things like "Recently edited", "recently added"
  // and other administrative items to make dealing with the complexity
  // of panels a little easier.

  $items['admin/panels'] = array(
    'title' => t('Panels'),
    'access arguments' => array('access administration pages'),
    'page callback' => 'system_admin_menu_block_page',
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system'),
    'description' => t('Administer items related to the Panels module.'),
  );

  return $items;
function panels_init() {
  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/') {
  require_once './' . panels_get_path("$path$include.inc");
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) {
  static $cache = array();
  $key = "$obj:$did";
  if ($skip_cache) {
    unset($cache[$key]);
  }

  if (!array_key_exists($key, $cache)) {
    $data = db_fetch_object(db_query("SELECT * FROM {panels_object_cache} WHERE sid = '%s' AND obj = '%s' AND did = %d", session_id(), $obj, $did));
    if ($data) {
      $cache[$key] = unserialize($data->data);
    }
  }
  return isset($cache[$key]) ? $cache[$key] : NULL;
 * Save the edited object into the cache.
function panels_cache_set($obj, $did, $cache) {
  panels_cache_clear($obj, $did);
  db_query("INSERT INTO {panels_object_cache} (sid, obj, did, data, timestamp) VALUES ('%s', '%s', %d, '%s', %d)", session_id(), $obj, $did, serialize($cache), time());
/**
 * Clear a object from the cache; used if the editing is aborted.
 */
function panels_cache_clear($obj, $did) {
  db_query("DELETE FROM {panels_object_cache} WHERE sid = '%s' AND obj = '%s' AND did = %d", session_id(), $obj, $did);
 * Implementation of hook_cron. Clean up old caches.
function panels_cron() {
  // delete anything 7 days old or more.
  db_query("DELETE FROM {panels_object_cache} WHERE timestamp < %d", time() - (86400 * 7));
}

// ---------------------------------------------------------------------------
 * @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) {
  panels_load_include('display-edit');
  panels_load_include('ajax');
  panels_load_include('plugins');
  return _panels_edit($display, $destination, $content_types);
}
/**
 * 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');
  panels_load_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();
  var $layout_settings = array();
  var $panel_settings = array();
  var $cache = array();
  var $title = '';
  var $hide_title = 0;

  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 = end($id);
    return ++$next_id;
 * }@ End of 'defgroup mainapi', although other functions are specifically added later

function panels_export_pane_across_displays($source_display, &$target_display, $pid, $location = FALSE) {
  $pane = $source_display->clone_pane($pid);
  $target_display->add_pane($pane, $location);
 * 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() {
  $display = new panels_display();
  $display->did = 'new';
  return $display;
}
function panels_new_pane($type, $subtype) {
  $pane = new stdClass();
  $pane->pid = 'new';
  $pane->type = $type;
  $pane->subtype = $subtype;
  $pane->configuration = array();
  $pane->access = array();
  $pane->shown = TRUE;
  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.
 */
function panels_load_displays($dids) {
  $displays = array();
  if (empty($dids) || !is_array($dids)) {
    return $displays;
  }
  $subs = implode(', ', array_fill(0, count($dids), '%d'));
  $result = db_query("SELECT * FROM {panels_display} WHERE did IN ($subs)", $dids);
  while ($obj = db_fetch_array($result)) {
    $display = new panels_display();
    foreach ($obj as $key => $value) {
      $display->$key = $value;
      // unserialize important bits
      if (in_array($key, array('layout_settings', 'panel_settings', 'cache'))) {
        $display->$key = empty($display->$key) ? array() : unserialize($display->$key);
    $display->panels = $display->content = array();

    $displays[$display->did] = $display;
  }

  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 ($subs) ORDER BY did, panel, position", $dids);
  while ($pane = db_fetch_object($result)) {
    $pane->configuration = unserialize($pane->configuration);
    $pane->cache = empty($pane->cache) ? array() : unserialize($pane->cache);
    $pane->access = ($pane->access ? explode(', ', $pane->access) : array());
    // Old panels may not have shown property, so enable by default when loading.
    $pane->shown = isset($pane->shown) ? $pane->shown : TRUE;

    $displays[$pane->did]->panels[$pane->panel][] = $pane->pid;
    $displays[$pane->did]->content[$pane->pid] = $pane;
  }
  return $displays;
 * 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 panels_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) {
  // @todo -- update all this to just use drupal_write_record or something like it.
  if (!empty($display->did) && $display->did != 'new') {
    db_query("UPDATE {panels_display} SET layout = '%s', layout_settings = '%s', panel_settings = '%s', cache = '%s', title = '%s', hide_title = %d WHERE did = %d", $display->layout, serialize($display->layout_settings), serialize($display->panel_settings), serialize($display->cache), $display->title, $display->hide_title, $display->did);
    // 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;
  }
  else {
    db_query("INSERT INTO {panels_display} (layout, layout_settings, panel_settings, cache, title, hide_title) VALUES ('%s', '%s', '%s', '%s', '%s', %d)", $display->layout, serialize($display->layout_settings), serialize($display->panel_settings), serialize($display->cache), $display->title, $display->hide_title);
    $display->did = db_last_insert_id('panels_display', 'did');
    $pids = array();
  // update all the panes
  panels_load_include('plugins');
  foreach ((array) $display->panels as $id => $panes) {
    $position = 0;
    $new_panes = array();
    foreach ((array) $panes as $pid) {
      $pane = $display->content[$pid];
      $pane->position = $position++;
      // make variables right.
      $type = panels_get_content_type($pane->type);
      $access = isset($pane->access) ? implode(', ', $pane->access) : '';
      $visibility = $type['visibility serialize'] ? serialize($pane->visibility) : $pane->visibility;
      $pane->shown = isset($pane->shown) ? $pane->shown : TRUE;

      if (empty($pane->cache)) {
        $pane->cache = array();
      $v = array($display->did, $pane->panel, $pane->type, $pane->subtype, serialize($pane->configuration), serialize($pane->cache), $pane->shown, $access, $visibility, $pane->position);

      if (!is_numeric($pid)) {
        unset($display->content[$pid]);
        // doin it this way for readability
        $f = 'did, panel, type, subtype, configuration, cache, shown, access, visibility, position';
        $q = "%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d";

        db_query("INSERT INTO {panels_pane} ($f) VALUES ($q)", $v);
        $pane->pid = db_last_insert_id('panels_pane', 'pid');
      }
      else {
        $v[] = $pane->pid;
        $f = "did = %d, panel = '%s', type = '%s', subtype = '%s', configuration = '%s', cache = '%s', shown = '%s', access = '%s', visibility = '%s', position = '%d'";
        db_query("UPDATE {panels_pane} SET $f WHERE pid = %d", $v);
      }
      // and put it back so our pids and positions can be used
      $display->content[$pane->pid] = $pane;
      $new_panes[] = $pane->pid;
      if (isset($pids[$pane->pid])) {
        unset($pids[$pane->pid]);
    $display->panels[$id] = $new_panes;
  }
  if ($pids) {
    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 = '') {
  $output = '';
  $output .= $prefix . '$display = new panels_display()' . ";\n";
  $output .= $prefix . '$display->did = \'new\'' . ";\n";
  $fields = array('name', 'layout', 'layout_settings', 'panel_settings');
  foreach ($fields as $field) {
    $output .= $prefix . '$display->' . $field . ' = ' . panels_var_export($display->$field, $prefix) . ";\n";
  $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 .= panels_export_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";
function panels_export_pane($pane, $prefix = '') {
  $output = '';
  $output = $prefix . '$pane = new stdClass()'  . ";\n";
  $fields = array('pid', 'panel', 'type', 'subtype', 'access', 'configuration');
  foreach ($fields as $field) {
    $output .= "$prefix  " . '$pane->' . $field . ' = ' . panels_var_export($pane->$field, "$prefix  ") . ";\n";
  }
  return $output;
function panels_var_export($object, $prefix = '') {
  if (is_array($object) && empty($object)) {
    $output = 'array()';
  else {
    $output = var_export($object, TRUE);
  if ($prefix) {
    $output = str_replace("\n", "\n$prefix", $output);
/**
 * 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');
  return _panels_render_display($display);
}

/**
 * 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) {
  drupal_add_css(panels_get_path('css/panels_admin.css'));
  $file = $layout['path'] . '/' . $layout['icon'];
  $image = l(theme('image', $file), $link, array('html' => true));
  $title = l($layout['title'], $link);
  return theme('panels_layout_link', $title, $id, $image, $link);
}