Catching HEAD back up with all the changes on DRUPAL-6--3. Now ready to move forward...
[project/panels.git] / panels.module
index 30fdf03..e5777f5 100644 (file)
@@ -7,11 +7,11 @@
  * Core functionality for the Panels engine.
  */
 
-/**
- * Error code bitmask used for identifying argument behavior at runtime.
- */
-define('PANELS_ARG_IS_BAD', 1);
-define('PANELS_ARG_USE_FALLBACK', 1 << 1);
+define('PANELS_REQUIRED_CTOOLS_API', '1.1.1');
+
+define('PANELS_TITLE_FIXED', 0); // Hide title use to be true/false. So false remains old behavior.
+define('PANELS_TITLE_NONE', 1); // And true meant no title.
+define('PANELS_TITLE_PANE', 2); // And this is the new behavior, where the title field will pick from a pane.
 
 /**
  * Returns the API version of Panels. This didn't exist in 1.
@@ -19,10 +19,18 @@ define('PANELS_ARG_USE_FALLBACK', 1 << 1);
  * @return An array with the major and minor versions
  */
 function panels_api_version() {
-  return array(2, 0);
+  return array(3, 0);
 }
 
+/**
+ * Implementation of hook_theme()
+ */
 function panels_theme() {
+  // Safety: go away if CTools is not at an appropriate version.
+  if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
+    return array();
+  }
+
   $theme = array();
   $theme['panels_layout_link'] = array(
     'arguments' => array('title', 'id', 'image', 'link'),
@@ -30,9 +38,6 @@ function panels_theme() {
   $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',
@@ -42,46 +47,23 @@ function panels_theme() {
     'file' => 'includes/display-edit.inc',
   );
   $theme['panels_pane'] = array(
-    'arguments' => array('content', 'pane', 'display'),
-    'file' => 'includes/display-render.inc',
+    'arguments' => array('output' => array(), 'pane' => array(), 'display' => array()),
+    'path' => drupal_get_path('module', 'panels') . '/templates',
+    'template' => 'panels-pane',
   );
   $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_render_display_form'] = array(
+    'arguments' => array('form' => NULL),
   );
-  $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',
+
+  $theme['panels_dashboard'] = array(
+    'arguments' => array(),
+    'path' => drupal_get_path('module', 'panels') . '/templates',
+    'file' => '../includes/callbacks.inc',
+    'template' => 'panels-dashboard',
   );
 
   // Register layout and style themes on behalf of all of these items.
@@ -91,15 +73,18 @@ function panels_theme() {
   // 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']);
+    foreach (array('theme', 'admin theme') as $callback) {
+      if (!empty($data[$callback])) {
+        $theme[$data[$callback]] = array(
+          'arguments' => array('css_id' => NULL, 'content' => NULL, 'settings' => NULL, 'display' => NULL),
+          'path' => $data['path'],
+        );
+
+        // if no theme function exists, assume template.
+        if (!function_exists("theme_$data[theme]")) {
+          $theme[$data[$callback]]['template'] = str_replace('_', '-', $data[$callback]);
+          $theme[$data[$callback]]['file'] = $data['file']; // for preprocess.
+        }
       }
     }
   }
@@ -108,7 +93,7 @@ function panels_theme() {
   foreach ($styles as $name => $data) {
     if (!empty($data['render pane'])) {
       $theme[$data['render pane']] = array(
-        'arguments' => array('content' => NULL, 'pane' => NULL, 'display' => NULL),
+        'arguments' => array('output' => NULL, 'pane' => NULL, 'display' => NULL),
       );
     }
     if (!empty($data['render panel'])) {
@@ -117,6 +102,14 @@ function panels_theme() {
       );
     }
 
+    if (!empty($data['hook theme'])) {
+      if (is_array($data['hook theme'])) {
+        $theme += $data['hook theme'];
+      }
+      else if (function_exists($data['hook theme'])) {
+        $data['hook theme']($theme, $data);
+      }
+    }
   }
 
   return $theme;
@@ -126,6 +119,10 @@ function panels_theme() {
  * Implementation of hook_menu
  */
 function panels_menu() {
+  // Safety: go away if CTools is not at an appropriate version.
+  if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
+    return array();
+  }
   $items = array();
 
   // Provide some common options to reduce code repetition.
@@ -135,75 +132,146 @@ function panels_menu() {
     'access arguments' => array('access content'),
     'type' => MENU_CALLBACK,
     'file' => 'includes/display-edit.inc',
+    'page arguments' => array(3),
   );
 
-  $items['panels/ajax/add-pane'] = array(
+  $items['panels/ajax/add-pane/%panels_edit_cache'] = array(
     'page callback' => 'panels_ajax_add_pane_choose',
   ) + $base;
-  $items['panels/ajax/add-pane-config'] = array(
+  $items['panels/ajax/add-pane-config/%panels_edit_cache'] = array(
     'page callback' => 'panels_ajax_add_pane_config',
   ) + $base;
-  $items['panels/ajax/configure'] = array(
+  $items['panels/ajax/configure/%panels_edit_cache'] = array(
     'page callback' => 'panels_ajax_configure_pane',
   ) + $base;
-  $items['panels/ajax/show'] = array(
+  $items['panels/ajax/show/%panels_edit_cache'] = array(
     'page callback' => 'panels_ajax_toggle_shown',
-    'page arguments' => array('show'),
+    'page arguments' => array('show', 3),
   ) + $base;
-  $items['panels/ajax/hide'] = array(
+  $items['panels/ajax/hide/%panels_edit_cache'] = array(
     'page callback' => 'panels_ajax_toggle_shown',
-    'page arguments' => array('hide'),
+    'page arguments' => array('hide', 3),
   ) + $base;
-  $items['panels/ajax/cache-method'] = array(
+  $items['panels/ajax/cache-method/%panels_edit_cache'] = array(
     'page callback' => 'panels_ajax_cache_method',
   ) + $base;
-  $items['panels/ajax/cache-settings'] = array(
+  $items['panels/ajax/cache-settings/%panels_edit_cache'] = array(
     'page callback' => 'panels_ajax_cache_settings',
   ) + $base;
-
-  // For panel settings on the edit layout settings page
-  $items['panels/ajax/style-settings'] = array(
+  $items['panels/ajax/display-settings/%panels_edit_cache'] = array(
+    'page callback' => 'panels_ajax_display_settings',
+  ) + $base;
+  $items['panels/ajax/panel-title/%panels_edit_cache'] = array(
+    'page callback' => 'panels_ajax_set_display_title',
+  ) + $base;
+  $items['panels/ajax/style-type/%/%panels_edit_cache'] = array(
+    'page callback' => 'panels_ajax_style_type',
+    'page arguments' => array(3, 4),
+  ) + $base;
+  $items['panels/ajax/style-settings/%/%panels_edit_cache'] = array(
     'page callback' => 'panels_ajax_style_settings',
-    'file' => 'includes/display-layout-settings.inc',
+    'page arguments' => array(3, 4),
   ) + $base;
-
-  // Non-display editor callbacks
-  $items['panels/node/autocomplete'] = array(
-    'title' => 'Autocomplete node',
-    'page callback' => 'panels_node_autocomplete',
-    'file' => 'includes/callbacks.inc',
+  $items['panels/ajax/pane-css/%panels_edit_cache'] = array(
+    'page callback' => 'panels_ajax_configure_pane_css',
   ) + $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',
+  $items['panels/ajax/access-settings/%panels_edit_cache'] = array(
+    'page callback' => 'panels_ajax_configure_access_settings',
   ) + $base;
-  $items['panels/ajax/context-configure'] = array(
-    'page callback' => 'panels_ajax_context_item_edit',
-    'file' => 'includes/common-context.inc',
+  $items['panels/ajax/access-test/%panels_edit_cache'] = array(
+    'page callback' => 'panels_ajax_configure_access_test',
   ) + $base;
-  $items['panels/ajax/context-delete'] = array(
-    'page callback' => 'panels_ajax_context_item_delete',
-    'file' => 'includes/common-context.inc',
+  $items['panels/ajax/access-add/%panels_edit_cache'] = array(
+    'page callback' => 'panels_ajax_add_access_test',
+  ) + $base;
+  $items['panels/ajax/preview/%panels_edit_cache'] = array(
+    'page callback' => 'panels_ajax_preview',
   ) + $base;
 
+  $admin_base = array(
+    'file' => 'includes/callbacks.inc',
+    'access arguments' => array('use panels dashboard'),
+  );
   // Provide a nice location for a panels admin panel.
-  $items['admin/panels'] = array(
+  $items['admin/build/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.',
-  );
+  ) + $admin_base;
+
+  $items['admin/build/panels/dashboard'] = array(
+    'title' => 'Dashboard',
+    'page callback' => 'panels_admin_page',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  ) + $admin_base;
+
+  $items['admin/build/panels/settings'] = array(
+    'title' => 'Settings',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('panels_admin_settings_page'),
+    'type' => MENU_LOCAL_TASK,
+  ) + $admin_base;
+
+  $items['admin/build/panels/settings/general'] = array(
+    'title' => 'General',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('panels_admin_settings_page'),
+    'access arguments' => array('administer page manager'),
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  ) + $admin_base;
+
+  if (module_exists('page_manager')) {
+    $items['admin/build/panels/settings/panel-page'] = array(
+      'title' => 'Panel pages',
+      'page callback' => 'panels_admin_panel_context_page',
+      'type' => MENU_LOCAL_TASK,
+      'weight' => -10,
+    ) + $admin_base;
+  }
+
+  panels_load_include('plugins');
+  $layouts = panels_get_layouts();
+  foreach ($layouts as $name => $data) {
+    if (!empty($data['hook menu'])) {
+      if (is_array($data['hook menu'])) {
+        $items += $data['hook menu'];
+      }
+      else if (function_exists($data['hook menu'])) {
+        $data['hook menu']($items, $data);
+      }
+    }
+  }
 
   return $items;
 }
 
 /**
+ * Menu loader function to load a cache item for Panels AJAX.
+ *
+ * This load all of the includes needed to perform AJAX, and loads the
+ * cache object and makes sure it is valid.
+ */
+function panels_edit_cache_load($cache_key) {
+  panels_load_include('display-edit');
+  panels_load_include('plugins');
+  ctools_include('ajax');
+  ctools_include('modal');
+  ctools_include('context');
+
+  return panels_edit_cache_get($cache_key);
+}
+
+/**
  * Implementation of hook_init()
  */
 function panels_init() {
+  // Safety: go away if CTools is not at an appropriate version.
+  if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
+    return;
+  }
+
   drupal_add_css(panels_get_path('css/panels.css'));
   drupal_add_js(panels_get_path('js/panels.js'));
 }
@@ -237,7 +305,8 @@ function panels_perm() {
     'administer pane visibility',
     'administer pane access',
     'administer advanced pane settings',
-    'use panels caching features'
+    'use panels caching features',
+    'use panels dashboard',
   );
 }
 
@@ -245,42 +314,27 @@ function panels_perm() {
  * 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;
+  ctools_include('object-cache');
+  // we often store contexts in cache, so let's just make sure we can load
+  // them.
+  ctools_include('context');
+  return ctools_object_cache_get($obj, 'panels_display:' . $did, $skip_cache);
 }
 
 /**
  * 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());
+  ctools_include('object-cache');
+  return ctools_object_cache_set($obj, 'panels_display:' . $did, $cache);
 }
 
 /**
  * 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));
+  ctools_include('object-cache');
+  return ctools_object_cache_clear($obj, 'panels_display:' . $did);
 }
 
 // ---------------------------------------------------------------------------
@@ -349,11 +403,11 @@ function panels_cron() {
  *      - 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) {
+function panels_edit($display, $destination = NULL, $content_types = NULL, $title = FALSE) {
   panels_load_include('display-edit');
-  panels_load_include('ajax');
+  ctools_include('ajax');
   panels_load_include('plugins');
-  return _panels_edit($display, $destination, $content_types);
+  return _panels_edit($display, $destination, $content_types, $title);
 }
 
 /**
@@ -400,60 +454,6 @@ function panels_edit_layout($display, $finish, $destination = NULL, $allowed_lay
   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
 
@@ -468,12 +468,7 @@ class panels_display {
   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 = '';
+  var $did = 'new';
 
   function add_pane($pane, $location = FALSE) {
     $pane->pid = $this->next_new_pid();
@@ -512,7 +507,7 @@ class panels_display {
         $id[] = substr($pid, 4);
       }
     }
-    $next_id = end($id);
+    $next_id = max($id);
     return ++$next_id;
   }
 }
@@ -521,11 +516,6 @@ class panels_display {
  * }@ 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.
  *
@@ -538,6 +528,7 @@ function panels_export_pane_across_displays($source_display, &$target_display, $
  *   The sanitized display object.
  */
 function panels_sanitize_display(&$display) {
+  return;
   if (!isset($display->args)) {
     $display->args = array();
   }
@@ -559,20 +550,23 @@ function panels_sanitize_display(&$display) {
  * Creates a new display, setting the ID to our magic new id.
  */
 function panels_new_display() {
-  $display = new panels_display();
+  ctools_include('export');
+  $display = ctools_export_new_object('panels_display', FALSE);
   $display->did = 'new';
   return $display;
 }
 
+/**
+ * Create a new pane.
+ *
+ * @todo -- use schema API for some of this?
+ */
 function panels_new_pane($type, $subtype) {
-  $pane = new stdClass();
+  ctools_include('export');
+  $pane = ctools_export_new_object('panels_pane', FALSE);
   $pane->pid = 'new';
   $pane->type = $type;
   $pane->subtype = $subtype;
-  $pane->configuration = array();
-  $pane->access = array();
-  $pane->shown = TRUE;
-  $pane->visibility = '';
   return $pane;
 }
 
@@ -586,6 +580,8 @@ function panels_new_pane($type, $subtype) {
  *
  * @return $displays
  *  An array of displays, keyed by their display dids.
+ *
+ * @todo schema API can drasticly simplify this code.
  */
 function panels_load_displays($dids) {
   $displays = array();
@@ -593,41 +589,23 @@ function panels_load_displays($dids) {
     return $displays;
   }
 
-  $result = db_query("SELECT * FROM {panels_display} WHERE did IN (".  db_placeholders($dids) .")", $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);
-      }
-    }
+  $result = db_query("SELECT * FROM {panels_display} WHERE did IN (" .  db_placeholders($dids) . ")", $dids);
 
-    $display->panels = $display->content = array();
-
-    $displays[$display->did] = $display;
+  ctools_include('export');
+  while ($obj = db_fetch_object($result)) {
+    $displays[$obj->did] = ctools_export_unpack_object('panels_display', $obj);
+    // Modify the hide_title field to go from a bool to an int if necessary.
   }
 
-  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);
 
-  $result = db_query("SELECT * FROM {panels_pane} WHERE did IN (". db_placeholders($dids) .") 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;
+  while ($obj = db_fetch_object($result)) {
+    $pane = ctools_export_unpack_object('panels_pane', $obj);
 
     $displays[$pane->did]->panels[$pane->panel][] = $pane->pid;
     $displays[$pane->did]->content[$pane->pid] = $pane;
   }
+
   return $displays;
 }
 
@@ -655,7 +633,7 @@ function panels_load_displays($dids) {
  *    - $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.
+ * it must be set using one of the ctools_context_create() functions.
  */
 function panels_load_display($did) {
   $displays = panels_load_displays(array($did));
@@ -681,9 +659,11 @@ function panels_load_display($did) {
  * @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);
+  $update = (isset($display->did) && is_numeric($display->did)) ? array('did') : array();
+  drupal_write_record('panels_display', $display, $update);
+
+  $pids = array();
+  if ($update) {
     // Get a list of all panes currently in the database for this display so we can know if there
     // are panes that need to be deleted. (i.e, aren't currently in our list of panes).
     $result = db_query("SELECT pid FROM {panels_pane} WHERE did = %d", $display->did);
@@ -691,51 +671,47 @@ function panels_save_display(&$display) {
       $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');
+  ctools_include('content');
 
-  foreach ((array) $display->panels as $id => $panes) {
+  foreach ($display->panels as $id => $panes) {
     $position = 0;
     $new_panes = array();
     foreach ((array) $panes as $pid) {
+      if (!isset($display->content[$pid])) {
+        continue;
+      }
       $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 = !empty($type['visibility serialize']) ? serialize($pane->visibility) : $pane->visibility;
-      $pane->shown = isset($pane->shown) ? $pane->shown : TRUE;
+      $type = ctools_get_content_type($pane->type);
 
-      if (empty($pane->cache)) {
-        $pane->cache = array();
+      $pane->position = $position++;
+      $pane->did = $display->did;
+
+      $old_pid = $pane->pid;
+      drupal_write_record('panels_pane', $pane, is_numeric($pid) ? array('pid') : array());
+
+      if ($pane->pid != $old_pid) {
+        // and put it back so our pids and positions can be used
+        unset($display->content[$id]);
+        $display->content[$pane->pid] = $pane;
+
+        // If the title pane was one of our panes that just got its ID changed,
+        // we need to change it in the database, too.
+        if (isset($display->title_pane) && $display->title_pane == $old_pid) {
+          $display->title_pane = $pane->pid;
+          // Do a simple update query to write it so we don't have to rewrite
+          // the whole record. We can't just save writing the whole record here
+          // because it was needed to get the did. Chicken, egg, more chicken.
+          db_query("UPDATE {panels_display} SET title_pane = %d WHERE did = %d", $pane->pid, $display->did);
+        }
       }
 
-      $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;
+      // re-add this to the list of content for this panel.
       $new_panes[] = $pane->pid;
+
+      // Remove this from the list of panes scheduled for deletion.
       if (isset($pids[$pane->pid])) {
         unset($pids[$pane->pid]);
       }
@@ -796,27 +772,25 @@ function panels_delete_display($display) {
  *  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');
-  // 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";
-  }
+  ctools_include('export');
+  $output = ctools_export_object('panels_display', $display, $prefix);
 
+  // Initialize empty properties.
   $output .= $prefix . '$display->content = array()' . ";\n";
   $output .= $prefix . '$display->panels = array()' . ";\n";
   $panels = array();
 
+  $title_pid = 0;
   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 . '  ');
+      $pid = 'new-' . ++$pid_counter;
+      if ($pane->pid == $display->title_pane) {
+        $title_pid = $pid;
+      }
+      $pane->pid = $pid;
+      $output .= ctools_export_object('panels_pane', $pane, $prefix . '  ');
       $output .= "$prefix  " . '$display->content[\'' . $pane->pid . '\'] = $pane' . ";\n";
       if (!isset($region_counters[$pane->panel])) {
         $region_counters[$pane->panel] = 0;
@@ -824,31 +798,21 @@ function panels_export_display($display, $prefix = '') {
       $output .= "$prefix  " . '$display->panels[\'' . $pane->panel . '\'][' . $region_counters[$pane->panel]++ .'] = \'' . $pane->pid . "';\n";
     }
   }
-  return $output;
-}
-
-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;
-}
-
-function panels_var_export($object, $prefix = '') {
-  if (is_array($object) && empty($object)) {
-    $output = 'array()';
-  }
-  else {
-    // Remove extra space to match Drupal coding standards.
-    $output = str_replace('array (', 'array(', var_export($object, TRUE));
+  $output .= $prefix . '$display->hide_title = ';
+  switch ($display->hide_title) {
+    case PANELS_TITLE_FIXED:
+      $output .= 'PANELS_TITLE_FIXED';
+      break;
+    case PANELS_TITLE_NONE:
+      $output .= 'PANELS_TITLE_NONE';
+      break;
+    case PANELS_TITLE_PANE:
+      $output .= 'PANELS_TITLE_PANE';
+      break;
   }
+  $output .= ";\n";
 
-  if ($prefix) {
-    $output = str_replace("\n", "\n$prefix", $output);
-  }
+  $output .= $prefix . '$display->title_pane =' . " '$title_pid';\n";
   return $output;
 }
 
@@ -864,10 +828,34 @@ function panels_var_export($object, $prefix = '') {
 function panels_render_display(&$display) {
   panels_load_include('display-render');
   panels_load_include('plugins');
+  ctools_include('context');
+
+  if (!empty($display->context)) {
+    if ($form_context = ctools_context_get_form($display->context)) {
+      $form_context->form['#theme'] = 'panels_render_display_form';
+      $form_context->form['#display'] = &$display;
+      $form_context->form['#form_context_id'] = $form_context->id;
+      return drupal_render_form($form_context->form_id, $form_context->form);
+    }
+  }
   return _panels_render_display($display);
 }
 
 /**
+ * Theme function to render our panel as a form.
+ *
+ * When rendering a display as a form, the entire display needs to be
+ * inside the <form> tag so that the form can be spread across the
+ * panes. This sets up the form system to be the main caller and we
+ * then operate as a theme function of the form.
+ */
+function theme_panels_render_display_form($form) {
+  $form['#children'] = _panels_render_display($form['#display']);
+  drupal_render($form);
+  return theme('form', $form);
+}
+
+/**
  * For external use: Given a layout ID and a $content array, return the
  * panel display. The content array is filled in based upon the content
  * available in the layout. If it's a two column with a content
@@ -919,11 +907,15 @@ function theme_panels_layout_link($title, $id, $image, $link) {
  * Print the layout link. Sends out to a theme function.
  * @layout
  */
-function panels_print_layout_link($id, $layout, $link) {
+function panels_print_layout_link($id, $layout, $link, $options = array()) {
+  if (isset($options['query']['q'])) {
+    unset($options['query']['q']);
+  }
+
   drupal_add_css(panels_get_path('css/panels_admin.css'));
   $file = $layout['path'] . '/' . $layout['icon'];
-  $image = l(theme('image', $file), $link, array('html' => true));
-  $title = l($layout['title'], $link);
+  $image = l(theme('image', $file), $link, array('html' => true) + $options);
+  $title = l($layout['title'], $link, $options);
   return theme('panels_layout_link', $title, $id, $image, $link);
 }
 
@@ -931,8 +923,154 @@ function panels_print_layout_link($id, $layout, $link) {
  * Implementation of hook_ctools_plugin_directory() to let the system know
  * we implement task and task_handler plugins.
  */
-function panels_ctools_plugin_directory($plugin) {
-  if ($plugin == 'tasks' || $plugin == 'task_handlers') {
+function panels_ctools_plugin_directory($module, $plugin) {
+  // Safety: go away if CTools is not at an appropriate version.
+  if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
+    return;
+  }
+  if ($module == 'page_manager' || $module == 'panels') {
     return 'plugins/' . $plugin;
   }
 }
+
+/**
+ * Inform CTools that the layout plugin can be loaded from themes.
+ */
+function panels_ctools_plugin_layouts() {
+  return array(
+    'load themes' => TRUE,
+  );
+}
+
+/**
+ * Inform CTools that the style plugin can be loaded from themes.
+ */
+function panels_ctools_plugin_styles() {
+  return array(
+    'load themes' => TRUE,
+  );
+}
+
+/**
+ * Get the display that is currently being rendered as a page.
+ *
+ * Unlike in previous versions of this, this only returns the display,
+ * not the page itself, because there are a number of different ways
+ * to get to this point. It is hoped that the page data isn't needed
+ * at this point. If it turns out there is, we will do something else to
+ * get that functionality.
+ */
+function panels_get_current_page_display($change = NULL) {
+  static $display = NULL;
+  if ($change) {
+    $display = $change;
+  }
+
+  return $display;
+}
+
+/**
+ * Get display edit cache on behalf of panel context.
+ *
+ * The key is the second half of the key in this form:
+ * panel_context:TASK_NAME:HANDLER_NAME;
+ */
+function panel_context_panels_cache_get($key) {
+  panels_load_include('common');
+  ctools_include('context');
+  ctools_include('context-task-handler');
+  // this loads the panel context inc even if we don't use the plugin.
+  $plugin = page_manager_get_task_handler('panel_context');
+
+  list($task_name, $handler_name) = explode(':', $key, 2);
+  $page = page_manager_get_page_cache($task_name);
+  if (isset($page->display_cache[$handler_name])) {
+    return $page->display_cache[$handler_name];
+  }
+
+  if ($handler_name) {
+    $handler = &$page->handlers[$handler_name];
+  }
+  else {
+    $handler = &$page->new_handler;
+  }
+  $cache = new stdClass();
+
+  $cache->display = &panels_panel_context_get_display($handler);
+  $cache->display->context = ctools_context_handler_get_all_contexts($page->task, $page->subtask, $handler);
+  $cache->display->cache_key = 'panel_context:' . $key;
+  $cache->content_types = panels_common_get_allowed_types('panels_page', $cache->display->context);
+  $cache->display_title = TRUE;
+
+  return $cache;
+}
+
+/**
+ * Store a display edit in progress in the page cache.
+ */
+function panel_context_panels_cache_set($key, $cache) {
+  list($task_name, $handler_name) = explode(':', $key, 2);
+  $page = page_manager_get_page_cache($task_name);
+  $page->display_cache[$handler_name] = $cache;
+  if ($handler_name) {
+    $page->handlers[$handler_name]->conf['display'] = $cache->display;
+    $page->handler_info[$handler_name]['changed'] |= PAGE_MANAGER_CHANGED_CACHED;
+  }
+  else {
+    $page->new_handler->conf['display'] = $cache->display;
+  }
+  page_manager_set_page_cache($page);
+}
+
+/**
+ * Clean up the panel pane variables for the template.
+ */
+function template_preprocess_panels_pane($vars) {
+  $content = $vars['output'];
+  // basic classes
+  $vars['classes'] = 'panel-pane';
+  $vars['id'] = '';
+
+  // Add some usable classes based on type/subtype
+  ctools_include('cleanstring');
+  $type_class = $content->type ? 'pane-'. ctools_cleanstring($content->type, array('lower case' => TRUE)) : '';
+  $subtype_class = $content->subtype ? 'pane-'. ctools_cleanstring($content->subtype, array('lower case' => TRUE)) : '';
+
+  // Sometimes type and subtype are the same. Avoid redudant classes.
+  if ($type_class != $subtype_class) { 
+    $vars['classes'] .= " $type_class $subtype_class";
+  }
+  else { 
+    $vars['classes'] .= " $type_class";
+  }
+
+  // Add id and custom class if sent in.
+  if (!empty($content->content)) {
+    if (!empty($content->css_id)) {
+      $vars['id'] = ' id="' . $content->css_id . '"';
+    }
+    if (!empty($content->css_class)) {
+      $vars['classes'] .= ' ' . $content->css_class;
+    }
+  }
+
+  // administrative links, only if there is permission.
+  $vars['admin_links'] = '';
+  if (user_access('view pane admin links') && !empty($content->admin_links)) {
+    $vars['admin_links'] = theme('links', $content->admin_links);
+  }
+
+  $vars['title'] = !empty($content->title) ? $content->title : '';
+
+  $vars['feeds'] = !empty($content->feeds) ? implode(' ', $content->feeds) : '';
+  $vars['content'] = !empty($content->content) ? $content->content : '';
+
+  $vars['links'] = !empty($content->links) ? theme('links', $content->links) : '';
+  $vars['more'] = '';
+  if (!empty($content->more)) {
+    if (empty($content->more['title'])) {
+      $content->more['title'] = t('more');
+    }
+    $vars['more'] = l($content->more['title'], $content->more['href'], $content->more);;
+  }
+}