~ 4 min read

Restricting Drupal’s upload module to N attachments

share this story on
Assign a per-content type permission to limit the number of attachments per node on Drupal 6

If you’ve enabled the node attachments support in Drupal 6 and needed to limit it’s use for only allowing to attach one file per node and didn’t know how then this post is for you.

You can also achieve the same thing with the CCK’s FileField module by attaching it and setting the field to appear once instead of ‘Unlimited’ though there may be cases where you just don’t want or can’t use that CCK field and revert to Drupal’s upload module:

  1. It has site-wide settings for things like quote per user, per role, size of files to be uploaded and such while with the CCK FileField you’d need to set it per field in each node content type (or re-use the field)
  2. It’s Drupal built-in and has, presumably, better integration than other modules.

Let’s begin a possible solution for this problem:

  1. We’ll define a variable to hold the setting of attachment counts per content type – this promises that the solution is generic enough rather than a dirty hack
  2. While it’s possible to create a new module and use the correct hooks to enforce the required behavior I’ll explain how to patch the upload module for the sake of simpler and shorter post.

Our function to handle the attachment count needs to check the amount of files attached to the node in question as well as alter the form elements so we obviously need these 2 items.

The logic for this function is pretty straight-forward, if such limit is defined for a given content type and is set to 1 attachment item then we remove the ‘Attach’ ajax button so it’s only possible to choose a file but not yet to upload it to the server (it will be saved/uploaded with the form submit). If on the other hand a higher limit is set then we count how many items are set in the $node->files array (without the possible stale ‘upload’ array) and based on that choose to remove the whole attachment wrapper altogether from the form (which is stored in $form['new']):

function _upload_limits_attachment_count_set(&$form, &$node) {

$upload_limits_attachment_count = (int) variable_get('upload_limits_attachment_count_'.$node->type);

// Remove the upload element from the array which get sets if user decides to delete a node
unset($node->files['upload']);

// If limit is only for one item let's just remove the Attach ajax action
if ($upload_limits_attachment_count == 1) {
unset($form['new']['attach']);
} elseif ($upload_limits_attachment_count > 1) {
// If a larger limit is defined then we check if that limit has reached and disable the option to upload more files altogether
if (count($node->files) >= $upload_limits_attachment_count)
unset($form['new']);
}

}

We’ll have to call this function in the form that handles the ajax call as well as the form that draws for the first time, so effectively calling this function in:

  1. upload_js() function just before the drupal_alter
  2. upload_form() function at the very end, just before the return statement

To do this right we’ll also need to hook into hook_nodeapi() to make sure that the attached files that match and are going to be saved and related to this node are correct. This is more like a security measure to make sure that no one injected files in a programmed manner or manipulated the form somehow manually.
Hooking into nodeapi() then:

/**
* Implementation of hook_nodeapi().
*
* @param &$node The node the action is being performed on.
* @param $op What kind of action is being performed. Possible values: alter, delete, delete revision, insert, load, prepare, prepare translation, print, rss item, search result, presave, update, update index, validate, view
* @param $a3
* @param $a4
*/

function mymodule_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {

$upload_limits_attachment_count = (int) variable_get('upload_limits_attachment_count_'.$node->type, '');

if (!empty($upload_limits_attachment_count)) {
$i = 0;
foreach ($node->files as $key => $value) {
$i++;
if ($i > $upload_limits_attachment_count)
$node->files[$key]['remove'] = 1;
}
}

}