array('administer site configuration'), 'page callback' => 'drupal_get_form', 'page arguments' => array('node_export_settings'), 'title' => 'Node export', 'description' => 'Configure the settings for Node export.', 'file' => 'node_export.pages.inc', ); $selected_formats = variable_get('node_export_format', array('node_code')); if (count(array_filter($selected_formats)) > 1) { $format_handlers = node_export_format_handlers(); foreach ($format_handlers as $format_handler => $format) { if (in_array($format_handler, $selected_formats)) { $items['node/%node/node_export/'. $format_handler] = array( 'access callback' => 'node_export_access_export', 'access arguments' => array(1), 'page callback' => 'node_export_gui', 'page arguments' => array(1, $format_handler), 'title' => 'Node export (' . $format['#title'] . ')', 'weight' => 5, 'type' => MENU_LOCAL_TASK, 'file' => 'node_export.pages.inc', ); } } } else { $items['node/%node/node_export'] = array( 'access callback' => 'node_export_access_export', 'access arguments' => array(1), 'page callback' => 'node_export_gui', 'page arguments' => array(1), 'title' => 'Node export', 'weight' => 5, 'type' => MENU_LOCAL_TASK, 'file' => 'node_export.pages.inc', ); } $items['admin/content/node_export'] = array( 'access arguments' => array('export nodes'), 'page callback' => 'node_export_gui', 'page arguments' => array(NULL, NULL), 'title' => 'Node export', 'type' => MENU_CALLBACK, 'file' => 'node_export.pages.inc', ); $items['node/add/node_export'] = array( 'title' => 'Node export: import', 'page callback' => 'drupal_get_form', 'page arguments' => array('node_export_import_form'), 'access callback' => 'node_export_access_import', 'description' => 'Import content using Node export.', 'file' => 'node_export.pages.inc', ); return $items; } /** * Check access to export a node. */ function node_export_access_export($node) { global $user; if (is_numeric($node)) { $node = node_load($node); } if (function_exists('drush_main')) { // Always allow drush to export nodes. $access = TRUE; } else { // Check basic role permissions first. $access = (user_access('export nodes') || ($user->uid && ($node->uid == $user->uid) && user_access('export own nodes'))); // Make sure the user can view the original node content. $access = $access && node_access('view', $node); } // Let other modules alter this - for example to only allow some users // to export specific nodes or types. drupal_alter("node_export_access_export", $access, $node); return $access; } /** * Check access to import a node. */ function node_export_access_import($node = NULL) { global $user; if (function_exists('drush_main')) { // Always allow drush to import nodes. $access = TRUE; } elseif (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'install') { // During the install phase $user is the Anonymous user; however, in // practice $user is performing tasks only granted to the admin user // (eg: installing modules, changing site settings). For this reason // it seems sensible to allow this "Anonymous admin" user to import // any nodes they wish. $access = TRUE; } else { // Check basic role permissions first. $access = user_access('use PHP to import nodes'); if (!is_null($node) && $access) { // Check node conditions. $access = node_access('create', $node->type) && filter_access($node->format); } } // Let other modules alter this - for example to only allow some users // to import specific nodes or types. drupal_alter("node_export_access_import", $access, $node); return $access; } /** * Check access to export an array of nodes. */ function node_export_access_export_nodes($nodes) { // Convert to array if it isn't already. if (is_object($nodes)) { $nodes = array($nodes); } foreach ($nodes as &$node) { if (!node_export_access_export($node)) { return FALSE; } } return TRUE; } /** * Check access to import an array of nodes. */ function node_export_access_import_nodes($nodes) { // Convert to array if it isn't already. if (is_object($nodes)) { $nodes = array($nodes); } foreach ($nodes as &$node) { if (!node_export_access_import($node)) { return FALSE; } } return TRUE; } /** * Implementation of hook_node_type(). */ function node_export_node_type($op, $type_obj) { switch ($op) { case 'delete': variable_del('node_export_reset_'. $type_obj->type); break; case 'update': if (!empty($type_obj->old_type) && $type_obj->old_type != $type_obj->type) { if (variable_get('node_export_reset_'. $type_obj->old_type, FALSE)) { variable_del('node_export_reset_'. $type_obj->old_type); variable_set('node_export_reset_'. $type_obj->type, TRUE); } } break; } } /** * Implementation of hook_views_api(). */ function node_export_views_api() { return array( 'api' => 2, 'path' => drupal_get_path('module', 'node_export') .'/views', ); } /** * Implementation of hook_node_operations(). */ function node_export_node_operations() { $operations = array(); if (user_access('export nodes')) { $selected_formats = variable_get('node_export_format', array('node_code')); if (count(array_filter($selected_formats)) > 1) { $format_handlers = node_export_format_handlers(); foreach ($format_handlers as $format_handler => $format) { if ($selected_formats[$format_handler]) { $operations['node_export_'. $format_handler] = array( 'label' => t('Node export') ." (". $format['#title'] .")", 'callback' => 'node_export_bulk_operation', 'callback arguments' => array($format_handler, NULL), ); } } } else { $operations = array( 'node_export' => array( 'label' => t('Node export'), 'callback' => 'node_export_bulk_operation', 'callback arguments' => array(NULL, NULL), ), ); } } return $operations; } /** * Callback for use with hook_node_operations(). */ function node_export_bulk_operation($nodes = NULL, $format = NULL, $delivery = NULL) { module_load_include('inc', 'node_export', 'node_export.pages'); return node_export_gui($nodes, $format, $delivery); } /** * Export nodes. * * @param $nids * A node ID or array of node IDs to export. * @param $format * The format to use for export. * @param $msg_t * Function used to translate. * @return * An array with keys 'success' which is a boolean value representing whether * the export was successful and 'output' which contains the code string or an * array of translated error messages to be shown to the user. */ function node_export($nids, $format = NULL, $msg_t = 't') { global $user; // Make $nids an array if it isn't. if (is_numeric($nids)) { $nids = array($nids); } elseif (is_object($nids)) { $nids = array($nids->nid); } $nodes = array(); foreach ($nids as $nid) { $original_node = node_load($nid); if (!node_export_access_export($original_node)) { // Halt exporting. $error = $msg_t("You do not have permission to perform a Node export on one or more of these nodes. No nodes exported."); return array( 'success' => FALSE, 'output' => array($error), ); } $node = node_export_prepare_node($original_node); $nodes[] = $node; } // Get the node code from the format handler $format_handlers = node_export_format_handlers(); $format_handler = $format ? $format : reset(variable_get('node_export_format', array('node_code'))); if (!isset($format_handlers[$format_handler])) { $format_handler = 'node_code'; } // Let other modules do special fixing up. drupal_alter('node_export', $nodes, 'export', $format_handler); // If any nodes are set to FALSE, then an error was triggered in another module. // Currently modules doing this should also leave a watchdog warning. if (in_array(FALSE, $nodes)) { // Halt exporting. $error = $msg_t('An error occurred when processing nodes, please check your logs. No nodes exported.'); return array( 'success' => FALSE, 'output' => array($error), ); } $code_string = module_invoke( $format_handlers[$format_handler]['#module'], 'node_export', $nodes, $format_handler ); // Let modules modify the node code. drupal_alter('node_export_encode', $code_string, $nodes, $format_handler); return array( 'success' => TRUE, 'output' => $code_string, 'format' => $format_handler, ); } /** * Prepare a single node during export. */ function node_export_prepare_node(&$original_node) { // Create UUID if it's not there. // Currently this uses a module_exists() until UUID becomes a dependency. if (!uuid_get_uuid('node', 'nid', $original_node->nid)) { $original_node->uuid = uuid_set_uuid('node', 'nid', $original_node->nid); // Save it so future node exports are consistent. node_save($original_node); } $node = drupal_clone($original_node); // Fix taxonomy array if (isset($node->taxonomy) && count($node->taxonomy)) { $vocabularies = taxonomy_get_vocabularies(); $new_taxonomy = array(); foreach ($node->taxonomy as $term) { // Free-tagging vocabularies need a different format if ($vocabularies[$term->vid]->tags) { $new_taxonomy['tags'][$term->vid][] = $term->name; } else { $new_taxonomy[$term->vid][$term->tid] = $term->tid; } } if (isset($new_taxonomy['tags']) && count($new_taxonomy['tags'])) { // Comma seperate the tags foreach ($new_taxonomy['tags'] as $vid => $tags) { $new_taxonomy['tags'][$vid] = implode(', ', $tags); } } $node->taxonomy = $new_taxonomy; } // Fix menu array $node->menu = node_export_get_menu($original_node); $node = node_export_remove_recursion($node); // Let other modules do special fixing up. drupal_alter('node_export_node', $node, $original_node, 'export'); return $node; } /** * Check if all types in the import exist. * * @return * TRUE if all types exist, otherwise an array of missing type names. */ function node_export_import_types_check($nodes) { $missing_types = array(); foreach ($nodes as $node) { if (node_get_types('name', $node) == FALSE) { $missing_types[$node->type] = $node->type; } } return (!empty($missing_types) ? $missing_types : TRUE); } /** * Import Function * * @param $code_string * The string of export code. * @param $msg_t * Function used to translate. * @return * An array with keys 'success' which is a boolean value representing whether * the import was successful and 'output' which contains an array of * translated strings to be shown to the user as messages. */ function node_export_import($code_string, $msg_t = 't') { // Early check to avoid letting hooligans and the elderly pass data to the // eval() function call. if (!node_export_access_import()) { $error = $msg_t( 'You do not have permission to import any nodes.' ); return array( 'success' => FALSE, 'output' => array($error), ); } // Allow modules to manipulate the $code_string. drupal_alter('node_export_decode', $code_string); // Pass the string to each format handler until one returns something useful. $format_handlers = node_export_format_handlers(); $nodes = array(); $used_format = ""; foreach ($format_handlers as $format_handler => $format) { $nodes = module_invoke( $format_handlers[$format_handler]['#module'], 'node_export_import', $code_string ); if (!empty($nodes)) { $used_format = $format_handler; break; } } if (isset($nodes['success']) && !$nodes['success']) { // Instead of returning nodes, the format handler returned an error array. // Translate the errors and return them. foreach ($nodes['output'] as $key => $output) { $nodes['output'][$key] = $msg_t($output); } return array( 'success' => FALSE, 'output' => $nodes['output'], ); } if ($used_format == "") { $error = $msg_t( 'Node export was unable to recognize the format of the supplied code. No nodes imported.' ); return array( 'success' => FALSE, 'output' => array($error), ); } $nodes = node_export_restore_recursion($nodes); $types_exist = node_export_import_types_check($nodes); if ($types_exist !== TRUE) { // There was a problem with the content types check. $error = $msg_t( 'Error encountered during import. Node types unknown on this site: %t. No nodes imported.', array('%t' => implode(", ", $types_exist)) ); return array( 'success' => FALSE, 'output' => array($error), ); } if (!node_export_access_import_nodes($nodes)) { // There was a problem with permissions. $error = $msg_t( 'You do not have permission to perform a Node export: import on one or more of these nodes. No nodes imported.' ); return array( 'success' => FALSE, 'output' => array($error), ); } $count = 0; $total = count($nodes); // Let other modules do special fixing up. drupal_alter('node_export', $nodes, 'import', $used_format); $new_nodes = array(); $new_nid = 0; $messages = array(); foreach ($nodes as $original_node) { $skip = FALSE; $node = node_export_node_clone($original_node); // Handle existing nodes. $uuid_nid = node_export_node_get_by_uuid($node->uuid, TRUE); if (!empty($uuid_nid)) { $existing = variable_get('node_export_existing', 'new'); switch ($existing) { case 'new': $node->is_new = TRUE; unset($node->nid); unset($node->uuid); break; case 'revision': $node->nid = $uuid_nid; $node->is_new = FALSE; $node->revision = 1; break; case 'skip': $skip = TRUE; break; } } // Let other modules do special fixing up. drupal_alter('node_export_node', $node, $original_node, 'import'); if (!$skip) { node_export_save($node); $new_nodes[] = $node; $messages[] = $msg_t("Imported node !nid: !node", array('!nid' => $node->nid, '!node' => l($node->title, 'node/'. $node->nid))); $count++; } } drupal_alter('node_export', $new_nodes, 'after import', $used_format); $messages[] = $msg_t("!count of !total nodes were imported. Some values may have been reset depending on Node export's configuration.", array('!total' => $total, '!count' => $count)); // Clear the page and block caches. cache_clear_all(); return array( 'success' => TRUE, 'output' => $messages, ); } /** * Save a node object into the database. * * A modified version of node_save(). */ function node_export_save(&$node) { // Let modules modify the node before it is saved to the database. node_invoke_nodeapi($node, 'presave'); global $user; $node->is_new = FALSE; // Apply filters to some default node fields: if (empty($node->nid)) { // Insert a new node. $node->is_new = TRUE; // When inserting a node, $node->log must be set because // {node_revisions}.log does not (and cannot) have a default // value. If the user does not have permission to create // revisions, however, the form will not contain an element for // log so $node->log will be unset at this point. if (!isset($node->log)) { $node->log = ''; } // For the same reasons, make sure we have $node->teaser and // $node->body. We should consider making these fields nullable // in a future version since node types are not required to use them. if (!isset($node->teaser)) { $node->teaser = ''; } if (!isset($node->body)) { $node->body = ''; } } elseif (!empty($node->revision)) { $node->old_vid = $node->vid; } else { // When updating a node, avoid clobberring an existing log entry with an empty one. if (empty($node->log)) { unset($node->log); } } // Set some required fields: if (empty($node->created)) { $node->created = time(); } // The update of the changed value is forced in the original node_save(). if (empty($node->changed)) { $node->changed = time(); } $node->timestamp = time(); $node->format = isset($node->format) ? $node->format : FILTER_FORMAT_DEFAULT; // Generate the node table query and the node_revisions table query. if ($node->is_new) { _node_save_revision($node, $user->uid); drupal_write_record('node', $node); db_query('UPDATE {node_revisions} SET nid = %d WHERE vid = %d', $node->nid, $node->vid); $op = 'insert'; } else { drupal_write_record('node', $node, 'nid'); if (!empty($node->revision)) { _node_save_revision($node, $user->uid); db_query('UPDATE {node} SET vid = %d WHERE nid = %d', $node->vid, $node->nid); } else { _node_save_revision($node, $user->uid, 'vid'); } $op = 'update'; } // Call the node specific callback (if any). node_invoke($node, $op); node_invoke_nodeapi($node, $op); // Update the node access table for this node. node_access_acquire_grants($node); } /** * Generates a pristine cloned node such that it is ready for import. Note, the * node_export_features module uses this function to generate cleaner exports. */ function node_export_node_clone($original_node) { global $user; $node = drupal_clone($original_node); $node->nid = NULL; $node->vid = NULL; $node->name = $user->name; $node->uid = $user->uid; if (variable_get('node_export_reset_created_'. $node->type, TRUE)) { $node->created = NULL; } if (variable_get('node_export_reset_changed_'. $node->type, TRUE)) { $node->changed = NULL; } if (variable_get('node_export_reset_revision_timestamp_'. $node->type, TRUE)) { $node->revision_timestamp = NULL; } if (variable_get('node_export_reset_last_comment_timestamp_'. $node->type, TRUE)) { $node->last_comment_timestamp = NULL; } if (variable_get('node_export_reset_menu_'. $node->type, TRUE)) { $node->menu = NULL; } if (variable_get('node_export_reset_path_'. $node->type, TRUE)) { $node->path = NULL; } else { if (is_array($node->path) && isset($node->path['pid'])) { unset($node->path['pid']); } if (module_exists('pathauto')) { // Prevent pathauto from creating a new path alias. $node->pathauto_perform_alias = FALSE; } } if (variable_get('node_export_reset_book_mlid_'. $node->type, TRUE) && isset($node->book['mlid'])) { $node->book['mlid'] = NULL; } $node->files = array(); if (variable_get('node_export_reset_'. $node->type, FALSE)) { $node_options = variable_get('node_options_'. $node->type, array('status', 'promote')); // Fill in the default values. foreach (array('status', 'moderate', 'promote', 'sticky', 'revision') as $key) { $node->$key = in_array($key, $node_options); } } return $node; } /** * Create a new menu entry with title, parent and weight exported from * another nodes menu. Returns NULL if the node has no menu title. */ function node_export_get_menu($node) { // This will fetch the existing menu item if the node had one. node_invoke_nodeapi($node, 'prepare'); // Only keep the values we care about. if (!empty($node->menu)) { // Store a copy of the old menu $old_menu = $node->menu; // Now fetch the defaults for a new menu entry. $node = NULL; node_invoke_nodeapi($node, 'prepare'); // Make a list of values to attempt to copy. $menu_fields = array( 'link_title', 'plid', 'menu_name', 'weight', // These should import properly always. 'hidden', 'expanded', 'has_children', // These will only import properly on 'Save as a new node then edit' imports. ); // Copy those fields from the old menu over the new menu defaults. foreach ($menu_fields as $menu_field) { $node->menu[$menu_field] = $old_menu[$menu_field]; } // Return the menu. return $node->menu; } } /** * Remove recursion problem from an object or array. */ function node_export_remove_recursion($o) { static $replace; if (!isset($replace)) { $replace = create_function( '$m', '$r="\x00{$m[1]}ecursion_export_node_";return \'s:\'.strlen($r.$m[2]).\':"\'.$r.$m[2].\'";\';' ); } if (is_array($o) || is_object($o)) { $re = '#(r|R):([0-9]+);#'; $serialize = serialize($o); if (preg_match($re, $serialize)) { $last = $pos = 0; while (false !== ($pos = strpos($serialize, 's:', $pos))) { $chunk = substr($serialize, $last, $pos - $last); if (preg_match($re, $chunk)) { $length = strlen($chunk); $chunk = preg_replace_callback($re, $replace, $chunk); $serialize = substr($serialize, 0, $last) . $chunk . substr($serialize, $last + ($pos - $last)); $pos += strlen($chunk) - $length; } $pos += 2; $last = strpos($serialize, ':', $pos); $length = substr($serialize, $pos, $last - $pos); $last += 4 + $length; $pos = $last; } $serialize = substr($serialize, 0, $last) . preg_replace_callback($re, $replace, substr($serialize, $last)); $o = unserialize($serialize); } } return $o; } /** * Restore recursion to an object or array. */ function node_export_restore_recursion($o) { return unserialize( preg_replace( '#s:[0-9]+:"\x00(r|R)ecursion_export_node_([0-9]+)";#', '\1:\2;', serialize($o) ) ); } /** * Get a list of possible format handlers (other than the default). * * @return * An array of format handlers from hook implementations. * @see hook_node_export_format_handlers() */ function node_export_format_handlers() { static $format_handlers; // Get default format handler. module_load_include('inc', 'node_export', 'node_export.node_code'); if (empty($format_handlers)) { $format_handlers = module_invoke_all('node_export_format_handlers'); } return $format_handlers; } // Remove once http://drupal.org/node/858274 is resolved. if (!function_exists('uuid_set_uuid')) { /** * API function to set the UUID of an object based on its serial ID. * * @param $table * Base table of the object. Currently, one of node, revision_revisions, * users, vocabulary or term_data. * @param $key * The name of the serial ID column. * @param $serial_id * The serial ID of the object. * @param $uuid * Optional UUID. If omitted, a UUID will be generated. * @return * The UUID on success, FALSE if the uuid provided is not valid. */ function uuid_set_uuid($table, $key, $serial_id, $uuid = FALSE) { if (empty($uuid)) { $uuid = uuid_uuid(); } if (!uuid_is_valid($uuid)) { return FALSE; } $uuid_table = 'uuid_'. $table; db_query("UPDATE {". $uuid_table ."} SET uuid = '%s' WHERE ". $key ." = %d", $uuid, $serial_id); if (!db_affected_rows()) { @db_query("INSERT INTO {". $uuid_table ."} (". $key .", uuid) VALUES (%d, '%s')", $serial_id, $uuid); } return $uuid; } } // Remove once http://drupal.org/node/858274 is resolved. if (!function_exists('uuid_get_uuid')) { /** * API function to get the UUID of an object based on its serial ID. * * @param $table * Base table of the object. Currently, one of node, revision_revisions, * users, vocabulary or term_data. * @param $key * The name of the serial ID column. * @param $id * The serial ID of the object. * @return * The UUID of the object, or FALSE if not found. */ function uuid_get_uuid($table, $key, $id) { $uuid_table = 'uuid_'. $table; return db_result(db_query("SELECT uuid FROM {{$uuid_table}} WHERE $key = %d", $id)); } } /** * Returns the node associated with a UUID. Has the same functionality as * node_get_by_uuid() in uuid.module but does NOT uses db_rewrite_sql. * * @return * Either the $node object, nid, or FALSE on failure. */ function node_export_node_get_by_uuid($uuid, $nid_only = FALSE) { $nid = db_result(db_query( "SELECT n.nid FROM {node} AS n INNER JOIN {uuid_node} AS un ON n.nid = un.nid WHERE un.uuid = '%s'", $uuid )); if ($nid && !$nid_only) { return node_load($nid); } else if ($nid) { return $nid; } else { return FALSE; } }