background image
Welcome to Eric's Drupal Blog, a place for me to share my Drupal coding experiences. Please post your comments and suggestions and I'll respond at my earliest convenience. Please note, this website is privately operated and is not affiliated with Drupal.
Eric's picture

Drush is a great command line module for administering a Drupal site. It's core features are listed here. Check out the README.txt for usage and installation documentation.

Drush can be used to setup a base Drupal installation with a few quick commands, awesome. One of it's biggest time saving features is downloading all the contributed modules when setting up a new site. Here are a few commands I use to setup a new Drupal site based on feature sets. The following commands should be run in the root directory of your drupal installation.

# Download development modules and themes:
drush dl admin admin_menu coder devel devel_themer reroute_email simpletest views_bulk_operations --destination=sites/all/modules/contrib/ --uri=http://yourhostname.com

# Download common/essential modules:
drush dl cck views date wysiwyg pathauto token captcha recaptcha location emfield link jquery_ui webform htmlpurifier luceneapi vertical_tabs --destination=sites/all/modules/contrib/ --uri=http://yourhostname.com

# Download SEO modules:
drush dl google_analytics xmlsitemap globalredirect site_verify --destination=sites/all/modules/contrib/ --uri=http://yourhostname.com

# Download structural/building modules:
drush dl context features ctools panels --destination=sites/all/modules/contrib/ --uri=http://yourhostname.com

# Download image handling modules:
drush dl imageapi transliteration filefield mimedetect imagefield imagecache imagecache_actions --destination=sites/all/modules/contrib/ --uri=http://yourhostname.com

NOTE: the destination and uri flags on the drush command are optional, but I recommend using them. In recent Drupal development I stopped using sites/default in favor of sites/hostname.com, to ensure that if I ever decide to move the site into a multi-site configuration, I will not have overlapping sites/default/files directories.

You can even enable the downloaded modules from the command line by using the "drush en" command..

drush help en

Eric'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

Eric's picture

In this tutorial I'll show you how to upload an image using the Forms API, create a new node, and attach the image to the CCK (filefield/imagefield) field. I wrote this code to work with the modules I primarily use for image processing: cck, filefield, imageapi, imagecache, imagefield, mimedetect, and transliteration.

After I installed those modules, I created a new node type (admin/content/types/add) called "Image" and added a single imagefield field.

Image node fields

Next, I created a custom module with a hook_menu() implementation:

<?php
// NOTE: this variable is used through the code,
// so I thought it would be better to put it in a constant
define('IMAGE_UPLOAD_CONTAINER', 'image_upload');

/**
* Implements hook_menu()
*/
function helper_menu() {

 
// create a blank array of menu items
 
$items = array();
 
 
// define page callback for upload form
  // NOTE: you'll want to restrict permission better [see: access arguments]
 
$items['upload'] = array(
   
'title' => t('Upload'),
   
'description' => t('Upload'),
   
'page callback' => 'drupal_get_form',
   
'page arguments' => array('helper_page_callback_upload_form'),
   
'access arguments' => array('access content'),
   
'type' => MENU_CALLBACK,
  );
 
 
// return menu items
 
return $items;

}
?>

I defined the form function page callback:

<?php
/**
* Implements page callback for upload form
*/
function helper_page_callback_upload_form() {

 
// create an empty form array
 
$form = array();
 
 
// set the form encoding type
 
$form['#attributes']['enctype'] = "multipart/form-data";
 
 
// add a file upload file
 
$form[IMAGE_UPLOAD_CONTAINER] = array(
   
'#type' => 'file',
   
'#title' => t('Upload an image'),
  );
  
 
// add a submit button
 
$form['submit'] = array(
   
'#type' => 'submit',
   
'#value' => 'Submit',
  );
 
 
// return form array
 
return $form;

}
?>

This page callback function results in the following form:

Image node form

Then I added the form validation and submit handler functions:

<?php
/**
* Implements form validation handler
*/
function helper_page_callback_upload_form_validate($form, &$form_state) {

 
// if a file was uploaded, process it.
 
if (isset($_FILES['files']) && is_uploaded_file($_FILES['files']['tmp_name'][IMAGE_UPLOAD_CONTAINER])) {

   
// validate file extension
    // NOTE: you can ellaborate on this code and add additional validation
   
if ($_FILES['files']['type'][IMAGE_UPLOAD_CONTAINER] != 'image/jpeg') {
     
form_set_error(IMAGE_UPLOAD_CONTAINER, 'Invalid file extension.');
      return;
    }

   
// attempt to save the uploaded file
   
$file = file_save_upload(IMAGE_UPLOAD_CONTAINER, array(), file_directory_path());

   
// set error if file was not uploaded
   
if (!$file) {
     
form_set_error(IMAGE_UPLOAD_CONTAINER, 'Error uploading file.');
      return;
    }
      
   
// set files to form_state, to process when form is submitted
   
$form_state['storage'][IMAGE_UPLOAD_CONTAINER] = $file;
      
  }
  else {
   
// set error
   
form_set_error(IMAGE_UPLOAD_CONTAINER, 'Error uploading file.');
    return;  
  }

}

/**
* Implements form submit handler
*/
function helper_page_callback_upload_form_submit($form, &$form_state) {
 
 
// create new node object
 
$new_node = (object) array(
   
'type' => 'image',
   
'uid' => $GLOBALS['user']->uid,
   
'name' => $GLOBALS['user']->name,
   
'title' => t('YOUR NODE TITLE'),
   
'status' => 1,
   
'field_image' => array(
      (array)
$form_state['storage'][IMAGE_UPLOAD_CONTAINER],
    ),
  );
   
 
// save node
 
node_save($new_node);
 
 
// clear form storage, to allow form to submit
 
$form_state['storage'] = array();
 
 
// redirect user, set message, etc!

}
?>

After using the form to upload an image, the following node was created:

New image node

Eric's picture

In this tutorial, I'll show a way to pass PHP/Drupal variables to javascript using drupal_add_js(), and a way you can debug javascript variables using the FireBug console.

I'll start by creating a hook_menu() implementation to establish a page callback:

<?php
function helper_menu() {

 
$items = array();
 
 
$items['js-vars'] = array(
   
'title' => t('Javascript Variables'),
   
'description' => t('Javascript Variables'),
   
'page callback' => 'helper_page_callback_js_vars',
   
'access arguments' => array('access content'),
   
'type' => MENU_CALLBACK,   
  );
 
  return
$items;

}
?>

Next, I'll define the page callback:

<?php
function helper_page_callback_js_vars() {

 
// include module javascript file
 
drupal_add_js(drupal_get_path('module','helper') . '/js/helper.js');

 
// define variables you'd like to pass to the DOM
 
$js_vars = array(
   
'js_vars' => array(
     
'message' => t('Hello @username', array('@username' => $GLOBALS['user']->name)),
     
'an_array' => array(
       
'color' => t('red'),
       
'name' => t('Eric'),
      ),
    ),
  );
 
 
// pass variables to javascript
 
drupal_add_js($js_vars, 'setting');
 
 
// generate some page output
 
return "TEST";

}
?>

And here is the contents of the javascript include file I stuck in my module directory:

$(document).ready(function(){

  // debug variables directly in FireBug
  console.debug(Drupal.settings.js_vars);
 
  // popup mesage passed from Drupal
  alert(Drupal.settings.js_vars.message);
 
});

When viewing this page in a browser, you'll see the following. The javascript popup window is using variables passed directly from a Drupal/PHP array:
javascript popup

The console.debug() javascript method was used to send data directly to the FireBug console. If you open FireBug, you'll see the following:
FireBug Console

If you click on the Object shown, you can drill into the variables further:
FireBug Console

Eric's picture

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