'fieldset', '#title' => t('Dependencies'), ); $modules_options = array(); $modules = module_implements('node_export_dependency'); foreach ($modules as $module) { if ($module != 'field') { $module_info = system_get_info('module', $module); $modules_options[$module] = $module_info['name']; } } $modules = module_implements('node_export_dependency_field'); foreach ($modules as $module) { $module_info = system_get_info('module', $module); $modules_options[$module] = t('Field') . ': ' . $module_info['name']; } natcasesort($modules_options); $form['node_export_dependency']['node_export_dependency_disable_modules'] = array( '#type' => 'checkboxes', '#title' => t('Disable dependencies by module'), '#default_value' => variable_get('node_export_dependency_disable_modules', array()), '#options' => $modules_options, '#description' => t('Choose modules for which to disable dependencies.'), ); $form['node_export_dependency']['node_export_dependency_attach_nodes'] = array( '#type' => 'checkbox', '#title' => t('Attach dependent nodes to export automatically.'), '#default_value' => variable_get('node_export_dependency_attach_nodes', 1), ); $form['node_export_dependency']['node_export_dependency_abort'] = array( '#type' => 'checkbox', '#title' => t('Abort the export when a dependent node cannot be exported.'), '#default_value' => variable_get('node_export_dependency_abort', 0), '#description' => t('Applies when attaching dependent nodes.'), ); $form['node_export_dependency']['node_export_dependency_existing'] = array( '#type' => 'checkbox', '#title' => t('Maintain dependency to original node.'), '#default_value' => variable_get('node_export_dependency_existing', 1), '#description' => t('Applies when Create a new node imports a duplicate dependent node.') . '' . t('Disabling this is not yet supported.') . '', '#disabled' => TRUE, ); $disabled_modules = variable_get('node_export_dependency_disable_modules', array()); foreach (element_children($form['publishing']) as $type) { if (empty($disabled_modules['node'])) { $form['publishing'][$type]['node_export_reset_author_' . $type]['#disabled'] = TRUE; $form['publishing'][$type]['node_export_reset_author_' . $type]['#description'] .= ' ' . t('Disabled by Node export dependency because Node module dependencies are enabled.') . ''; $form['publishing'][$type]['node_export_reset_author_' . $type]['#default_value'] = FALSE; variable_set('node_export_reset_author_' . $type, FALSE); } if (empty($disabled_modules['book'])) { $form['publishing'][$type]['node_export_reset_book_mlid_' . $type]['#disabled'] = TRUE; $form['publishing'][$type]['node_export_reset_book_mlid_' . $type]['#description'] .= ' ' . t('Disabled by Node export dependency because Book module dependencies are enabled.') . ''; $form['publishing'][$type]['node_export_reset_book_mlid_' . $type]['#default_value'] = FALSE; variable_set('node_export_reset_book_mlid_' . $type, FALSE); } } } /** * Implements hook_node_export_alter(). */ function node_export_dependency_node_export_alter(&$nodes, $format) { // Keyed nodes are important for preventing duplicate nodes. $keyed_nodes = array(); foreach ($nodes as $node) { $keyed_nodes[$node->nid] = $node; } foreach (array_keys($keyed_nodes) as $nid) { node_export_dependency_load_dependencies($keyed_nodes, $nid); } $nodes = array_values($keyed_nodes); } /** * Recursively load dependencies. */ function node_export_dependency_load_dependencies(&$nodes, $nid, $reset = FALSE) { $node = &$nodes[$nid]; $dependencies = node_export_dependency_get_dependencies('node', $node); foreach ($dependencies as $dep_key => &$dependency) { $disabled_modules = variable_get('node_export_dependency_disable_modules', array()); if (!empty($disabled_modules[$dependency['module']])) { unset($dependencies[$dep_key]); continue; } $uuid = node_export_dependency_get_uuid($dependency['type'], $dependency['id']); $dependency['uuid'] = $uuid; if ($dependency['type'] == 'node' && variable_get('node_export_dependency_attach_nodes', 1)) { // It the node doesn't exist in keyed nodes, add it. if (!isset($nodes[$dependency['id']])) { $new_node = node_load($dependency['id'], NULL, $reset); if (node_export_access_export($new_node, $reset)) { $new_node = node_export_prepare_node($new_node); $nodes[$new_node->nid] = $new_node; // Recursively load dependent nodes. node_export_dependency_load_dependencies($nodes, $new_node->nid); } elseif (variable_get('node_export_dependency_abort', 0)) { // Set this node to FALSE to trigger an error in node export. // Do not use $new_node in this code in case there is a problem with it. $nodes[$dependency['id']] = FALSE; // Add a warning to watchdog. watchdog('node_export_dependency', 'No access to export node dependency %nid', array('%nid' => $dependency['id']), WATCHDOG_WARNING); drupal_set_message(t('No access to export node dependency %nid', array('%nid' => $dependency['id'])), 'error', FALSE); } } } } if (!empty($dependencies)) { $node->node_export_dependency = $dependencies; } } /** * Implements hook_node_export_import_alter(). */ function node_export_dependency_node_export_after_import_alter($nodes, $format, $save) { $node_export_dependency = variable_get('node_export_dependency', array()); foreach ($nodes as $node) { if (isset($node->node_export_dependency)) { foreach ($node->node_export_dependency as $dep_key => $dependency) { // Try to handle this dependency now, and unset if successful. // Only do this now if maintaining dependency to original node, because // if that setting is turned off, doing this at this stage will break // things. if (variable_get('node_export_dependency_existing', 1) && node_export_dependency_handle_dependency($node, $dependency)) { unset($node->node_export_dependency[$dep_key]); } else { // Couldn't handle, store for later. $node_export_dependency[$node->uuid][] = $dependency; // Set the property to 0 to prevent database errors. node_export_dependency_set_property($node, $dependency, 0); } } unset($node->node_export_dependency); node_save($node); } } if (!empty($node_export_dependency)) { variable_set('node_export_dependency', $node_export_dependency); } else { variable_del('node_export_dependency'); } } /** * Attempt to process outstanding dependencies. * * This should only be called when the parent node to fix is already saved. * * @param $iterations * How many iterations to run. * @param $seconds * How long to lock others from processing (will release upon completion). * @param $reset * Whether to reset the node_load_multiple cache. */ function node_export_dependency_process_outstanding_dependencies($iterations, $seconds = 240, $reset = FALSE) { if (REQUEST_TIME - variable_get('node_export_dependency_lock', REQUEST_TIME) >= 0) { variable_set('node_export_dependency_lock', REQUEST_TIME + $seconds); $node_export_dependency = variable_get('node_export_dependency', array()); // Iterate $node_export_dependency and try to handle any others. $node_export_dependency_keys = array_keys($node_export_dependency); // Shuffle so we don't get 'stuck' on a bunch of unsolvable cases. shuffle($node_export_dependency_keys); for ($count = 0; $count < $iterations; $count++) { $node_uuid = next($node_export_dependency_keys); if ($node_uuid === FALSE && empty($node_export_dependency_keys)) { break; } else { $node_uuid = reset($node_export_dependency_keys); } $dependencies = &$node_export_dependency[$node_uuid]; foreach ($dependencies as $dep_key => &$dependency) { $nids = entity_get_id_by_uuid('node', array($node_uuid)); $node = node_load($nids[$node_uuid], $reset); if (!empty($node)) { // Try to handle this dependency now, and unset if successful. if (node_export_dependency_handle_dependency($node, $dependency)) { unset($dependencies[$dep_key]); node_save($node); } } } if (empty($node_export_dependency[$node_uuid])) { unset($node_export_dependency[$node_uuid]); } } if (!empty($node_export_dependency)) { variable_set('node_export_dependency', $node_export_dependency); } else { variable_del('node_export_dependency'); } variable_del('node_export_dependency_lock'); } } /** * Implements hook_cron(). */ function node_export_dependency_cron() { node_export_dependency_process_outstanding_dependencies(50); } /** * Implements hook_init(). */ function node_export_dependency_init() { $node_export_dependency = variable_get('node_export_dependency', array()); if (!empty($node_export_dependency)) { node_export_dependency_process_outstanding_dependencies(10); if (count($node_export_dependency) > 20) { drupal_set_message( t( 'There are %num outstanding Node export dependencies, please complete the imports and run cron as soon as possible.', array('%num' => count($node_export_dependency)) ), 'warning' ); } } } /** * Attempt to handle a dependency. * * Handles field collection items excluding file/image fields (not supported * yet) and adds the data to the given node. * * Passes all dependencies to hook_node_export_dependency_alter() for external * handling. * * @param $node * Node object before importing it. * @param $dependency * Associative array with data for the given dependency as exported under the * 'node_export_dependency' key. * * @return * TRUE or FALSE whether the dependency was handled. * * @todo * Implement file/image field handling for field collection items. */ function node_export_dependency_handle_dependency(&$node, $dependency) { $handled = FALSE; $disabled_modules = variable_get('node_export_dependency_disable_modules', array()); if (!empty($disabled_modules[$dependency['module']])) { // We're not handling it, so it is 'handled'. return TRUE; } // Handle exported field collection items. if ($dependency['type'] == 'field_collection_item' && isset($dependency['node_export_field_collection_data'])) { $entity_controller = new EntityAPIController("field_collection_item"); $field_collection_item = $entity_controller ->create($dependency['node_export_field_collection_data']); // The import of file/image field data is not yet supported. Thus we need // to remove any file data from the field collection item's fields to avoid // errors about non-existent files during import. $supported_file_fields = array_map('trim', explode(',', variable_get('node_export_file_supported_fields', 'file, image'))); // Gather information about the field collection item's individual fields. $field_info = field_info_instances('field_collection_item', $dependency['field_name']); // Loop all fields to remove possibly contained file data. foreach ($field_info as $field_name => $info) { // If this is some file field. if (in_array($info['widget']['module'], $supported_file_fields) && is_array($field_collection_item->{$field_name})) { // Import the files, similar to node_export_file_field_import(). foreach ($field_collection_item->{$field_name} as $language => $files) { if (is_array($files)) { foreach ($files as $i => $field_value) { $file = (object) $field_value; $result = _node_export_file_field_import_file($file); // The file was saved successfully, update the file field (by reference). if ($result == TRUE && isset($file->fid)) { // Retain any special properties from the original field value. $field_collection_item->{$field_name}[$language][$i] = array_merge($field_value, (array) $file); } } } } } } // Get the nid of the host node in the target system, if it already exits. $nid = db_query('SELECT nid FROM {node} WHERE uuid = :uuid', array(':uuid' => $node->uuid))->fetchField(); if ($nid) { // Find the field collection item's current ID if it already exists. $item_id = db_query(' SELECT d.' . $dependency['field_name'] . '_value FROM {field_data_' . $dependency['field_name'] . '} d INNER JOIN {field_collection_item} ci ON ci.item_id = d.' . $dependency['field_name'] . '_value AND ci.revision_id = d.' . $dependency['field_name'] . '_revision_id WHERE d.entity_id = :nid AND d.delta = :delta AND ci.field_name = :field_name', array( ':nid' => $nid, ':delta' => $dependency['delta'], ':field_name' => $dependency['field_name']) )->fetchField(); $field_collection_item->item_id = $item_id ? $item_id : NULL; } else { $field_collection_item->item_id = NULL; } if ($field_collection_item->item_id) { // The item already exists in the DB, so we need its uuid and revision_id // to overwrite exactly the existing one with the new data. $data = db_query(' SELECT revision_id, uuid FROM {field_collection_item} WHERE item_id = :item_id', array(':item_id' => $field_collection_item->item_id))->fetchAssoc(); $field_collection_item->uuid = $data['uuid']; $field_collection_item->revision_id = $data['revision_id']; // Property is not needed. if (property_exists($field_collection_item, 'is_new')) { unset($field_collection_item->is_new); } } // If there is no item_id, i.e. this is a new field collection item. else { $field_collection_item->is_new = TRUE; $field_collection_item->revision_id = NULL; // Remove the old uuid, a new one will be created. unset($field_collection_item->uuid); } // Add the field collection item data to the node where node_save() expects // it. It will save the new data later. $node->{$dependency['field_name']}[$dependency['langcode']] [$dependency['delta']]['entity'] = $field_collection_item; $handled = TRUE; } if (!isset($dependency['relationship'])) { // Entity id. $entity_ids = entity_get_id_by_uuid($dependency['type'], array($dependency['uuid'])); $entity_id = $entity_ids ? reset($entity_ids) : FALSE; if ($entity_id) { node_export_dependency_set_property($node, $dependency, $entity_id); } $handled = TRUE; } drupal_alter('node_export_dependency', $handled, $node, $dependency); return $handled; } /** * Implements hook_node_export_dependency_alter(). */ function node_export_dependency_node_export_dependency_alter(&$handled, &$node, $dependency) { // @todo special fixing up for Book and OG nodes and other special cases? } /** * Set a property according to $dependency for the property location and $new_value * for the new value. */ function node_export_dependency_set_property(&$entity, $dependency, $new_value) { if (isset($dependency['field_name'])) { // This is a field. $entity->{$dependency['field_name']}[$dependency['langcode']] [$dependency['delta']][$dependency['property']] = $new_value; } else { // Some other property. if (isset($dependency['property'])) { $property_path = $dependency['property']; if (!is_array($property_path)) { $property_path = array($property_path); } $value = &$entity; foreach ($property_path as $p) { if (is_object($value) && isset($value->{$p})) { $value = &$value->{$p}; } elseif (is_array($value) && isset($value[$p])) { $value = &$value[$p]; } } $value = $new_value; } } } /** * Helper function to add entity dependencies to a dependency array. * * We never treat user UID 0 or 1 as dependencies. Those are low level user * accounts ("anonymous" and "root") that already exists in most systems. * * @param $dependencies * The dependency array. * @param $objects * Array of objects that should be checked for dependencies in $properties. * @param $entity_type * The type of entity that $properties will add dependency on. * @param $properties * An array of properties that adds dependencies to $objects. All properties * must only point to one entity type at the time. A property can be a key * on the object, or an array of parent keys to identify the property. * @todo remove if this is solved [#1590312] */ function node_export_dependency_add(&$dependencies, $objects, $entity_type, $properties) { if (!is_array($objects)) { $objects = array($objects); } if (!is_array($properties)) { $properties = array($properties); } foreach ($objects as $delta => $object) { foreach ($properties as $property) { $property_path = $property; if (!is_array($property_path)) { $property_path = array($property_path); } $value = $object; foreach ($property_path as $p) { if (is_object($value) && isset($value->{$p})) { $value = $value->{$p}; } elseif (is_array($value) && isset($value[$p])) { $value = $value[$p]; } } if (!empty($value) && $value != $object && !($entity_type == 'user' && (int)$value == 1)) { $dependencies[] = array( 'type' => $entity_type, 'id' => $value, 'delta' => $delta, 'property' => $property, ); } } } } /** * Get UUID based on entity id. */ function node_export_dependency_get_uuid($entity_type, $id) { $entity_info = entity_get_info($entity_type); $id_key = $entity_info['entity keys']['id']; return uuid_get_uuid($entity_type, $id_key, $id); } /** * Get dependencies of an entity. * * @todo rewrite if this is solved [#1590312] */ function node_export_dependency_get_dependencies($entity_type, $entity) { // @todo: remove the node_export_dependency.core.inc file if solved: [#1590312] module_load_include('inc', 'node_export_dependency', 'node_export_dependency.core'); $all_dependencies = array(); foreach (module_implements('node_export_dependency') as $module) { $dependencies = module_invoke($module, 'node_export_dependency', $entity, $entity_type); if (isset($dependencies) && is_array($dependencies)) { foreach ($dependencies as &$dependency) { if (empty($dependency['module'])) { $dependency['module'] = $module; } } $all_dependencies = array_merge_recursive($all_dependencies, $dependencies); } } return $all_dependencies; }