Removing translation directories
[project/panels.git] / panels.module
index 85a0d93..7822450 100644 (file)
 <?php
-// $Id$
 
 /**
- * Implementation of hook_help()
+ * @file panels.module
+ *
+ * Core functionality for the Panels engine.
  */
-function panels_help($section = '') {
-  switch ($section) {
-    case 'admin/modules#description':
-      return t('The panels module allows the creation of pages with flexible layouts.');
-    case 'admin/panels':
-    case 'admin/panels/list':
-      return t('<p>You may peruse a list of your current panels layouts and edit them, or click add to create a new page.</p>');
-    case 'admin/panels/add':
-      return t('<p>Choose a layout for your new page from the list below.</p>');
-  }
-}
 
 /**
- * Implementation of hook_perm()
+ * Error code bitmask used for identifying argument behavior at runtime.
  */
-function panels_perm() {
-  return array('create panels');
-}
+define('PANELS_ARG_IS_BAD', 1);
+define('PANELS_ARG_USE_FALLBACK', 1 << 1);
 
 /**
- * Implementation of hook_menu()
+ * Returns the API version of Panels. This didn't exist in 1.
+ *
+ * @return An array with the major and minor versions
  */
-function panels_menu($may_cache) {
-  if ($may_cache) {
-    $access = user_access('create panels');
-    $items[] = array(
-      'path' => 'admin/panels',
-      'title' => t('panels'),
-      'access' => $access,    
-      'callback' => 'panels_list_page',
-    );
-    $items[] = array(
-      'path' => 'admin/panels/list',
-      'title' => t('list'),
-      'access' => $access,    
-      'callback' => 'panels_list_page',
-      'weight' => -10,
-      'type' => MENU_DEFAULT_LOCAL_TASK,
-    );
-    $items[] = array(
-      'path' => 'admin/panels/add',
-      'title' => t('add'),
-      'access' => $access,    
-      'callback' => 'panels_add_page',
-      'type' => MENU_LOCAL_TASK,
-    );
-    $items[] = array(
-      'path' => 'admin/panels/add/layout',
-      'title' => t('add'),
-      'access' => $access,    
-      'callback' => 'panels_add_layout_page',
-      'type' => MENU_LOCAL_TASK,
-    );
-    $items[] = array(
-      'path' => 'admin/panels/edit',
-      'title' => t('edit panels'),
-      'access' => $access,    
-      'callback' => 'panels_edit_page',
-      'type' => MENU_CALLBACK,
-    );
-    $items[] = array(
-      'path' => 'admin/panels/delete',
-      'title' => t('delete panels'),
-      'access' => $access,    
-      'callback' => 'panels_delete_page',
-      'type' => MENU_CALLBACK,
-    );
-
-    $items[] = array(
-      'path' => 'panels/node/autocomplete',
-      'title' => t('autocomplete node'),
-      'callback' => 'panels_node_autocomplete',
-      'access' => user_access('access content'),
-      'type' => MENU_CALLBACK
-    );
-
-    // load panels from database
-    $result = db_query("SELECT * FROM {panels_info}");
-    // FIXME: Fow now we're making these all callbacks, but we
-    // should steal code from Views so they can be normal, tabs,
-    // etc
-    while ($panels = db_fetch_object($result)) {
-      $items[] = array(
-        'path' => $panels->path,
-        'title' => $panels->title,
-        'access' => panels_access(unserialize($panels->access)),
-        'callback' => 'panels_panels_page',
-        'callback arguments' => array($panels->did),
-        'type' => MENU_CALLBACK
+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_context_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',
+  );
+  $theme['profile_fields_pane'] = array(
+    'arguments' => array('category' => NULL, 'vars' => NULL),
+    'template' => 'content_types/profile_fields_pane',
+  );
+
+  // 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),
+        'path' => $data['path'],
       );
+
+      // 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;
+}
+
+/**
+ * Implementation of hook_menu
+ */
+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' => '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;
+  $items['panels/ajax/context-configure'] = array(
+    'page callback' => 'panels_ajax_context_item_edit',
+    'file' => 'includes/common-context.inc',
+  ) + $base;
+  $items['panels/ajax/context-delete'] = array(
+    'page callback' => 'panels_ajax_context_item_delete',
+    'file' => 'includes/common-context.inc',
+  ) + $base;
+
+  // Provide a nice location for a panels admin panel.
+  $items['admin/panels'] = array(
+    'title' => 'Panels',
+    'access arguments' => array('access administration pages'),
+    'page callback' => 'panels_admin_page',
+    'file' => 'includes/callbacks.inc',
+    'description' => 'Administer items related to the Panels module.',
+  );
+
   return $items;
 }
 
 /**
- * Determine whether or not the current user has access to this
- * panels.
+ * Implementation of hook_init()
  */
-function panels_access($access) {
-  // for now
-  return TRUE;
+function panels_init() {
+  drupal_add_css(panels_get_path('css/panels.css'));
+  drupal_add_js(panels_get_path('js/panels.js'));
 }
 
 /**
- * panels path helper function
+ * Load a panels include file.
  */
-function panels_get_file_path($module, $file, $base_path = true) {
-  if ($base_path) {
-    $output = base_path();
+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;
   }
-  return $output . drupal_get_path('module', $module) . '/' . $file;
 }
 
-// ---------------------------------------------------------------------------
-// panels custom image button
-
 /**
- * Custom form element to do our nice images.
+ * panels path helper function
  */
-function panels_elements() {
-  $type['panels_imagebutton'] = array('#input' => TRUE, '#button_type' => 'submit',);
-  return $type;
+function panels_get_path($file, $base_path = FALSE, $module = 'panels') {
+  $output = $base_path ? base_path() : '';
+  return $output . drupal_get_path('module', $module) . '/' . $file;
 }
 
 /**
- * Theme our image button.
+ * Implementation of hook_perm
  */
-function theme_panels_imagebutton($element) {
-  return '<input type="image" class="form-'. $element['#button_type'] .'" name="'. $element['#name'] .'" value="'. check_plain($element['#default_value']) .'" '. drupal_attributes($element['#attributes']) . ' src="' . $element['#image'] . '" alt="' . $element['#title'] . '" title="' . $element['#title'] . "\" />\n";
+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_imagebutton_value() {
-  // null function guarantees default_value doesn't get moved to #value.
+/**
+ * Get an object from cache.
+ */
+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;
 }
 
 /**
- * Add a single button to a form.
+ * Save the edited object into the cache.
  */
-function panels_add_button($image, $name, $text) {
-  $module_path = base_path() . drupal_get_path('module', 'panels');
+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());
+}
 
-  return array(
-    '#type' => 'panels_imagebutton',
-    '#image' => $module_path . '/images/' . $image,
-    '#title' => $text,
-    '#default_value' => $name,
-  );
+/**
+ * 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);
 }
 
 /**
- * Set a button to a blank image -- used for placeholders when buttons are
- * not relevant but just removing it would be visually unappealing.
+ * Implementation of hook_cron. Clean up old caches.
  */
-function panels_set_blank(&$form) {
-  $form['#type'] = 'markup';
-  $form['#value'] = theme('image', drupal_get_path('module', 'panels') . '/images/blank.gif');
+function panels_cron() {
+  // delete anything 7 days old or more.
+  db_query("DELETE FROM {panels_object_cache} WHERE timestamp < %d", time() - (86400 * 7));
 }
 
 // ---------------------------------------------------------------------------
-// panels administrative pages
+// panels display editing
 
 /**
- * Provide a list of panels, with links to edit or delete them.
+ * @defgroup mainapi Functions comprising the main panels API
+ * @{
  */
-function panels_list_page() {
-  $result = db_query("SELECT * FROM {panels_info} ORDER BY title");
-  while ($panels = db_fetch_object($result)) {
-    $item = array();
-    $item[] = check_plain($panels->title);
-    $item[] = l($panels->path, $panels->path);
-    $item[] = implode(' | ', array(
-      l('edit', "admin/panels/edit/$panels->did"),
-      l('delete', "admin/panels/delete/$panels->did"),
-    ));
-    $items[] = $item;
-  }
-  $header = array(
-    t('Panel title'),
-    t('URL'),
-    t('Operations'),
-  );
-  $output = theme('table', $header, $items);
-  return $output;
-}
 
-/*
- * Provide a form to confirm deletion of a panel page.
+/**
+ * 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_delete_page($did = '') {
-  $panels = panels_load_panels($did);
-
-  if (!$panels) {
-    return 'admin/panels';
-  }
+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);
+}
 
-  $form['did'] = array('#type' => 'value', '#value' => $panels->did);
-  return confirm_form('panels_delete_confirm', $form,
-    t('Are you sure you want to delete %title?', array('%title' => $panels->title)),
-    $_GET['destination'] ? $_GET['destination'] : 'admin/panels',
-    t('This action cannot be undone.'),
-    t('Delete'), t('Cancel')
-  );
+/**
+ * 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);
 }
 
-/*
- * Handle the submit button to delete a panel page.
+/**
+ * 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_delete_confirm_submit($formid, $form) {
-  if ($form['confirm']) {
-    panels_delete_panels((object) $form);
-    drupal_goto('admin/panels');
-  }
+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
+
 /**
- * Handle the add panels page
+ * Forms the basis of a panel display
+ *
  */
-function panels_add_page($layout = NULL) {
-  $layouts = panels_get_layouts();
-  theme_add_style(drupal_get_path('module', 'panels') . '/panels_admin.css');
-  if (!$layout) {
-    foreach ($layouts as $id => $layout) {
-      if (!$default_id) {
-        // grab the first one for our default.
-        $default_id = $id;
+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;
+  var $layout = '';
+
+  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;
+        }
       }
-      $file = panels_get_file_path($layout['module'], $layout['icon'], false);
-      $output .= theme('panels_add_image', $layout[title], $id, l(theme('image', $file), $_GET['q'] . '/' . $id, NULL, NULL, NULL, NULL, TRUE));
     }
-    return $output;
+    else {
+      $this->panels[$location][] = $pane->pid;
+    }
   }
 
-  if (!$layouts[$layout]) {
-    return drupal_not_found();
+  function duplicate_pane($pid, $location = FALSE) {
+    $pane = $this->clone_pane($pid);
+    $this->add_pane($pane, $location);
   }
 
-  $panels->layout = $layout;
-  return panels_edit_form($panels);
-}
-
-function theme_panels_add_image($title, $id, $image) {
-  $output .= '<div class="layout-link">';
-  $output .= $image;
-  $output .= '<div>' . l($title, $_GET['q'] . '/' . $id) . '</div>';
-  $output .= '</div>';
-  return $output;
-}
-// ---------------------------------------------------------------------------
-// panels administrative pages
+  function clone_pane($pid) {
+    $pane = drupal_clone($this->content[$pid]);
+    foreach (array_keys($this->content) as $pidcheck) {
+      // necessary?
+      unset($pane->position);
+    }
+    return $pane;
+  }
 
-function panels_edit_page($did = NULL) {
-  if (!$did || !($panels = panels_load_panels($did))) {
-    return drupal_not_found();
+  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;
   }
-  return panels_edit_form($panels);
 }
 
 /**
- * shortcut to ease the syntax of the various form builder tricks we use.
+ * }@ End of 'defgroup mainapi', although other functions are specifically added later
  */
-function panels_form_builder(&$form, $form_id = 'panels_edit_form') {
-  $form = form_builder($form_id, $form);
+
+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);
 }
 
 /**
- * Edit an already loaded panels.
+ * 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_edit_form($panels) {
-  theme_add_style(drupal_get_path('module', 'panels') . '/panels_admin.css');
-  $layouts = panels_get_layouts();
-  $layout = $layouts[$panels->layout];
-
-  $content_types = panels_get_content_types();
-
-  // Process all our add button stuff first so we can add stuff to the
-  // form semi dynamically.
-
-  $form['add'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Add content'),
-    '#collapsible' => false,
-    '#description' => t('Select an area to add content to, then select a type of content and click the appropriate button. The content will be added.'),
-  );
-
-  $default_radio = array_shift(array_keys($layout['content areas']));
-  $form['add']['area'] = array(
-    '#type' => 'radios',
-    '#title' => t('Area'),
-    '#options' => $layout['content areas'],
-    '#prefix' => '<div class="container-inline">',
-    '#suffix' => '</div>',
-    '#default_value' => $default_radio,
-  );
-  foreach ($content_types as $id => $type) {
-    $function = $type['admin'];
-    if (function_exists($function)) {
-      global $form_values;
-      $form['add'][$id] = $function('add button', $dummy); 
-      
-      // $dummy needed for cause you can't have default args on a reference.
-      $form['add'][$id]['#parents'] = array('add', $id);
-      $form['add'][$id]['#tree'] = true;
-      panels_form_builder($form['add'][$id]);
-
-      if ($conf = $function('add', $form_values['add'][$id])) {
-        $add->configuration = $conf;
-        $add->type = $id;
-        $form['add']['area']['#parents'] = array('area');
-        panels_form_builder($form['add']['area']);
-        $add->area = $form_values['area'];
-      }
-    }
+function panels_sanitize_display(&$display) {
+  if (!isset($display->args)) {
+    $display->args = array();
   }
 
-  $form['layout'] = array(
-    '#type' => 'value',
-    '#value' => $panels->layout
-  );
+  if (!isset($display->incoming_content)) {
+    $display->incoming_content = NULL;
+  }
 
-  $form['did'] = array(
-    '#type' => 'value',
-    '#value' => $panels->did,
-  );
+  if (!isset($display->context)) {
+    $display->context = array();
+  }
 
-  $form['info'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('General information'),
-    '#collapsible' => false,
-  );
+  if (!isset($display->css_id)) {
+    $display->css_id = NULL;
+  }
+}
 
-  $file = panels_get_file_path($layout['module'], $layout['icon'], false);
-  $icon .= theme('image', $file);
-  $form['info']['layout-icon'] = array(
-    '#value' => '<div class="layout-icon">' . $icon . '</div>',
-  );
+/**
+ * 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;
+}
 
-  $form['info']['layout-display'] = array(
-    '#value' => '<strong>Layout</strong>: ' . $layout['title'],
-  );
+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;
+  $pane->visibility = '';
+  return $pane;
+}
 
-  $form['info']['title'] = array(
-    '#type' => 'textfield',
-    '#default_value' => $panels->title,
-    '#title' => t('Page title'),
-    '#description' => t('The page title for this panels layout'),
-  );
+/**
+ * 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;
+  }
 
-  $form['info']['css_id'] = array(
-    '#type' => 'textfield',
-    '#default_value' => $panels->css_id,
-    '#title' => t('CSS ID'),
-    '#description' => t('The CSS ID to apply to this page'),
-  );
+  $result = db_query("SELECT * FROM {panels_display} WHERE did IN (".  db_placeholders($dids) .")", $dids);
 
-  $form['info']['path'] = array(
-    '#type' => 'textfield',
-    '#default_value' => $panels->path,
-    '#title' => t('Path'),
-    '#description' => t('The URL path to give this page, i.e, path/to/page'),
-    '#required' => TRUE,
-  );
+  while ($obj = db_fetch_array($result)) {
+    $display = new panels_display();
 
-  $form['content'] = array(
-    '#tree' => true,
-  );
-  
-  // Go through our content areas and display what we have.
-  foreach ($layout['content areas'] as $area => $title) {
-    $list = array();
-    $form['content'][$area] = array(
-      '#tree' => true,
-    );
-
-    // Construct the order, feeding it the default order for what
-    // we know about. When we pull it back out, it may well be
-    // different due to past submits.
-    $order = array();
-    if (is_array($panels->content[$area])) {
-           $order = array_keys($panels->content[$area]);
-    }
-       $form['content'][$area]['order'] = array(
-      '#type' => 'hidden',
-      '#default_value' => serialize($order),
-      '#parents' => array('content', $area, 'order'),
-    );
-
-    // If an add button has added an item to the area, put it in and update
-    // the $order.
-    panels_form_builder($form['content'][$area]['order']);
-    $order = unserialize($form['content'][$area]['order']['#value']);
-    if ($add->area == $area) {
-      // say THIS 5 times real fast
-      if ($panels->content[$area]) {
-        $position = max(max(array_keys($order)), max(array_keys($panels->content[$area]))) + 1;
-      }
-      else if ($order) {
-        $position = max(array_keys($order)) + 1;
+    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);
       }
-      else {
-        $position = 0;
-      }
-
-      $panels->content[$area][$position] = $add;
-      $order[] = $position;
-      $form['content'][$area]['order']['#value'] = serialize($order);
     }
 
-    // Go through each item in the area and render it.
-    $count = count($order);
-    foreach ($order as $position => $id) {
-      // place buttons to re-order content. 
-      $form['content'][$area][$id]['buttons'] = array(
-        '#parents' => array('content', $area, $id, 'buttons'),
-        '#tree' => TRUE
-      );
-      panels_add_buttons($form['content'][$area][$id]['buttons'], $count, $position);
-
-      // figure out if one of those buttons was pressed
-      panels_form_builder($form['content'][$area][$id]['buttons']);
-      $deleted = false;
-      foreach ($GLOBALS['form_values']['content'][$area][$id]['buttons'] as $button => $value) {
-        if ($value) {
-          $function = 'panels_move_' . $button;
-          $function($order, $position);
-          $form['content'][$area]['order']['#value'] = serialize($order);
-          if ($button == 'delete')
-          $deleted = true;
-        }
-      }
-      // If a content item was deleted, it still has buttons. Get rid of them.
-      // It had buttons because we needed to give it buttons to see if its
-      // buttons were clicked.
-      if ($deleted) {
-        unset($form['content'][$area][$id]['buttons']);
-      }
-      else {
-        // we finally get to add the conent item's content to the form.
-        $area_record = $panels->content[$area][$id];
-        $form['content'][$area][$id]['type'] = array(
-          '#type' => 'hidden',
-          '#default_value' => $area_record->type,
-          '#parents' => array('content', $area, $id, 'type'),
-        );
-        // retrieve what was already there -- so we can retain edits and
-        // content items that were added.
-        panels_form_builder($form['content'][$area][$id]['type']);
-        $type = $form['content'][$area][$id]['type']['#value'];
-        $function = $content_types[$type]['admin'];
-        if (function_exists($function)) {
-          $array = array(
-            '#tree' => true,
-            '#parents' => array('content', $area, $id, 'configuration'),
-          );
-          $form['content'][$area][$id]['configuration'] = array_merge($array, $function('edit', $area_record->configuration, array('content', $area, $id, 'configuration')));
-          panels_form_builder($form['content'][$area][$id]['configuration']);
-        }        
-      }
-    }
+    $display->panels = $display->content = array();
+
+    $displays[$display->did] = $display;
   }
 
-  $form['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Save'),
-  );  
+  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);
 
-  return drupal_get_form('panels_edit_form', $form);
+  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;
 }
 
 /**
- * Display the form to edit a panels.
+ * 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 theme_panels_edit_form($form) {
-  $layouts = panels_get_layouts();
-  $layout = $layouts[$form['layout']['#value']];
-
-  $content_types = panels_get_content_types();
+function panels_load_display($did) {
+  $displays = panels_load_displays(array($did));
+  if (!empty($displays)) {
+    return array_shift($displays);
+  }
+}
 
-  $output .= form_render($form['info']);
-  foreach ($layout['content areas'] as $area => $title) {
-    $order = unserialize($form['content'][$area]['order']['#value']);
-    if (!$order) {
-      $area_content = t('This area has no content.');
-    }
-    else {
-      $area_content = '';
-      $count = count($order);
-      foreach ($order as $position => $id) {
-        if ($count > 1) {
-          if ($position == 0 ) {
-            panels_set_blank($form['content'][$area][$id]['buttons']['up']);
-          }
-          else if ($position == ($count - 1)) {
-            panels_set_blank($form['content'][$area][$id]['buttons']['down']);
-          }
-        }
-        $type = $form['content'][$area][$id]['type']['#value'];
-        $function = $content_types[$type]['admin'];
-        if (function_exists($function)) {
-          // figure out the actual values; using the global because we need it
-          // to be in the same format it'll be in 'submit'.
-          global $form_values;
-          $conf_form = $form_values['content'][$area][$id]['configuration'];
-          $conf = $function('save', $conf_form);
-          $fieldset = array(
-            '#title' => t('Configure'), 
-            '#children' => form_render($form['content'][$area][$id]['configuration']), 
-            '#collapsible' => true, 
-            '#collapsed' => true
-          );
-          $buttons = form_render($form['content'][$area][$id]['buttons']);
-          $area_content .= $buttons . '&nbsp;' . $function('list', $conf) . 
-            theme('fieldset', $fieldset)  /* . '<br />' */;
-        }
-      }
+/**
+ * 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;
     }
-    $content[$area] = theme('fieldset', array('#title' => check_plain($title), '#value' => $area_content));
+  }
+  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();
   }
 
-  $output .= panels_get_layout($layout, $content);
+  // update all the panes
+  panels_load_include('plugins');
 
-  $output .= form_render($form);
-  return $output;
-}
+  foreach ((array) $display->panels as $id => $panes) {
+    $position = 0;
+    $new_panes = array();
+    foreach ((array) $panes as $pid) {
+      $pane = $display->content[$pid];
+      $pane->position = $position++;
 
-function panels_edit_form_validate($form_id, $form_values, $form) {
-  $content_types = panels_get_content_types();
-  foreach ($form_values['content'] as $area => $content) {
-    foreach ($content as $id => $item) {
-      if (is_numeric($id)) {
-        $function = $content_types[$item['type']]['admin'];
-        if (function_exists($function)) {
-          $function('validate', $item['configuration'], $form['content'][$area][$id]['configuration']);
-        }
+      // make variables right.
+      $type = panels_get_content_type($pane->type);
+      $access = isset($pane->access) ? implode(', ', $pane->access) : '';
+      $visibility = !empty($type['visibility serialize']) ? serialize($pane->visibility) : $pane->visibility;
+      $pane->shown = isset($pane->shown) ? $pane->shown : TRUE;
+
+      if (empty($pane->cache)) {
+        $pane->cache = array();
       }
-    }
-  }
-}
 
-function panels_edit_form_submit($form_id, $form_values) {
-  $panels = (object) $form_values;
-  // be sure we get the order right.
-  foreach ($form_values['content'] as $area => $content) {
-    $array = array();
-    $order = unserialize($content['order']);
-    if (is_array($order)) {
-      foreach($order as $id) {
-        $array[] = $content[$id];
+      $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]);
       }
     }
-    $panels->content[$area] = $array;
+
+    $display->panels[$id] = $new_panes;
+  }
+  if (!empty($pids)) {
+    db_query("DELETE FROM {panels_pane} WHERE pid IN (" . db_placeholders($pids) . ")", $pids);
   }
-  panels_save_panels($panels);
-  drupal_set_message(t('The panels has been saved.'));
-  return 'admin/panels';
-}
 
-/**
- * add the buttons to a content item
- */
-function panels_add_buttons(&$form, $count, $position) {
-  $form['delete'] = panels_add_button('user-trash.png', 'delete', t('Delete this item'));
-  // Leaving these in but commented out as I'm not convinced we don't want them.
-//  if ($count > 2) {
-//    $form['top'] = panels_add_button('go-top.png', 'top', t('Move item to top'));
-//  }
-  if ($count > 1) {
-    $form['up'] = panels_add_button('go-up.png', 'up', t('Move item up'));
-    $form['down'] = panels_add_button('go-down.png', 'down', t('Move item down'));
-  }
-//  if ($count > 2) {
-//    $form['bottom'] = panels_add_button('go-bottom.png', 'bottom', t('Move item to bottom'));
-//  }
-//  if ($count > 1) {
-//    $form['spacer'] = panels_add_blank();
-//  }
-  return $form;
-}
+  // Clear any cached content for this display.
+  panels_clear_cached_content($display);
 
-/**
- * move an item in an array to the top
- */
-function panels_move_top(&$array, &$position) {
-  $value = $array[$position];
-  unset($array[$position]);
-  array_unshift($array, $value);
-  // reindex the array now
-  $array = array_values($array);
+  // to be nice, even tho we have a reference.
+  return $display;
 }
 
 /**
- * move an item in an array to the bottom
+ * Delete a display.
  */
-function panels_move_bottom(&$array, &$position) {
-  $value = $array[$position];
-  unset($array[$position]);
-  $array[] = $value;
-  // reindex the array now
-  $array = array_values($array);
-}
-
-/**
- * move an item in an array up one position
- */
-function panels_move_up(&$array, &$position) {
-  $value = $array[$position];
-  $array[$position] = $array[$position - 1];
-  $array[$position - 1] = $value;
-}
-
-/**
- * move an item in an array up one position
- */
-function panels_move_down(&$array, &$position) {
-  $value = $array[$position];
-  $array[$position] = $array[$position + 1];
-  $array[$position + 1] = $value;
+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);
 }
 
 /**
- * Remove an item from an array
+ * 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_move_delete(&$array, &$position) {
-  unset($array[$position]);
-  // reindex the array now
-  $array = array_values($array);
-}
-
-// ---------------------------------------------------------------------------
-// panels database functions
-
-function panels_load_panels($did) {
-  $panels = db_fetch_object(db_query("SELECT * FROM {panels_info} WHERE did = %d", $did));
-  if (!$panels) {
-    return NULL;
-  }
-  $result = db_query("SELECT * FROM {panels_area} WHERE did = %d ORDER BY area, position", $did);
-  while ($area = db_fetch_object($result)) {
-    $area->configuration = unserialize($area->configuration);
-    $panels->content[$area->area][] = $area;
+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');
+  // TODO 'name' field was removed several months ago, so temporarily removing
+  // it from here whil investigations into exactly why it was removed continue
+  $fields = array('layout', 'layout_settings', 'panel_settings');
+  foreach ($fields as $field) {
+    $output .= $prefix . '$display->' . $field . ' = ' . panels_var_export($display->$field, $prefix) . ";\n";
   }
-  return $panels;
-}
 
-function panels_save_panels($panels) {
-  if ($panels->did) {
-    db_query("UPDATE {panels_info} SET title = '%s', access = '%s', path = '%s', css_id = '%s', layout = '%s' WHERE did = %d", $panels->title, $panels->access, $panels->path, $panels->css_id, $panels->layout, $panels->did);
-    db_query("DELETE FROM {panels_area} WHERE did = %d", $panels->did);
-  }
-  else {
-    $panels->did = db_next_id("{panels_info_id}");
-    db_query("INSERT INTO {panels_info} (did, title, access, path, css_id, layout) VALUES (%d, '%s', '%s', '%s', '%s', '%s')", $panels->did, $panels->title, $panels->access, $panels->path, $panels->css_id, $panels->layout);
-  }
-  foreach ($panels->content as $area => $info) {
-    foreach ($info as $position => $block) {
-      if (is_numeric($position)) { // don't save some random form stuff that may've been here.
-        $block = (object) $block;
-        db_query("INSERT INTO {panels_area} (did, area, type, configuration, position) VALUES(%d, '%s', '%s', '%s', %d)", $panels->did, $area, $block->type, serialize($block->configuration), $position);
+  $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";
     }
   }
-  cache_clear_all('menu:', TRUE);
+  return $output;
 }
 
-function panels_delete_panels($panels) {
-  db_query("DELETE FROM {panels_info} WHERE did = %d", $panels->did);
-  db_query("DELETE FROM {panels_area} WHERE did = %d", $panels->did);
-  cache_clear_all('menu:', TRUE);
+function panels_export_pane($pane, $prefix = '') {
+  $output = '';
+  $output = $prefix . '$pane = new stdClass()'  . ";\n";
+  $fields = array('pid', 'panel', 'type', 'shown', 'subtype', 'access', 'configuration', 'cache');
+  foreach ($fields as $field) {
+    $output .= "$prefix  " . '$pane->' . $field . ' = ' . panels_var_export($pane->$field, "$prefix  ") . ";\n";
+  }
+  return $output;
 }
-// ---------------------------------------------------------------------------
-// panels page
 
-function panels_panels_page($did) {
-  $panels = panels_load_panels($did);
-  if (!$panels) {
-    return drupal_not_found();
+function panels_var_export($object, $prefix = '') {
+  if (is_array($object) && empty($object)) {
+    $output = 'array()';
   }
-
-  $layouts = panels_get_layouts();
-  $layout = $layouts[$panels->layout];
-
-  if (!$layout) {
-    watchdog('panels', t('Unable to find requested layout %s', array('%s' => check_plain($panels->layout))));
-    return drupal_not_found();
+  else {
+    // Remove extra space to match Drupal coding standards.
+    $output = str_replace('array (', 'array(', var_export($object, TRUE));
   }
 
-  $content_types = panels_get_content_types();
-
-  foreach ($panels->content as $location => $list) {
-    foreach ($list as $area) {
-      $function = $content_types[$area->type]['callback'];
-      if (function_exists($function)) {
-        $content[$area->area] .= $function($area->configuration);
-      }
-    }
+  if ($prefix) {
+    $output = str_replace("\n", "\n$prefix", $output);
   }
-  $output = panels_get_layout($layout, $content);
   return $output;
 }
 
-function panels_get_layout($layout, $content) {
-  $output = theme($layout['theme'], check_plain($layout['css_id']), $content);
-
-  if ($output) {
-    if (file_exists(path_to_theme() . '/' . $layout['css'])) {
-      theme_add_style(path_to_theme() . '/' . $layout['css']);
-    }
-    else {
-      theme_add_style(drupal_get_path('module', $layout['module']) . '/' . $layout['css']);
-    }
-  }
-  return $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
- * finished layout.
+ * 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
  */
 function panels_print_layout($id, $content) {
-  $layouts = panels_get_layouts();
-  $layout = $layouts[$id];
-  if (!$layout) {
-    return;
-  }
-  
-  return panels_get_layout($layout, $content);
+  panels_load_include('plugins');
+  return _panels_print_layout($id, $content);
 }
 
-// ---------------------------------------------------------------------------
-// panels data loading
-
-function panels_load_includes($directory, $callback) {
-  // Load all our module 'on behalfs'.
-  $path = drupal_get_path('module', 'panels') . '/' . $directory;
-  $files = system_listing('.inc$', $path, 'name', 0);
-
-  foreach($files as $file) {
-    require_once($file->filename);
-  }
-  $output = module_invoke_all($callback);
-  foreach ($files as $file) {
-    $function = 'panels_' . $file->name . '_' . $callback;
-    if (function_exists($function)) {
-      $result = $function();
-      if (isset($result) && is_array($result)) {
-        $output = array_merge($output, $result);
-      }
-    }
+// @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>';
   }
+  $output .= '</div>';
   return $output;
 }
 
-function panels_get_layouts() {
-  static $layout = NULL;
-  if (!$layout) {
-    $layouts = panels_load_includes('layouts', 'panels_layouts');
-  }
-  return $layouts;
+/**
+ * Theme the layout link image
+ * @layout
+ */
+function theme_panels_layout_link($title, $id, $image, $link) {
+  $output = '<div class="layout-link">';
+  $output .= $image;
+  $output .= '<div>' . $title . '</div>';
+  $output .= '</div>';
+  return $output;
 }
 
-function panels_get_content_types() {
-  static $layout = NULL;
-  if (!$layout) {
-    $layouts = panels_load_includes('content_types', 'panels_content_types');
-  }
-  return $layouts;
+/**
+ * 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);
 }
 
 /**
- * Helper function for autocompletion of node titles.
- * This is mostly stolen from clipper.
+ * Implementation of hook_ctools_plugin_directory() to let the system know
+ * we implement task and task_handler plugins.
  */
-function panels_node_autocomplete($string) {
-  if ($string != '') { // if there are node_types passed, we'll use those in a MySQL IN query.
-    $result = db_query_range(db_rewrite_sql('SELECT n.title, u.name FROM {node} n INNER JOIN {users} u ON u.uid = n.uid WHERE LOWER(title) LIKE LOWER("%%%s%%")'), $string, 0, 10);
-    $prefix = count($array) ? implode(', ', $array) .', ' : '';
-
-    $matches = array();
-    while ($node = db_fetch_object($result)) {
-      $n = $node->title;
-      // Commas and quotes in terms are special cases, so encode 'em.
-      if (preg_match('/,/', $node->title) || preg_match('/"/', $node->title)) {
-        $n = '"'. preg_replace('/"/', '""', $node->title) .'"';
-      }
-      $matches[$prefix . $n] = '<span class="autocomplete_title">'. check_plain($node->title) .'</span> <span class="autocomplete_user">('. t('by %user', array('%user' => check_plain($node->name))) .')</span>';
-    }
-    print drupal_to_js($matches);
-    exit();
+function panels_ctools_plugin_directory($plugin) {
+  if ($plugin == 'tasks' || $plugin == 'task_handlers') {
+    return 'plugins/' . $plugin;
   }
 }