node_export_dependency.module 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. <?php
  2. /**
  3. * @file
  4. * The Node export dependency module.
  5. *
  6. * Helps maintain relationships to dependent entities.
  7. */
  8. /**
  9. * Callback for node reference settings form.
  10. */
  11. function node_export_dependency_form_node_export_settings_alter(&$form, &$form_state, $form_id) {
  12. // @todo: remove the node_export_dependency.core.inc file if solved: [#1590312]
  13. module_load_include('inc', 'node_export_dependency', 'node_export_dependency.core');
  14. $form['node_export_dependency'] = array(
  15. '#type' => 'fieldset',
  16. '#title' => t('Dependencies'),
  17. );
  18. $modules_options = array();
  19. $modules = module_implements('node_export_dependency');
  20. foreach ($modules as $module) {
  21. if ($module != 'field') {
  22. $module_info = system_get_info('module', $module);
  23. $modules_options[$module] = $module_info['name'];
  24. }
  25. }
  26. $modules = module_implements('node_export_dependency_field');
  27. foreach ($modules as $module) {
  28. $module_info = system_get_info('module', $module);
  29. $modules_options[$module] = t('Field') . ': ' . $module_info['name'];
  30. }
  31. natcasesort($modules_options);
  32. $form['node_export_dependency']['node_export_dependency_disable_modules'] = array(
  33. '#type' => 'checkboxes',
  34. '#title' => t('Disable dependencies by module'),
  35. '#default_value' => variable_get('node_export_dependency_disable_modules', array()),
  36. '#options' => $modules_options,
  37. '#description' => t('Choose modules for which to disable dependencies.'),
  38. );
  39. $form['node_export_dependency']['node_export_dependency_attach_nodes'] = array(
  40. '#type' => 'checkbox',
  41. '#title' => t('Attach dependent nodes to export automatically.'),
  42. '#default_value' => variable_get('node_export_dependency_attach_nodes', 1),
  43. );
  44. $form['node_export_dependency']['node_export_dependency_abort'] = array(
  45. '#type' => 'checkbox',
  46. '#title' => t('Abort the export when a dependent node cannot be exported.'),
  47. '#default_value' => variable_get('node_export_dependency_abort', 0),
  48. '#description' => t('Applies when attaching dependent nodes.'),
  49. );
  50. $form['node_export_dependency']['node_export_dependency_existing'] = array(
  51. '#type' => 'checkbox',
  52. '#title' => t('Maintain dependency to original node.'),
  53. '#default_value' => variable_get('node_export_dependency_existing', 1),
  54. '#description' => t('Applies when <em>Create a new node</em> imports a duplicate dependent node.') . '<strong>' . t('Disabling this is not yet supported.') . '</strong>',
  55. '#disabled' => TRUE,
  56. );
  57. $disabled_modules = variable_get('node_export_dependency_disable_modules', array());
  58. foreach (element_children($form['publishing']) as $type) {
  59. if (empty($disabled_modules['node'])) {
  60. $form['publishing'][$type]['node_export_reset_author_' . $type]['#disabled'] = TRUE;
  61. $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>';
  62. $form['publishing'][$type]['node_export_reset_author_' . $type]['#default_value'] = FALSE;
  63. variable_set('node_export_reset_author_' . $type, FALSE);
  64. }
  65. if (empty($disabled_modules['book'])) {
  66. $form['publishing'][$type]['node_export_reset_book_mlid_' . $type]['#disabled'] = TRUE;
  67. $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>';
  68. $form['publishing'][$type]['node_export_reset_book_mlid_' . $type]['#default_value'] = FALSE;
  69. variable_set('node_export_reset_book_mlid_' . $type, FALSE);
  70. }
  71. }
  72. }
  73. /**
  74. * Implements hook_node_export_alter().
  75. */
  76. function node_export_dependency_node_export_alter(&$nodes, $format) {
  77. // Keyed nodes are important for preventing duplicate nodes.
  78. $keyed_nodes = array();
  79. foreach ($nodes as $node) {
  80. $keyed_nodes[$node->nid] = $node;
  81. }
  82. foreach (array_keys($keyed_nodes) as $nid) {
  83. node_export_dependency_load_dependencies($keyed_nodes, $nid);
  84. }
  85. $nodes = array_values($keyed_nodes);
  86. }
  87. /**
  88. * Recursively load dependencies.
  89. */
  90. function node_export_dependency_load_dependencies(&$nodes, $nid, $reset = FALSE) {
  91. $node = &$nodes[$nid];
  92. $dependencies = node_export_dependency_get_dependencies('node', $node);
  93. foreach ($dependencies as $dep_key => &$dependency) {
  94. $disabled_modules = variable_get('node_export_dependency_disable_modules', array());
  95. if (!empty($disabled_modules[$dependency['module']])) {
  96. unset($dependencies[$dep_key]);
  97. continue;
  98. }
  99. $uuid = node_export_dependency_get_uuid($dependency['type'], $dependency['id']);
  100. $dependency['uuid'] = $uuid;
  101. if ($dependency['type'] == 'node' && variable_get('node_export_dependency_attach_nodes', 1)) {
  102. // It the node doesn't exist in keyed nodes, add it.
  103. if (!isset($nodes[$dependency['id']])) {
  104. $new_node = node_load($dependency['id'], NULL, $reset);
  105. if (node_export_access_export($new_node, $reset)) {
  106. $new_node = node_export_prepare_node($new_node);
  107. $nodes[$new_node->nid] = $new_node;
  108. // Recursively load dependent nodes.
  109. node_export_dependency_load_dependencies($nodes, $new_node->nid);
  110. }
  111. elseif (variable_get('node_export_dependency_abort', 0)) {
  112. // Set this node to FALSE to trigger an error in node export.
  113. // Do not use $new_node in this code in case there is a problem with it.
  114. $nodes[$dependency['id']] = FALSE;
  115. // Add a warning to watchdog.
  116. watchdog('node_export_dependency', 'No access to export node dependency %nid', array('%nid' => $dependency['id']), WATCHDOG_WARNING);
  117. drupal_set_message(t('No access to export node dependency %nid', array('%nid' => $dependency['id'])), 'error', FALSE);
  118. }
  119. }
  120. }
  121. }
  122. if (!empty($dependencies)) {
  123. $node->node_export_dependency = $dependencies;
  124. }
  125. }
  126. /**
  127. * Implements hook_node_export_import_alter().
  128. */
  129. function node_export_dependency_node_export_after_import_alter($nodes, $format, $save) {
  130. $node_export_dependency = variable_get('node_export_dependency', array());
  131. foreach ($nodes as $node) {
  132. if (isset($node->node_export_dependency)) {
  133. foreach ($node->node_export_dependency as $dep_key => $dependency) {
  134. // Try to handle this dependency now, and unset if successful.
  135. // Only do this now if maintaining dependency to original node, because
  136. // if that setting is turned off, doing this at this stage will break
  137. // things.
  138. if (variable_get('node_export_dependency_existing', 1) && node_export_dependency_handle_dependency($node, $dependency)) {
  139. unset($node->node_export_dependency[$dep_key]);
  140. }
  141. else {
  142. // Couldn't handle, store for later.
  143. $node_export_dependency[$node->uuid][] = $dependency;
  144. // Set the property to 0 to prevent database errors.
  145. node_export_dependency_set_property($node, $dependency, 0);
  146. }
  147. }
  148. unset($node->node_export_dependency);
  149. node_save($node);
  150. }
  151. }
  152. if (!empty($node_export_dependency)) {
  153. variable_set('node_export_dependency', $node_export_dependency);
  154. }
  155. else {
  156. variable_del('node_export_dependency');
  157. }
  158. }
  159. /**
  160. * Attempt to process outstanding dependencies.
  161. *
  162. * This should only be called when the parent node to fix is already saved.
  163. *
  164. * @param $iterations
  165. * How many iterations to run.
  166. * @param $seconds
  167. * How long to lock others from processing (will release upon completion).
  168. * @param $reset
  169. * Whether to reset the node_load_multiple cache.
  170. */
  171. function node_export_dependency_process_outstanding_dependencies($iterations, $seconds = 240, $reset = FALSE) {
  172. if (REQUEST_TIME - variable_get('node_export_dependency_lock', REQUEST_TIME) >= 0) {
  173. variable_set('node_export_dependency_lock', REQUEST_TIME + $seconds);
  174. $node_export_dependency = variable_get('node_export_dependency', array());
  175. // Iterate $node_export_dependency and try to handle any others.
  176. $node_export_dependency_keys = array_keys($node_export_dependency);
  177. // Shuffle so we don't get 'stuck' on a bunch of unsolvable cases.
  178. shuffle($node_export_dependency_keys);
  179. for ($count = 0; $count < $iterations; $count++) {
  180. $node_uuid = next($node_export_dependency_keys);
  181. if ($node_uuid === FALSE && empty($node_export_dependency_keys)) {
  182. break;
  183. }
  184. else {
  185. $node_uuid = reset($node_export_dependency_keys);
  186. }
  187. $dependencies = &$node_export_dependency[$node_uuid];
  188. foreach ($dependencies as $dep_key => &$dependency) {
  189. $nids = entity_get_id_by_uuid('node', array($node_uuid));
  190. $node = node_load($nids[$node_uuid], $reset);
  191. if (!empty($node)) {
  192. // Try to handle this dependency now, and unset if successful.
  193. if (node_export_dependency_handle_dependency($node, $dependency)) {
  194. unset($dependencies[$dep_key]);
  195. node_save($node);
  196. }
  197. }
  198. }
  199. if (empty($node_export_dependency[$node_uuid])) {
  200. unset($node_export_dependency[$node_uuid]);
  201. }
  202. }
  203. if (!empty($node_export_dependency)) {
  204. variable_set('node_export_dependency', $node_export_dependency);
  205. }
  206. else {
  207. variable_del('node_export_dependency');
  208. }
  209. variable_del('node_export_dependency_lock');
  210. }
  211. }
  212. /**
  213. * Implements hook_cron().
  214. */
  215. function node_export_dependency_cron() {
  216. node_export_dependency_process_outstanding_dependencies(50);
  217. }
  218. /**
  219. * Implements hook_init().
  220. */
  221. function node_export_dependency_init() {
  222. $node_export_dependency = variable_get('node_export_dependency', array());
  223. if (!empty($node_export_dependency)) {
  224. node_export_dependency_process_outstanding_dependencies(10);
  225. if (count($node_export_dependency) > 20) {
  226. drupal_set_message(
  227. t(
  228. 'There are %num outstanding Node export dependencies, please complete the imports and run cron as soon as possible.',
  229. array('%num' => count($node_export_dependency))
  230. ),
  231. 'warning'
  232. );
  233. }
  234. }
  235. }
  236. /**
  237. * Attempt to handle a dependency.
  238. *
  239. * Handles field collection items excluding file/image fields (not supported
  240. * yet) and adds the data to the given node.
  241. *
  242. * Passes all dependencies to hook_node_export_dependency_alter() for external
  243. * handling.
  244. *
  245. * @param $node
  246. * Node object before importing it.
  247. * @param $dependency
  248. * Associative array with data for the given dependency as exported under the
  249. * 'node_export_dependency' key.
  250. *
  251. * @return
  252. * TRUE or FALSE whether the dependency was handled.
  253. *
  254. * @todo
  255. * Implement file/image field handling for field collection items.
  256. */
  257. function node_export_dependency_handle_dependency(&$node, $dependency) {
  258. $handled = FALSE;
  259. $disabled_modules = variable_get('node_export_dependency_disable_modules', array());
  260. if (!empty($disabled_modules[$dependency['module']])) {
  261. // We're not handling it, so it is 'handled'.
  262. return TRUE;
  263. }
  264. // Handle exported field collection items.
  265. if ($dependency['type'] == 'field_collection_item' &&
  266. isset($dependency['node_export_field_collection_data'])) {
  267. $entity_controller = new EntityAPIController("field_collection_item");
  268. $field_collection_item = $entity_controller
  269. ->create($dependency['node_export_field_collection_data']);
  270. // The import of file/image field data is not yet supported. Thus we need
  271. // to remove any file data from the field collection item's fields to avoid
  272. // errors about non-existent files during import.
  273. $supported_file_fields = array_map('trim', explode(',',
  274. variable_get('node_export_file_supported_fields', 'file, image')));
  275. // Gather information about the field collection item's individual fields.
  276. $field_info = field_info_instances('field_collection_item',
  277. $dependency['field_name']);
  278. // Loop all fields to remove possibly contained file data.
  279. foreach ($field_info as $field_name => $info) {
  280. // If this is some file field.
  281. if (in_array($info['widget']['module'], $supported_file_fields) && is_array($field_collection_item->{$field_name})) {
  282. // Import the files, similar to node_export_file_field_import().
  283. foreach ($field_collection_item->{$field_name} as $language => $files) {
  284. if (is_array($files)) {
  285. foreach ($files as $i => $field_value) {
  286. $file = (object) $field_value;
  287. $result = _node_export_file_field_import_file($file);
  288. // The file was saved successfully, update the file field (by reference).
  289. if ($result == TRUE && isset($file->fid)) {
  290. // Retain any special properties from the original field value.
  291. $field_collection_item->{$field_name}[$language][$i] = array_merge($field_value, (array) $file);
  292. }
  293. }
  294. }
  295. }
  296. }
  297. }
  298. // Get the nid of the host node in the target system, if it already exits.
  299. $nid = db_query('SELECT nid FROM {node} WHERE uuid = :uuid',
  300. array(':uuid' => $node->uuid))->fetchField();
  301. if ($nid) {
  302. // Find the field collection item's current ID if it already exists.
  303. $item_id = db_query('
  304. SELECT d.' . $dependency['field_name'] . '_value
  305. FROM {field_data_' . $dependency['field_name'] . '} d
  306. INNER JOIN {field_collection_item} ci ON
  307. ci.item_id = d.' . $dependency['field_name'] . '_value
  308. AND ci.revision_id = d.' . $dependency['field_name'] . '_revision_id
  309. WHERE d.entity_id = :nid AND d.delta = :delta
  310. AND ci.field_name = :field_name',
  311. array(
  312. ':nid' => $nid,
  313. ':delta' => $dependency['delta'],
  314. ':field_name' => $dependency['field_name'])
  315. )->fetchField();
  316. $field_collection_item->item_id = $item_id ? $item_id : NULL;
  317. }
  318. else {
  319. $field_collection_item->item_id = NULL;
  320. }
  321. if ($field_collection_item->item_id) {
  322. // The item already exists in the DB, so we need its uuid and revision_id
  323. // to overwrite exactly the existing one with the new data.
  324. $data = db_query('
  325. SELECT revision_id, uuid
  326. FROM {field_collection_item}
  327. WHERE item_id = :item_id',
  328. array(':item_id' => $field_collection_item->item_id))->fetchAssoc();
  329. $field_collection_item->uuid = $data['uuid'];
  330. $field_collection_item->revision_id = $data['revision_id'];
  331. // Property is not needed.
  332. if (property_exists($field_collection_item, 'is_new')) {
  333. unset($field_collection_item->is_new);
  334. }
  335. }
  336. // If there is no item_id, i.e. this is a new field collection item.
  337. else {
  338. $field_collection_item->is_new = TRUE;
  339. $field_collection_item->revision_id = NULL;
  340. // Remove the old uuid, a new one will be created.
  341. unset($field_collection_item->uuid);
  342. }
  343. // Add the field collection item data to the node where node_save() expects
  344. // it. It will save the new data later.
  345. $node->{$dependency['field_name']}[$dependency['langcode']]
  346. [$dependency['delta']]['entity'] = $field_collection_item;
  347. $handled = TRUE;
  348. }
  349. if (!isset($dependency['relationship'])) {
  350. // Entity id.
  351. $entity_ids = entity_get_id_by_uuid($dependency['type'], array($dependency['uuid']));
  352. $entity_id = $entity_ids ? reset($entity_ids) : FALSE;
  353. if ($entity_id) {
  354. node_export_dependency_set_property($node, $dependency, $entity_id);
  355. }
  356. $handled = TRUE;
  357. }
  358. drupal_alter('node_export_dependency', $handled, $node, $dependency);
  359. return $handled;
  360. }
  361. /**
  362. * Implements hook_node_export_dependency_alter().
  363. */
  364. function node_export_dependency_node_export_dependency_alter(&$handled, &$node, $dependency) {
  365. // @todo special fixing up for Book and OG nodes and other special cases?
  366. }
  367. /**
  368. * Set a property according to $dependency for the property location and $new_value
  369. * for the new value.
  370. */
  371. function node_export_dependency_set_property(&$entity, $dependency, $new_value) {
  372. if (isset($dependency['field_name'])) {
  373. // This is a field.
  374. $entity->{$dependency['field_name']}[$dependency['langcode']]
  375. [$dependency['delta']][$dependency['property']] = $new_value;
  376. }
  377. else {
  378. // Some other property.
  379. if (isset($dependency['property'])) {
  380. $property_path = $dependency['property'];
  381. if (!is_array($property_path)) {
  382. $property_path = array($property_path);
  383. }
  384. $value = &$entity;
  385. foreach ($property_path as $p) {
  386. if (is_object($value) && isset($value->{$p})) {
  387. $value = &$value->{$p};
  388. }
  389. elseif (is_array($value) && isset($value[$p])) {
  390. $value = &$value[$p];
  391. }
  392. }
  393. $value = $new_value;
  394. }
  395. }
  396. }
  397. /**
  398. * Helper function to add entity dependencies to a dependency array.
  399. *
  400. * We never treat user UID 0 or 1 as dependencies. Those are low level user
  401. * accounts ("anonymous" and "root") that already exists in most systems.
  402. *
  403. * @param $dependencies
  404. * The dependency array.
  405. * @param $objects
  406. * Array of objects that should be checked for dependencies in $properties.
  407. * @param $entity_type
  408. * The type of entity that $properties will add dependency on.
  409. * @param $properties
  410. * An array of properties that adds dependencies to $objects. All properties
  411. * must only point to one entity type at the time. A property can be a key
  412. * on the object, or an array of parent keys to identify the property.
  413. * @todo remove if this is solved [#1590312]
  414. */
  415. function node_export_dependency_add(&$dependencies, $objects, $entity_type, $properties) {
  416. if (!is_array($objects)) {
  417. $objects = array($objects);
  418. }
  419. if (!is_array($properties)) {
  420. $properties = array($properties);
  421. }
  422. foreach ($objects as $delta => $object) {
  423. foreach ($properties as $property) {
  424. $property_path = $property;
  425. if (!is_array($property_path)) {
  426. $property_path = array($property_path);
  427. }
  428. $value = $object;
  429. foreach ($property_path as $p) {
  430. if (is_object($value) && isset($value->{$p})) {
  431. $value = $value->{$p};
  432. }
  433. elseif (is_array($value) && isset($value[$p])) {
  434. $value = $value[$p];
  435. }
  436. }
  437. if (!empty($value) && $value != $object && !($entity_type == 'user' && (int)$value == 1)) {
  438. $dependencies[] = array(
  439. 'type' => $entity_type,
  440. 'id' => $value,
  441. 'delta' => $delta,
  442. 'property' => $property,
  443. );
  444. }
  445. }
  446. }
  447. }
  448. /**
  449. * Get UUID based on entity id.
  450. */
  451. function node_export_dependency_get_uuid($entity_type, $id) {
  452. $entity_info = entity_get_info($entity_type);
  453. $id_key = $entity_info['entity keys']['id'];
  454. return uuid_get_uuid($entity_type, $id_key, $id);
  455. }
  456. /**
  457. * Get dependencies of an entity.
  458. *
  459. * @todo rewrite if this is solved [#1590312]
  460. */
  461. function node_export_dependency_get_dependencies($entity_type, $entity) {
  462. // @todo: remove the node_export_dependency.core.inc file if solved: [#1590312]
  463. module_load_include('inc', 'node_export_dependency', 'node_export_dependency.core');
  464. $all_dependencies = array();
  465. foreach (module_implements('node_export_dependency') as $module) {
  466. $dependencies = module_invoke($module, 'node_export_dependency', $entity, $entity_type);
  467. if (isset($dependencies) && is_array($dependencies)) {
  468. foreach ($dependencies as &$dependency) {
  469. if (empty($dependency['module'])) {
  470. $dependency['module'] = $module;
  471. }
  472. }
  473. $all_dependencies = array_merge_recursive($all_dependencies, $dependencies);
  474. }
  475. }
  476. return $all_dependencies;
  477. }