I have been inspired by some work done by Moshe Weitzman in his macro module to create a module that will add in the Views-like import/export of CCK field definitions. I am attaching a zip file that has a content_copy.module and its info file. Install the module and go to the 'Manage Fields' tab for any content type and you will see import and export tabs. The export allows you to select one or all fields for that content type to be exported. The export works by submitting the field settings form and recording a macro of their form values. The export results can be copied and pasted into another content type as an import, either in the same database or elsewhere.

This is a partial response to http://drupal.org/node/98895 and http://drupal.org/node/80600. Running a macro is a great idea, but you have to turn it on and save the values when you create each field, so if you need to tweak and test your fields before you know they are working right, that won't help a lot. The import/exportapi module seems like it will be the best way to copy data from one place to another and it does have some provision for copying field definitions, but may still need some work and the module far too complex for me to easily figure out what might need to be fixed, and I could see from the work that Moshe has done that there was a fairly simple way to get this functionality working now.

There is some error checking and validation done, but that probably needs more work. It won't add fields to content types that already have those fields, since fields cannot exist twice in the same content type, and it tests that the imported field modules are enabled. It also checks whether a field already exists in the database to know whether to add it as a separate or shared field.

This is Drupal 5 only since it relies heavily on new features and functions only available in that version. If it seems to work well, I propose this as an addition to CCK core. It is a separate module, so it can be installed or not, depending on whether you anticipate needing this functionality, or you can install it when building a database, then uninstall it. If it doesn't seem to belong in the core CCK package, I can put it into a separate module. If it seems too duplicative of other modules, I'll just drop it, but I thought I'd make it available for discussion.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

yched’s picture

I'm currently testing this and going through the code. This is rather spectacular.
As I answered to Moshe in http://drupal.org/node/58153#comment-156928, I thought that implementing import / export trhough the (yet to complete) CRUD API would be more solid than through programmatic forms. I'm not so sure right now, I guess we can debate this when the API is done (will have to get done one day, right ?)

Anyway, _this_ is working now, and quite beautifully too. We should probably ask for JonBob's approval, but IMO this belongs to the core CCK package. Kudos, and thanks to Moshe as well.

I'm marking http://drupal.org/node/58153 as a duplicate.

yched’s picture

FileSize
11.94 KB

As my previous post shows, I was so baffled at this that I could not resist to dive into the code.
Here are a few remarks / suggestions - some of them are related to the code in devel's original 'generic' macros.
Maybe you're working yourself on this and I'm not bringing anything new, but, well, here's my review - and a modified version of the module for a more tangible illustration.

- I don't get the if ($step < 2){ } part : moving the declarations in this branch to the switch ($step) case 1: seems to work the same ?

- It seems the 'recorded' form does not need to go through its regular '_content_admin_field_submit' callback.
In the _form_alter, the '#submit' property could just be overridden to have the form go directly to content_copy_record_macro.

- I'm not sure I get the field_name generation part in content_copy_record_macro.
Don't we already have a name for the field ? I'm probably missing something, though.

- In the exported code :
The 'parameters' and 'form_id' parts could be removed : we already know the form id, and we can get the other info from the 'field' part.
Plus in the 'field' part, the 'submit', 'form_id' and even 'type_name' can be removed.
So the export result would be something like

$field['field_field1'] = array(
  'widget-type' => ...
  'label' => ...
  ...
);
$field['field_field2'] = array(
  'widget-type' => ...
  'label' => ...
  ...
);

That is, nothing more nothing less than a list of fields definitions. Cleaner to the user's eyes, and more open to subsequent (?) module-defined content types...

- the include of install.inc during import does not seem required

- and that's all, actually... I'm not sure what additional validation could be made during import, aside from what is already done. You get the field validation errors in case something is wrong (even though the error messages might be puzzling when you don't actually have the form in front of you, but I guess this is a downside of programmatic form submission anyway)

KarenS’s picture

I haven't yet tested your alternative patch, but I'll explain the reasons behind the items you mentioned:

First, I wasn't quite sure where best to put the import/export option. I started out putting it under the 'Fields' tab in the content type overview (so when you chose 'Fields' you saw sub-tabs showing 'List' (the current screen), 'Export', and 'Import'. In that spot, my export form needed three steps, a first step to select a content type, the second to display the fields from that type, and then to display the export script. I then decided it felt awkward in that location and moved it to the 'Manage Fields' tab for each content type. In that location you don't need to select a content type, so the first step needed to be dropped, which is why there was some odd looking cruft for the submit buttons, etc.

However, I later started thinking it might be nice to expand on this a bit to export and import both a content type and its fields -- so that the export would export content type info as well as field info. Then on the import you could choose to add the exported fields to a selected content type, as you can do with the original module, or create a new content type along with the fields so the end user doesn't even need to create a content type. If we had the module do that, it needs to be moved out of the 'Manage Fields' section and back to the content types overview section, and we'll again need to offer an option to select a content type on both the import and the export, since you won't know by the url which type to use.

Second, I preserved the output and input methods used by the macro module, even though not everything about them is needed. I did that partly for consistency with the macro module, since it appears from discussions in the related issues that there is a possibility that it will end up in core, and partly because if we later expand the functionality to export and import content type info as well as field info, we'll need more of that information.

Anyhow, when I get a chance, I'll take a look at what you've come up with. It may be that I'm getting too ambitious and we should just leave it simple. I was thinking ahead to these other options, but knew the explanation would make little sense to anyone who hadn't dug into the code, so I left all that out in my original post.

KarenS’s picture

FileSize
15.54 KB

To illustrate what I meant about exporting the content type as well as the fields, I have updated the module. You still need the .info file included in the first zip file. To use this version, go to admin/content/types and you will see a list of content types, along with Import and Export tabs. The export now exports the content type definitions as well as field definitions. The import has a new option where you can select the type to import into or <Create> a new type with the imported fields.

liquidcms’s picture

Any chance there is a content export/import module for 4.7?

yched’s picture

Most probably not. As Karen said, this module relies heavily on 5.0 forms API.
But you should be able to get the feature using Jaza's much more complete (and massive...) 'Import / Export API' module : http://drupal.org/project/importexportapi

KarenS’s picture

FileSize
15.67 KB

I updated this to work with recent changes.

KarenS’s picture

FileSize
159 bytes

And here's the info file.

moshe weitzman’s picture

i just learned about this effort. very nice work. i will try to provide feedback soon.

moshe weitzman’s picture

UI looks terrific.

I exported the 'page' content type after adding a node ref field to it. then i got errors on importing to a new site (i deleted page there before anything). Here is what I saw. If need be, I can debug further ...

* warning: array_merge() [function.array-merge]: Argument #2 is not an array in /Library/WebServer/contributions/modules/cck/content_copy.module on line 239.
* warning: Missing argument 1 for drupal_execute() in /Library/WebServer/htdocs/bpr/includes/form.inc on line 152.
* warning: Missing argument 2 for drupal_execute() in /Library/WebServer/htdocs/bpr/includes/form.inc on line 152.
* warning: array_key_exists() [function.array-key-exists]: The second argument should be either an array or an object in /Library/WebServer/contributions/modules/cck/content_copy.module on line 287.
* An illegal choice has been detected. Please contact the site administrator.
* warning: array_merge() [function.array-merge]: Argument #1 is not an array in /Library/WebServer/contributions/modules/cck/content_admin.inc on line 693.
* warning: array_merge() [function.array-merge]: Argument #1 is not an array in /Library/WebServer/contributions/modules/cck/content_admin.inc on line 694.

* Created field parentref.
* The field parentref (field_parentref) was added to the content type , but an error has occured updating the field settings. Please check the errors displayed for more details.

KarenS’s picture

@moshe, I can't replicate your problem. I can export a type, do a clean install, and import the type with no problem. The location of the error messages indicates that the part of the macro that defines the content type was missing. Any chance you didn't get the whole thing copied and pasted??

Either way, it does look like I need to add some error trapping in for situations like that. I'll try to post something with some more error trapping in it. It looks like either the form values array or the args array for the content type was missing or not an array for some reason.

If you had the whole macro and still got this message, could this be a php 5 thing? I'm using php 4.

KarenS’s picture

Another thought. If you had the whole macro and it didn't work, I wonder if there were line endings that corrupted it. I've tested by using copy and paste from one windows installation to another, but haven't tried going from, say, windows to unix.

moshe weitzman’s picture

tried again and still have same error. here is what i am pasting. i also see some additional status messages. see below ... this will be more readable via web than email:

$macro[page]['form_id'] = 'node_type_form';
$macro[page]['values']  = array (
  'node_options' => 
  array (
    'status' => true,
    'promote' => true,
    'sticky' => false,
    'revision' => false,
  ),
  'comment' => 2,
  'upload' => 1,
);
$macro[page]['parameters']  = 'a:1:{i:0;O:8:"stdClass":14:{s:4:"type";s:4:"page";s:4:"name";s:4:"page";s:6:"module";s:4:"node";s:11:"description";s:83:"If you want to add a static page, like a contact page or an about page, use a page.";s:4:"help";s:0:"";s:9:"has_title";s:1:"1";s:11:"title_label";s:5:"Title";s:8:"has_body";s:1:"1";s:10:"body_label";s:4:"Body";s:14:"min_word_count";s:1:"0";s:6:"custom";s:1:"1";s:8:"modified";s:1:"1";s:6:"locked";s:1:"0";s:9:"orig_type";s:4:"page";}}';

$macro[field_parentref]['form_id'] = '_content_admin_field';
$macro[field_parentref]['values']  = array (
  'widget_type' => 'nodereference_select',
  'label' => 'parentref',
  'weight' => '0',
  'description' => '',
  'group' => false,
  'required' => '0',
  'multiple' => '0',
  'referenceable_types' => 
  array (
    'poll' => true,
    'group' => true,
    'page' => true,
    'story' => true,
  ),
  'advanced_view' => '--',
  'submit' => 'Save field settings',
  'type_name' => 'page',
  'field_name' => 'field_parentref',
  'field_type' => 'nodereference',
  'module' => 'nodereference',
  'form_id' => '_content_admin_field',
);
$macro[field_parentref]['parameters']  = 'a:2:{i:0;s:4:"page";i:1;s:15:"field_parentref";}';
Added field parentref.
The field parentref (field_parentref) was added to the content type , but an error has occured updating the field settings. Please check the errors displayed for more details.
KarenS’s picture

It's definitely a problem creating the content type and all the other errors come from the fact that the type did not get created, so first of all I need to have the script bail out if the type is not created. Next I need to figure out why the type is not created in your situation when it works fine in mine. I see you have 'upload' enabled for this type. Wonder if that has anything to do with this. I'll do some more digging...

KarenS’s picture

I can import your macro into my installation with no problem. The content type is created, there is only a single error message about an illegal choice which I think is because your nodereference referenceable types list contains content types that don't exist in my installation, so I need to think about how to handle that situation. But it creates both the content type and the field.

What else do you have installed? What versions are you using of everything?

moshe weitzman’s picture

Status: Needs review » Needs work

FYI, the bug I reported before happens only when importing as new content type. importing into an existing content type works well.

I am seeing group=false in the export even though a field is in a group. Ideally, we would export field group info as well.

I suggest using radio buttons on the initial page of the Export form. Is a bit faster than choosing a type from a select. Similarly, use checkboxes on the second page (field selection). Without looking at code, it appears you are using a multistep form for export. If you want to simplify code, you could add an export link beside each content type in the content types list. Either way is fine. Import should stay as own tab.

minor bug: when i do an export, i see three confusing status messages as below. Maybe a little refactoring would get them out of the export code path:

    * The content type story has been updated.
    * Saved field qty.
    * Saved field Assigned to.

even more minor are these nearly duplicate status messages after successfully adding fields to an existing content type. I guess removing them would make normal field creation more confusing. So lets ignore it:

    * Created field qty.
    * Saved field qty.
    * Created field Assigned to.
    * Saved field Assigned to.
KarenS’s picture

The status messages are triggered because I am programmatically submitting each form to get its form values. That works fine but you do see those messages. Ditto on the import when I submit the forms to create the data. Do you see any other way to get the form values on the export? I can get the form array with no problem and no messages, but that array is huge and has lots of info I don't need and I really just need the form values.

I am testing by creating a new content type and that works fine. Obviously you are using OG and I'm not. I wonder if that's creating the problem? I'll try testing that way.

moshe weitzman’s picture

@KarenS - ideally, the non existant node types should not create an error and import proceeds.

I tried exporting a field of type=date.module and it worked!

so, the major outstanding issue is my inability to create a new node type during import. i am using latest HEAD CCK and core. I removed og from the export site but still no luck. (the import site is plain HEAD). i'm gonna bust out the debugger and see whats up. i will hopefully learn from your code that way too.

about removing the status messages: you have a couple of options which may or may not be worth it. You could unset the #submit handlers ... less easy would be to take control of the form processing yourself. basically, you will run through your own drupal_execute() and drupal_process_form() with the intent of skipping drupal_submit_form().

KarenS’s picture

I am also digging into group info and it may take a bit of refactoring of the fieldgroup module and the import routine. There are two problems:

1) The fieldgroup module is inserting its values using hook_form_alter and relying on getting the content type from arg(3) and the field type from arg(5). Obviously, when we submit that programmatically there is no arg(3) or arg(5) so I need to make sure the form is getting that info from form values.

2) There is a separate form used to create new groups and I need to see if the imported group already exists and if not, create it, as another step in the import.

moshe weitzman’s picture

@karen - i do think this is a php5 problem. i am getting much further by changing your cast in content_copy to an array: $param = (array) unserialize($imported_type['parameters']);. i will keep fooling with this, since i still am getting an error (but the types/fields look good).

FYI, drupal_execute() returns an $errors array that you can display as desired.

I will return to this in a few hours. have to assume dad duties for a while.

thanks for looking into fieldgroups. swat those pesky arg() calls away!

eaton’s picture

FYI, drupal_execute() returns an $errors array that you can display as desired.

Unfortunately, that feature was removed. you still have to call form_get_errors() to see what went wrong. Just one more step, though. :)

moshe weitzman’s picture

@eaton - thanks. the function comment on drupal_execute still talks about errors as return value. i looked at it and i think the return value is now the redirect path. i will patch the docs if you agree.

KarenS’s picture

@moshe, I am starting to think this is php 5, too. Here's what I ran into with php 4. When I submitted the form to create the content type without casting it as an object, the creation failed. I looked at that function in the node module and it is expecting an object as a parameter, so I cast the parameter as an object and things started working in php 4. Now you're saying that casting it as an object makes it fail in php 5 and casting it as an array works better.

So I just tested changing that cast to an array and that does also seem to work in php 4. I then tried not casting it at all and it also works. But I know the node type creation failed when the param was an array when I first put this code together, that's why the cast is in there. Very odd...

Anyway, fields work OK for both of us, I think, because the field functions aren't expecting objects as parameters. Our problems are in the creation of the node type, which takes an object as a parameter.

So, how do you use that info to fix this? I'm not real familiar with php 5 yet, but I know there are some differences in the way objects and arrays are handled between php4 and php5 and I'm starting to wonder if that is our issue??

moshe weitzman’s picture

All I had to do was step away for a few hours and lo and behold, it is importing perfectly now. Just be sure to change that cast to array() as I highlighted earlier. i am doing so because in php5 it is invalid to array_merge() when one of the arguments is an object. maybe try calling drupal_execute() directly instead of through call_user_func_array()? anyway, if array() cast works for you, i think this is RTBC. Fieldgroups would be (big) icing on the cake.

This is some lovely code. Mad props to Eaton and chx and Adrian and Karen. Watching this in the debugger is magical.

@all - my tour in the debugger did take me through a strange section where we "// Save or reset persistent variable values." at bottom of node_type_form_submit(). That section of code ended up doing variable_set('status', 0) and variable_set('promote', 0) which is harmless cruft, except it does cause us to reload the variable cache each time. maybe some content type expert can comment on this code. anyway, not related to this issue.

it is possible that some of yched's suggested simplifications in #2 are still valid. i haven't looked closely.

moshe weitzman’s picture

@karen - I got rid of the status messages during export ... Just use this line in content_copy_form_alter() where you currently add a the record_macro submit handler. defining it this way causes fapi not to add the default submit handler.

$form['#submit'] = array('content_copy_record_macro' => array($form));

i'm ok with just leaving the import status messages as they are.

KarenS’s picture

I have re-written the module to incorporate fieldgroups, yched's ideas about simplifying the macro (now that I am more certain about exactly which values we need and which we don't), and moshe's suggestions to change the selects to checkboxes and radios.

I found we need some patching of CCK to get this all working and have submitted a patch at http://drupal.org/node/105270 that needs reviews.

I think it's all working now, but I'm going to do a little more testing of various scenarios, then I'll post the results, probably later this morning.

KarenS’s picture

FileSize
19.58 KB

Here is the updated module. If the patch mentioned above is applied, it should allow you to import and export content types, groups, and fields. You can import a complete package into a new installation, or export just fields or just groups from one content type and import them into another.

It displays a lot of 'success' messages, which are coming from the underlying forms that are being executed. Maybe it would be better to try to get rid of them, but I think the only way to do that is to break the drupal_executes up into stages, which is more code and more complexity, so for now I'm not worrying about it.

@moshe, since this has a lot of changes, we'll need to see if the changed code works on your installation.

KarenS’s picture

Status: Needs work » Needs review

Resetting status...

Christoph C. Cemper’s picture

interesting - subscribing

bdragon’s picture

subscribing

yched’s picture

Thanks again Karen for this.

I briefly tested export - will try imports (and the related CCK patch) shortly :-)

- on exporting a content type with no cck fields, i get
Invalid argument supplied for foreach() in content_copy.module on line 196.

- cosmetic :
why not group the two exported definitions :

$macro[type]  = array (
...
);
$macro[fields]  = array (
...
);

in one :

$content_type = array (
  'type' => array(
  ...
  ),
  'fields' => array(
  ...
  ),
);

Or maybe, if you do want to keep both separate :

$type  = array (
...
);
$fields  = array (
...
);

(or anything to get rid of the "$macro" variable, whose reasons for being named that way is strictly internal)

- even more cosmetic (and probably a matter of personal and arguable taste :-) ) : the numeric keys in the 'fields' array sort of seem out of place (see Views exports, or views tables definitions). Maybe those keys could be ditched altogether, or the field (machine) name be used instead ?

KarenS’s picture

I have no objection to grouping the arrays and getting rid of 'macro'. That should be a minor change, and I can re-write it that way if that seems better.

I initally tried using field and group names as keys, but group names have a hyphen in them which is invalid as a key name (all such keys get converted to zeros) so that won't work unless the construct of the group name is changed from 'group-' to 'group_'.

KarenS’s picture

Plus, I think we need the option to provide blank values for field and group names if you want the system to create them (see my comments about that in the patch issue), so I don't think the import code can depend on the field or group name as the key.

yched’s picture

I can't reproduce the "no hyphen in key names" issue. but maybe the hyphen in group names should be replaced in the first place, just to make them coherent with the 'field_myfieldname' pattern.
(I can provide the patch and the upgrade function if you like - and if this is agreed)

yched’s picture

Sorry,cross posted with your comment 33.

Alright - this numeric keys thing is really minor anyway. I'll see if I can propose a patch later on that removes the keys altogether (like views export does : avoids var_export for arrays and manually build a string like "Array(".$values.")").

I'll propose a patch for hyphens in group names in a separate thread.

Anyway, I guess you're mostly expecting reviews on the import process :-)

moshe weitzman’s picture

Status: Needs review » Needs work

Looking real good. Only found 1 bug: field weights look proper in the export but are not recognized during import.

KarenS’s picture

I figured out what was going on with weights. In the regular (manual) form, weights are hidden values that have #value set on them. Apparently that prevents the weights I pass in using drupal_execute to override the original setting (which is always zero for new fields). I can get around this by changing the form to use #default_value instead of #value for the hidden weight field, but I'm not sure I'm supposed to be doing that. The FAPI instructions say that hidden values should have #value set. Any ideas on the best way to keep that field hidden in the manual form and still let drupal_execute override it with $form_values??

KarenS’s picture

FileSize
20.02 KB

I found a few more small fixes to make sure the new field name gets picked up if you submit a blank value and the program calculates it. Here is the latest version of the module. I think everything is working but weights, and weights work if you change the $form array in _content_admin_field to use #default_value instead of #value.

KarenS’s picture

FileSize
19.99 KB

Oops, that copy still had debugging cruft in it. Here's a clean copy.

moshe weitzman’s picture

what form has hidden fields weight? why are they hidden? anyway, #default_value sounds just fine. i will try this again soon.

KarenS’s picture

Status: Needs work » Needs review

There are two hidden values that are affected. One is in the form to edit fields and one in the form to edit groups. They are there so there, partly, so the values get picked up when I execute the macros, and I need to update those fields with the imported values when I playback the macro in the import. I changed my patch to update cck with programmatic improvements to change '#value' to '#default_value' so this will work, so, hopefully with the updated patch and this updated module all things should be working right..

KarenS’s picture

Whew! My last message got really garbled. Basically use the latest patch and the latest module and hopefully everything now works.

moshe weitzman’s picture

Status: Needs review » Reviewed & tested by the community

works like a dream.

yched’s picture

Status: Reviewed & tested by the community » Needs work

Still having "Warning : Invalid argument supplied for foreach() in content_copy.module on line 196"
on exporting a content type that has no cck fields (the export seems to happen just fine, though)

KarenS’s picture

Status: Needs work » Fixed

I fixed the array problem when there are no fields and committed this to HEAD and 5.x.

Thanks moshe and yched for your help and reviews!

Anonymous’s picture

Status: Fixed » Closed (fixed)
Summit’s picture

Status: Closed (fixed) » Active

Hi,

Where is this module to be found please?
EDIT: I found it on CVS, but not on drupal.org/project/content_copy http://cvs.drupal.org/viewvc.py/drupal/contributions/modules/cck/content...

Will it be placed on drupal.org/project/content_copy ?
Greetings,
Martijn

KarenS’s picture

Status: Active » Fixed

It is included in the cck core package, there is no separate project page for it.

Anonymous’s picture

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for two weeks with no activity.