Programatic CCK Now Possible!

Events happening in the community are now at Drupal community events on www.drupal.org.
joshk's picture

I just wanted to point out a very important change to content_copy.module that makes it much more fasible to create CCK field programatically as part of an install profile. As of last week, the DRUPAL-5 CVS version of content_copy.module no longer ends content_copy_import_form_submit() with a drupal_goto().

Previously, if you attemted to import a CCK node-type using drupal_execute(), your script would redirect to an admin page, which is problematic during an install profile as it short-cirtuits the process. This is no longer the case.

You can now run code similar to the following:

<?php
 
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'] = /** YOUR CCK EXPORT DUMP HERE **/
 
drupal_execute("content_copy_import_form", $values);
?>

And continue with your install script. Happy profiling!

Comments

SWEEEET!

jwolf's picture

Thanks for the heads up.

Very exciting

Amazon's picture

Josh, if you have some simple examples I am sure we could start building snippets.

Kieran

To seek, to strive, to find, and not to yield

New Drupal career! Drupal profile builders.
Try pre-configured and updatable profiles on CivicSpaceOnDemand

Real soon

joshk's picture

We'll be making an initial check in of our conference organizing distribution in the next two or three days. This will show how we're using the above code in conjunction with flat text files containing the CCK information.

http://www.chapterthreellc.com | http://www.outlandishjosh.com

Will add to install profile api

boris mann's picture

Something like:


<?php
function install_create_content($macro) {
  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'] = $macro;
 
drupal_execute("content_copy_import_form", $values);
}
?>

Except those include_once's need some love depending on where you have your code, methinks.

Used in an install profile as:

  install_create_content($macro);

Where the best way to maintain the macro would likely be to suck this in from an external file -- e.g. cck_userprofile.inc, etc.

are includes needed?

mikey_p's picture

I may be missing something here, but from my testing a few months back, no includes are needed since the form being called is in content_copy.module and an install profile only executes after a full bootstrap.

Would it be considered bad form to just call content_copy_import_form_submit with the values requried? It wouldn't really be bypassing any submit handlers, just avoiding an extra call to drupal_execute (which is what content_copy_import_form_submit calls anyway).

I'll work on some code to parse an external file if I get a chance. I think there is some applicable code in panels that could be reused ;)

There are a few gotchas here

verbal@drupal.org's picture

I guess this bug had been submitted many times, but my last submission of it finally got it fixed (http://drupal.org/node/160130). When you do an export of a cck type you have to change the exported code a little bit. Most notably, the export will use single quotes so you cant wrap your string in single quotes, so you can use either double quotes or heredoc syntax. Also in PHP when using variables inside a heredoc statement or double quotes, the variable will get replaced and not be taken as the literal string, for example:

<?php
  $var
= 1;
  print
"$var"; // 1
 
print '$var'; // $var
?>

So we have to escape our variables. So a dump of a simple cck type named 'topics', which has 'title' and 'description' would be:

$content[type]  = array (
          'name' => 'Topic',
          'type' => 'topic',
          'description' => 'A Topic is the overarching category which contains Goals.',
       'title_label' => 'Title',
       'body_label' => 'Description',
          'min_word_count' => '0',
        'help' => '',
       'node_options' =>
        array (
          'status' => true,
         'promote' => false,
           'sticky' => false,
            'revision' => false,
        ),
         'comment' => '2',
       'upload' => '1',
        //'event_nodeapi' => 'never',
       'upload_inline' => 0,
         'old_type' => 'topic',
          'orig_type' => '',
          'module' => 'node',
         'custom' => '1',
        'modified' => '1',
          'locked' => '0',
      );

And the code to submit this programatically:

<?php
//////// define the 'topic' node type ///////
$form_values['type_name'] = '<create>';
 
$form_values['macro'] = <<<TOPICS
       \$content[type]  = array (
       'name' => 'Topic',
          'type' => 'topic',
          'description' => 'A Topic is the overarching category which contains Goals.',
       'title_label' => 'Title',
       'body_label' => 'Description',
          'min_word_count' => '0',
        'help' => '',
       'node_options' =>
        array (
          'status' => true,
         'promote' => false,
           'sticky' => false,
            'revision' => false,
        ),
         'comment' => '2',
       'upload' => '1',
        //'event_nodeapi' => 'never',
       'upload_inline' => 0,
         'old_type' => 'topic',
          'orig_type' => '',
          'module' => 'node',
         'custom' => '1',
        'modified' => '1',
          'locked' => '0',
      );
TOPICS;
drupal_execute('content_copy_import_form', $form_values);
?>

Note: if your content type has additional fields it would have a 2 variables that need to be escaped, $content[type] and $content[fields]

I didn't know how to do heredoc syntax until chx showed me a snip with some heredoc in it, and I got caught up because i was indenting the the close tag "TOPICS;" and that needs to be on its own line and NOT indented.

-Steve

Yes

joshk's picture

Gotta watch out handling text. I am actually pursuing the method of keeping the cck export data as a flat text files and packaging that as part of the install profile.

http://www.chapterthreellc.com | http://www.outlandishjosh.com

Can I help?

verbal@drupal.org's picture

I've been trying to get into to development for the community for a while. I have been doing private development for my job, but nothing I am allowed to contribute back ( I know.... I don't like it either ). I've already got a few ideas of how to accomplish this.
- With cck in core, we could think about adding an additional profile hook. Something like _profile_content_types() which would search the profile folder for cck text file exports and import those types.
- Another idea I had was to check the array returned by _proifle_modules() and use this to define content types as well. So while going through the returned array turning on modules, if the modules wasn't found via standard module means, check the profile folder for a cck text file that would create the type.

-Steve

Can't get this to work

wonder95's picture

Okay, I've been fighting with this for a couple weeks now and have been trying to follow this, but I can't get it to work. If I had much hair left, I'd have pulled it out by now. I'm trying to create a CCK content type as part of a module install, but no matter which way I go and how I follow this example, I get an error.

Here's what I have to this point:

<?php
function imagelist_install() {
  drupal_set_message('Installing imagelist');
  $form_values['type_name'] = '<create>';
  $form_values['macro'] = <<<IMAGES
    \$content[type]  = array (
      'name' => 'Image',
      'type' => 'image',
      'description' => 'Image to be displayed',
      'title_label' => 'Title',
      'body_label' => 'Description',
      'min_word_count' => '0',
      'help' => 'Size must be less than 1.5 MB',
      'node_options' =>
      array (
        'status' => true,
        'promote' => false,
        'sticky' => false,
        'revision' => false,
      ),
      'comment' => '2',
      'old_type' => 'image',
      'orig_type' => '',
      'module' => 'node',
      'custom' => '1',
      'modified' => '1',
      'locked' => '0',
    );
    \$content[fields]  = array (
      0 =>
      array (
        'widget_type' => 'image',
        'label' => 'Photo',
        'weight' => '0',
        'max_resolution' => 0,
        'image_path' => 'photos',
        'custom_alt' => 0,
        'custom_title' => 1,
        'description' => '',
        'group' => false,
        'required' => '1',
        'multiple' => '0',
        'field_name' => 'field_photo',
        'field_type' => 'image',
        'module' => 'imagefield',
      ),
    );
IMAGES;
  drupal_execute('content_copy_import_form', $form_values);
}

If I run it like this, I get the following error when I submit the Module admin page with my module checked:

    * Installing imagelist
    * The configuration options have been saved.
    * Installing imagelist
    * The configuration options have been saved.
    * Installing imagelist
    * An error has occured adding the content type image.
      Please check the errors displayed for more details.
    * The configuration options have been saved.

    * The import data is not valid import text.
    * warning: Cannot modify header information - headers already sent by (output started at C:\Program Files\Apache Software Foundation\Apache2.2\htdocs\test\sites\all\modules\cck\content_copy.module(251) : eval()'d code:1) in C:\Program Files\Apache Software Foundation\Apache2.2\htdocs\test\includes\common.inc on line 309.
    * The import data is not valid import text.
    * warning: Cannot modify header information - headers already sent by (output started at C:\Program Files\Apache Software Foundation\Apache2.2\htdocs\test\sites\all\modules\cck\content_copy.module(251) : eval()'d code:1) in C:\Program Files\Apache Software Foundation\Apache2.2\htdocs\test\includes\common.inc on line 309.
    * warning: call_user_func_array() [function.call-user-func-array]: First argument is expected to be a valid callback, 'node_type_form' was given in C:\Program Files\Apache Software Foundation\Apache2.2\htdocs\test\includes\form.inc on line 217.

Everything within the heredoc tags is a straight export from a working CCK type. Can anyone explain what I could be missing that is causing the error?

Thanks.

I'm liking it!

mlncn's picture

As I posted over at 2bits:

How big a step [might this be] toward inline creation of nodes (say I'm creating a user profile node and I can create my organization profile node at the same time if it doesn't exist already)?

And it seems to get Drupal closer to my other dream: registering (or logging into) a site simultaneous with posting content for the first time...

Now I've put in my four cents twice, but I can't be alone in wanting more, more, more ;-)

~ ben melançon

member, Agaric Design Collective
http://AgaricDesign.com - "Open Source Web Development"

benjamin, agaric

lol - you proved us wrong

dmitrig01's picture

Today we had a meetup and we were talking about CCK. Apparently we couldn't do this... now we can ;)... yay!

Uninstall hook

colan's picture

Anyone know of a good example of how these content types can be removed in a module's uninstall hook?

remove would be nice

liquidcms's picture

Drupal 6 -

My module isn't importing an entire node type; just a few fields to any existing node type - i.e. i have a check box on the node type admin form which goes with a custom submit callback which eventually runs only this code:

  $form_state['values']['type_name'] = $type;
  $form_state['values']['macro'] = file_get_contents(drupal_get_path('module', 'capsa') . "/cck-CAPSA-fields.txt");
  content_copy_import_form_submit(null, $form_state);

and, my new fields are added to $type (passed from submit callback) - no seriously.. thats it, 3 lines.. :) .

Would be really cool if some had already coded the "remove the fields" routine for me. :)

and here's how

liquidcms's picture

for Drupal 6, i use this to read the same CCK fields export file and REMOVE those fields. I trigger this off a checkbox on the node type's admin page. As mentioned above, submit callback when unchecked calls this function:

// removes field definitions from the node Type
//    NOTE - does NOTE remove content
function _remove_cck_fields($type) {
  $file = file_get_contents(drupal_get_path('module', 'capsa') . "/cck-CAPSA-fields.txt"); 
  @eval($file);
 
  // remove groups
  foreach($content['groups'] as $group) {
    $groups[] = "'" . $group['group_name'] . "'";
  }
  $groupstr = join(",", $groups);
  db_query("DELETE FROM {content_group} WHERE type_name = '%s' AND group_name IN ($groupstr)", $type);
  db_query("DELETE FROM {content_group_fields} WHERE type_name = '%s' AND group_name IN ($groupstr)", $type);
 
  // remove fields
  foreach($content['fields'] as $field) {
    $fields[] = "'" . $field['field_name'] . "'";
  }
  $fieldstr = join(",", $fields);
  db_query("DELETE FROM {content_node_field_instance} WHERE type_name = '%s' AND field_name IN ($fieldstr)", $type);
 
  // clean up secondary tabs on manage fields page
  drupal_flush_all_caches();
}

Thank you very much,

haggins's picture

Thank you very much, Peter. You saved me a lot of time and hair! :)

Removed cck fields and groups in a module's uninstall hook

nobarte's picture

Drupal 6, CCK 6.x-2.9

<?php
function MYMODULE_uninstall() {
 
$t = get_t();

 
// CONTENT FIELDS
 
$content = content_types('MYNODETYPE');
  if (
count($content['fields']) > 0){
   
// Load up cck crud interface
   
module_load_include('inc', 'content', 'includes/content.crud');
    foreach(
$content['fields'] as $key => $field){
     
content_field_instance_delete($key, 'MYNODETYPE', FALSE);
    }
   
drupal_set_message($t('cck fields have been deleted from MYNODETYPE content type'));
  }
 
// flush caches
 
content_clear_type_cache(TRUE);
 
menu_rebuild();
 
 
// GROUP FIELDS - using fieldgroup module functions!
 
$groups = fieldgroup_groups('MYNODETYPE');
  if (
count($groups) > 0){
    foreach(
$groups as $group){
     
fieldgroup_delete('MYNODETYPE', $group['group_name']);
    }
   
drupal_set_message($t('group fields have been deleted from MYNODETYPE content type'));
  }

// ... other content...
}
?>

--
Bartosz Nowicki

Sk8erPeter's picture

Your code works correctly, thank you for that!

But e.g. when defining a new normal node type, and implementing hook_content_extra_fields() in your own module e.g. with MYMODULE_content_extra_fields() to let extra fields be shown and let them be rearranged on "Manage fields" interface, an extra "content_extra_weights_MYNODETYPE" row gets also created in "variable" table. You should delete it too.

So an example of implementing hook_content_extra_fields():

<?php
// MYMODULE.module file

/**
* Implements hook_node_info()
* - providing information (metadata) about our node types
*
* @see http://api.drupal.org/api/drupal/developer--hooks--node.php/function/hoo...
*/
function MYMODULE_node_info() {
   
// now we define only one content type

   
return array(
     
'MYNODETYPE' => array(
       
'module' => 'MYMODULE', // this could be other too when defining multiple content types...
       
'name' => t('My node type'),
       
'description' => t('You can create a new instance of my node type here!'),
       
'has_title' => TRUE,
       
'title_label' => t("My node type's title"),
       
'has_body' => TRUE,
       
'body_label' => t("Description of my node type"),
       
'min_word_count' => 2,
       
'locked' => TRUE,
      ),
    );
}

// ...

/**
* Implements hook_content_extra_fields().
*
* Adds the "MYEXTRAFIELD" extra field
*/
function MYMODULE_content_extra_fields($type_name){
   
$extra = array();
   
$type = node_get_types('type', $type_name);

    if (
$type->module == 'MYMODULE') { // it's equal to MYMODULE if this is the appropriate returned array key in MYMODULE_node_info(), 'MYNODETYPE' ...

       
$extra['MYEXTRAFIELD'] = array(
         
'label' => t('My extra field'),
         
'description' => t('This is the description of my extra field.'),
         
'weight' => -1
       
);
    }
}

// ...
?>

You can do delete the extra field like this:

<?php
// ...
function MYMODULE_uninstall(){
   
// ...

   
$node_type = 'MYNODETYPE';

   
// delete CCK weights if implementing hook_content_extra_fields()! (e.g. 'content_extra_weights_MYNODETYPE')
   
db_query("DELETE FROM {variable} WHERE name LIKE 'content_extra_weights_" . $node_type . "%%' ");
   
$deleted_CCK_weight_variables = db_affected_rows();

    if (!empty(
$deleted_CCK_weight_variables)) {
       
$message = $t('<strong>@deleted_CCK_weight_variables</strong> CCK extra weight variables deleted from "variable" table.', array('@deleted_CCK_weight_variables' => $deleted_CCK_weight_variables));
       
drupal_set_message($message);
    }

   
// ...
}
?>

Is this possible outside an

lefnire's picture

Is this possible outside an install script? When I run the function from my module's functions, the content types don't get created.. but it works fine in an install script.

Reason I want to do it in a module is to create a content-type that represents the fdf fields of an uploaded PDF, so it's done on a regular basis not just on install.

programmatically create CCK fields

davidwhthomas's picture

This function does it for me:

<?php
/**
* Programmatically create CCK fields and types using the content copy module
* @param $type string
* content type to create, defaults to new type, if type exists, only fields will be added
* @param $macro array
* exported array from content types -> export. If file is not specified, macro will be used
* @param $file string
* path to file containing content copy exported macro data structure. no escaping needed.
*/
function base_create_content($type = '<create>', $macro = '', $file = '') {
  if(!
module_exists("content_copy")){
   
drupal_set_message('Programmatically creating CCK fields requires the Content Copy module. Exiting.');
    return;
  }
 
$values = array();
 
$values['type_name'] = $type;
 
//get macro import data, prefer file first
 
if($file){
    if(
file_exists($file)){
     
$values['macro'] = file_get_contents($file);
    }else{
     
drupal_set_message('Unable to read input file for import. Exiting.');
      return;
    }
  }elseif(
$macro){
   
$values['macro'] = $macro;
  }
 
//include required files
 
include_once './'. drupal_get_path('module', 'node') .'/content_types.inc';
  include_once(
'./'. drupal_get_path('module', 'content') .'/content_admin.inc');
 
//import content by executing content copy import form and passing macro
 
drupal_execute("content_copy_import_form", $values);
}

?>

I then call it like:

<?php
 
//use absolute path to include file
 
base_create_content($type = 'my_type', $macro = '', $file = realpath('.') . '/sites/all/modules/my_module/cck/fields.inc');
?>

where fields.inc contains the exported macro and my_module defines the my_type type in hook_node_info. You could also make 'my_type' into '<create>' and create a new type.

hope that helps.

It's a good feature :-)
DT

Where should i Define base_create_content function?

deepthistars's picture

Hi Davidwhthomas,

Where should i write that base_create_content function and from where i need to call

base_create_content function?

Thanks

can any one help me to do this in D6

kuldip's picture

Hello Experts..

I am developing installer profile in Drupal 6 , i want to import my CCK's from the text file

i have tried this code but its not working for me,

<?php
 
include_once('./'. drupal_get_path('module', 'node') .'/content_types.inc');
  include_once(
'./'. drupal_get_path('module', 'content') .'/includes/content.admin.inc');

 
$values = array();
 
$values['type_name'] ='<create>';
 
$values['macro'] = implode("\n", file(dirname(<strong>file</strong>)."/cck_import.txt"));
 
drupal_execute("content_copy_import_form", $values);
?>

so please help me....

In D6 Form API has changed.

jcisio's picture

In D6 Form API has changed. Try this:

<?php
  $form_state
= array(
   
'values' => array(
     
'type_name' => '<create>',
     
'macro' => $content,
    ),
  );
  include_once(
'./'. drupal_get_path('module', 'node') .'/content_types.inc');
  include_once(
'./'. drupal_get_path('module', 'content') .'/includes/content.admin.inc');
 
drupal_execute('content_copy_import_form', $form_state);
 
content_clear_type_cache();
?>

--
[vi] www.thongtincongnghe.com
Trang tin điện tử về CNTT, Viễn thông, Điện tử...

in D6 i have tried this but not working

kuldip's picture

Error is

* warning: Missing argument 2 for drupal_retrieve_form() in /var/www/d6/includes/form.inc on line 318.
* warning: Missing argument 3 for drupal_process_form(), called in /var/www/d6/includes/form.inc on line 295 and defined in var/www/d6/includes/form.inc on line 389.

<?php
$content
= implode("\n", file(dirname(<strong>file</strong>)."/cck_files/custom_cck.txt"));

$form_state = array(
   
'values' => array(
     
'type_name' => '<create>',
     
'macro' => $content,
    ),
  );
  include_once(
'./'. drupal_get_path('module', 'node') .'/content_types.inc');
  include_once(
'./'. drupal_get_path('module', 'content') .'/includes/content.admin.inc');
 
drupal_execute('content_copy_import_form', $form_state);
 
content_clear_type_cache();
?>

I have try this code but it gives above error/warning message

my custom_cck.txt file contains exported text of the custom CCK

Thanks... for the reply

This works for me in Drupal 6

eikes's picture

You don't need the includes. (Also the module that does the importing is now called content_copy)

I put this in the mymodule.install file and the export result in a file called mymodule.cck

<?php
function mymodule_enable() { 
  
// Get the files content
  
$filename = drupal_get_path('module','mymodule') . "/mymodule.cck";
 
$content = implode ('', file ($filename));
  
  
// Build form state
   
$form_state = array(
      
'values' => array(
           
'type_name' => '<create>',
           
'macro' => $content,
      ),
     );

  
// Put it in there
    
drupal_execute("content_copy_import_form", $form_state);

}
?>

Thanks

Brigadier's picture

Thanks, this worked for me in my custom install profile. My code's only slightly different:

<?php
   
// Create story as CCK content type, contents of file come from CCK export
   
$content_type = file_get_contents('profiles/custom/story.cck');
   
   
// Build form state
   
$form_state = array(
     
'values' => array(
          
'type_name' => '<create>',
          
'macro' => $content_type,
     ),
    );
   
// content_copy is a module for importing & exporting CCK types
   
drupal_execute("content_copy_import_form", $form_state);
   
content_clear_type_cache();
?>

better solution

liquidcms's picture

I have posted a better interim solution for handling this until the content_copy export routine gets fixed for noderef fields. You can check out code here: http://drupal.org/node/128038#comment-921630

Peter Lindstrom
LiquidCMS - Content Management Solution Experts

Programmatic Updating

mikemccaffrey's picture

This is a great method to programmatically add cck types and fields. However, what is the best way to then alter them slightly in update functions? How can you change something small like a field's allowed values, without resorting to deleting and recreating the whole field?

change something small like a field's allowed values

kleve's picture

Hi

Did you find a solution for your question "How can you change something small like a field's allowed values, without resorting to deleting and recreating the whole field" ?

//Andreas

Could anyone please explain

epg's picture

Could anyone please explain where the import from cck code snippet gets called from?

I put the import cck function (like the one suggested by Eikaa) in in a thisismymodule.inc file in the thisismymodule module directory.

Now where do I call it from? Should it be from thisismymodule_install()? Or something else? Or do you just drop it in thisismymodule.install and not call it? I don't understand when/where/by whom the thisismymodule_enable() function gets called...

Php and module newbie here.

Another question: How do you

epg's picture

Another question: How do you then delete the node type you imported at uninstall? If I do:

function thisismymodule_uninstall()
{
    node_type_delete('thisisthenameofthenode');
}

The content fields are deleted but the node information (implemented in thisismymodule_node_info() ) still shows up in the "Create Content" page...

Programmatic create and update CCK Content Types

Vincenzo's picture

To those who might be interested, the following is my HowTo to create and update CCK Content Types programmatically:

http://neminis.org/blog/drupal/programmatic-cck-content-types-updated/

You should follow the links therein to understand why it does depend of a little module I wrote (and I am just about to submit to drupal.org/project).

Cheers,

Vincenzo

PS: @epg my HowTo also includes a fix for that; you just need call menu_rebuild() after that.

Ok guys, I am still having

itstoshi's picture

Ok guys, I am still having this problem. I really need this to create some content types. Am not sure what am doing wrong but am getting this error "The import data is not valid import text.".. I have the 6.14 and the latest CCK module.

Thanks

Features Module

mikemccaffrey's picture

I'd seriously suggest checking out the Features module for moving content types and adding them programatically. It make things much much easier than trying to hack it into code on your own.

Features is easy to work with

redben's picture

But it doesn't seem to export field groups...

cck field groups are

greggles's picture

cck field groups are supported in features as of http://drupal.org/node/480978

Thanks :)

redben's picture

Just saw that after commenting and updated my dev environment right away!

.

Raf's picture

Anyone here know if it's possible to use just field types in modules? I need to use a file upload with description field and "add more" button in a module of mine and need to add it through the form API. The only things I can find to get that field, is through exporting the entire content type. I don't know how to get just the field I need from there and convert it to something the FAPI can use.

Distributions

Group organizers

Group notifications

This group offers an RSS feed. Or subscribe to these personalized, sitewide feeds:

Hot content this week