background image
HomeRecent PostsDrupalSearchTagsRSSContactAboutAccount
Eric.London's picture

In anticipation of my new iPhone 4 (and a larger hard drive), I decided to write a script to help choose which albums I should include. Through iTunes you can export your library as text, which can then be imported into MySQL, and SQL can be executed to show.. well, whatever your heart desires!

I started by exporting my entire music library, and chose Plain Text as the format. This iTunes functionality creates a tab separated list of columns and data.

I then created a new MySQL database and table (SEE: TABLE_NAME) to contain my iTunes song data. I also added an additional column as a primary key, called sql_id.

Next, I created a PHP script to parse the text file, dynamically add the columns to my table, and import all my song data. This code uses PEAR's DB library for the database access layer.

<?php
// define table name
define('TABLE_NAME', 'itunes');

// create database connection
require_once('DB.php');
$dsn = 'mysqli://db_user:db_password@db_host/database_name';
$DB =& DB::connect($dsn);
if (
DB::isError($DB)) {
  die(
$DB->getMessage());
}
$DB->setFetchMode(DB_FETCHMODE_ASSOC);

// load text file
$file = file_get_contents('Music.txt');

// explode on new line
$file = explode("\r", $file);

// get a list of existing columns
$sql = "show columns in " . TABLE_NAME;
$result = $DB->getAll($sql);
$existing_columns = array();
foreach (
$result as $key => $value) {
 
$existing_columns[] = $value['Field'];
}

// create a variable to contain sql column names
$sql_column_name = array();

// loop through each line in the file
foreach ($file as $key => $value) {

 
// explode on tab to get column list
 
$exploded = explode("\t", $value);

 
// check for first row, which contains column headers
 
if ($key == 0) {

   
// loop through column list and ensure they exist
   
foreach ($exploded as $new_table) {
   
     
// create a new column name without spaces
     
$new_table = str_replace(' ' , '_', $new_table);

     
// check if the new column should be added   
     
if (!in_array($new_table, $existing_columns)) {
          
       
// define sql to add new column
       
$sql = "alter table " . TABLE_NAME . " add column `$new_table` varchar(255) default null";
       
$result = $DB->query($sql);
       
       
// check for error
       
if (!$result) {
          echo
"<pre>" . print_r($result, true) . "</pre>";
          die;
        }
               
       
// define sql to add index
       
$sql = "alter table " . TABLE_NAME . " add index `index_$new_table` ($new_table)";
       
$result = $DB->query($sql);
       
       
// check for error
       
if (!$result) {
          echo
"<pre>" . print_r($result, true) . "</pre>";
          die;         
        }
     
      }
// end if
     
     
$sql_column_names[] = $new_table;
   
    }
// end foreach
     
 
} // end if ($key[0])
 
else {
   
   
// prepare values to insert
   
$insert_values = array();
    foreach (
$exploded as $k => $v) {
     
$insert_values[$k] = $DB->quoteSmart($v);
    }
   
   
// define SQL to insert data into table
   
$sql = "insert into " . TABLE_NAME . " (" . implode(',', $sql_column_names) . ") values (" . implode(',', $insert_values) . ")";
   
   
// execute sql
   
$result = $DB->query($sql);
   
   
// check for SQL error
   
if (!$result) {
      echo
"<pre>" . print_r($result, true) . "</pre>";
    }
 
  }

}
?>

Now, all my iTunes data is accessible by SQL, sweet. I decided to create a view showing my albums with an average ranking.

create view album_rankings as
select Artist, Album, Genre, avg(My_Rating) as album_rating, count(*) as countX
from itunes
where Album != 'misc'
group by Artist, Album
order by avg(My_Rating) desc, countX desc

And finally, I executed the following SQL to show my highest rated albums.

select *
from album_rankings
where countX > 3

Top Rated Albums

In this tutorial, I'll show you how you can create a table with re-sortable column headers and pagination from custom SQL. I strongly recommend you attempt to implement this functionality using the Views module first :)

<?php
/**
* Implements hook_menu()
*/
function helper_menu() {

 
$items = array();
 
 
$items['pager-test'] = array(
   
'title' => t('Pager Test'),
   
'description' => t('Pager Test'),
   
'page callback' => 'helper_page_callback_pager_test',
   
'access arguments' => array('access content'),
   
'type' => MENU_CALLBACK,
  );
 
  return
$items;

}

/**
* Implements page callback for pager-test url
*/
function helper_page_callback_pager_test() {

 
// define number of results per page
 
$number_results = 10;

 
// define a list of columns to select
 
$columns = array(
   
'nid' => t('ID'),
   
'type' => t('Type'),
   
'title' => t('Title'),
  );

 
// define sql to fetch results
 
$sql = "select %s from {node} where status = 1";

 
// define sql to fetch number of results
 
$sql_count = "select count(*) from {node} where status = 1";

 
// create resort header from column list
 
$header = array();
  foreach (
$columns as $key => $value) {
   
$header[] = array(
     
'data' => $value,
     
'field' => $key,
    );
  }
 
 
// add tablesort to SQL
 
$sql .= tablesort_sql($header);

 
// execute query
 
$resource = pager_query($sql, $number_results, 0, $sql_count, implode(', ', array_keys($columns)));

 
// fetch results
 
$results = array();
  while (
$row = db_fetch_array($resource)) {
   
$results[] = $row
  }

 
// define variable to contain page callback output
 
$output = "";
 
 
// create table html
 
$output .= theme('table', $header, $results);
 
 
// create pager html
 
$output .= theme('pager', NULL, $number_results, 0);

  return
$output;

}
?>

The above code results in the following table:

Pager Test

Eric.London's picture

Last year I wrote a quick code snippet to Prevent the user from creating more than one node of a certain type for Drupal 5. I received a comment request to update this code for Drupal 6.

<?php
function MYMODULE_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {

 
// define node type that a user will only be allowed to create one instance of
 
$singleNodeType = 'YOUR-NODE-TYPE';
 
 
// test for node/add/NODETYPE page
 
if ($node->type==$singleNodeType && $op=='prepare' && arg(0)=='node' && arg(1)=='add') {

   
// define sql to create node table
   
$sql = "select nid from {node} where type='%s' and uid='%d'";
   
   
// execute sql
   
$resource = db_query($sql, $singleNodeType, db_escape_string($GLOBALS['user']->uid));
   
$result = db_result($resource);

   
// test for result
   
if (!empty($result)) {
     
     
// set a message
     
drupal_set_message("Sorry, you are only allow to create one $singleNodeType.");
     
     
// redirect the user
     
drupal_goto('node/add');
     
    }
       
  }
 
}
?>

The above code tests if the user is on the node/add/NODETYPE page and if the user has already created an instance of the node type, sets a message and redirects them away from the node/add page.

Eric.London's picture

Here's a quick code snippet I just wrote to dump and compress (gzip) all mysql databases on a server into separate files using PHP and PEAR.

<?php
// include PEAR DB library
require_once('DB.php');

// define the DSN in an array
// NOTE: user must have access to all databases
$dsn = array(
 
'phptype' => 'mysql',
 
'username' => 'YOURUSER',
 
'password' => 'YOURPASSWORD',
 
'hostspec' => 'HOSTNAME', // localhost?
);

// instantiate a PEAR DB object
$DB =& DB::connect($dsn);

// check for an error
if (DB::isError($DB)) die($DB->getMessage());

// set the DB fetch mode to associative
$DB->setFetchMode(DB_FETCHMODE_ASSOC);

// define sql statement
$sql = "show databases";

// fetch sql result
$databases = $DB->getAll($sql);

// loop through results
foreach ($databases as $index => $result) {

 
// define the mysqldump command
 
$command = "mysqldump -u " . $dsn['username'] . " "
   
. "-p" . $dsn['password'] . " "
   
. "-h " . $dsn['hostspec'] . " "
   
. $result['Database'] . " | gzip > "
   
. $result['Database'] . ".sql.gz";

 
// execute command
 
`$command`;
   
}
?>

Eric.London's picture

Functionality that I regularly create in applications is the ability to take a SQL statement and return a table of results for reports, debugging, etc. Here's a helper function I created to implement this functionality. I purposely left off pagination, sortable columns, and other features to focus on how the Drupal theme_table() function operates. This helper function will use the column names from the SQL as the table column headers, so make sure they are properly formatted.

<?php
// first, I defined this function in my module:
function _MYMODULE_sql_to_table($sql) {
   
 
$html = "";
   
 
// execute sql
 
$resource = db_query($sql);
   
 
// fetch database results in an array
 
$results = array();
  while (
$row = db_fetch_array($resource)) $results[] = $row;
   
 
// ensure results exist
 
if (!count($results)) {
   
$html .= "Sorry, no results could be found.";
    return
$html;   
  }

 
// create an array to contain all table rows
 
$rows = array();
   
 
// get a list of column headers
 
$columnNames = array_keys($results[0]);
   
 
// loop through results and create table rows
 
foreach ($results as $key => $data) {

   
// create row data
   
$row = array();
   
   
// loop through column names
   
foreach ($columnNames as $c) {
     
$row[] = array(
       
'data' => $data[$c],
       
'class' => strtolower(str_replace(' ', '-', $c)),
      );
    }

   
// add row to rows array
   
$rows[] = $row;
       
  }
   
 
// loop through column names and create headers
 
$header = array();
  foreach (
$columnNames as $c) {
   
$header[] = array(
     
'data' => $c,
     
'class' => strtolower(str_replace(' ', '-', $c)),
    );
  }
   
 
// generate table html
 
$html .= theme('table', $header, $rows);
   
  return
$html;
   
}

// then you can call it in your code...
function _MYMODULE_some_page_callback() {
 
 
$html = "";

 
$sql = "select * from {node}";

 
$html .= _MYMODULE_sql_to_table($sql);

  return
$html;
}
?>

Syndicate content