Index: panels.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/panels/panels.module,v
retrieving revision 1.10.4.93
diff -u -r1.10.4.93 panels.module
--- panels.module	9 May 2008 18:20:51 -0000	1.10.4.93
+++ panels.module	10 May 2008 05:07:35 -0000
@@ -459,9 +459,22 @@
  * @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 array $layout_types
- *  An array of layout system names indicating which layouts are to be allowed for selection when
- *  the form is generated. If no value is provided, then all available layouts are allowed.
+ * @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. 
Index: includes/display_edit.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/panels/includes/Attic/display_edit.inc,v
retrieving revision 1.1.2.28
diff -u -r1.1.2.28 display_edit.inc
--- includes/display_edit.inc	9 May 2008 13:59:55 -0000	1.1.2.28
+++ includes/display_edit.inc	10 May 2008 05:07:35 -0000
@@ -243,11 +243,22 @@
  *
  * @see panels_edit_layout() for details on the various behaviors of this function.
  */
-function _panels_edit_layout($display, $finish, $destination) {
+function _panels_edit_layout($display, $finish, $destination, $allowed_layouts) {
+  panels_load_include('common');
+  if (is_string($allowed_layouts)) { // module_name has been provided; the data was saved by the api_save() method.
+    $allowed_layouts = variable_get($allowed_layouts . "_allowed_layouts", NULL);
+  }
+  if (is_null($allowed_layouts)) { // if no parameter was provided, or the variable_get failed
+    $allowed_layouts = new panels_allowed_layouts(); // simply creates a dummy version where all layouts are allowed.
+    $allowed_layouts->inclusive = TRUE;
+  }
+  // sanitize allowed layout listing; this is redundant if the $allowed_layouts param was null, but the data is cached anyway
+  $allowed_layouts->sync_with_available();
+  unset ($allowed_layouts->layout_settings['flexible'], $allowed_layouts->layout_settings['twocol_stacked'], $allowed_layouts->layout_settings['twocol_bricks'], $allowed_layouts->layout_settings['twocol']);
   // Break out the form pieces so we can return the new $display upon
   // successful submit.
   $form_id = 'panels_choose_layout';
-  $form = drupal_retrieve_form($form_id, $display, $finish, $destination);
+  $form = drupal_retrieve_form($form_id, $display, $finish, $destination, array_filter($allowed_layouts->layout_settings));
 
   if ($result = drupal_process_form($form_id, $form)) {
     // successful submit
@@ -262,9 +273,8 @@
  * 
  * @ingroup forms
  */
-function panels_choose_layout($display, $finish, $destination) {
-  $layouts = panels_get_layouts();
-
+function panels_choose_layout($display, $finish, $destination, $allowed_layouts) {
+  $layouts = array_intersect_key(panels_get_layouts(), $allowed_layouts);
   foreach ($layouts as $id => $layout) {
     $options[$id] = panels_print_layout_icon($id, $layout, check_plain($layout['title']));
   }
@@ -274,7 +284,7 @@
     '#type' => 'radios',
     '#title' => t('Choose layout'),
     '#options' => $options,
-    '#default_value' => $display->layout,
+    '#default_value' => in_array($display->layout, array_keys($layouts)) ? $display->layout : NULL, 
   );
 
   $form['clearer'] = array(
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/panels/includes/Attic/common.inc,v
retrieving revision 1.1.2.21
diff -u -r1.1.2.21 common.inc
--- includes/common.inc	31 Dec 2007 06:30:16 -0000	1.1.2.21
+++ includes/common.inc	10 May 2008 05:07:35 -0000
@@ -3,12 +3,167 @@
 
 /**
  * @file
- * Functions used by more than one panel implementation module.
+ * Functions used by more than one panels client module.
 */
 
 /**
+ * Class definition for the allowed layouts governing structure.
+ * 
+ * @ingroup mainapi
+ * 
+ * This class is designed to handle panels allowed layouts data from start to finish.
+ * To call the settings form, instantiate a new object of this class (or, if your client 
+ * module's needs are heavy-duty, extend this class and instantiate your subclass), assign
+ * values to any desired members, and call $this->set_allowed():
+ * 
+ *    $obj = new panels_allowed_layouts();
+ *    $obj->inclusive = TRUE;
+ *    $obj->module_name = 'client_module';
+ *    $result = $obj->set_allowed();
+ * 
+ * Until the $finish/$destination system in use by analogous API functions is improved and put
+ * into use here, the easiest way to distinguish the phase of form processing you're in (rendering
+ * vs. completion/submission) is to sniff $_POST. Follow the above lines with:
+ * 
+ *    if (!isset($_POST['op']) {
+ *      return $result;
+ *    }
+ * 
+ * if $_POST['op'] isn't set, then it means no form action has been taken, and we should simply
+ * return $result, as it's already been passed through drupal_render_form() and is ready to be
+ * passed through the theme layer for outputting. 
+ * 
+ * However, if $_POST['op'] is set, then some form processing action is being taken and we should 
+ * react. It's really up to your client module how you handle the rest; $this->save() (or
+ * $this->api_save(), if that's the route you're going) will have already been called, so if those
+ * methods handle your save routine, then all there is left to do is handle redirects, if you want.
+ * The allowed layouts form currently never redirects, so it's up to you to control where the user
+ * ends up next. 
+ * 
+ * 
+ * Note that when unserializing saved tokens of this class, you must
+ * run panels_load_include('common') before unserializing in order to ensure
+ * that the object is properly loaded.
+ * 
+ * Client modules extending this class should implement a save() method and use it for
+ * their custom data storage routine. You'll need to rewrite other class methods if
+ * you choose to go another route.
+ * 
+ */
+class panels_allowed_layouts {
+  /**
+   * Indicates whether $this treats newly-added layouts inclusively 
+   * (if TRUE, the default: newly added layouts ARE available) or exclusively
+   * (if FALSE: newly added layouts are NOT available until explicitly added). 
+   *
+   * @var bool
+   */
+  var $inclusive = TRUE;
+  
+  /**
+   * Optional member. If provided, the Panels API will generate a drupal variable using
+   * variable_set($module_name . 'allowed_layouts', serialize($this)), thereby handling the
+   * storage of this object entirely within the Panels API. This object will be
+   * called and rebuilt by panels_edit_layout() if the same $module_name string is passed in
+   * for the $allowed_types parameter. @see panels_edit_layout()
+   * 
+   * This is primarily intended for convenience - client modules doing heavy-duty implementations
+   * of the Panels API will probably want to create their own storage method.
+   *
+   * @var string
+   */
+  var $module_name = NULL;
+  
+  /**
+   * An associative array of all available layouts, keyed by layout name (as defined
+   * in the corresponding layout plugin definition), with value = 1 if the layout is
+   * allowed, and value = 0 if the layout is not allowed.
+   * 
+   * Calling array_filter($this->layout_settings) will return an associative array 
+   * containing only the allowed layouts, and wrapping that in array_keys() will
+   * return an indexed version of that array.
+   * 
+   * @var array
+   */
+  var $layout_settings = array();
+
+  /**
+   * Constructor function; loads the $layout_settings array with values according
+   *
+   * @param bool $start_allowed
+   *  $start_allowed determines whether all available layouts will be marked
+   *  as allowed or not allowed on the initial call to $this->set_allowed
+   * 
+   */
+  function panels_allowed_layouts($start_allowed = TRUE) {
+    foreach (array_keys(panels_get_layouts()) as $layout_name) { // TODO would be nice if there was a way to just fetch the names easily
+      $this->layout_settings[$layout_name] = $start_allowed ? 1 : 0;
+    }
+  }
+    
+  /**
+   * Manage panels_common_set_allowed_layouts(), the FAPI code for selecting allowed layouts.
+   * 
+   * MAKE SURE to set $this->inclusive before calling this method. If you want the panels API
+   * to handle saving these allowed layout settings, $this->module_name must also be set. 
+   * 
+   * @return mixed $result
+   *  - On the first passthrough when the form is being rendered, $result is this form's structured 
+   *    HTML, ready to be pushed to the screen with a call to theme('page', ...).
+   *  - A successful second passthrough indicates a successful submit, and $result === $this->layout_settings. 
+   *    Returning it is simply for convenience.
+   */
+  function set_allowed() {
+    $this->sync_with_available();
+    $form_id = 'panels_common_set_allowed_layouts';
+    $form = drupal_retrieve_form($form_id, $this, array_keys(array_filter($this->layout_settings)));
+  
+    if ($result = drupal_process_form($form_id, $form)) {
+      // successful submit
+      return $result;
+    }
+    $result = drupal_render_form($form_id, $form);
+    return $result;
+  }
+  
+  /**
+   * Checks for newly-added layouts and deleted layouts. If any are found, updates $this->layout_settings; 
+   * new additions are made according to $this->inclusive, while deletions are unset(). 
+   *
+   */
+  function sync_with_available() {
+    $update = FALSE;
+    $layouts = array_keys(panels_get_layouts());
+    foreach (array_diff($layouts, array_keys($this->layout_settings)) as $new_layout) {
+      $this->layout_settings[$new_layout] = $this->inclusive ? 1 : 0;
+      $update = TRUE;
+    }
+    foreach (array_diff(array_keys($this->layout_settings), $layouts) as $deleted_layout) {
+      unset($this->layout_settings[$deleted_layout]);
+      $update = TRUE;
+    }
+    if ($update) {
+      method_exists($this, 'save') ? $this->save() : $this->api_save();
+    }
+  }
+  
+  /**
+   * Use $this->module_name to generate a variable for variable_set, in which 
+   * a serialized version of $this will be stored.
+   * 
+   * Does nothing if $this->module_name is not set. 
+   *
+   */
+  function api_save() {
+    if (!is_null($this->module_name)) {
+      variable_set($this->module_name . "_allowed_layouts", drupal_clone($this));
+    }
+  }
+}
+
+/**
  * A common settings page for Panels modules, because this code is relevant to
- * any modules that don't already ahve special requirements.
+ * any modules that don't already have special requirements.
  */
 function panels_common_settings($module_name = 'panels_common') {
   panels_load_include('plugins');
@@ -126,6 +281,74 @@
 }
 
 /**
+ * The FAPI code for generating an 'allowed layouts' selection form.
+ * 
+ * NOTE: Because the Panels API does not guarantee a particular method of storing the data on layouts,
+ * it is not_possible for the Panels API to implement any checks that determine whether reductions in
+ * the set of allowed layouts conflict with pre-existing layout selections. $displays in that category 
+ * will continue to function with their current layout as normal until the user/owner/admin attempts
+ * to change layouts on that display, at which point they will have to select from the new set of 
+ * allowed layouts. If this is not the desired behavior for your client module, it's up to you to
+ * write a validation routine that determines what should be done with conflicting layouts.
+ * 
+ * Remember that changing layouts where panes have already been created can result in data loss;
+ * consult panels_change_layout() to see how the Panels API handles that process. Running 
+ * drupal_execute('panels_change_layout', ...) is one possible starting point. 
+ * 
+ * @ingroup forms
+ * 
+ * @param array $allowed_layouts
+ *  The set of allowed layouts that should be used as the default values
+ *  for this form. If none is provided, then by default no layouts will be restricted.
+
+  * @param bool $inclusive
+ *  Specifies whether the list of allowed layouts should be inclusive (a list of the layouts
+ *  that ARE allowed) or exclusive (a list of the layouts that are NOT allowed). Defaults to
+ *  exclusive, which is more permissive but less of an administrative hassle if/when you
+ *  add new layouts. Note that this parameter will be derived from $allowed_layouts if a value
+ *  is passed in.
+ */ // TODO need to add something that handles $finish & $destination-type stuff. 
+function panels_common_set_allowed_layouts($allowed_layouts, $defaults) {
+  $layouts = panels_get_layouts();
+  foreach ($layouts as $id => $layout) {
+    $options[$id] = panels_print_layout_icon($id, $layout, check_plain($layout['title']));
+  }
+  
+  $form['variables'] = array('#type' => 'value', '#value' => array($allowed_layouts));
+
+  drupal_add_js(panels_get_path('js/layout.js'));
+  $form['layouts'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Select allowed layouts'),
+    '#options' => $options,
+    '#description' => t('Check the boxes for all layouts you want to allow. You must allow at least one layout.'),
+    '#default_value' => $defaults,
+  );
+  
+  $form['clearer'] = array('#value' => '<div class="clear-block"></div>');
+  $form['#redirect'] = FALSE;
+  $form['submit']['#value'] = t('Save');
+  
+  $form['#token'] = FALSE;
+  return $form;
+}
+
+function panels_common_set_allowed_layouts_validate($form_id, $form_values, $form) {
+  if (empty($form_values['layouts'])) {
+    form_set_error('layouts', 'You must choose at least one layout to allow.');
+  }
+}
+  
+function panels_common_set_allowed_layouts_submit($form_id, $form_values) {
+  list($allowed_layouts) = $form_values['variables'];
+  if ($allowed_layouts->layout_settings != $form_values['variables']) {
+    $allowed_layouts->layout_settings = $form_values['variables'];
+    method_exists($allowed_layouts, 'save') ? $allowed_layouts->save() : $allowed_layouts->api_save();
+  }
+  return $allowed_layouts->layout_settings;
+}
+
+/**
  * The layout information fieldset displayed at admin/edit/panel-%implementation%/add/%layout%.
  */
 function panels_common_get_layout_information($panel_implementation, $contexts = array()) {

