Drupal 6.x | Eric's Drupal Blog

Content tagged with: Drupal 6.x

Eric's picture

Creating a Batch API operation to parse a large CSV file

In this code snippet, I'll show how you can parse a (large) CSV file using Drupal's Batch API. The purpose of batching an operation is to avoid PHP memory limits and time outs. Before you begin, I recommend reviewing the following two articles. Be sure to review the additional batch parameters outlined in the documentation, you might need to use them.

Batch API
Batch Operations

We can start by defining the arguments that will be passed into the batch_set() function. For this example, I added this code in an arbitrary page callback function.

<?php
function MYMODULE_callback_csv_import() {

 
// define path to CSV file
 
$csv_file_path = file_directory_path() . '/import_path/myfile.csv';

 
// define a redirect path upon batch completion
 
$redirect_path = 'admin/import-csv';

 
// define batch array structure
  // NOTE: minimal parameters defined to simplify code
 
$batch = array(
   
'title' => t('Reading File'),
   
'operations' => array(
      array(
       
'_MYMODULE_batch_read', array($csv_file_path),
      ),
    ),
  );

 
// set batch
 
batch_set($batch);

 
// process batch
 
batch_process($redirect_path);

}
?>

Next, we'll define the batch callback function. This function will be called repeatedly until the $context['finished'] variable is set to "1".

<?php
function _MYMODULE_batch_read($csv_file_path, &$context) {
 
 
// define batch limit
 
$batch_limit = 100;

 
// assume the batch process has not completed
 
$context['finished'] = 0;

 
// open the file for reading
 
$file_handle = fopen($csv_file_path, 'r');

 
// check if file pointer position exists in the sandbox, and jump to location in file
 
if ($context['sandbox']['file_pointer_position']) {
   
fseek($file_handle, $context['sandbox']['file_pointer_position']);
  }
 
 
// loop through the file and stop at batch limit
 
for ($i = 0; $i < $batch_limit; $i++) {

   
// get file line as csv
   
$csv_line = fgetcsv($file_handle);

   
// NOTE: at this point, do what ever you'd like with the CSV array data!
   
if (is_array($csv_line)) {
     
// db_query(), etc   
   
}

   
// retain current file pointer position
   
$context['sandbox']['file_pointer_position'] = ftell($file_handle);

   
// check for EOF
   
if (feof($file_handle)) {
     
// complete the batch process
     
$context['finished'] = 1;

     
// end loop
     
break;
    }

  }

}
?>

The batch operation will be called until the end of the CSV file is reached. The $context variable is passed by reference into the batch callback so you can maintain data through each iteration; in this case, the position of the file pointer. When the batch operation is complete, the user will be redirected to the batch_process() path argument.

It's important to read the full Batch API documentation so you can take advantage of its additional features: finished callback, init_message, progress_message, error_message, etc.

gradient spacer
Eric's picture

Programmatically submit a webform using drupal_execute()

In this snippet, I'll show you how you can submit a webform programmatically using drupal_execute(). The first thing you'll need to do is figure out what the $form_state data looks like when the webform is submitted, so you can recreate that structure and pass it into drupal_execute().

One way to accomplish this is to add a validation/submit handler to the form using hook_form_alter() and then output the contents of the submitted data (using krumo, print_r, etc).

The following code will prepend a validation handler to the webform $form, so we can dump the submitted data to the screen:

<?php
function MYMODULE_form_alter(&$form, $form_state, $form_id) {
 
 
// define node id of webform
 
$webform_node_id = 146;
 
 
// check for form id of webform
 
if ($form_id == 'webform_client_form_' . $webform_node_id) {
 
   
// prepend a validation callback to the form
   
array_unshift($form['#validate'],
     
'_MYMODULE_form_alter_webform_' . $webform_node_id . '_validate');
 
  }
 
}
?>

And here is the validation handler which will dump the submitted data to the screen. NOTE: krumo() is available from the devel module; you could use print_r() as well:

<?php
function _MYMODULE_form_alter_webform_146_validate($form, &$form_state) {
 
// debug
 
krumo($form_state);
  die;
}
?>

Now, if I populate the webform with some data:

And submit the form, I will get the following debug output:

You'll need to mirror the structure of the submitted data when creating your $form_state variable, which will be passed into drupal_execute().

In the following function, I show how you can submit a webform programmatically. There is even additional code in there which loads a user's previously submitted data, if you care.

<?php
function MYMODULE_some_function() {

 
// define webform node id
 
$webform_node_id = 146;

 
// define user submitting the webform
 
$user_id = 2;

 
// load webform node
 
if ($node = node_load($webform_node_id)) {

   
// load module include file, per loading previously submitted webform data
   
module_load_include('inc', 'webform', 'webform_report');

   
// get submissions for user
   
$submissions = webform_get_submissions($node->nid, NULL, $user_id);

   
// get submission data ($submission) and submission id ($sid)
   
if (FALSE !== ($sid = key($submissions))) {
     
$submission = $submissions[$sid];
    }
    else {
     
$submission = NULL;
     
$sid = NULL;
    }

   
// create array of $form_state data
    // NOTE: be sure to use the debug output as a guide to make this array match!
   
$form_state = array(
     
'submitted' => true,
     
'values' => array(
       
'submission' => $submission,
       
'submitted' => array(
         
'test_field_1' => 'abc',
         
'test_field_2' => '123',
         
'test_field_3' => 'def',
        ),
       
'details' => array(
         
'email_subject' => $node->webform['email_subject'],
         
'email_from_name' => $node->webform['email_from_name'],
         
'email_from_address' => $node->webform['email_from_address'],
         
'nid' => $webform_node_id,
         
'sid' => $sid,
        ),
       
'op' => t('Submit'),
       
'submit' => t('Submit'),
       
'form_id' => 'webform_client_form_'. $webform_node_id,
      ),
    );

   
// Saves the webform data submited prior to login.
   
drupal_execute('webform_client_form_'. $webform_node_id, $form_state, $node, $submission, TRUE, FALSE);
   
  }

}
?>

gradient spacer
Eric's picture

Removing a column from a view for unauthenticated users using hook_views_pre_build()

In this tutorial I'll show how you can programmatically and dynamically remove a column from your view. In my example, I chose to hide an email column from unauthenticated users, but you could apply this code to do pretty much anything.

To get things started, I defined a content type of "Profile" to contain some user details (using the Content Profile module) and used the Devel module's generate users and nodes functionality to create some sample data. I then created a view to show the data:

Here is my page view display:

Now you can add the hook_views_pre_build() function in your module. Since the $view object is large, I recommend using krumo() (which comes with the Devel module) to browse through the $view object's properties.

<?php
function MYMODULE_views_pre_build(&$view) {

 
// check for your view name (in my example: people)
 
if ($view->name=='people') {
   
krumo($view);
  }

}
?>

The above code will neatly format the $view object in a hierarchical display. You can click to expand each class property.

As you can see, the $view object has a lot of data in it. At this point, you'll need to get acquainted with the structure of the handler object of the view ($view->display['default']->handler) and determine what to modify. To remove the email column for unauthenticated users, you can add the following code:

<?php
function MYMODULE_views_pre_build(&$view) {

 
// check for your view name (in my example: people)
 
if ($view->name=='people') {
  
   
// check if the user is anon
   
if (in_array('anonymous user',$GLOBALS['user']->roles)) {

     
// remove view hander properties for email column
     
unset(
       
$view->display['default']->handler->options['fields']['mail'],
       
$view->display['default']->handler->options['style_options']['columns']['mail'],
       
$view->display['default']->handler->options['style_options']['info']['mail']
      );

    }

  }

}
?>

When I logout, my view no longer shows the email column:

gradient spacer
Eric's picture

Creating a module update function to disable your development modules when deploying to production

There are some great development modules for Drupal (Devel, Coder, Reroute_Email, Demo, etc), but you probably don't want to have them enabled in a production environment. Deployments to production can be simplified by adding a hook_update_N function in your module's .install file. In this function you can take care of administrative functions such as importing views and CCK node type definitions (essentially, anything exportable). In this quick code snippet, I'll show how you can create a module update function to disable your development modules on update.

<?php
// NOTE: see the documentation on hook_update_N for version naming conventions
function MYMODULE_update_6100() {
 
 
// check for production environment hostname
 
if ($_SERVER['HTTP_HOST'] == 'your-production-hostname') {
   
   
// rebuild the module cache
   
module_rebuild_cache();
   
   
// define a list of development modules to disable
   
$modules_disable = array(
     
'reroute_email',
     
'coder',
     
'demo',
     
'performance',
     
'devel_node_access',
     
'devel_generate',
     
'devel_themer',
     
'devel',
    );
   
   
// disable modules
   
module_disable($modules_disable);

  }

}
?>

Now that your module update function is created, you can deploy your file updates to production (preferably using subversion) and run the update.php script, apply your module update, and disable your development modules.

gradient spacer
Eric's picture

Creating a Google Map showing user locations using the Content Profile, Location, and GMap modules

In this blog entry I'll show how you can create a Google map showing where your users are located using CCK, Content Profile, Views, GMap, and Location modules. Once you've downloaded and extracted those modules, enable the following modules (and set permissions accordingly):

Content
Location
Location CCK
Content Profile
GMap
GMap Location
Views
Views UI

Next you'll need to configure these new modules. On the GMap settings page (admin/settings/gmap) you'll need to add your API Key and enable the setting to Use AutoZoom. On the Location settings page (admin/settings/location) enable the checkbox to use a Google Map to set latitude and longitude and enable JIT geocoding. On the geocoding settings page (admin/settings/location/geocoding), you'll have to enable Google Maps for the desired countries.

The Content Profile module creates a new content type called "Profile". You'll need to add a new field to this content type for the user's location (admin/content/node-type/profile/fields). Enter a label for the node type (Location), enter a field name (field_location), choose "Location" for the field type, and choose "Location Field" for the widget.

On the next screen edit the Locative information sections (Collection and Display) to your liking, and save the field settings. For my example, I enabled Street location, Additional, City, State, Postal code, Country, and Coordinate Chooser.

Now if you create your profile node (node/add/profile) you can enter your location information and save the new node. If all is working at this point, if you return to the profile node edit screen and scroll down to the location information, you should now see a marker on map for your location and the geocoded information will be shown.

Now you can add a new view (admin/build/views/add) to show the user's profile locations. Enter a name for your view, choose node for the view type, and click the next button. Add a new filter for "Node: Published" is true and "Node: Type" is one of "Profile". Add a new relationship for Content: Location (field_location). Add fields for "Location: Latitude" (using the Location relationship), "Location: Longitude" (using the Location relationship), and any additional fields you'd like to display in the GMap marker (title, parts of the address, etc). For the Style of the view choose "GMap". For the GMap style settings, select "Choose latitude and longitude fields" for the Data Source and ensure the Latitude and Longitude fields as set.

Lastly, I added a page view so I could see the results in action..

ps> special thanks to everyone at CommonPlaces for their great technical discussions about these modules!

gradient spacer
Syndicate content