node_export_dependency.module in Node export 7.3
The Node export dependency module.
Helps maintain relationships to dependent entities.
File
modules/node_export_dependency/node_export_dependency.moduleView source
<?php
/**
* @file
* The Node export dependency module.
*
* Helps maintain relationships to dependent entities.
*/
/**
* Callback for node reference settings form.
*/
function node_export_dependency_form_node_export_settings_alter(&$form, &$form_state, $form_id) {
// @todo: remove the node_export_dependency.core.inc file if solved: [#1590312]
module_load_include('inc', 'node_export_dependency', 'node_export_dependency.core');
$form['node_export_dependency'] = array(
'#type' => '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 <em>Create a new node</em> imports a duplicate dependent node.') . '<strong>' . t('Disabling this is not yet supported.') . '</strong>',
'#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'] .= ' <strong>' . t('Disabled by <em>Node export dependency</em> because <em>Node module</em> dependencies are enabled.') . '</strong>';
$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'] .= ' <strong>' . t('Disabled by <em>Node export dependency</em> because <em>Book module</em> dependencies are enabled.') . '</strong>';
$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 (!empty($new_node)) {
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);
}
}
else {
watchdog('node_export_dependency', "Found an empty dependency when trying to load dependency with id %id. Dependency array was: %vals", array(
'%id' => $dependency['id'],
'%vals' => print_r($dependency, 1),
), WATCHDOG_WARNING);
}
}
}
}
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);
}
}
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;
}