Programmatically Create, Insert, and Update CCK Nodes

Submitted by doug on October 21, 2007 - 6:42pm

I am working on a new module (uscongress module). The basics of the module is that it imports data and makes it available via CCK nodes. The dataset is a common one (U.S. Congressional Bills) that many people might want to develop applications around. I decided to try to implement the data using CCK rather than as a custom data model, thinking that CCK nodes would be more flexible to application developers.

It has posed three important development hurdles.

Create Content Types

Export the content type from CCK (admin/content/types/export), save the array in an array, and then pass it to the function below. This improves upon the previously known techniques, in that the exported CCK types no longer need to be escaped.


function _install_create_content($content) {
  $type = $content['type']['type'];
  global $_install_macro;
  $_install_macro[$type] = $content;
  include_once './'. drupal_get_path('module', 'node') .'/content_types.inc';
  include_once('./'. drupal_get_path('module', 'content') .'/content_admin.inc');
  $macro = 'global $_install_macro; $content = $_install_macro['. $type .'];';
  drupal_execute('content_copy_import_form',
    array('type_name' => '', 'macro' => $macro));
  content_clear_type_cache();
}

Inserting Nodes

See Quick and Dirty CCK Imports.

The correct way to insert and update nodes is with drupal_execute, rather than node_submit because it handles form alter and form validation.

Debug the node edit form by placing a print_r($node) in node.module at the top of node_submit, edit node/add/yourtype, submit the form, remove your debugging print_r, then create your values array to match these values.


$node->type = 'yourtype';
$values = array();
$values[...] = ...;
drupal_execute('yourtype_node_form', $values, $node);
$errors = form_get_errors();
if (count($errors)) {
  // do something ...
}

Updating Nodes

When updating nodes, you'll need to load the node using node_load, but then before updating the values, the default values need to be pre-populated from the node.


$node = node_load($nid);
_content_widget_invoke('prepare form values', $node);

SQL

Don't assume the database tables and columns are, even if they are created in by your module. As CCK fields, the administrator can add them to other tables, in which case, the single instance fields become multiple instance fields, and they database references change.

Always use content_database_info to get the database info. For example:


  $field1 = content_database_info(content_fields('yourfield1', 'yourtable'));
  $table1 = $field1['table'];
  $column1 = $field1['columns']['value']['column'];

  $field2 = content_database_info(content_fields('yourfield2', 'yourtable'));
  $table2 = $field2['table'];
  $column2 = $field2['columns']['value']['column'];

  if ($table1 == $table2) {
    $sql = "SELECT n.*, t1.$column1, t1.$column2 FROM {node} n ";
    $sql .= " INNER JOIN {$table1} t1 ON t1.nid = n.nid AND t1.vid = n.vid";
  }
  else {
    $sql = "SELECT n.*, t1.$column1, t2.$column2 FROM {node} n ";
    $sql .= " INNER JOIN {$table1} t1 ON t1.nid = n.nid AND t1.vid = n.vid";
    $sql .= " INNER JOIN {$table2} t2 ON t2.nid = n.nid AND t2.vid = n.vid";
  }
  $sql .= " WHERE n.nid = %d";

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Submitted by Wim Leers on October 21, 2007 - 11:54pm.

I've been using this snippet:

/**
* Helper function to import a CCK content type definition from a text file.
*
* @param $cck_definition_file
*   The full path to the file containing the CCK definition.
*/
function _create_content_type($cck_definition_file) {
  include_once('./'. drupal_get_path('module', 'node') .'/content_types.inc');
  include_once('./'. drupal_get_path('module', 'content') .'/content_admin.inc');
  $values = array();
  $values['type_name'] = '<create>';
  $values['macro'] = file_get_contents($cck_definition_file);
  drupal_execute("content_copy_import_form", $values);
}

This allows you to export your CCK definition, put it in a file and then simply import it from there. I'm not sure why you're using global variables.

http://snipplr.com/view/3891/import-cck-definition-from-text-file/

Submitted by doug on October 22, 2007 - 6:43am.

Wim, thanks for your comments, but I think your missing the point. I'm introducing a new technique here. The old technique required that the exported $cck_definition was a string. My technique uses the $cck_definition as a php array, just as it comes out of the CCK export interface.

In your example it is in an external file and must be read in with some php function like fread(). In most examples I've seen, the string actually must escape all dollar ($) references.

There are two disadvantages to the old way: (1) escaping variables is a pain, (2) putting php code in strings is error prone.

The advantage to my method is that the opposites of the disadvantages of the old method. You don't have to escape variables and you get php parsing of your code. Just copy the php code directly as it comes out of the CCK export box, put it into a new function, return it, and pass this to the new function.

Submitted by michaelkirk on February 13, 2008 - 2:18pm.

I'm using this method to import nodes that have file attachments, but after much stress, I can't figure out how to take care of that. Have you any ideas?

Submitted by greggles on February 20, 2008 - 5:54am.

I kept getting errors for "illegal choice detected, contact admin" which was not very helpful.

There is a slight update to the code presented above which is visible in the uscongress.install - http://cvs.drupal.org/viewvc.py/drupal/contributions/modules/uscongress/...

<?php
function _uscongress_install_create_content($content, $type_name = '<create>') {
 
$type = $content['type']['type'];
  global
$_uscongress_install_macro;
 
$_uscongress_install_macro[$type] = $content;
  include_once
drupal_get_path('module', 'node') .'/content_types.inc';
  include_once
drupal_get_path('module', 'content') .'/content_admin.inc';
 
$macro = 'global $_uscongress_install_macro; $content = $_uscongress_install_macro['. $type .'];';
 
drupal_execute('content_copy_import_form', array('type_name' => $type_name, 'macro' => $macro));
 
content_clear_type_cache();
}
?>

Submitted by darrenmUK on April 23, 2008 - 1:54am.

There's a facet to the create node process, in that I couldn't get it to import images using the $values[] array.

I found that I could create all the CCK fields by assigning them fields in the $value[] array, but for imagefield values I had to instantiate them in the $node object instead.

Here's my code for importing imagefields, take from an XML/JPEG import script:

<?php
  $node
->type = 'album';
 
$node->field_cover_image = array(
    array(
     
'fid' => 'upload',
     
'title' => (string)$xml->album->album_title,
     
'filename' => $img_file->filename,
     
'filepath' => $img_file->filepath,
     
'filesize' => $img_file->filesize,
    ),
  );
 
$values = array();
 
$values['field_album_upc'][0]['value'] = (string)$xml->album->album_upc;
 
$values['title'] = (string)$xml->album->album_title;
 
$values[...] = ...; // rest of CCK fields created here
 
 
drupal_execute('album_node_form', $values, $node);
?>

Submitted by Goofy2k on September 7, 2008 - 4:37am.

I have defined a CCK type and have a set of nodes of that type, partly filled with data. When a new user comes in, I want to create a copy of that set, make the new user author of those nodes. All of this needs to be done programmatically.

The new user can fill in the rest of the data later into the set of nodes. So, in fact I want to clone a set of existing CCK nodes (1 type) and change authorship (and maybe some other fields).

I am a relative newbie on this. I'm using Drupal 5.x. Could you point out if and how I can use the way of working that you propose.

From your article the following is not clear to me:

Create Content Type: are you really creating the content type, or are you creating nodes of that type? Where to copy the exported array data into your code?

Should I use the Create Content Type procedure or the Inserting Nodes procedure to do my thing?

thanks
Goofy2k

Submitted by adamtyoung on January 28, 2009 - 3:12pm.

I have tried the update method, using _content_widget_invoke, with no success. Here is my code (abridged):

$reg_nid = '21814';
if ($node = node_load($reg_nid, NULL, TRUE)) {
  $node->log = t('Updated by sims_import.module on '. date('g:i:s a'));
  _content_widget_invoke('prepare form values', $node);
  $values = (array) $node;
  // value of cck field that I want to update
  $values['field_reg_status'][0]['value'] = 'DROP';

  drupal_execute('registration_node_form', $values, $node);
  $errors = form_get_errors();
  if (count($errors)) {
    return "Errors occurred while changing registration.";
  }
}

Any help would be grealy appreciated.

Submitted by jmorahan on February 3, 2009 - 6:34am.

The SQL example is the best example I've seen of how to use content_database_info() - thanks! But there's one small mistake (actually 3 but they're all the same):

$sql .= " INNER JOIN {$table1} t1 ON t1.nid = n.nid AND t1.vid = n.vid";

PHP's string interpolation will swallow the {} around the variable, so you won't get table prefixing. This could change to

$sql .= " INNER JOIN {". $table1 ."} t1 ON t1.nid = n.nid AND t1.vid = n.vid";

Submitted by toemaz (not verified) on February 24, 2009 - 7:15am.

Thanks greggles, it worked out nicely.

Submitted by Chris Johnson (not verified) on March 27, 2009 - 2:21pm.

I receive a fatal PHP error when try the function posted in the original message and in greggle's follow-up:

Fatal error: Only variables can be passed by reference in /Sites/d6trunk/modules/foo/foo.install on line 98

Is this because the code in this posting is for D5 and I'm using D6?

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <p> <a> <em> <strong> <cite> <ul> <ol> <li> <dl> <dt> <dd> <img> <b> <i> <h2> <h3> <pre> <br> <font> <hr> <s> <strike> <code> <span> <blockquote> <acronym> <del> <ins> <table> <th> <tbody> <tr> <td> <div>
  • Lines and paragraphs break automatically.
  • You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.
  • Web page addresses and e-mail addresses turn into links automatically.

More information about formatting options