content_copy.module in Content Construction Kit (CCK) 5
Adds capability to import/export cck field data definitions.
Emulates the import/export process introduced in Views to export the field settings arrays as text them copy and paste text to import the field settings back into another content type.
Functions to create and playback macros borrowed from moshe weitzman's macro module. Macros created using drupal_execute() on the field settings form for each of the requested forms. Multi-part forms built using examples from Jeff Eaton's example at http://jeff.viapositiva.net/drupal/dynamic-forms.
You can export one or two fields and/or groups from one content type and import them into another content type in the same or a different installation, or export a complete content type with all groups and fields and create it as an exact copy in another installation.
Content type, group and field names will be imported exactly as exported. If the names are already in use, no import will be performed.
Note: The "display fields" information is being handled a little differently than the rest of the data that's imported and exported. Instead of calling through the create and playback macros, we get and set the data directly from/into the database. the reason for this is that the playback macro method does not lend itself well to the display fields.
File
content_copy.moduleView source
<?php
/**
* @file
* Adds capability to import/export cck field data definitions.
*
* Emulates the import/export process introduced in Views to export the field settings arrays as text
* them copy and paste text to import the field settings back into another content type.
*
* Functions to create and playback macros borrowed from moshe weitzman's macro module.
* Macros created using drupal_execute() on the field settings form for each of the requested forms.
* Multi-part forms built using examples from Jeff Eaton's example at
* http://jeff.viapositiva.net/drupal/dynamic-forms.
*
* You can export one or two fields and/or groups from one content type and import them into
* another content type in the same or a different installation,
* or export a complete content type with all groups and fields and create it
* as an exact copy in another installation.
*
* Content type, group and field names will be imported exactly as exported.
* If the names are already in use, no import will be performed.
*
*
* Note: The "display fields" information is being handled a little differently than the rest of the
* data that's imported and exported. Instead of calling through the create and playback macros,
* we get and set the data directly from/into the database. the reason for this is that the
* playback macro method does not lend itself well to the display fields.
*/
/**
* Implementation of hook_menu().
*/
function content_copy_menu($may_cache) {
$items = array();
$access = user_access('administer content types');
if ($may_cache) {
$items[] = array(
'path' => 'admin/content/types/export',
'title' => t('Export'),
'callback' => 'drupal_get_form',
'callback arguments' => 'content_copy_export_form',
'access' => $access,
'type' => MENU_LOCAL_TASK,
'weight' => 3,
);
$items[] = array(
'path' => 'admin/content/types/import',
'title' => t('Import'),
'callback' => 'drupal_get_form',
'callback arguments' => 'content_copy_import_form',
'access' => $access,
'type' => MENU_LOCAL_TASK,
'weight' => 4,
);
}
return $items;
}
/**
* A form to export field definitions.
*/
function content_copy_export_form($form_values = NULL) {
include_once './' . drupal_get_path('module', 'content') . '/content_admin.inc';
include_once './' . drupal_get_path('module', 'content') . '/content_crud.inc';
include_once './' . drupal_get_path('module', 'node') . '/content_types.inc';
$step = intval($form_values['step'] + 1);
$type_name = $form_values['type_name'];
$types = content_copy_types();
$fields = content_copy_fields($type_name);
if (module_exists('fieldgroup')) {
$groups = content_copy_groups($type_name);
}
// If a content type has been selected and there are no fields or groups to select,
// jump straight to export.
if ($step == 2 && !$groups && !$fields) {
$step = 3;
}
$form['step'] = array(
'#type' => 'hidden',
'#value' => $step,
);
$form['#prefix'] = t('This form will process a content type and one or more fields from that type and export the settings. The export created by this process can be copied and pasted as an import into the current or any other database. The import will add the fields to into an existing content type or create a new content type that includes the selected fields.');
$form['#multistep'] = TRUE;
$form['#redirect'] = FALSE;
switch ($step) {
case 1:
// Select a content type.
$form['type_name'] = array(
'#title' => t('Types'),
'#type' => 'radios',
'#multiple' => FALSE,
'#options' => $types,
'#description' => t('Select the content type to export.'),
);
// For some reason using #default_value in step 2 won't initialize
// all fields as checked, but setting a hidden value in step 1 will do it.
if (module_exists('fieldgroup')) {
$form['groups'] = array(
'#type' => 'hidden',
'#value' => serialize(array_keys(content_copy_groups())),
);
}
$form['fields'] = array(
'#type' => 'hidden',
'#value' => serialize(array_keys(content_fields())),
);
break;
case 2:
// Select groups and fields.
$form['type_name'] = array(
'#type' => 'hidden',
'#value' => $type_name,
);
if (module_exists('fieldgroup') && !empty($groups)) {
$form['groups'] = array(
'#title' => t('Groups'),
'#type' => 'checkboxes',
'#multiple' => TRUE,
'#options' => $groups,
'#description' => t('Select the group definitions to export from %type.', array(
'%type' => node_get_types('name', $type_name),
)),
);
}
$form['fields'] = array(
'#title' => t('Fields'),
'#type' => 'checkboxes',
'#multiple' => TRUE,
'#options' => $fields,
'#description' => t('Select the field definitions to export from %type.', array(
'%type' => node_get_types('name', $type_name),
)),
);
break;
case 3:
// Display the export macro.
$GLOBALS['content_copy']['count'] = 0;
$form['export'] = array(
'#title' => t('Export data'),
'#type' => 'textarea',
'#cols' => 60,
'#value' => content_copy_export($form_id, $form_values),
'#rows' => max(40, $GLOBALS['content_copy']['count']),
'#description' => t('Copy the export text and paste it into another content type using the import function.'),
);
break;
}
if ($step < 3) {
// Omit submit button on the textarea block to display the export data.
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
}
return $form;
}
/**
* Process the export, get field admin forms for all requested fields
* and save the form values as formatted text.
*/
function content_copy_export($form_id, $form_values) {
include_once './' . drupal_get_path('module', 'content') . '/content_admin.inc';
include_once './' . drupal_get_path('module', 'content') . '/content_crud.inc';
include_once './' . drupal_get_path('module', 'node') . '/content_types.inc';
// Set a global variable to tell when to intervene with form_alter().
$GLOBALS['content_copy']['status'] = 'export';
// Get the content type info by submitting the content type form.
drupal_execute('node_type_form', array(), node_get_types('type', $form_values['type_name']));
// Get an array of groups to export.
// Record a macro for each group by submitting the group edit form.
// TODO come back and do this without using drupal_execute().
if (is_array($form_values['groups']) && module_exists('fieldgroup')) {
$content_type = content_types($form_values['type_name']);
foreach ($form_values['groups'] as $group) {
drupal_execute('fieldgroup_edit_group_form', array(), $content_type, $group, 'edit');
}
}
// Get an array of fields to export
// Record a macro for each field by submitting the field settings form.
// Omit fields from the export if their module is not currently installed
// otherwise the system will generate errors when the macro tries to execute their forms.
// TODO come back and do this without using drupal_execute().
$type = content_types($form_values['type_name']);
foreach ((array) $form_values['fields'] as $field_name) {
$field = $type['fields'][$field_name];
$field_types = _content_field_types();
$field_module = $field_types[$field['type']]['module'];
$widget_types = _content_widget_types();
$widget_module = $widget_types[$field['widget']['type']]['module'];
if (!empty($field_module) && module_exists($field_module) && !empty($widget_module) && module_exists($widget_module)) {
$values = content_field_instance_collapse($field);
drupal_execute('_content_admin_field', $values, $form_values['type_name'], $field_name);
}
}
// Convert the macro array into formatted text.
return content_copy_get_macro();
}
/**
* A form to import formatted text created with export.
*
* The macro can be filled from a file, if provided.
* Example:
*
* The macro can be filled from a file, if provided.
* Provide a type_name to force the fields to be added to a specific
* type, or leave out type_name to create a new content type.
*
* Example:
* // If Content Copy is enabled, offer an import link.
* if (module_exists('content_copy')) {
* $form['macro'] = array(
* '#type' => 'fieldset',
* '#title' => t('Create a content type'),
* '#description' => t('Follow this link to create automatically a content type and import preconfigured fields.'),
* '#collapsible' => TRUE,
* '#collapsed' => FALSE,
* );
* $form['macro']['link'] = array(
* '#type' => 'markup',
* '#value' => l(t('import'), 'admin/content/types/import', array(), 'type_name=event¯o_file='. drupal_get_path('module', 'my_module') .'/my_content_type.txt'),
* );
* }
*/
function content_copy_import_form($type_name = '') {
include_once './' . drupal_get_path('module', 'content') . '/content_admin.inc';
include_once './' . drupal_get_path('module', 'node') . '/content_types.inc';
$form['#prefix'] = t('This form will import field definitions exported from another content type or another database.<br/>Note that fields cannot be duplicated within the same content type, so imported fields will be added only if they do not already exist in the selected type.');
$form['type_name'] = array(
'#type' => 'select',
'#options' => array(
'<create>' => t('<Create>'),
) + node_get_types('names'),
'#default_value' => $type_name,
'#title' => t('Content type'),
'#description' => t('Select the content type to import these fields into.<br/>Select <Create> to create a new content type to contain the fields.'),
);
$form['macro'] = array(
'#type' => 'textarea',
'#rows' => 40,
'#title' => t('Import data'),
'#required' => TRUE,
'#description' => t('Paste the text created by a content export into this field.'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
// Read in a file if there is one and set it as the default macro value.
if (isset($_REQUEST['macro_file']) && ($file = file_get_contents($_REQUEST['macro_file']))) {
$form['macro']['#default_value'] = $file;
if (isset($_REQUEST['type_name'])) {
$form['type_name']['#default_value'] = $_REQUEST['type_name'];
}
$form['#prefix'] .= '<p class="error">' . t('A file has been pre-loaded for import.') . '</p>';
}
return $form;
}
/**
* Submit handler for import form.
* For each submitted field:
* 1) add new field to the database
* 2) execute the imported field macro to update the settings to the imported values
*/
function content_copy_import_form_submit($form_id, $form_values) {
// Get the content type we are importing into.
$type_name = $form_values['type_name'];
$type_label = node_get_types('name', $type_name);
$content = NULL;
// Convert the import formatted text back into a $content array.
// Return if errors generated or not an array.
eval($form_values['macro']);
// Preliminary error trapping, must have valid arrays to work with.
if (!is_array($content) || !is_array($content['type'])) {
form_set_error('macro', t('The import data is not valid import text.'));
return;
}
// Get all type and field info for this database.
$content_info = _content_type_info();
$imported_type = $content['type'];
$imported_type_name = $imported_type['type'];
$imported_type_label = $imported_type['name'];
// It is allowed to import a type with no fields,
// so the fields array could be empty and must be cast as an array.
$imported_fields = (array) $content['fields'];
// Perform more pre-import error trapping.
// If there are potential problems, exit without doing the import.
$not_enabled = array();
// The groups array could be empty and still valid, make sure to cast it as an array.
// If there are groups in the import, make sure the fieldgroup module is enabled.
if (module_exists('fieldgroup')) {
$imported_groups = (array) $content['groups'];
}
elseif (is_array($content['groups'])) {
$not_enabled[] = 'fieldgroup';
}
// Make sure that all the field and widget modules in the import are enabled in this database.
foreach ($imported_fields as $field) {
if (empty($field['module'])) {
$not_enabled[] = $field['field_name'];
}
else {
$modules = explode(', ', $field['module']);
foreach ($modules as $module) {
if (!module_exists($module)) {
$not_enabled[] = $module;
}
}
}
}
// If any required module is not enabled, set an error message and exit.
if ($not_enabled) {
form_set_error('macro', t('The following modules must be enabled for this import to work: %modules.', array(
'%modules' => implode(', ', array_unique($not_enabled)),
)));
}
// Make sure the imported content type doesn't already exist in the database.
if ($form_values['type_name'] == '<create>') {
if (in_array($imported_type_name, array_keys($content_info['content types']))) {
form_set_error('macro', t('The content type %type already exists in this database.', array(
'%type' => $imported_type_name,
)));
}
}
if (form_get_errors()) {
drupal_set_message(t('Exiting. No import performed.'), 'error');
return;
}
// Create the content type, if requested.
if ($form_values['type_name'] == '<create>') {
$node = (object) $imported_type;
$values = $imported_type;
drupal_execute('node_type_form', $values, $node);
// Reset type and database values once new type has been added.
$type_name = $imported_type_name;
$type_label = node_get_types('name', $type_name);
$content_info = _content_type_info();
if (form_get_errors() || !is_array($content_info['content types'][$type_name])) {
drupal_set_message(t("An error has occured adding the content type %type.<br/>Please check the errors displayed for more details.", array(
'%type' => $imported_type_name,
)));
return;
}
}
// Create the groups for this type, if they don't already exist.
if (module_exists('fieldgroup')) {
$groups = fieldgroup_groups($type_name);
$content_type = content_types($type_name);
$errors = FALSE;
foreach ($imported_groups as $group) {
$group_name = $group['group_name'];
if (!is_array($groups[$group_name])) {
$values = (array) $group;
drupal_execute('fieldgroup_edit_group_form', $values, $content_type, $group_name, 'add');
}
}
cache_clear_all('fieldgroup_data', 'cache_content');
// Reset the static variable in fieldgroup_groups() with new data.
fieldgroup_groups('', FALSE, TRUE);
}
$should_clear_type_cache = 0;
// Iterate through the field forms in the import and execute each.
foreach ($imported_fields as $field) {
// Make sure the field doesn't already exist in the type.
// If so, do nothing, fields can't be duplicated within a content type.
$field_name = $field['field_name'];
$field_values = $field;
$field_label = $field_values['label'];
if (!empty($field['field_name']) && is_array($content_info['content types'][$type_name]['fields'][$field_name])) {
drupal_set_message(t("The imported field %field_label (%field_name) was not added to %type because that field already exists in %type.", array(
'%field_label' => $field_label,
'%field_name' => $field_name,
'%type' => $type_name,
)));
}
else {
$values = array();
$errors = FALSE;
// Check if field already exists in the database.
// If not, add new, otherwise, add existing.
if (!array_key_exists($field_name, $content_info['fields'])) {
$values['field_name'] = $field_values['field_name'];
$values['label'] = $field_values['label'];
$values['field_widget_type'] = $field_values['field_type'] . '-' . $field_values['widget_type'];
$values['type_name'] = $type_name;
$field_url = drupal_execute('_content_admin_field_add_new', $values, $type_name, $field_name);
if (form_get_errors()) {
drupal_set_message(t("An error has occured adding the field %field_label (%field_name).<br/>Please check the errors displayed for more details.", array(
'%field_label' => $field_label,
'%field_name' => $field_name,
)));
$errors = TRUE;
}
else {
// Retrieve the new field name by picking out the arg from the url returned by
// _content_admin_field_add_new(), needed so the right field can be updated in the next step.
$args = explode('/', $field_url);
$field_name = array_pop($args);
$field_values['field_name'] = $field_name;
}
}
else {
$values['field_name'] = $field_values['field_name'];
$values['type_name'] = $type_name;
drupal_execute('_content_admin_field_add_existing', $values, $type_name);
if (form_get_errors()) {
drupal_set_message(t("An error has occured adding the field %field_label (%field_name).<br/>Please check the errors displayed for more details.", array(
'%field_label' => $field_label,
'%field_name' => $field_name,
)));
$errors = TRUE;
}
}
// Once the field has been added, update the settings with the macro values.
// Replace the export type name with the import type name.
if (!$errors) {
$field_values['type_name'] = $type_name;
drupal_execute('_content_admin_field', $field_values, $type_name, $field_name);
if (form_get_errors()) {
drupal_set_message(t("The field %field_label (%field_name) was added to the content type %type, but an error has occured updating the field settings.<br/>Please check the errors displayed for more details.", array(
'%field_label' => $field_label,
'%field_name' => $field_name,
'%type' => $type_label,
)));
}
else {
// If we successfully updated the form, serialize the display_settings info
// and insert the display_settings (if it exists) into the database.
if (array_key_exists('display_settings', $field)) {
$query = 'UPDATE {node_field_instance} SET display_settings = \'%s\' WHERE field_name = \'%s\'';
db_query($query, serialize($field['display_settings']), $field['field_name']);
if ($db_err = db_error()) {
drupal_set_message(t("The field %field_label (%field_name) was added to the content type %type, but an error has occured updating the field's 'display_settings'.<br/>The db error is: '%db_err'.", array(
'%field_label' => $field_label,
'%field_name' => $field_name,
'%type' => $type_label,
'%db_err' => $db_err,
)));
}
else {
// If any data was successfully inserted into the DB,
// we make sure the cache is cleared.
$should_clear_type_cache = 1;
}
}
}
}
}
}
// If no errors occured when inserting field data directly into the DB,
// clear the type cache.
if ($should_clear_type_cache) {
content_clear_type_cache();
}
if (!form_get_errors()) {
if (sizeof($imported_fields) > 0 || sizeof($imported_groups) > 0) {
return 'admin/content/types/' . $content_info['content types'][$type_name]['url_str'] . '/fields';
}
else {
return 'admin/content/types';
}
}
}
/**
* Implementation of hook_form_alter().
* Intervene to run form through macro when doing export
*/
function content_copy_form_alter($form_id, &$form) {
$alter_forms = array(
'node_type_form',
'_content_admin_field',
'fieldgroup_edit_group_form',
);
if ($GLOBALS['content_copy']['status'] == 'export' && in_array($form_id, $alter_forms)) {
$form['#submit'] = array(
'content_copy_record_macro' => array(
$form,
),
);
}
}
/**
* Get all the fields for a content type.
*/
function content_copy_fields($type_name) {
$fields = array();
if (!$type_name) {
return $fields;
}
$content_info = _content_type_info();
foreach ($content_info['content types'][$type_name]['fields'] as $field_name => $field) {
// Omit fields from the export if their module is not currently installed
// otherwise the system will generate errors when the macro tries to execute their forms.
$field_types = _content_field_types();
$field_module = $field_types[$field['type']]['module'];
$widget_types = _content_widget_types();
$widget_module = $widget_types[$field['widget']['type']]['module'];
if (!empty($field_module) && module_exists($field_module) && !empty($widget_module) && module_exists($widget_module)) {
$fields[$field_name] = $field['widget']['label'] . ' (' . $field['field_name'] . ')';
}
}
return $fields;
}
/**
* Get all the groups for a content type.
*/
function content_copy_groups($type_name = '') {
$groups = array();
if (!$type_name || !module_exists('fieldgroup')) {
return $groups;
}
$data = fieldgroup_groups($type_name);
foreach ($data as $group_name => $group) {
$groups[$group_name] = $group['label'] . ' (' . $group['group_name'] . ')';
}
return $groups;
}
/**
* Get all content types.
*/
function content_copy_types() {
$types = array();
$content_info = _content_type_info();
foreach ($content_info['content types'] as $type_name => $val) {
$types[$type_name] = check_plain($val['name']) . ' (' . $type_name . ')';
}
return $types;
}
/**
* A handler that stores the form submissions into a $GLOBALS array
*/
function content_copy_record_macro($form_id, $edit, $form) {
$subs = $GLOBALS['content_copy']['submissions'];
// Get the form values and store them in a $GLOBALS['content_copy']['submissions'] array.
// Update $GLOBALS['content_copy']['count'] with an approximation of the number of rows in this item.
// Count is used to approximate necessary size of textarea in form.
unset($edit['type_name'], $edit['submit'], $edit['delete'], $edit['form_id']);
switch ($form_id) {
case 'node_type_form':
$subs['type'] = $edit;
$GLOBALS['content_copy']['count'] += sizeof($edit) + 5;
break;
case 'fieldgroup_edit_group_form':
$subs['groups'][] = $edit;
$GLOBALS['content_copy']['count'] += sizeof($edit) + 5;
break;
default:
if ($edit['field_widget_type']) {
$tmp = explode('-', $edit['field_widget_type']);
$field_name = $tmp[0];
}
else {
$field_name = $edit['field_name'];
}
// The display settings are being fetched directly from the DB. During import,
// we'll re-insert the data directly as well.
//
$query = 'SELECT display_settings FROM {node_field_instance} WHERE field_name = \'%s\'';
$row_info = db_fetch_array(db_query($query, $field_name));
// If an error occurs, notify the user.
if ($db_err = db_error()) {
drupal_set_message(t("An error occurred when exporting the 'display settings' data for the field %field_name.<br/>The db error is: '%db_err'.", array(
'%field_name' => $field_name,
'%db_err' => $db_err,
)));
}
else {
// The db fetch occurred successfully, unserialize the data blob and
// insert it into a new "display_settings" field of the data.
if ($display_settings = unserialize($row_info['display_settings'])) {
$edit['display_settings'] = $display_settings;
}
}
$subs['fields'][] = $edit;
$GLOBALS['content_copy']['count'] += sizeof($edit) + 5;
break;
}
$GLOBALS['content_copy']['submissions'] = $subs;
}
/**
* @return a code representation of the recorded macro.
*/
function content_copy_get_macro() {
foreach ($GLOBALS['content_copy']['submissions'] as $form_type => $form) {
$string .= "\$content[{$form_type}] = " . var_export((array) $form, TRUE) . ";\n";
}
return $string;
}
Functions
Name | Description |
---|---|
content_copy_export | Process the export, get field admin forms for all requested fields and save the form values as formatted text. |
content_copy_export_form | A form to export field definitions. |
content_copy_fields | Get all the fields for a content type. |
content_copy_form_alter | Implementation of hook_form_alter(). Intervene to run form through macro when doing export |
content_copy_get_macro | |
content_copy_groups | Get all the groups for a content type. |
content_copy_import_form | A form to import formatted text created with export. |
content_copy_import_form_submit | Submit handler for import form. For each submitted field: 1) add new field to the database 2) execute the imported field macro to update the settings to the imported values |
content_copy_menu | Implementation of hook_menu(). |
content_copy_record_macro | A handler that stores the form submissions into a $GLOBALS array |
content_copy_types | Get all content types. |