? tw_exportable.patch
Index: tw.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/tw/tw.module,v
retrieving revision 1.1.2.31
diff -u -p -r1.1.2.31 tw.module
--- tw.module	3 May 2009 18:58:21 -0000	1.1.2.31
+++ tw.module	14 May 2009 17:08:39 -0000
@@ -464,6 +464,13 @@ function tw_menu() {
     'type' => MENU_CALLBACK,
     'file' => 'tw_pages.inc',
   );
+  $items['admin/content/tw/export'] = array(
+    'title' => 'Code for hook_views_data()',
+    'page callback' => 'tw_export',
+    'access arguments' => array(TW_ACCESS),
+    'type' => MENU_CALLBACK,
+    'file' => 'tw_pages.inc',
+  );
   return $items;
 }
 
Index: tw.views.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/tw/tw.views.inc,v
retrieving revision 1.1.2.13
diff -u -p -r1.1.2.13 tw.views.inc
--- tw.views.inc	3 May 2009 18:51:00 -0000	1.1.2.13
+++ tw.views.inc	14 May 2009 17:08:39 -0000
@@ -6,140 +6,15 @@
  * Views hooks
  */
 
-/**
- * Implementation of hook_views_data()
- */
-function tw_views_data() {
-  $tables = array();
-  
-  // Create table definitions for each import table
-  $sql = "SELECT * FROM {tw_tables}";
-  $tblresult = db_query($sql);
-  while ($tblrow = db_fetch_object($tblresult)) {
-    // Table Wizard stores the true DB table name - Drupal knows the name without
-    // the table prefix (if there is one). We'll use the true name for display
-    $tablename = $tblrow->tablename;
-    $dbconnection = $tblrow->dbconnection;
-    if ($dbconnection == 'default') {
-      $rawtablename = schema_unprefix_table($tablename);
-      $disptablename = $tablename;
-    } 
-    else {
-      $rawtablename = $tablename;
-      $disptablename = $dbconnection . '.' . $tablename;
-    }
-    $twtid = $tblrow->twtid;
-    $table = array();
-    // Add each viewable column to the table definition
-    // Note that we include the primary key column even if it's empty - this means
-    // the table is totally empty, and we need to have at least one column present
-    // to prevent errors in the view
-    $sql = "SELECT twcid, colname, primarykey, secure, coltype
-            FROM {tw_columns} 
-            WHERE twtid=%d AND ignorecol=0
-            ORDER BY weight";
-    $colresult = db_query($sql, $twtid);
-    $pk = array();
-    while ($colrow = db_fetch_object($colresult)) {
-      $colname = $colrow->colname;
-      $table[$colname] = array(
-        'title' => $colname,
-        'help' => $colname,
-        // TODO: Truncate text at 80 characters.
-        // TODO: Better yet, do some jQuery magic to expand to the full text
-        'field' => array(
-          'handler' => 'views_handler_field',
-          'click sortable' => TRUE,
-        ),
-        'filter' => array(
-          'handler' => _tw_views_handler_type($colrow->coltype),
-          'allow empty' => TRUE,
-        ),
-        // TODO: Add support for arguments
-        //'argument'
-        'sort' => array(
-          'handler' => 'views_handler_sort'
-        ),
-      );
-      if ($colrow->primarykey) {
-        $pk[] = $colname;
-      }
-    }
-    // Any table with a single primary key column can be a base table
-    if (count($pk) == 1) {
-      $table['table'] = array(
-        'group' => t($disptablename),
-        'base' => array(
-          'field' => $pk[0],
-          'title' => t('Database table @tablename', array('@tablename' => $disptablename)),
-          'help' => t('Table managed by the Table Wizard'),
-          'weight' => 10,
-          'database' => $dbconnection,
-        ),
-      );
-    }
-    $tables[$rawtablename] = $table;
-  }
-
-  // Now that all tables are present, fill in relationships defined by foreign keys
-  $sql = "SELECT twt1.tablename tbl1, twc1.colname col1, twt2.tablename tbl2, twc2.colname col2
-          FROM {tw_relationships} twr
-          INNER JOIN {tw_columns} twc1 ON twr.leftcol=twc1.twcid
-          INNER JOIN {tw_tables} twt1 ON twc1.twtid=twt1.twtid
-          INNER JOIN {tw_columns} twc2 ON twr.rightcol=twc2.twcid
-          INNER JOIN {tw_tables} twt2 ON twc2.twtid=twt2.twtid
-          ORDER BY tbl1, col1, tbl2, col2";
-  $result = db_query($sql);
-
-  // To allow multiple joins from one tbl/col, must use an alias and 
-  // 'relationship field' for the left side
-  $i = 0;
-  while ($row = db_fetch_array($result)) {
-    extract($row);
-    $rawtbl1 = schema_unprefix_table($tbl1);
-    $rawtbl2 = schema_unprefix_table($tbl2);
-    if (!isset($tables[$rawtbl1][$col1]['relationship'])) {
-      $tables[$rawtbl1][$col1]['title'] = t("$col1 (joins to $tbl2)");
-      $tables[$rawtbl1][$col1]['relationship'] = array(
-        'base' => $rawtbl2,
-        'base field' => $col2,
-        'label' => t("Join $tbl1 to $tbl2"),
-      );
-    } 
-    else {
-      $i++;
-      $mungedcol = $col1 . '_' . $i;
-      $tables[$rawtbl1][$mungedcol] = $tables[$rawtbl1][$col1];
-      $tables[$rawtbl1][$mungedcol]['title'] = t("$col1 (joins to $tbl2)");
-      $tables[$rawtbl1][$mungedcol]['real field'] = $col1;
-      $tables[$rawtbl1][$mungedcol]['relationship'] = array(
-        'base' => $rawtbl2,
-        'base field' => $col2,
-        'relationship field' => $col1,
-        'label' => t("Join $tbl1 to $tbl2"),
-      );
-    }
-  }
-  return $tables;
-}
+require_once drupal_get_path('module', 'tw') . '/tw_tablebuild.inc';
 
 /**
- * Return a views handler based on the column type.
+ * Implementation of hook_views_data().
+ *
+ * Utilizes shared table building functions to generate the full views data
+ * array.
  */
-function _tw_views_handler_type($sqltype) {
-  preg_match('/^[a-zA-Z]+/', $sqltype, $matches);
-  $type = tw_column_type($matches[0]);
-  switch ($type) {
-    case 'numeric':
-      $filter = 'views_handler_filter_numeric';
-      break;
-    case 'datetime':
-      $filter = 'views_handler_filter_date';
-      break;
-    default:
-      $filter = 'views_handler_filter_string';
-      break;
-  }
-
-  return $filter;
+function tw_views_data() {
+  $tables = _tw_generate_views_table_data();
+  return _tw_generate_views_columnar_data($tables);
 }
Index: tw_pages.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/tw/tw_pages.inc,v
retrieving revision 1.1.2.32
diff -u -p -r1.1.2.32 tw_pages.inc
--- tw_pages.inc	3 May 2009 15:13:10 -0000	1.1.2.32
+++ tw_pages.inc	14 May 2009 17:08:40 -0000
@@ -48,10 +48,10 @@ function tw_sources_form($form_state) {
       '#type' => 'value',
       '#value' => array(
         array('data' => '', 'class' => 'select-all'),
-        array('data' => ''),
         $connheader,
         $nameheader,
         array('data' => t('Row count')),
+        array('data' => t('Table Analysis')),
       ),
     );
   } 
@@ -59,10 +59,10 @@ function tw_sources_form($form_state) {
     $form['header'] = array(
       '#type' => 'value',
       '#value' => array(
-        array('data' => t('Delete'), 'class' => 'select-all'),
-        array('data' => ''),
+        array('data' => t('Select'), 'class' => 'select-all'),
         $nameheader,
         array('data' => t('Row count')),
+        array('data' => t('Table Analysis')),
       ),
     );
   }
@@ -86,7 +86,7 @@ function tw_sources_form($form_state) {
       $last_connection = 'default';
     }
     $form['analyze'][$row->twtid] = array('#value' => 
-      l(t('Analysis'), 'admin/content/tw/analyze/' . $row->twtid, array('html' => TRUE)));
+      l(t('analyze'), 'admin/content/tw/analyze/' . $row->twtid, array('html' => TRUE)));
     if ($extconns >= 1) {
       $form['dbconnection'][$row->twtid] = array('#value' => $row->dbconnection);
     }
@@ -120,6 +120,10 @@ function tw_sources_form($form_state) {
     '#type' => 'submit',
     '#value' => t('Remove selected tables'),
   );
+  $form['export'] = array(
+    '#type' => 'submit',
+    '#value' => t('Export selected tables'),
+  );
 
   // Keep each type's fields apart
   $form['#tree'] = TRUE;
@@ -142,12 +146,12 @@ function theme_tw_sources($form) {
       // Don't show the table id
       $null = drupal_render($form['twtid']);
       $row[] = drupal_render($form['checks'][$twtid]);
-      $row[] = drupal_render($form['analyze'][$twtid]);
       if (isset($form['dbconnection'][$twtid])) {
         $row[] = drupal_render($form['dbconnection'][$twtid]);
       }
       $row[] = drupal_render($form['tablename'][$twtid]);
       $row[] = drupal_render($form['rowcount'][$twtid]);
+      $row[] = drupal_render($form['analyze'][$twtid]);
       $rows[] = $row;
     }
   }
@@ -158,6 +162,7 @@ function theme_tw_sources($form) {
   }
   $output .= theme('table', $header, $rows);
   $output .= drupal_render($form['delete']);
+  $output .= drupal_render($form['export']);
   $output .= drupal_render($form);
   return $output;
 }
@@ -177,7 +182,11 @@ function tw_sources_form_submit($form, &
           array('%tablename' => $row->tablename, '%connection' => $row->dbconnection)));
       }
     }
-  } 
+  }
+  elseif ($type == 'export') {
+    $twtids = array_values(array_filter($form_state['values']['checks']));
+    $form_state['redirect'] = 'admin/content/tw/export/' . implode(',', $twtids);
+  }
   else {
     // Submit hooks return arrays of tablenames they're bringing in
     $values = $form_state['values'][$type];
@@ -800,3 +809,105 @@ function tw_relationships_form_submit($f
   }
   return;
 }
+
+function tw_export($twtids = array()) {
+  require_once drupal_get_path('module', 'tw') . '/tw_tablebuild.inc';
+  if (empty($twtids)) {
+    return drupal_access_denied();
+  }
+  $twtids = explode(',', check_plain($twtids));
+  foreach ($twtids as $twtid) {
+    if (!is_numeric($twtid)) {
+      // Crude but effecive security measure
+      return drupal_not_found();
+    }
+  }
+  return drupal_get_form('tw_export_form', $twtids);
+}
+
+function tw_export_form(&$form_state, $twtids) {
+  $tables = _tw_generate_views_table_data($twtids, TRUE);
+  $tables = _tw_generate_views_columnar_data($tables, $twtids, TRUE);
+  $code = tw_data_export($tables);
+  $lines = substr_count($code, "\n");
+  $form['code'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Table Wizard generated code for hook_views_data() implementation'),
+    '#default_value' => $code,
+    '#rows' => $lines + 1,
+  );
+  return $form;
+}
+
+function tw_data_export($tables) {
+  $output = '  $data = array();' . "\n";
+  foreach ($tables as $table => $data) {
+    $output .= '  $data[\'' . $table . '\'] = ';
+    $output .= _tw_data_export($data, '  ');
+    $output .= ";\n";
+  }
+  $output .= '  return $data;';
+  return $output;
+}
+
+function _tw_data_export($var, $prefix = '') {
+  if (is_array($var)) {
+    if (empty($var)) {
+      $output = 'array()';
+    }
+    else {
+      $output = "array(\n";
+      foreach ($var as $key => $value) {
+        $output .= "  '$key' => " . _tw_data_export($value, '  ') . ",\n";
+      }
+      $output .= ')';
+    }
+  }
+  else if (is_bool($var)) {
+    $output = $var ? 'TRUE' : 'FALSE';
+  }
+  else {
+    $output = var_export($var, TRUE);
+  }
+
+  if ($prefix) {
+    $output = str_replace("\n", "\n$prefix", $output);
+  }
+
+  return $output;
+}
+
+/**
+ * Wraps strings that would normally be immediately translated via t() with the
+ * t() call as a string, in a format appropriate for export.
+ * 
+ * @param string $string
+ * @param array $args
+ * @return string
+ */
+function _tw_t_wrap($string, $args = array()) {
+  if (empty($args)) {
+    return 't(' . $string . ')';
+  }
+  else {
+    // Transform arguments before inserting them.
+    foreach ($args as $key => $value) {
+      switch ($key[0]) {
+        case '@':
+          // Escaped only.
+          $args[$key] = check_plain($value);
+          break;
+
+        case '%':
+        default:
+          // Escaped and placeholder.
+          $args[$key] = theme('placeholder', $value);
+          break;
+
+        case '!':
+          // Pass-through.
+      }
+    }
+    return 't(' . strtr($string, $args) . ')';
+  }
+}
Index: tw_tablebuild.inc
===================================================================
RCS file: tw_tablebuild.inc
diff -N tw_tablebuild.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tw_tablebuild.inc	14 May 2009 17:08:40 -0000
@@ -0,0 +1,161 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Shared functions used by tw's views hook implementations and exporter to
+ * generate metadata for hook_views_data().
+ */
+
+/**
+ * Return a views handler based on the column type.
+ */
+function _tw_views_handler_type($sqltype) {
+  preg_match('/^[a-zA-Z]+/', $sqltype, $matches);
+  $type = tw_column_type($matches[0]);
+  switch ($type) {
+    case 'numeric':
+      $filter = 'views_handler_filter_numeric';
+      break;
+    case 'datetime':
+      $filter = 'views_handler_filter_date';
+      break;
+    default:
+      $filter = 'views_handler_filter_string';
+      break;
+  }
+
+  return $filter;
+}
+
+function _tw_generate_views_table_data($twtids = NULL, $export = FALSE) {
+  $t = $export ? '_tw_t_wrap' : 't';
+  $sql = "SELECT * FROM {tw_tables}";
+  if (!is_null($twtids)) {
+    // If a specific subset of tables are requested, add the appropriate
+    // constraining WHERE clause
+    $sql .= ' WHERE twtid IN (' . db_placeholders($twtids) . ')';
+    $tblresult = db_query($sql, $twtids);
+  }
+  else {
+    $tblresult = db_query($sql);
+  }
+
+  // Use slightly different helptext when generating for export vs. tw data hook
+  $help = $export ?
+    _tw_t_wrap('Views data hook implementation generated by the Table Wizard') :
+    t('Table managed by the Table Wizard');
+
+  $tables = array();
+  while ($tblrow = db_fetch_object($tblresult)) {
+    // Table Wizard stores the true DB table name - Drupal knows the name without
+    // the table prefix (if there is one). We'll use the true name for display
+    $tablename = $tblrow->tablename;
+    $dbconnection = $tblrow->dbconnection;
+    if ($dbconnection == 'default') {
+      $rawtablename = schema_unprefix_table($tablename);
+      $disptablename = $tablename;
+    }
+    else {
+      $rawtablename = $tablename;
+      $disptablename = $dbconnection . '.' . $tablename;
+    }
+    $twtid = $tblrow->twtid;
+    $table = array();
+    // Add each viewable column to the table definition
+    // Note that we include the primary key column even if it's empty - this means
+    // the table is totally empty, and we need to have at least one column present
+    // to prevent errors in the view
+    $sql = "SELECT twcid, colname, primarykey, secure, coltype
+            FROM {tw_columns}
+            WHERE twtid=%d AND ignorecol=0
+            ORDER BY weight";
+    $colresult = db_query($sql, $twtid);
+    $pk = array();
+    while ($colrow = db_fetch_object($colresult)) {
+      $colname = $colrow->colname;
+      $table[$colname] = array(
+        'title' => $export ? _tw_t_wrap($colname) : $colname,
+        'help' => $export ? _tw_t_wrap($colname) : $colname,
+        // TODO: Truncate text at 80 characters.
+        // TODO: Better yet, do some jQuery magic to expand to the full text
+        'field' => array(
+          'handler' => 'views_handler_field',
+          'click sortable' => TRUE,
+        ),
+        'filter' => array(
+          'handler' => _tw_views_handler_type($colrow->coltype),
+          'allow empty' => TRUE,
+        ),
+        // TODO: Add support for arguments
+        //'argument'
+        'sort' => array(
+          'handler' => 'views_handler_sort'
+        ),
+      );
+      if ($colrow->primarykey) {
+        $pk[] = $colname;
+      }
+    }
+    // Any table with a single primary key column can be a base table
+    if (count($pk) == 1) {
+      $table['table'] = array(
+        'group' => $export ? _tw_t_wrap($disptablename) : $disptablename,
+        'base' => array(
+          'field' => $pk[0],
+          'title' => $t('Database table @tablename', array('@tablename' => $disptablename)),
+          'help' => $help,
+          'weight' => 10,
+          'database' => $dbconnection,
+        ),
+      );
+    }
+    $tables[$rawtablename] = $table;
+  }
+  return $tables;
+}
+
+function _tw_generate_views_columnar_data($tables, $twtids = NULL, $export = FALSE) {
+  $t = $export ? '_tw_t_wrap' : 't';
+  $where = is_null($twtids) ? '' : 'WHERE twt1.twtid IN (' . db_placeholders($twtids) . ') ';
+  // Now that all tables are present, fill in relationships defined by foreign keys
+  $sql = "SELECT twt1.tablename tbl1, twc1.colname col1, twt2.tablename tbl2, twc2.colname col2
+          FROM {tw_relationships} twr
+          INNER JOIN {tw_columns} twc1 ON twr.leftcol=twc1.twcid
+          INNER JOIN {tw_tables} twt1 ON twc1.twtid=twt1.twtid
+          INNER JOIN {tw_columns} twc2 ON twr.rightcol=twc2.twcid
+          INNER JOIN {tw_tables} twt2 ON twc2.twtid=twt2.twtid
+          {$where}ORDER BY tbl1, col1, tbl2, col2";
+  $result = is_null($twtids) ? db_query($sql) : db_query($sql, $twtids);
+
+  // To allow multiple joins from one tbl/col, must use an alias and
+  // 'relationship field' for the left side
+  $i = 0;
+  while ($row = db_fetch_array($result)) {
+    extract($row);
+    $rawtbl1 = schema_unprefix_table($tbl1);
+    $rawtbl2 = schema_unprefix_table($tbl2);
+    if (!isset($tables[$rawtbl1][$col1]['relationship'])) {
+      $tables[$rawtbl1][$col1]['title'] = $t("@column (joins to @table)", array('@column' => $col1, '@table' => $tbl2));
+      $tables[$rawtbl1][$col1]['relationship'] = array(
+        'base' => $rawtbl2,
+        'base field' => $col2,
+        'label' => $t("Join @table1 to @table2", array('@table1' => $tbl1, '@table2' => $tbl2)),
+      );
+    }
+    else {
+      $i++;
+      $mungedcol = $col1 . '_' . $i;
+      $tables[$rawtbl1][$mungedcol] = $tables[$rawtbl1][$col1];
+      $tables[$rawtbl1][$mungedcol]['title'] = $t("@column (joins to @table)", array('@column' => $col1, '@table' => $tbl2));
+      $tables[$rawtbl1][$mungedcol]['real field'] = $col1;
+      $tables[$rawtbl1][$mungedcol]['relationship'] = array(
+        'base' => $rawtbl2,
+        'base field' => $col2,
+        'relationship field' => $col1,
+        'label' => $t("Join @table1 to @table2", array('@table1' => $tbl1, '@table2' => $tbl2)),
+      );
+    }
+  }
+  return $tables;
+}
