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