Posted by joshk on July 23, 2007 at 8:40pm
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!
Thanks for the heads up.
Very exciting
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
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
https://pantheon.io | http://www.chapterthree.com | https://www.outlandishjosh.com
Will add to install profile api
Something like:
<?phpfunction 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?
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
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
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
https://pantheon.io | http://www.chapterthree.com | https://www.outlandishjosh.com
Can I help?
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
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:
<?phpfunction 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!
As I posted over at 2bits:
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
Today we had a meetup and we were talking about CCK. Apparently we couldn't do this... now we can ;)... yay!
Uninstall hook
Anyone know of a good example of how these content types can be removed in a module's uninstall hook?
remove would be nice
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. :)
Peter Lindstrom
LiquidCMS - Content Solution Experts
and here's how
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();
}
Peter Lindstrom
LiquidCMS - Content Solution Experts
Thank you very much,
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
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
Delete weights too when implementing hook_content_extra_fields()
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. withMYMODULE_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
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
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.inccontains the exported macro andmy_moduledefines themy_typetype inhook_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?
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
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.
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
Error is
<?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
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
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
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
Peter Lindstrom
LiquidCMS - Content Solution Experts
Programmatic Updating
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
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
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
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
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
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
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
But it doesn't seem to export field groups...
cck field groups are
cck field groups are supported in features as of http://drupal.org/node/480978
knaddison blog | Morris Animal Foundation
Thanks :)
Just saw that after commenting and updated my dev environment right away!
.
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.