Tag Archives: Drupal

Ryan’s Drupal 7 troubleshooting checklist

New to Drupal or a little rusty? This checklist is to help speed up your troubleshooting process. The list is currently at version 0 and I hope to improve it by giving more structure along with associating/clustering each item with symptoms. Let me know if you want to help! Continue reading

Triggering default Ajax behaviour in Drupal textfields for better UX

By default, the addressfield form elements triggers Ajax requests on change, which means textfields only triggers when blurred. This may leave the user wondering what to do if the next field depends on the results of the Ajax request. To trigger the default Ajax behaviour on other events, you can do


$('body').on('change keyup paste', '.selector.for.your.element', function(e) {
var current_value = $(this).val();
// Do some checks before the request.
if (current_value.length === 4){
// Blur the textfield.
$(this).trigger("blur");
return false;
}
});

A few notes:
on() is used so that the event is live. i.e. still bound after the element is updated, which is common in Ajax forms. If event listeners are added to the element directly, it is lost after the form is updated.

The events ‘change’, ‘keyup’, ‘paste’ is used to capture common events where the contents of the field might change.

Adding even and odd classes to checkboxes in Drupal 7

While theming a page for a client, I came across an interesting challenge: adding even and odd classes to checkboxes. Normally I would have gone with javascript or CSS3 (:nth-child), but I wondered if it can be achieved in ‘the Drupal way’ and achieve the best cross browser support. Google didn’t turn up any results so I decided to do a quick exercise on form element theming. Here’s how I got it to work.

Add our own checkboxes processor. See hook_element_info_alter.

function MYTEMPLATE_element_info_alter(&$type){
  // Append our processor
  $type['checkboxes']['#process'][] = 'MYTEMPLATE_process_checkboxes';
}

Implement the customer checkboxes processor to add the classes we want. See form_process_checkboxes

function MYTEMPLATE_process_checkboxes($element){
  // Loop through options to add striping class and attribute
  if (count($element['#options']) > 0) {
    $i = 0;
    foreach ($element['#options'] as $key => $choice) {
      $element[$key]['#attributes']['class'][] = ($i % 2 == 0) ? 'odd' : 'even';
      $i++;
    }
  }
  return $element;
}

I quickly realized that while this added the even and odd classes on the <input> elements, it would be easier for the themer to have it on the form element wrapper as well. So I decided to add a ‘striping’ key to ‘#attributes’ so that I can identify it in the form element wrapper.

The new processor:

function MYTEMPLATE_process_checkboxes($element){
  // Loop through options to add striping class and attribute
  if (count($element['#options']) > 0) {
    $i = 0;
    foreach ($element['#options'] as $key => $choice) {
      $element[$key]['#attributes']['class'][] = ($i % 2 == 0) ? 'odd' : 'even';
      $element[$key]['#attributes']['striping'] = ($i % 2 == 0) ? 'odd' : 'even';
      $i++;
    }
  }
  return $element;
}

Next, modify the template_form_element() to add the classes on the form element wrapper.

function MYTEMPLATE_form_element($variables) {
  ...
  if (!empty($element['#attributes']['striping'])) {
    $attributes['class'][] = $element['#attributes']['striping'];
  }
  $output = '<div' . drupal_attributes($attributes) . '>' . "\n";
  ...
}

Clear the class registry and we are done. This is by no means the best method available, but is definitely the most straightforward way that jumped at me. If you have a more efficient way do share in the comments.

Drupal 7 – Debugging Illegal offset type in isset or empty in … and other errors

Though the title mentions Drupal 7, this article is really about the debugging technique and so is applicable to general php as well. I want to share a recent bug we had to solve at work. Though experienced developers would usually have an idea what new code is breaking, mysterious warnings and errors sometimes do slip through while working in teams (even when having a proper svn system in place!). You check with your co-developers and the one who broke it have no idea where this might be coming from, or worse: they have seen and have been ignoring this error for some time because they felt it didn’t matter. What do you do then?

Read the error
The error message “Illegal offset type in isset or empty in…” points to an illegal type used in a call to isset or empty, and it conveniently points you to the offending line – but with Drupal’s (and most of today’s frameworks’) complex page execution flows, it is hard to spot where is the real source of error, especially if the line is in one of the framework’s frequently called core routines (in my case it was in user_access). Since this is not much help in trying to deduce the real source, I resort to setting my own error handler (temporarily overriding Drupal’s) so that I can do a debug_back_trace().

To set your own error handler, define a function and feed the name to php’s set_error_handler():
[php]
// Your custom handler
function my_error_handler($errno, $errstr, $errfile, $errline
, array $errcontext)
{
// error was suppressed with the @-operator, ignore
if (0 === error_reporting()) {
return false;
}

// print the backtrace
echo ‘<pre>’.print_r(debug_backtrace(),true).'</pre>’;

// throw an error exception if you want to catch it at the caller
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}

// Back at the offending line:
set_error_handler(‘my_error_handler’);

try {
// original code
}
catch (ErrorException $e) {
// print your own debug log or what not
}

// Restore Drupal’s own error handler. You don’t want a backtrace
// on every other E_USER_NOTICE’s do you?
restore_error_handler();

[/php]

While you may find it useful to convert every error into an exception, note that even E_USER_NOTICE would then halt your page execution, so please use with care (refer to php manual on the ErrorException class for a handler that ignores non-fatal errors).

Now, what’s left is to carefully read through the debug backtrace and spot the problem. For my case, it was a line of code someone embedded in a view context. Hope this helped, share your own debugging methods and opinions in the comments.

End note
Some people may feel that my style of debugging isn’t very ‘elegant’ but my experience is that the fastest way to find the problem isn’t necessarily the most elegant way. Rather than going through hours of deducing which functions might or might not be involved, and later realising the problem was in one of those functions that “logically shouldn’t have mattered in this case”, I find that getting a solid backtrace – after a quick deduction fails – is usually my best bet in the long run.

Fixing Drupal’s Canonical link meta tag

Fblint complained that one of my page’s canonical link mismatches the og:url meta tag. Turns out Drupal 7 outputs relative canonical links by default and although there’s nothing strictly forbidden about relative links, both facebook and google seems to prefer absolute links. Rather than altering the offending tag through hook_html_head_alter() or even worse, mess with the core files, I decided to install the Metagtag module (http://drupal.org/project/metatag).

The default configuration looked fine (the module even supports og meta tags!) so I ran the page through fblint again, hoping it to be another fine install-set-go Drupal 7 module. 58827 sites were using this module at last count, that’s pretty reliable. I was dismayed when I got the same results and a quick check revealed that apparently the module modified none of my head tags. I also noticed that its only the front page that was having this issue. The module works fine on all other pages. A few drupal.org threads later I arrived at the solution: calling render($page['content']); is all it needs to get it to work on overidding page–*.tpl.php’s. Yup just calling render() will do, no need to print.

Solution thread: http://drupal.org/node/1293214

Drupal 7 – Handling file uploads

When trying to deal with custom forms with file upload fields, you may sometimes get a error regarding extensions:

The specified file “your file name here” could not be uploaded. Only files with the following extensions are allowed: “jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp”

To allow all extensions, you have to manually set the ‘file_validate_extensions’ validator to an empty array.

e.g.
$validators = array('file_validate_extensions' => array());
$file = file_save_upload('upload', $validators);

For more information, check out Drupal API’s documentation at http://api.drupal.org/api/drupal/includes–file.inc/function/file_save_upload/7

The bare requirements for a file upload form is the “multipart/form-data” enctype, a “file” field, and the standard “submit” button. An example of a basic structure:


$form ['upload'] = array (
'#type' => 'file',
'#title' => 'Choose file',
'#description' => 'Upload a file.' );
$form ['submit'] = array ('#type' => 'submit', '#value' => t ( 'Submit' ) );
$form ['#attributes'] = array ('enctype' => "multipart/form-data" );

Creating nodes programmatically in Drupal (including cck and location fields)

Creating a new node in drupal is trivial, but when the node includes custom cck fields, then a few things can trip you up. To save time and energy, here’s a quick guide and some tips to make your drupal life easier.

Creating a basic node:

global $user; //get current logged in user
$new_node = new stdClass();
$new_node->type = 'YOUR_NODE_TYPE_HERE';
$new_node->uid = $user->uid; //you can specify some other userID here if you want
$new_node->name = $user->name;
$new_node->title = $YOUR_NODE_TITLE;
$new_node->body = $YOUR_NODE_BODY;
$new_node->status = 1; // published
node_save($new_node);

The function node_save() does a few things behind the scene. It checks if the nid property is empty to determine if this is a new insert or an update, then calls the appropriate functions so that you would not have duplicates. Put simply, if you want to do an update, populate the nid, and leave it out otherwise.

Creatnig a node with CCK fields involves this additional line (for each field):

$new_node->field_your_field[0]['value'] = $YOUR_FIELD_DATA;

Point to note is the [0][‘value’], because CCK treats each field as multiple-value fields even if you specified otherwise in the field settings. If you see the error Fatal error: Cannot unset string offsets in …/modules/cck/content.module on line 1248, you’re probably missing the [0][‘value’] and setting the value directly into $new_node->field_your_field.

To create a node with location, we need to save the location first to get a location ID before saving it along with our node:

$location = array(
'street' => $address,
'postal_code' => $postal,
);
$locationID = location_save($location);
$new_node = new stdClass();
// ... the usual stuff ...
$new_node->locations[0]['lid'] = $newLocationId;
node_save($new_node);

There you go. Another method is to manually craft an array to simulate a $_POST from a drupal form. To do that you basically have to know the form structure, populate the require fields and pass the array on to the drupal_execute() call. Refer to the example here for more details.

Drupal references:
Node_save: drupal documentation

Drupal Tip #3 – Determining Drupal Version

http://www.mydrupal.ca/2010-06-23/what-version-drupal-your-site-using has a helpful check list to go through when you need to determine the drupal version with missing Changelog files:

1. First thing to do is to read the CHANGELOG.txt file. It’s located in the root directory of your Drupal website. All versions of Drupal from 4.5-present have this file. Check the top entry in this file. It will have the Drupal version number. Unfortunately for our purposes today, many developers delete this file on live sites because it is perceived as a security risk.
2. Second thing to do is to check the VERSION constant in modules/system.module. This was added in Drupal 5.0.
3. If you can’t find the VERSION constant in modules/system.module then your version number is most likely 4.7 or earlier. In Drupal 4.7, the site ‘offline for maintenance’ feature was added.
4. In Drupal 4.6, a site-wide contact form (modules/contact.module) and search block were added.
5. In Drupal 4.5, all GIF’s in the default themes were replaced with PNG’s. If you can’t find the modules/contact.module, then your site is most likely Drupal 4.5.
6. You can actually download the 4.x versions on Drupal.org. Then you can compare the files in more detail. This article covers the major releasees and not the minor releases. For example upgrading from 4.51-4.52, is called a minor release.

Drupal Tip #2 – Limiting long strings

Use truncate_utf8 to quickly limit strings to certain lengths, and define whether the break should occur at character level or word level. Also optionally appends ‘ …’. Point to take note is that the ‘ …’ would not add to the resultant length – drupal compensates it by limiting the original string 4 characters less. This would be obvious in the example below, as each output never exceeds the 20 character limit.

For example:

$sentence = 'a quick brown fox jumps over the lazy dog';
echo truncate_utf8($sentence, 20); //a quick brown fox ju
echo truncate_utf8($sentence, 20, TRUE); //a quick brown fox
echo truncate_utf8($sentence, 20, TRUE, TRUE); //a quick brown ...
echo truncate_utf8($sentence, 20, FALSE, TRUE); //a quick brown fo ...

// Output side-by-side:
//a quick brown fox ju
//a quick brown fox
//a quick brown ...
//a quick brown fo ...

Drupal 4.7 – 6:
truncate_utf8($string, $len, $wordsafe = FALSE, $dots = FALSE)

Drupal Api Reference: truncate_utf8

Drupal Tip #1 – Formating Time Ago

Drupal has a nice little function called format_interval that formats a time interval in a “x secs ago” fashion. Note that it accepts a time interval, not an absolute time value, thus you need to subtract the time value from time().

For example, to print your node’s created time, use:

<?php echo 'Submitted ',format_interval(time()-$node->created),' ago'; ?>

Drupal API ref for format_interval()