Retaining default values with disabled cck date fields

May 5
blog author

Appno Blogger

Appnovation Coop

A recent project had me looking into CCK date fields and the complications involved when trying to restrict access to some users. The project required that multiple users be able to view and edit the same node, but have different access to certain fields. In particular, some date fields were to be editable by one user, but disabled for others.

Note: This was done in Drupal 6 with CCK and date modules.

The problem:

When the date fields are disabled, we want them to retain the default or current value. When they are editable, we want the existing value to be replaced by the input value from the form.

Our solution:

1. Disable the fields in a custom #after_build handler.

2. Re-populate the fields in a custom #submit handler. The following code adds a custom #after_build handler and a custom #submit handler to the node form through hook_form_alter().


/**
 * Implementation of hook_form_alter().
 */
function mymodule_form_alter(&$form, &$form_state, $form_id) { 

  if ($form_id == 'mytype_node_form') {
    // Adds after-build handler to disable date fields
    $form['#after_build'][] = 'mymodule_form_after_build';    
    // Adds submit handler to re-populate defaults in disabled date fields.
    $form['#submit'][] = 'mymodule_form_submit_handler';
  }

}

The first part of the solution, disabling the date fields, is provided by markus_petrux as explained on drupal.org here. Fields can't simply be disabled in the hook_form_alter() function. This must be done after the form is built and is nicely handled here by a function called from within the after_build function.


/**
 * After build function for mytype node form.
 */
function mymodule_form_after_build($form, &$form_state) {

  global $user;

  // Disables date fields for all users except author.
  if ($user->uid != $form['uid']['#value']) {
    // If the targetted fields are within a fieldgroup (as in this case)),
    // they'll be nested within the fieldgroup in the $form array passed into
    // the after_build function (unlike the $form array passed into 
    // hook_form_alter()). Otherwise just use $form['field_name'] here.
    _mymodule_fix_disabled($form['group_name']['field_date_one']);
    _mymodule_fix_disabled($form['group_name']['field_date_two']);
    _mymodule_fix_disabled($form['group_name']['field_date_three']);    
  }
  return $form;

}

/**
 * Recursively set the disabled attribute of a CCK field
 * and all its dependent FAPI elements.
 * 
 * Credit to markus_petrux for this function.
 */
function _mymodule_fix_disabled(&$elements) {

  foreach (element_children($elements) as $key) {
    if (isset($elements[$key]) && $elements[$key]) {
      // Recurse through all children elements.
      _mymodule_fix_disabled($elements[$key]);
    }
  }
  if (!isset($elements['#attributes'])) {
    $elements['#attributes'] = array();
  }
  $elements['#attributes']['disabled'] = 'disabled';

}

This effectively disables the date fields for everyone but the author. The next complication is that although other users cannot alter the date fields, we still want to allow them to edit the node. When the node is saved with the date fields disabled, the values appearing on the form for those fields are not re-submitted on the save. They are lost and the date fields are empty. That's where the submit handler comes in. A key point here is that the date value is stored in two different places depending on whether the field is disabled. When the field is disabled, the value (a string) can be retrieved from $form['group_name']['field_name'][0]['#value']['value'] or $form['field_name'][0]['#value']['value'] if there's no fieldgroup. When the field is not disabled, you have to go one deeper to get the 'date' string within the 'value' array: $form['group_name']['field_name'][0]['#value']['value']['date'].


/**
 * Submit handler for mytype node form. 
 */
function mymodule_form_submit_handler($form, &$form_state) {

  $datefields = array(
    'field_date_one',
    'field_date_two',
    'field_date_three',
  );

  // Re-populates date field values if they are disabled.
  foreach ($datefields as $key => $id) {
    if ($form['group_name'][$id][0]['#attributes']['disabled']) {
      $value = strtotime($form['group_name'][$id][0]['#value']['value']);
    }
    else {
      $value = strtotime($form['group_name'][$id][0]['#value']['value']['date']);
    }
    $form_state['values'][$id][0]['value'] = $value;
    $form_state['values'][$id][0]['value2'] = $value;
  } 

}

The value retrieved from the $form array is assigned to the $form_state['values'] array to be submitted on the node save. The first value ('value') is the 'From date' of the date field. The second value ('value2') is the 'To date' of the date field. In our case they are the same. Also, in our case we had no need for time, so the granularity on the date was set to day as the smallest unit. We also disabled some text fields but these were much simpler since the existing values are preserved when the node is edited even if the fields are disabled. If anyone has any other solutions or comments on date fields or other CCK field types, I'd be happy to hear about them. (I did try making the date fields readonly but had similar issues with preserving the values on node edit).