On this page: Loading...

Drupal Planet

Testing your module p.1 - Intro to Selenium video added to your shopping cart.

It's tricker than it seems to get the URL to the current page you're on in Drupal, and the solution you use depends on what your goal is. Let's explore a couple of options. Let's say the URL to the current page is the following:

http://example.com/user?queryvar=1

Note that I've added a query string to demonstrate how it can complicate things. Let's look at the first option, which probably will work for us most of the time:

request_url();

The request_uri() function is a utility function supplied by Drupal for just this use. Here's what it returns:

/user?queryvar=1

We can go ahead and slip that right into a link, but to manipulate it would take some work. It's not a value we can pass into url() or l() and have work. And if we're not sure if there's going to be a query string, we can't just go and add to it without a check. Let's look at another option:

$_GET['q'];

This $_GET variable typically corresponds directly to the the path that you would pass to a url() or l() function, and in this case it would typically return the following:

user

This is something we can work with, but I've worked with several sites where the $_GET value doesn't necessarily correspond directly to the current page. For example, on the project that spurred this post, $_GET['q'] on /user will return /user/8423, which will return a different url if passed to url(). So, it didn't work for this particular case. The solution I found seems a little twisted, but here it is:

trim($_SERVER["REDIRECT_URL"], '/');

This returns the correct value for me every time, with the query string stripped off. It is an absolute URL, which means that before we trim the slash off of it, $_SERVER["REDIRECT_URL"] would return /user.

Good luck, and I hope you found this before totally blowing a fuse.

Most of the time, 403 and 404 errors don't need to be dished out manually. Users will get a 404 when they hit a URL that doesn't correspond to an actual file or page, and will get a 403 when they are denied access through various access control utilities in Drupal. However, on occasion you'll need to explicitly send someone to a 404 or 403 error page, and in that case, drupal_not_found and drupal_access_denied are your friends. With them, you don't have to worry about manually settings headers or redirecting to the error page that's been defined in the error handling configuration.

Serving a 403 page with drupal_access_denied()

Let's take a look at an example of drupal_access_denied from the SimpleNews module:

  // If non-admin is trying to edit someone else's subscription, access denied.
  if ($user->uid && $user->uid != $subscription->uid && !user_access('administer simplenews subscriptions')) {
    drupal_access_denied();
    return;
  }

In this example, a little logic is need to determine if a user has access. First, it checks to make sure the user is logged in, then insures that if the user editing the subscription doesn't have a particular permission, they get booted with a drupal_access_denied().

Note that the drupal_access_denied function will only print the 403 page, but will not stop the script from continuing. So, you need to make sure that the functionality you're preventing access to doesn't get run by using a return after the call.

Serving a 404 page with drupal_not_found()

Next, let's look at an example of drupal_not_found. This example comes from the Masquerade module:

  $new_user = user_load(array('uid' => $uid));
  if (!$new_user->uid) {
    return drupal_not_found();
  }

The Masquerade module is checking to make sure that a user exists, before allowing access to a page specific to the user. If the user doesn't exist, then the page also doesn't exist, therefore the current user should get a 404. Notice that this snippet uses a slightly different syntax for the return. Instead of returning on a separate line, return is used on the function itself. drupal_not_found will not return any value, but the syntax still works and is slightly more elegant.

In this post, you'll learn how to manipulate arrays in Drupal. If you haven't already taken a look at How to find the structure of a Drupal array, give it a quick look over before continuing.

There are a number of useful PHP utilities for manipulating arrays. We're going to use the example here of hook_form_alter(), a hook supplied by Drupal to allow us to manipulate forms. Since forms are really just arrays of data, altering the structure of a form is synonymous with altering an array. Let's take a look at a simple $form array from the user login form, defined in user.module in the modules/user folder:

  $form = array(
    '#action' => url($_GET['q'], array('query' => drupal_get_destination())),
    '#id' => 'user-login-form',
    '#validate' => user_login_default_validators(),
    '#submit' => array('user_login_submit'),
  );
  $form['name'] = array('#type' => 'textfield',
    '#title' => t('Username'),
    '#maxlength' => USERNAME_MAX_LENGTH,
    '#size' => 15,
    '#required' => TRUE,
  );
  $form['pass'] = array('#type' => 'password',
    '#title' => t('Password'),
    '#maxlength' => 60,
    '#size' => 15,
    '#required' => TRUE,
  );
  $form['submit'] = array('#type' => 'submit',
    '#value' => t('Log in'),
  );

Adding an element to an array

In this example you can see that the $form variable is first defined, and then new variables are added to it by using the following structure:

$form[$key] = $value;

The important part here is that the $key is unique, otherwise the existing value for the key will be overwritten.

Replace an array value

That last example gives gives us a clue as to how we would override a array value. Say we wanted to replace the label on the 'Username' input to something like 'What should we call you?'. To do that, we'd do:

$form['name']['#title'] = 'What should we call you?';

Notice that we're having to change an array element that's within an array. We dig into the structure on an array using the [] brackets. Because $form['name'] is an array, and we need to identify the #title element in that array, we use $form['name']['#title'].

In Drupal, it's a convention to add a hash tag (#) to the beginning of variable names that correspond to a setting, rather than an element. This is done to avoid namespace collisions.

Removing an element from an array

To remove an item from an array, you can use the unset() function. Say we wanted to remove the password input from the above form. We'd do that with the following:

unset($form['pass']);

Moving an element off the end or beginning of an array

There are a couple of handy tools that you can use to move elements off of the beginning and end of an array - kind of tricky to do without the help of these functions. To remove the first element off an array, you would use the following:

array_shift($form);

This isn't very practical for our $form example, but believe me, it will come in handy down the road. And to remove an element off the end of an array, use the following:

array_pop($form);

These two functions also return the element that was removed, so if you wanted to do something with the element, just assign a variable to the function, as in the following:

$element_removed = array_pop($form);

There, now you're an array manipulation ninja. Go forth!

Drupal is rife with arrays, so knowing how to work with them effectively will open up some great possibilities for you. There's a lot you can do with arrays, but before you really dig in, here's a short article on how to find the contents of an array.

In Drupal, arrays are handled almost exclusively through vanilla PHP functions. Let's take a look at a common use of an array in Drupal - altering a form using hook_form_alter(). This example comes from the Advanced Poll module:

function advpoll_form_alter(&$form, $form_state, $form_id) {

The first value passed to this function is $form, which is an array containing all the elements in the form. The easiest way to browse the contents of this array is to use a PHP debugger, but we can also use a nice little function called var_dump() to output the array in a fairly readable format. Here's how you would use var_dump():

var_dump($form);

This will output a tree-like structure containing the values of the $form variable. Here's an example of what a partial var_dump() output looks like:

array
  'file_directory_path' => 
    array
      '#type' => string 'textfield' (length=9)
      '#title' => string 'File system path' (length=16)
      '#default_value' => string 'sites/default/files' (length=19)
      '#maxlength' => int 255
      '#description' => string 'A file system path where the files will be stored. This directory must exist and be writable by Drupal. If the download method is set to public, this directory must be relative to the Drupal installation directory and be accessible over the web. If the download method is set to private, this directory should not be accessible over the web. Changing this location will modify all download paths and may cause unexpected problems on an existing site.' (length=450)
      '#after_build' => 
        array
          0 => string 'system_check_directory' (length=22)

Items in the tree that aren't contained in quotes usually indicate the type of variable. In this case, the top-most variable is an array, and the array contains other arrays, some strings and an 'int' (integer) value. The next line indicates a key / value pair by linking the key 'file_directory_path' with the value - also an array - through the '=>' symbol. You can see that the output is indented to indicate when one variable is actually inside another variable. If you look down the output, you can see that there is one more array embedded in the embedded array. Arrays like this are called 'nested arrays', and arrays can be nested like this indefinitely. That makes them ideal candidates for organizing hierarchical data.

When you use var_dump() to output a array, you will likely need to view the source code in your browser to view the indentations. You can use var_dump() on any variable, and so it can also be very useful for getting the structure of an object.

In future posts, we'll cover some techniques for manipulating an array.

It quite the challenge to build a path to a file in the Drupal system without the help of the API. In particular, if you're building a module that needs to work regardless of where a site puts their files, you might end up lost in a rabbit hole of API functions that sound like they might do the trick, but don't.

The function you need in your tool belt to create well-formed Drupal file paths is file_create_path(). Here's an example of how the Filefield CCK module uses file_create_path:

$file = file_create_path($file);

Let's take a look at the code and see what's going on here:

function file_create_path($dest = 0) {
  $file_path = file_directory_path();
  if (!$dest) {
    return $file_path;
  }
  // file_check_location() checks whether the destination is inside the Drupal files directory.
  if (file_check_location($dest, $file_path)) {
    return $dest;
  }
  // check if the destination is instead inside the Drupal temporary files directory.
  else if (file_check_location($dest, file_directory_temp())) {
    return $dest;
  }
  // Not found, try again with prefixed directory path.
  else if (file_check_location($file_path .'/'. $dest, $file_path)) {
    return $file_path .'/'. $dest;
  }
  // File not found.
  return FALSE;
}

The first line calls file_directory_path, which just grabs the value set on the admin/settings/file-system page of your site. Next, it runs through a series of checks to first insure that that the path hasn't already been appended with the file system directory, next to see if the file isn't stuck in the temporary file directory, and finally adds the system path to the file and returns the full path if the file exists.

One thing to note is that once you have your full path, you shouldn't have to add anything else to use it. file_create_path should work on it's own to give you the file path you need.

I've used this function when I have a file that I know will always be in the file directory, but can't be sure where that directory will be. It took me way too long to dig this information up. There are a couple of other useful file-related API calls that I discovered along the way, including:

  • file_create_url - Creates a link to download a file
  • file_check_path - Insures that the directory that contains the file is writable by Drupal

Most PHP developers are initiated into the world of includes with the following:

include($file);

or

require($file);

The difference between include and require is subtle. Require - as the function name indicates - requires that the included file exist to continue the script. So, if require fails, the script stops. Using include will allow the script to continue. Most of the time, using require makes more sense because it's likely that the file we want to include includes some important information for the rest of the script.

After a while, most of us discover that we might be inadvertently including the same file several times, and learn some new functions to insure our files aren't being loaded multiple times, namely include_once and require_once to prevent

That's better, but when we start developing in Drupal, we're likely going to be stringing this along with some other functions to get the correct path to the file we need to include. There's a nice little function available for us in the Drupal API which simplifies this code. Here's an example from content.module in the CCK module of module_load_include:

module_load_include('inc', 'content', 'includes/content.node_form');

Let's take a quick look at the code behind the scenes:

function module_load_include($type, $module, $name = NULL) {
  if (empty($name)) {
    $name = $module;
  }

  $file = './'. drupal_get_path('module', $module) ."/$name.$type";

  if (is_file($file)) {
    require_once $file;
  }
  else {
    return FALSE;
  }
}

First, the function assumes that we'll be using an include file that has the same name as the module. If not, no problem, we just set the name explicitly in the third parameter.

Next, a path to the file is generated based on the module name we pass to the function. If you're still wrapping your mind around how to create portable paths in Drupal, this is a nice example. The dot-slash (./) indicates that the file should only be looked for in the current directory, and not in any shared PHP library directories. The drupal_get_path function is a nice API call to generate a path to a module or theme directory. Note that you have to supply the code with the extension for the included file, which will often be 'inc.'

Finally, the function checks to see if the file exists using is_file(). There is another function that operates similarly, called file_exists(), and the difference between the two is that file_exists will return TRUE if a file OR a directory exists with the name. If you know you're looking for a file, is_file usually makes more sense.

There we go, a nice little function to save you some code repetition that also gives you some ideas for best practices on including files.

Syndicate content