There are significant changes in CCK field modules in 6.x compared to 5.x. This document identifies the changes that developers will need to make in their modules.

Contents

Summary

A number of new hooks and functions are available in the D6 version, read down for more details:

New hooks:

  • hook_content_is_empty()
  • hook_content_fieldapi()

New functions:

  • content_notify()
  • content_module_delete()
  • content_field_form()
  • content_field_default_values()
  • content_instance_default_values()
  • content_field_instance_create()
  • content_field_instance_read()
  • content_field_instance_update()
  • content_field_instance_delete()

Miscellaneous

  • The content module can handle multiple values, and provides options to either select a specific number of multiple values to provide, or allow for unlimited multiple values using the AHAH 'add more' button processing.
  • To properly handle multiple values, CCK needs a reliable method to tell if a field is to be considered empty. To do this, each field module is expected to add a hook_content_is_empty() that will be passed an individual delta value and the field array. That hook should return TRUE if the item should be considered empty and FALSE otherwise. This system makes it possible for fields that have multiple columns to declare an element is empty only if a specific column is missing a value.
    /**
     * Implementation of hook_content_is_empty().
     */
    function userreference_content_is_empty($item, $field) {
      if (empty($item['uid'])) {
        return TRUE;
      }
      return FALSE;
    }
    
  • New functions are available to create 'default' field and widget arrays -- content_field_default_values() and content_instance_default_values(). These can be used to help expose field structure to external modules or as a starting point to creating field and widget arrays.
  • Drupal 6 uses E_ALL testing, which creates errors anytime a field array is missing any indexes. Field arrays are merged with the default value arrays to be sure all parts of the field and widget have indices set so they never create undefined index errors. This means all field values will always return true to isset(), and module authors should not use isset() to test if values exist for form elements on the field settings form. Instead use is_array(), is_numeric() or similar tests.
  • Update or add install(), uninstall(), enable() and disable() hooks for your module so each of those hooks include the function content_notify(). This will notify the content module that a module is being added or removed so it can remove fields, clean up views, and take other necessary action. Without this hook, the content module has no way to know what is going on with its field modules. The recommended place for all four of these hooks is in the .install file. An example:
    /**
     * Implementation of hook_install().
     */
    function text_install() {
      content_notify('install', 'text');
    }
    
    /**
     * Implementation of hook_uninstall().
     */
    function text_uninstall() {
      content_notify('uninstall', 'text');
    }
    
    /**
     * Implementation of hook_enable().
     */
    function text_enable() {
      content_notify('enable', 'text');
    }
    
    /**
     * Implementation of hook_disable().
     */
    function text_disable() {
      content_notify('disable', 'text');
    }
    
  • Update functions: Two aspects of core's update.php are problematic when it comes to CCK and field modules updates:
    - update.php does not ensure the order of updates execution respects module dependencies.
    - update.php runs updates even on disabled modules
    Cases have been reported of one or the other of the above badly breaking the update process CCK or contributed field modules.
    As a fix, it is recommended that *all* D6 update functions in field modules include the following code before attempting any change :
    function MODULE_update_N() {
      if ($abort = content_check_update('MODULE')) { // Where MODULE is the (machine-)name of your module
        return $abort;
      }
    
    
      // Regular update operations
      $ret = array();
    
      // (...)
    }
    

    The content_check_update() function, defined in content.install (and thus available during updates whenever CCK is present) checks that no updates are pending for content.module, and that both content.module and the module being updated are enabled.

Fields

  • hook_field_settings($op = 'view') is finally deprecated and is not used.
  • hook_field_settings($op = 'database columns') now uses Schema API syntax.
  • Field db columns are now forced to have 'not null' => FALSE so that empty fields are always NULL. This is needed to provide Views handlers that search for empty/not empty, and to make it possible for the content module to handle empty multiple values in a consistant manner. Fields should be defined that way in hook_field($op = 'columns'), but even if they are not the content module will override their schema to enforce this. When using 'not null' => FALSE, there is no need to provide a default value, so leave it out completely.
  • It appears hook_field($op = 'view') was also deprecated with Drupal 6.
  • hook_field_info() now has a 'description' field.
    /**
     * Implementation of hook_field_info().
     */
    function text_field_info() {
      return array(
        'text' => array(
          'label' => t('Text'),
          'description' => t('Store text in the database.'),
        ),
      );
    }
    
  • Additional information is also provided now in hook_widget_info(). Again, you can use the text module as an example.

Widgets

  • In the previous code, all CCK widgets were added to the form at once using content_widget_invoke(), and there was no easy way to get the form element for an individual field. In D6 there is a function that can be used to retrieve a single field's form element -- content_form_field(&$form, &$form_state, $field) will retrieve an entire form element for the requested field. An optional fourth argument, $get_delta, will retrieve only a specific delta value of that field's form element.
  • Widgets that include a file upload element need to add $form['#attributes']['enctype'] = "multipart/form-data"; to the $form array they receive as a parameter in hook_widget.
  • The content module now handles multiple values and default values for all fields unless they opt out in hook_widget_info() by changing 'multiple values' from CONTENT_HANDLE_CORE to CONTENT_HANDLE_MODULE and 'default value' from CONTENT_CALLBACK_DEFAULT to CONTENT_CALLBACK_CUSTOM or CONTENT_CALLBACK_NONE.
    /**
     * Implementation of hook_widget_info().
     *
     * Here we indicate that the content module will handle
     * the default value and multiple values for these widgets.
     *
     * Callbacks can be omitted if default handing is used.
     * They're included here just so this module can be used
     * as an example for custom modules that might do things
     * differently.
     */
    function text_widget_info() {
      return array(
        'text_textfield' => array(
          'label' => 'Text Field',
          'field types' => array('text'),
          'multiple values' => CONTENT_HANDLE_CORE,
          'callbacks' => array(
            'default value' => CONTENT_CALLBACK_DEFAULT,
            ),
        ),
        'text_textarea' => array(
          'label' => 'Text Area',
          'field types' => array('text'),
          'multiple values' => CONTENT_HANDLE_CORE,
          'callbacks' => array(
            'default value' => CONTENT_CALLBACK_DEFAULT,
            ),
        ),
      );
    }
    

There are significant changes in the way that widgets work in the D6 version. We now expect each widget to handle its own processing using FAPI. In older versions, hook_widget() was called multiple times with different ops to do pre processing, create a form, do post processing, do validation, etc. The widget module was responsible for creating multiple values where needed, and deciding when to keep or throw away empty values. Plus the old version never passed the $form array to the widget, so there was no way for the widget to directly manipulate the $form.

Now hook_widget() has no operations, it is only used to place the element into the form. If the widget is leaving the handling of multiple values up to the content module, it will only return a single delta item each time it is called. The widget is passed a reference to the $form and to $form_state that it can use in any way it wants, and it also receives a $delta value that it can use to tell which delta the content module is processing.

The widget could still build out a complete form element in this stage, but the core elements now only create a placeholder at this point, and are built out more completely using #process and are validated using #element_validate. They also use hook_elements() to make themselves available as FAPI element types.

This construct makes it possible to nest and reuse elements. For instance, the nodereference select field uses the optionwidgets select element and the nodereference autocomplete field uses the text element. There are plans to build on this capability to create 'combo' widgets that allow you to combine various elements together into a single widget. Contributed CCK modules can follow this example if they want their widgets to be reusable.

Look at any of the core field modules for examples of how they use hook_elements(), and note that if you use hook_elements() you must also provide a theme for each element.

/**
 * Implementation of hook_widget().
 *
 * Attach a single form element to the form. It will be built out and
 * validated in the callback(s) listed in hook_elements. We build it
 * out in the callbacks rather than here in hook_widget so it can be
 * plugged into any module that can provide it with valid
 * $field information.
 *
 * Content module will set the weight, field name and delta values
 * for each form element. This is a change from earlier CCK versions
 * where the widget managed its own multiple values.
 *
 * If there are multiple values for this field, the content module will
 * call this function as many times as needed.
 *
 * @param $form
 *   the entire form array, $form['#node'] holds node information
 * @param $form_state
 *   the form_state, $form_state['values'] holds the form values.
 * @param $field
 *   the field array
 * @param $delta
 *   the order of this item in the array of subelements (0, 1, 2, etc)
 *
 * @return
 *   the form item for a single element for this field
 */
function number_widget(&$form, &$form_state, $field, $items, $delta = 0) {
  $element = array(
    '#type' => $field['widget']['type'],
    '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL,
  );
  return $element;
}

One other thing to point out is that we now use #columns to identify the column names, rather than hard-coding them into the elements. This is done to make it easier to share and reuse widgets. In the old code, you would have seen $element['value'] = array(...), now you see $element[$field_key] = array(...), where $field_key is a value taken from #columns. This is what makes it possible for both nodereference, which has a column named 'nid', and text, which uses a column named 'value', to use optionwidgets select and checkboxes.

The content module adds the #columns value to the element, so they are available in all #process and #element_validate functions.

The content module also adds #delta to each element, so you can tell which is which in processes and themes.

The field name is available in $element['#field_name'] and the complete field array is available in $form['#field_info'][$element['#field_name']].

/**
 * Process an individual element.
 *
 * Build the form element. When creating a form using FAPI #process,
 * note that $element['#value'] is already set.
 *
 */
function text_textfield_process($element, $edit, $form_state, $form) {
  $field = $form['#field_info'][$element['#field_name']];
  $field_key = $element['#columns'][0];
  $delta = $element['#delta'];

  $element[$field_key] = array(
    '#type' => 'textfield',
    '#title' => t($field['widget']['label']),
    '#description' => t($field['widget']['description']),
    '#required' => $element['#required'],
    '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
    '#autocomplete_path' => $element['#autocomplete_path'],
  );
  return $element;
}

Example field code is available in the /examples/ folder of the module. The examples include an example similar to the core text module, that creates a re-usable form element that uses hook_elements. A simpler example is also available. The simple example creates a complete form element in hook_widget(), rather than using #process, similar to the way the older code works. This simple example doesn't use hook_elements(), and thus does not create re-usable widgets as the core field modules do.

API

The beginnings of an API is available in content_crud.inc, along with a hook_content_fiedapi() that modules can use to intervene when fields are created, read, updated, or deleted. The API is evolving and subject to change at any time, but currently:

The api functions include:

- content_field_instance_create()
- content_field_instance_read()
- content_field_instance_update()
- content_field_instance_delete()
- content_module_delete()

The hook_content_fieldapi() ops are:

- create instance
- read instance
- delete instance
- update instance

Formatters

Formatters are now just wrappers around regular Drupal theme functions. They receive an $element array of values and return a formatted display from those values.

The default behavior of formatters is that they will create a theme for a single field value, as has been done in previous versions of CCK. The D6 version now adds the possibility of creating multiple value formatters, as well. Multiple value formatters will receive all the values of a field so you can, for instance, plot all the values on a map or in a graph.

The 'view' operation (handled by the Content module) constructs the $node in a way that you can use drupal_render() to display the formatted output for an individual field.

i.e. print drupal_render($node->field_foo);

The node array will now look like:

  'Single value' formatter :
   $node->content['field_foo'] = array(
     '#type' => 'content_field_view',
     '#title' => 'label'
     '#field_name' => 'field_name',
     '#node' => $node,
     'items' =>
       0 => array(
         '#theme' => $theme,
         '#field_name' => 'field_name',
         '#type_name' => $node->type,
         '#formatter' => $formatter_name,
         '#item' => $items[0],
       ),
       1 => array(
         '#theme' => $theme,
         '#field_name' => 'field_name',
         '#type_name' => $node->type,
         '#formatter' => $formatter_name,
         '#item' => $items[1],
       ),
     ),
   );
  'Multiple value' formatter :


   $node->content['field_foo'] = array(
     '#type' => 'content_field_view',
     '#title' => 'label'
     '#field_name' => 'field_name',
     '#node' => $node,
     'items' => array(
       '#theme' => $theme,
       '#field_name' => 'field_name',
       '#type_name' => $node->type,
       '#formatter' => $formatter_name,
       0 => array(
         '#item' => $items[0],
       ),
       1 => array(
         '#item' => $items[1],
       ),
     ),
   );

Comments

ztyx’s picture

Looking at the Number CCK field they are calling drupal_load('module', 'content'); at the beginning of hook_install(), hook_uninstall(), hook_enable() and hook_disable(). This is not done in the example above. What is good practice?

j0rd’s picture

Can someone expand or provide docs for creating a formatter. There doesn't appear to be any good documentation that I can find. This is the best page so far.

If anyone knows any other documentation related to formatters it would be appreciated. I'm attempting to creating my own formatters for certain CCK elements, so that I can re-use them for other projects. This includes CCK Imagefield Lightbox2 Formatter and a CCK Location GMaps Formatter.


Drupal Freelancer, Drupal Themes and eCommerce with Ubercart
jhodgdon’s picture

I just published a tutorial article on my web site that presents a complete, working CCK field module (incorporates an image upload, caption, and taxonomy terms), for Drupal 6.x / CCK 2.x. It explains which hooks you need to implement, and what each one is used for, including widgets and formatters. Might be useful... http://poplarware.com/cckfieldmodule.html

I'd also recommend looking at existing Drupal 6.x / CCK 2.x field modules. For instance, the Text module included with CCK is a good example, as is FileField for something a bit more complex. ImageField's formatter might be illuminating.

joshk’s picture

That tutorial contained the magic that was missing for me: theme_modulename_formatter_format_name is the theme function CCK will look for.

------
Personal: Outlandish Josh
Professional: Pantheon

subspaceeddy’s picture

There's no way of saying thanks on your website so I'm doing it here. After spending all day trying to find documentation, looking at other modules that do this, doing a bunch of trial and error, knowing it must be something obvious and generally banging my head on the keyboard I eventually came upon this thread and looked at your tutorial. Fixed it in minutes.

As above, modulename_formatter_format_name in hook_theme was the thing I had missed.

Thanks...

joehudson’s picture

Hi,

I've just called in D6 with CCK 2:
$node = node_load($nid).
I've got a simple CCK text field associated with that node type, I've called it 'field_mytext'.
How do I get the raw text value of that field from $node?

I've tried:
$node->field_mytext
$node->field_mytext['value']
$node->content['field_mytext']['value']
and none work...

joehudson’s picture

ok, I found the solution by using print_r on the $node object.

$node->field_mytext[0]['value']

is what I was looking for!

I did attempt to use the documentation but couldn't find this basic information!

IckZ’s picture

hi, is it maybe possible that you post the full code you use? I want to display a single text field in my user-profile.tpl but i cant find any solution.

best regards.

ac00perw’s picture

JoeHudson is talking about using print_r($node); to get array keys you need. You can also use var_dump($node); to do the same thing. This is currently the simplest way to 'drill down' arrays (it works on $form, too, among others).

It'd be nice if there was an easier way to dig up this info. Does it exist, anybody? A module or CCK plugin or whatnot that reveals array keys/values?

rmiddle’s picture

The devel modules with dsm($node); or dpr($node); the nice thing about these is it doesn't generally show up to regular users so if you have to run a test on a live site for some reason you can do so without taking down the site.

Thanks
Robert

mattlc’s picture

Hi,

Can anyone give me the cck or drupal function (not node_load) for having the 'view' field in the field array like the node object available in the tpl files :

I have :
$node->field_selectX[0]['value']

I want :
$node->field_selectX[0]['value']
AND
$node->field_selectX[0]['view']

Tks for reply.

Matt

grahamgilchrist’s picture

Hi there, I can't remember the syntax off the top of my head, but I believe the content_format function (which is part of cck) is what you are looking for.