node.inc 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. <?php
  2. /**
  3. * @file
  4. * Support for node destinations.
  5. */
  6. // TODO:
  7. // Make sure this works with updates, explicit destination keys
  8. /**
  9. * Destination class implementing migration into nodes.
  10. */
  11. class MigrateDestinationNode extends MigrateDestinationEntity {
  12. static public function getKeySchema() {
  13. return array(
  14. 'nid' => array(
  15. 'type' => 'int',
  16. 'unsigned' => TRUE,
  17. 'description' => 'ID of destination node',
  18. ),
  19. );
  20. }
  21. /**
  22. * Return an options array for node destinations.
  23. *
  24. * @param string $language
  25. * Default language for nodes created via this destination class.
  26. * @param string $text_format
  27. * Default text format for nodes created via this destination class.
  28. */
  29. static public function options($language, $text_format) {
  30. return compact('language', 'text_format');
  31. }
  32. /**
  33. * Basic initialization
  34. *
  35. * @param string $bundle
  36. * A.k.a. the content type (page, article, etc.) of the node.
  37. * @param array $options
  38. * Options applied to nodes.
  39. */
  40. public function __construct($bundle, array $options = array()) {
  41. parent::__construct('node', $bundle, $options);
  42. }
  43. /**
  44. * Returns a list of fields available to be mapped for the node type (bundle)
  45. *
  46. * @param Migration $migration
  47. * Optionally, the migration containing this destination.
  48. * @return array
  49. * Keys: machine names of the fields (to be passed to addFieldMapping)
  50. * Values: Human-friendly descriptions of the fields.
  51. */
  52. public function fields($migration = NULL) {
  53. $fields = array();
  54. // First the core (node table) properties
  55. $fields['nid'] = t('Node: <a href="@doc">Existing node ID</a>',
  56. array('@doc' => 'http://drupal.org/node/1349696#nid'));
  57. $node_type = node_type_load($this->bundle);
  58. if ($node_type->has_title) {
  59. $fields['title'] = t('Node: <a href="@doc">',
  60. array('@doc' => 'http://drupal.org/node/1349696#title'))
  61. . $node_type->title_label . '</a>';
  62. }
  63. $fields['uid'] = t('Node: <a href="@doc">Authored by (uid)</a>',
  64. array('@doc' => 'http://drupal.org/node/1349696#uid'));
  65. $fields['created'] = t('Node: <a href="@doc">Created timestamp</a>',
  66. array('@doc' => 'http://drupal.org/node/1349696#created'));
  67. $fields['changed'] = t('Node: <a href="@doc">Modified timestamp</a>',
  68. array('@doc' => 'http://drupal.org/node/1349696#changed'));
  69. $fields['status'] = t('Node: <a href="@doc">Published</a>',
  70. array('@doc' => 'http://drupal.org/node/1349696#status'));
  71. $fields['promote'] = t('Node: <a href="@doc">Promoted to front page</a>',
  72. array('@doc' => 'http://drupal.org/node/1349696#promote'));
  73. $fields['sticky'] = t('Node: <a href="@doc">Sticky at top of lists</a>',
  74. array('@doc' => 'http://drupal.org/node/1349696#sticky'));
  75. $fields['revision'] = t('Node: <a href="@doc">Create new revision</a>',
  76. array('@doc' => 'http://drupal.org/node/1349696#revision'));
  77. $fields['log'] = t('Node: <a href="@doc">Revision Log message</a>',
  78. array('@doc' => 'http://drupal.org/node/1349696#log'));
  79. $fields['language'] = t('Node: <a href="@doc">Language (fr, en, ...)</a>',
  80. array('@doc' => 'http://drupal.org/node/1349696#language'));
  81. $fields['tnid'] = t('Node: <a href="@doc">The translation set id for this node</a>',
  82. array('@doc' => 'http://drupal.org/node/1349696#tnid'));
  83. $fields['revision_uid'] = t('Node: <a href="@doc">Modified (uid)</a>',
  84. array('@doc' => 'http://drupal.org/node/1349696#revision_uid'));
  85. $fields['is_new'] = t('Option: <a href="@doc">Indicates a new node with the specified nid should be created</a>',
  86. array('@doc' => 'http://drupal.org/node/1349696#is_new'));
  87. // Then add in anything provided by handlers
  88. $fields += migrate_handler_invoke_all('Entity', 'fields', $this->entityType, $this->bundle, $migration);
  89. $fields += migrate_handler_invoke_all('Node', 'fields', $this->entityType, $this->bundle, $migration);
  90. return $fields;
  91. }
  92. /**
  93. * Delete a batch of nodes at once.
  94. *
  95. * @param $nids
  96. * Array of node IDs to be deleted.
  97. */
  98. public function bulkRollback(array $nids) {
  99. migrate_instrument_start('node_delete_multiple');
  100. $this->prepareRollback($nids);
  101. node_delete_multiple($nids);
  102. $this->completeRollback($nids);
  103. migrate_instrument_stop('node_delete_multiple');
  104. }
  105. /**
  106. * Import a single node.
  107. *
  108. * @param $node
  109. * Node object to build. Prefilled with any fields mapped in the Migration.
  110. * @param $row
  111. * Raw source data object - passed through to prepare/complete handlers.
  112. * @return array
  113. * Array of key fields (nid only in this case) of the node that was saved if
  114. * successful. FALSE on failure.
  115. */
  116. public function import(stdClass $node, stdClass $row) {
  117. // Updating previously-migrated content?
  118. $migration = Migration::currentMigration();
  119. if (isset($row->migrate_map_destid1)) {
  120. // Make sure is_new is off
  121. $node->is_new = FALSE;
  122. if (isset($node->nid)) {
  123. if ($node->nid != $row->migrate_map_destid1) {
  124. throw new MigrateException(t("Incoming nid !nid and map destination nid !destid1 don't match",
  125. array('!nid' => $node->nid, '!destid1' => $row->migrate_map_destid1)));
  126. }
  127. }
  128. else {
  129. $node->nid = $row->migrate_map_destid1;
  130. }
  131. // Get the existing vid, tnid so updates don't generate notices
  132. $values = db_select('node', 'n')
  133. ->fields('n', array('vid', 'tnid'))
  134. ->condition('nid', $node->nid)
  135. ->execute()
  136. ->fetchAssoc();
  137. if (empty($values)) {
  138. throw new MigrateException(t("Incoming node ID !nid no longer exists",
  139. array('!nid' => $node->nid)));
  140. }
  141. $node->vid = $values['vid'];
  142. if (empty($row->tnid)) {
  143. $node->tnid = $values['tnid'];
  144. }
  145. }
  146. if ($migration->getSystemOfRecord() == Migration::DESTINATION) {
  147. if (!isset($node->nid)) {
  148. throw new MigrateException(t('System-of-record is DESTINATION, but no destination nid provided'));
  149. }
  150. $old_node = node_load($node->nid);
  151. if (empty($old_node)) {
  152. throw new MigrateException(t('System-of-record is DESTINATION, but node !nid does not exist',
  153. array('!nid' => $node->nid)));
  154. }
  155. if (!isset($node->created)) {
  156. $node->created = $old_node->created;
  157. }
  158. if (!isset($node->vid)) {
  159. $node->vid = $old_node->vid;
  160. }
  161. if (!isset($node->status)) {
  162. $node->status = $old_node->status;
  163. }
  164. if (!isset($node->uid)) {
  165. $node->uid = $old_node->uid;
  166. }
  167. }
  168. elseif (!isset($node->type)) {
  169. // Default the type to our designated destination bundle (by doing this
  170. // conditionally, we permit some flexibility in terms of implementing
  171. // migrations which can affect more than one type).
  172. $node->type = $this->bundle;
  173. }
  174. // Set some required properties.
  175. if ($migration->getSystemOfRecord() == Migration::SOURCE) {
  176. if (!isset($node->language)) {
  177. $node->language = $this->language;
  178. }
  179. // Apply defaults, allow standard node prepare hooks to fire.
  180. // node_object_prepare() will blow these away, so save them here and
  181. // stuff them in later if need be.
  182. if (isset($node->created)) {
  183. $created = MigrationBase::timestamp($node->created);
  184. }
  185. else {
  186. // To keep node_object_prepare() from choking
  187. $node->created = REQUEST_TIME;
  188. }
  189. if (isset($node->changed)) {
  190. $changed = MigrationBase::timestamp($node->changed);
  191. }
  192. if (isset($node->uid)) {
  193. $uid = $node->uid;
  194. }
  195. node_object_prepare($node);
  196. if (isset($created)) {
  197. $node->created = $created;
  198. }
  199. // No point to resetting $node->changed here, node_save() will overwrite it
  200. if (isset($uid)) {
  201. $node->uid = $uid;
  202. }
  203. }
  204. // Invoke migration prepare handlers
  205. $this->prepare($node, $row);
  206. if (!isset($node->revision)) {
  207. $node->revision = 0; // Saves disk space and writes. Can be overridden.
  208. }
  209. // Trying to update an existing node
  210. if ($migration->getSystemOfRecord() == Migration::DESTINATION) {
  211. // Incoming data overrides existing data, so only copy non-existent fields
  212. foreach ($old_node as $field => $value) {
  213. // An explicit NULL in the source data means to wipe to old value (i.e.,
  214. // don't copy it over from $old_node)
  215. if (property_exists($node, $field) && $node->$field === NULL) {
  216. // Ignore this field
  217. }
  218. elseif (!isset($node->$field)) {
  219. $node->$field = $old_node->$field;
  220. }
  221. }
  222. }
  223. if (isset($node->nid) && !(isset($node->is_new) && $node->is_new)) {
  224. $updating = TRUE;
  225. }
  226. else {
  227. $updating = FALSE;
  228. }
  229. migrate_instrument_start('node_save');
  230. node_save($node);
  231. migrate_instrument_stop('node_save');
  232. if (isset($node->nid)) {
  233. if ($updating) {
  234. $this->numUpdated++;
  235. }
  236. else {
  237. $this->numCreated++;
  238. }
  239. // Unfortunately, http://drupal.org/node/722688 was not accepted, so fix
  240. // the changed timestamp
  241. if (isset($changed)) {
  242. db_update('node')
  243. ->fields(array('changed' => $changed))
  244. ->condition('nid', $node->nid)
  245. ->execute();
  246. $node->changed = $changed;
  247. }
  248. // Potentially fix uid and timestamp in node_revisions.
  249. $query = db_update('node_revision')
  250. ->condition('vid', $node->vid);
  251. if (isset($changed)) {
  252. $fields['timestamp'] = $changed;
  253. }
  254. $revision_uid = isset($node->revision_uid) ? $node->revision_uid : $node->uid;
  255. if ($revision_uid != $GLOBALS['user']->uid) {
  256. $fields['uid'] = $revision_uid;
  257. }
  258. if (!empty($fields)) {
  259. // We actually have something to update.
  260. $query->fields($fields);
  261. $query->execute();
  262. if (isset($changed)) {
  263. $node->timestamp = $changed;
  264. }
  265. }
  266. $return = array($node->nid);
  267. }
  268. else {
  269. $return = FALSE;
  270. }
  271. $this->complete($node, $row);
  272. return $return;
  273. }
  274. }