Programmatic CCK Content Types

Update 28/11/2009: you need this patch to make this working 100%.

The fundament of Drupal is the node entity. In drupal (almost) everything is a node, which is the most elementary piece of content available, that can be “extended” to create new node types, also known as content types.

When you create new Drupal content types in the “classical” way, you do that by creating a new module. This way a content type is portable across different Drupal installations, just by copying and enabling the module in a new CMS instance.

Originally this was the only way to create content types. Then, CCK was created and it changed the life of many people, especially of those who use Drupal just as CMS. However, these people tend to create content types by means of the UI provided by CCK and to store them into the DB. This way a content type is not portable. Plus, it is not really friendly with complex development scenarios where Drupal is heavily used as framework and the code must be deployed across multiples environments.

Fortunately, we can achieve the same flexibility of the classical method, and more, taking advantage of all the CCK features, including the built-in integration with Views. Let’s see how. All you need is the latest versions of Drupal 6, CCK (and, optionally, Views).

Step 0: pick up a name and set up your module

In this post I will be using example_cck_content as name for both the module and the content type. So, let’s create a folder called example_cck_content and underneath that the following files:

  • example_cck_content.module
  • example_cck_content.install
  • example_cck_content.info
  • example_cck_content.def.inc

The first three files are common to almost every Drupal module. The last file will host the exported content type.

Step 1: create your content type using CCK

To create a new CCK content type go to: Administer » Content management » Content types » Add content type. For more information about this, read Getting started with CCK.

Step 2: export your newly created content type

To export the content type we just created, let’s go to Administer » Content management » Content types » Export. Follow the wizard instructions until you get the screen with the code exported (read more details about the steps in this wizard in the Step 2 of this post). The code exported will look like the following

$content['type']  = array (
      'name' => 'Example CCK Content',
      'type' => 'example_cck_content',
    // ... cut code ...
    $content['fields']  = array (
      0 =>
      array (
        'label' => 'Box Image',
        'field_name' => 'field_boximage',
    // ... cut code ...
    $content['extra']  = array (
      'title' => '-5',
      'revision_information' => '20',
      'comment_settings' => '30',
      'menu' => '-2',
    );

Step 3: paste the code in the module

Let’s go back to the module we have created at the Step 0 and open the file example_cck_content.def.inc. In this file we will create a stub function called

function _example_cck_content_cck_export() {

        // code of the exported content type goes here

        return $content;
}

Now, let’s go back to our Drupal site and copy the code from the screen we have got at the Step 2. Then, let’s paste it in place of the comment I put in the stub function above.

Step 4: write the content type create/update function

The function we are about to create will be invoked from both the hook_install and the hook_update_N. It invokes some CCK APIs in order to actually create/update the content type structure into the database.

So, let’s open the example_cck_content.install and let’s write down the following function

function _example_cck_content_save_cck_node($op = 'install') {
    module_load_include('inc', 'example_cck_content', 'example_cck_content.def');
    $content = _example_cck_content_cck_export();

    // we do not want too many modules enabled - the content_copy module is just needed
    // in order to install the content type, so we just require it here (require_once prevent to
    // include it more than once in case it is already enabled)
    require_once './' . drupal_get_path('module', 'content') .  '/modules/content_copy/content_copy.module';

    if ($op == 'install') {
        $form_state['values']['type_name'] = '<create>';
    }
    else {
        // the type_name must be the type_name
        // as specified in the .def.inc file
        $form_state['values']['type_name'] = 'example_cck_content';
    }

    $form_state['values']['macro'] = '$content = ' . var_export($content, TRUE) . ';';

    drupal_execute('content_copy_import_form', $form_state);
    content_clear_type_cache();
}

Step 5: install, uninstall and update_N hooks

Here it comes the interesting part. Like in the classical way, we can implement the hook_install to actually create the exported content type into the database, then implement the hook_uninstall to remove the content type when the module is uninstalled. Plus, a nice addition that other tutorials did not write in the past: the possibility of implementing the hook_update_N. The latter is very important to amend the structure of a content type when this is already on production.

So, let’s keep open the example_cck_content.install and write down the following.

/**
 * Implementation of hook_install
 */
function example_cck_content_install() {
	_example_cck_content_save_cck_node();
}

/**
 * Implementation of hook_uninstall
 */
function example_cck_content_uninstall() {
    // the type_name must be the type_name
    // as specified in the .def.inc file
    node_type_delete('example_cck_content');
}

// EXAMPLE hook_update_N
// every hook_update_N will look the same
// and you will need to write a new one
// every time you update the .def.inc file

//function example_cck_content_update_1() {
//    _example_cck_content_save_cck_node('update');
//    return array();
//}

The very interesting bit is about the hook_update_N. Unlike the classical way, to amend a content type we will be using the CCK interface, by going to Administer » Content management » Content types and then clicking the Edit for our content type. Once done, we will be exporting it again following the Steps 2 and 3. Finally, we will add a hook_update_N exactly the way it is showed in the commented code above. The hook_update_N will all look the same.

Step 6: handling dependencies

One mandatory dependency is the content module, also known as CCK. Plus, depending on which kind of fields you have added to your content type, you might need to make your module dependent on one or more CCK fields module (e.g. filefield, etc.).

In order to do so, we need to specify the dependencies in our example_cck_content.info file:

; $Id$
name = Example CCK Content
description = Provide an example CCK content type
dependencies[] = content
dependencies[] = filefield
dependencies[] = imagefield
dependencies[] = text
core = 6.x

Those are the actual dependencies of the module I created (and that you can download at the bottom of this post).

However, this is not going to work properly (hopefully, until Drupal 7). In fact, since we need those dependencies enabled and installed before our hook_install will be fired, but there is currently no mechanism that ensures that hook_install is run for dependencies before the dependent modules, the installation of our content type might fail if it depends on one or more currently disabled modules.

In order to enforce a stronger dependencies handling, we can only rely on the hook_requirements for the time being. This hook implementation must reside in the example_cck_content.install.

/**
* Implementation of hook_requirements()
*/
function example_cck_content_requirements($phase) {
    $requirements = array();
    $t = get_t();

    // an array of the dependencies
    // where the array key is the module machine-readable name
    // and the value is the module human-readable name
    $dependencies = array(
        'content' => 'Content',
        'filefield' => 'FileField',
        'imagefield' => 'ImageField',
        'text' => 'Text',
    );

    switch ($phase) {
        case 'install':
            $error = FALSE;
            $value = '';
            foreach ($dependencies as $dependency => $module_nice_name) {
                if (!module_exists($dependency)) {
                    $error = TRUE;
                    $value .= $t($module_nice_name . " to be pre-installed; ");
                    $severity = REQUIREMENT_ERROR;
                }
            }

            if ($error) {
                $requirements['example_cck_content'] = array(
                  'title' => $t('Example CCK Content requires: '),
                  'value' => $value . $t(' if the required modules are now installed, please enable this module again.'),
                  'severity' => $severity,
                );
            }
        break;
    }

    return $requirements;
}

Step 7: download and test

You can download the example module I created following this procedure and test that everything works as described. Enjoy!

Previous post:

Next post: