? quicktabs_ctools_export.patch
Index: quicktabs.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/quicktabs/quicktabs.install,v
retrieving revision 1.4.2.9
diff -u -p -r1.4.2.9 quicktabs.install
--- quicktabs.install	6 Apr 2009 18:17:54 -0000	1.4.2.9
+++ quicktabs.install	9 Sep 2009 17:34:06 -0000
@@ -7,14 +7,24 @@
 function quicktabs_schema() {
   $schema['quicktabs'] = array(
     'description' => 'The quicktabs table.',
+    // Optional CTools export.inc integration
+    'export' => array(
+      'key' => 'qtid',
+      'identifier' => 'quicktabs',
+      'default hook' => 'quicktabs_default_quicktabs',
+      'api' => array(
+        'owner' => 'quicktabs',
+        'api' => 'quicktabs',
+        'minimum_version' => 1,
+        'current_version' => 1,
+      ),
+    ),
     'fields' => array(
       'qtid' => array(
         'description' => 'The primary identifier for a qt block.',
-        'type' => 'serial',
-        'unsigned' => TRUE,
-        'size' => 'big',
+        'type' => 'varchar',
+        'length' => 255,
         'not null' => TRUE,
-        'disp-width' => '10',
       ),
       'ajax' => array(
         'description' => 'Whether this is an ajax views block.',
@@ -181,3 +191,42 @@ function quicktabs_update_6004() {
   }
   return $ret;
 }
+
+/**
+ * Migrate existing quicktabs over to new schema.
+ */
+function quicktabs_update_6005() {
+  $ret = array();
+
+  // Pull all existing quicktabs, and then delete existing quicktabs. We will reinsert.
+  $result = db_query("SELECT * FROM {quicktabs}");
+  $ret[] = update_sql("DELETE FROM {quicktabs}");
+
+  db_drop_field($ret, 'quicktabs', 'qtid');
+  $new_field = array(
+    'description' => 'The primary identifier for a qt block.',
+    'type' => 'varchar',
+    'length' => 255,
+    'not null' => TRUE,
+  );
+  db_add_field($ret, 'quicktabs', 'qtid', $new_field);
+  db_add_primary_key($ret, 'quicktabs', array('qtid'));
+
+  $used = array();
+  while ($row = db_fetch_array($result)) {
+    // Generate a machine-readable string
+    $qtid = strtolower(preg_replace('/[^a-zA-Z0-9_]+/', '_', $row['title']));
+    $i = 0;
+    while (in_array($i == 0 ? $qtid : "{$qtid}_{$i}", $used)) {
+      $i++;
+    }
+    $row['qtid'] = $i == 0 ? $qtid : "{$qtid}_{$i}";
+
+    $placeholders = implode(', ', array_keys($row));
+    $tokens = implode(', ', array_fill(0, count($row), "'%s'"));
+    db_query("INSERT INTO {quicktabs} ($placeholders) VALUES($tokens)", $row);
+
+    $ret[] = "Converted quicktab {$row['qtid']}.";
+  }
+  return $ret;
+}
Index: quicktabs.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/quicktabs/quicktabs.module,v
retrieving revision 1.10.2.65
diff -u -p -r1.10.2.65 quicktabs.module
--- quicktabs.module	18 Jul 2009 03:05:13 -0000	1.10.2.65
+++ quicktabs.module	9 Sep 2009 17:34:06 -0000
@@ -65,28 +65,44 @@ function quicktabs_menu() {
     'type' => MENU_LOCAL_TASK,
     'file' => 'includes/admin.inc',
   );
-  $items['admin/build/quicktabs/%quicktabs/edit'] = array(
-    'title' => 'Edit QT block',
+  $items['admin/build/quicktabs/%quicktabs'] = array(
+    'title' => 'Edit',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('quicktabs_form', 'edit', 3),
     'access arguments' => array('administer quicktabs'),
     'type' => MENU_CALLBACK,
     'file' => 'includes/admin.inc',
   );
+  $items['admin/build/quicktabs/%quicktabs/edit'] = array(
+    'title' => 'Edit',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('quicktabs_form', 'edit', 3),
+    'access arguments' => array('administer quicktabs'),
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'file' => 'includes/admin.inc',
+  );
   $items['admin/build/quicktabs/%quicktabs/delete'] = array(
-    'title' => 'Delete QT block',
+    'title' => 'Delete',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('quicktabs_block_delete', 3),
     'access arguments' => array('administer quicktabs'),
-    'type' => MENU_CALLBACK,
+    'type' => MENU_LOCAL_TASK,
     'file' => 'includes/admin.inc',
   );
   $items['admin/build/quicktabs/%quicktabs/clone'] = array(
-    'title' => 'Clone QT block',
+    'title' => 'Clone',
     'page callback' => 'quicktabs_clone',
     'page arguments' => array(3),
     'access arguments' => array('administer quicktabs'),
-    'type' => MENU_CALLBACK,
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'includes/admin.inc',
+  );
+  $items['admin/build/quicktabs/%quicktabs/export'] = array(
+    'title' => 'Export',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('quicktabs_export_form', 3),
+    'access arguments' => array('administer quicktabs'),
+    'type' => MENU_LOCAL_TASK,
     'file' => 'includes/admin.inc',
   );
 
@@ -142,10 +158,9 @@ function quicktabs_block($op = 'list', $
   switch ($op) {
     case 'list':
       $blocks = array();
-      $result = db_query('SELECT qtid, title FROM {quicktabs}');
-      while ($row = db_fetch_object($result)) {
-        $blocks[$row->qtid]['info'] = $row->title;
-        $blocks[$row->qtid]['cache'] = BLOCK_NO_CACHE;
+      foreach (quicktabs_get_all_quicktabs() as $qtid => $quicktabs) {
+        $blocks[$qtid]['info'] = $quicktabs['title'];
+        $blocks[$qtid]['cache'] = BLOCK_NO_CACHE;
       }
       return $blocks;
       break;
@@ -427,15 +442,54 @@ function quicktabs_quicktabs_tabstyles()
 }
 
 /**
+ * Load all from defaults and database quicktabs.
+ */
+function quicktabs_get_all_quicktabs() {
+  $quicktabs = array();
+
+  // Load quicktabs via ctools is present.
+  if (module_exists('ctools')) {
+    ctools_include('export');
+    $loaded = ctools_export_load_object('quicktabs', 'all');
+    foreach ($loaded as $qtid => $quicktab) {
+      $quicktabs[$qtid] = (array) $quicktab;
+    }
+    return $quicktabs;
+  }
+
+  // Otherwise, load from DB
+  $result = db_query('SELECT qtid, title FROM {quicktabs} ORDER BY title');
+  while ($quicktab = db_fetch_array($result)) {
+    $quicktabs[$quicktab['qtid']] = _quicktabs_unpack($quicktab);
+  }
+  return $quicktabs;
+}
+
+/**
  * Load the quicktabs data.
  */
 function quicktabs_load($qtid) {
+  // Load quicktabs via ctools is present.
+  if (module_exists('ctools')) {
+    ctools_include('export');
+    $defaults = ctools_export_load_object('quicktabs', 'names', array($qtid));
+    return isset($defaults[$qtid]) ? (array) $defaults[$qtid] : FALSE;
+  }
+
+  // Load quicktabs from the database.
   $quicktabs = db_fetch_array(db_query('SELECT qtid, title, tabs, ajax, style FROM {quicktabs} WHERE qtid = %d', $qtid));
-  if (!$quicktabs) {
-    return FALSE;
+  if ($quicktabs) {
+    return _quicktabs_unpack($quicktabs);
   }
+  return FALSE;
+}
 
+/**
+ * Unpack a quicktabs row array from the database.
+ */
+function _quicktabs_unpack($quicktabs) {
   $tabs = unserialize($quicktabs['tabs']);
+  $weight = array();
   foreach ($tabs as $key => $tab) {
     $weight[$key] = $tab['weight'];
     if ($tab['type'] == 'qtabs' && $tab['qtid'] == $qtid) {
Index: includes/admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/quicktabs/includes/Attic/admin.inc,v
retrieving revision 1.1.2.46
diff -u -p -r1.1.2.46 admin.inc
--- includes/admin.inc	15 Jun 2009 21:48:54 -0000	1.1.2.46
+++ includes/admin.inc	9 Sep 2009 17:34:06 -0000
@@ -5,19 +5,49 @@
  * Page callback for quicktabs admin landing page.
  */
 function quicktabs_list() {
-  $result = db_query('SELECT qtid, title FROM {quicktabs} ORDER BY title');
   $header = array(
     array('data' => t('Quicktab')),
-    array('data' => t('Operations'), 'colspan' => 3),
+    array('data' => t('Storage')),
+    array('data' => t('Operations'), 'colspan' => module_exists('ctools') ? 4 : 3),
   );
   $rows = array();
-  while ($row = db_fetch_object($result)) {
-    $tablerow = array(
-      array('data' => $row->title),
-      array('data' => l('Edit', 'admin/build/quicktabs/'. $row->qtid .'/edit')),
-      array('data' => l('Clone', 'admin/build/quicktabs/'. $row->qtid .'/clone')),
-      array('data' => l('Delete', 'admin/build/quicktabs/'. $row->qtid .'/delete')),
-    );
+
+  foreach (quicktabs_get_all_quicktabs() as $quicktabs) {
+    if (module_exists('ctools')) {
+      // Determine storage
+      switch ($quicktabs['export_type']) {
+        case EXPORT_IN_DATABASE | EXPORT_IN_CODE:
+          $storage = t('Overridden');
+          $delete = l(t('Revert'), 'admin/build/quicktabs/'. $quicktabs['qtid'] .'/delete');
+          break;
+        case EXPORT_IN_DATABASE:
+          $storage = t('Normal');
+          $delete = l(t('Delete'), 'admin/build/quicktabs/'. $quicktabs['qtid'] .'/delete');
+          break;
+        case EXPORT_IN_CODE:
+          $storage = t('Default');
+          $delete = '';
+          break;
+      }
+
+      $tablerow = array(
+        array('data' => $quicktabs['title']),
+        array('data' => $storage),
+        array('data' => l(t('Edit'), 'admin/build/quicktabs/'. $quicktabs['qtid'] .'/edit')),
+        array('data' => l(t('Export'), 'admin/build/quicktabs/'. $quicktabs['qtid'] .'/export')),
+        array('data' => l(t('Clone'), 'admin/build/quicktabs/'. $quicktabs['qtid'] .'/clone')),
+        array('data' => $delete),
+      );
+    }
+    else {
+      $tablerow = array(
+        array('data' => $quicktabs['title']),
+        array('data' => t('Normal')),
+        array('data' => l(t('Edit'), 'admin/build/quicktabs/'. $quicktabs['qtid'] .'/edit')),
+        array('data' => l(t('Clone'), 'admin/build/quicktabs/'. $quicktabs['qtid'] .'/clone')),
+        array('data' => l(t('Delete'), 'admin/build/quicktabs/'. $quicktabs['qtid'] .'/delete')),
+      );
+    }
     $rows[] = $tablerow;
   }
   if (!empty($rows)) {
@@ -44,12 +74,6 @@ function quicktabs_clone($quicktabs) {
 function quicktabs_form($form_state, $formtype, $quicktabs = array()) {
   $form = _qt_admin_main_form($form_state, $quicktabs);
 
-  if ($formtype == 'edit') {
-    $form['qtid'] = array(
-      '#type' => 'hidden',
-      '#value' => isset($quicktabs['qtid']) ? $quicktabs['qtid'] : 0,
-    );
-  }
   if (empty($quicktabs['tabs'])) {
     $quicktabs['tabs'] = array(
       0 => array(),
@@ -77,6 +101,27 @@ function _qt_admin_main_form($form_state
     $quicktabs = $form_state['quicktabs'] + (array)$quicktabs;
   }
 
+  if (empty($quicktabs['qtid'])) {
+    $form['qtid'] = array(
+      '#title' => t('Quicktabs ID'),
+      '#type' => 'textfield',
+      '#description' => t('A unique ID that will be used internally and in the CSS ID of your quicktabs block. must contain only lowercase letters, numbers, and underscores.'),
+      '#default_value' => '',
+      '#weight' => -8,
+      '#required' => TRUE,
+    );
+  }
+  else {
+    $form['qtid'] = array('#type' => 'value', '#value' => $quicktabs['qtid']);
+    $form['qtid_display'] = array(
+      '#title' => t('Quicktabs ID'),
+      '#type' => 'item',
+      '#value' => "<code>{$quicktabs['qtid']}</code>",
+      '#description' => t('This ID will be used internally by quicktabs and will be used in the CSS ID of your quicktabs block.'),
+      '#weight' => -8,
+    );
+  }
+
   $form['title'] = array(
     '#title' => t('Block title'),
     '#type' => 'textfield',
@@ -407,6 +452,12 @@ function quicktabs_ahah() {
 function quicktabs_form_validate($form, &$form_state) {
   // We don't want it to validate when we're just adding or removing tabs.
   if ($form_state['clicked_button']['#id'] == 'edit-submit-form') {
+    if (empty($form_state['values']['qtid'])) {
+      form_set_error('qtid', t('The quicktabs ID is required.'));
+    }
+    else if (!preg_match('!^[a-z0-9_]+$!', $form_state['values']['qtid'])) {
+      form_set_error('type', t('The quicktabs ID must contain only lowercase letters, numbers, and underscores.'));
+    }
     if (empty($form_state['values']['title'])) {
       form_set_error('title', t('Title is required for the quicktab block.'));
     }
@@ -437,8 +488,8 @@ function quicktabs_form_submit($form, &$
   // We don't want it to submit when we're just adding or removing tabs.
   if ($form_state['clicked_button']['#id'] == 'edit-submit-form') {
     $quicktabs = _quicktabs_convert_form_to_quicktabs($form_state);
-
-    if ($quicktabs['qtid'] > 0) {
+    $exists = quicktabs_load($quicktabs['qtid']);
+    if ($exists && empty($exists['in_code_only'])) {
       $ret = drupal_write_record('quicktabs', $quicktabs, 'qtid');
       if ($ret == SAVED_UPDATED) {
         drupal_set_message(t('The quicktab block has been updated.'));
@@ -467,13 +518,43 @@ function quicktabs_block_delete(&$form_s
  * Submit handler for quicktab block deletion.
  */
 function quicktabs_block_delete_submit($form, &$form_state) {
-  db_query('DELETE FROM {quicktabs} WHERE qtid = %d', $form_state['values']['qtid']);
+  db_query("DELETE FROM {quicktabs} WHERE qtid = '%s'", $form_state['values']['qtid']);
   drupal_set_message(t('The quicktab block %name has been removed.', array('%name' => $form_state['values']['qt_name'])));
   cache_clear_all();
   $form_state['redirect'] = 'admin/build/quicktabs';
 };
 
 /**
+ * Export form for quicktabs.
+ */
+function quicktabs_export_form(&$form_state, $quicktabs) {
+  ctools_include('export');
+
+  // Generate export code
+  $code = '$items = array();' ."\n";
+  $code .= ctools_export_object('quicktabs', (object) $quicktabs, '');
+  $code .= '$items["'. $quicktabs['qtid'] .'"] = $quicktabs;' ."\n";
+  $code .= 'return $items;';
+
+  // Create form
+  $form = array();
+  $form['export'] = array(
+    '#type' => 'textarea',
+    '#default_value' => $code,
+    '#rows' => substr_count($code, "\n") + 1,
+    '#resizable' => FALSE,
+    '#description' => t('Place this code in your module\'s implementation of <code>hook_quicktabs_default_quicktabs()</code> to provide it as a default quicktab.'),
+  );
+  $form['done'] = array(
+    '#type' => 'submit',
+    '#value' => t('Done'),
+  );
+  $form['#redirect'] = 'admin/build/quicktabs';
+
+  return $form;  
+}
+
+/**
  * Callback function for admin/settings/quicktabs. Display the settings form.
  */
 function quicktabs_settings() {
