Mercurial > hg > isophonics-drupal-site
comparison core/modules/node/tests/src/Functional/NodeRevisionsTest.php @ 17:129ea1e6d783
Update, including to Drupal core 8.6.10
author | Chris Cannam |
---|---|
date | Thu, 28 Feb 2019 13:21:36 +0000 |
parents | |
children | af1871eacc83 |
comparison
equal
deleted
inserted
replaced
16:c2387f117808 | 17:129ea1e6d783 |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\Tests\node\Functional; | |
4 | |
5 use Drupal\Core\Url; | |
6 use Drupal\field\Entity\FieldConfig; | |
7 use Drupal\field\Entity\FieldStorageConfig; | |
8 use Drupal\language\Entity\ConfigurableLanguage; | |
9 use Drupal\node\Entity\Node; | |
10 use Drupal\node\NodeInterface; | |
11 use Drupal\Component\Serialization\Json; | |
12 | |
13 /** | |
14 * Create a node with revisions and test viewing, saving, reverting, and | |
15 * deleting revisions for users with access for this content type. | |
16 * | |
17 * @group node | |
18 */ | |
19 class NodeRevisionsTest extends NodeTestBase { | |
20 | |
21 /** | |
22 * An array of node revisions. | |
23 * | |
24 * @var \Drupal\node\NodeInterface[] | |
25 */ | |
26 protected $nodes; | |
27 | |
28 /** | |
29 * Revision log messages. | |
30 * | |
31 * @var array | |
32 */ | |
33 protected $revisionLogs; | |
34 | |
35 /** | |
36 * {@inheritdoc} | |
37 */ | |
38 public static $modules = ['node', 'contextual', 'datetime', 'language', 'content_translation']; | |
39 | |
40 /** | |
41 * {@inheritdoc} | |
42 */ | |
43 protected function setUp() { | |
44 parent::setUp(); | |
45 | |
46 // Enable additional languages. | |
47 ConfigurableLanguage::createFromLangcode('de')->save(); | |
48 ConfigurableLanguage::createFromLangcode('it')->save(); | |
49 | |
50 $field_storage_definition = [ | |
51 'field_name' => 'untranslatable_string_field', | |
52 'entity_type' => 'node', | |
53 'type' => 'string', | |
54 'cardinality' => 1, | |
55 'translatable' => FALSE, | |
56 ]; | |
57 $field_storage = FieldStorageConfig::create($field_storage_definition); | |
58 $field_storage->save(); | |
59 | |
60 $field_definition = [ | |
61 'field_storage' => $field_storage, | |
62 'bundle' => 'page', | |
63 ]; | |
64 $field = FieldConfig::create($field_definition); | |
65 $field->save(); | |
66 | |
67 // Enable translation for page nodes. | |
68 \Drupal::service('content_translation.manager')->setEnabled('node', 'page', TRUE); | |
69 | |
70 // Create and log in user. | |
71 $web_user = $this->drupalCreateUser( | |
72 [ | |
73 'view page revisions', | |
74 'revert page revisions', | |
75 'delete page revisions', | |
76 'edit any page content', | |
77 'delete any page content', | |
78 'access contextual links', | |
79 'translate any entity', | |
80 'administer content types', | |
81 ] | |
82 ); | |
83 | |
84 $this->drupalLogin($web_user); | |
85 | |
86 // Create initial node. | |
87 $node = $this->drupalCreateNode(); | |
88 $settings = get_object_vars($node); | |
89 $settings['revision'] = 1; | |
90 $settings['isDefaultRevision'] = TRUE; | |
91 | |
92 $nodes = []; | |
93 $logs = []; | |
94 | |
95 // Get original node. | |
96 $nodes[] = clone $node; | |
97 | |
98 // Create three revisions. | |
99 $revision_count = 3; | |
100 for ($i = 0; $i < $revision_count; $i++) { | |
101 $logs[] = $node->revision_log = $this->randomMachineName(32); | |
102 | |
103 // Create revision with a random title and body and update variables. | |
104 $node->title = $this->randomMachineName(); | |
105 $node->body = [ | |
106 'value' => $this->randomMachineName(32), | |
107 'format' => filter_default_format(), | |
108 ]; | |
109 $node->untranslatable_string_field->value = $this->randomString(); | |
110 $node->setNewRevision(); | |
111 | |
112 // Edit the 1st and 2nd revision with a different user. | |
113 if ($i < 2) { | |
114 $editor = $this->drupalCreateUser(); | |
115 $node->setRevisionUserId($editor->id()); | |
116 } | |
117 else { | |
118 $node->setRevisionUserId($web_user->id()); | |
119 } | |
120 | |
121 $node->save(); | |
122 | |
123 // Make sure we get revision information. | |
124 $node = Node::load($node->id()); | |
125 $nodes[] = clone $node; | |
126 } | |
127 | |
128 $this->nodes = $nodes; | |
129 $this->revisionLogs = $logs; | |
130 } | |
131 | |
132 /** | |
133 * Checks node revision related operations. | |
134 */ | |
135 public function testRevisions() { | |
136 $node_storage = $this->container->get('entity.manager')->getStorage('node'); | |
137 $nodes = $this->nodes; | |
138 $logs = $this->revisionLogs; | |
139 | |
140 // Get last node for simple checks. | |
141 $node = $nodes[3]; | |
142 | |
143 // Confirm the correct revision text appears on "view revisions" page. | |
144 $this->drupalGet("node/" . $node->id() . "/revisions/" . $node->getRevisionId() . "/view"); | |
145 $this->assertText($node->body->value, 'Correct text displays for version.'); | |
146 | |
147 // Confirm the correct log message appears on "revisions overview" page. | |
148 $this->drupalGet("node/" . $node->id() . "/revisions"); | |
149 foreach ($logs as $revision_log) { | |
150 $this->assertText($revision_log, 'Revision log message found.'); | |
151 } | |
152 // Original author, and editor names should appear on revisions overview. | |
153 $web_user = $nodes[0]->revision_uid->entity; | |
154 $this->assertText(t('by @name', ['@name' => $web_user->getAccountName()])); | |
155 $editor = $nodes[2]->revision_uid->entity; | |
156 $this->assertText(t('by @name', ['@name' => $editor->getAccountName()])); | |
157 | |
158 // Confirm that this is the default revision. | |
159 $this->assertTrue($node->isDefaultRevision(), 'Third node revision is the default one.'); | |
160 | |
161 // Confirm that revisions revert properly. | |
162 $this->drupalPostForm("node/" . $node->id() . "/revisions/" . $nodes[1]->getRevisionid() . "/revert", [], t('Revert')); | |
163 $this->assertRaw(t('@type %title has been reverted to the revision from %revision-date.', [ | |
164 '@type' => 'Basic page', | |
165 '%title' => $nodes[1]->label(), | |
166 '%revision-date' => format_date($nodes[1]->getRevisionCreationTime()), | |
167 ]), 'Revision reverted.'); | |
168 $node_storage->resetCache([$node->id()]); | |
169 $reverted_node = $node_storage->load($node->id()); | |
170 $this->assertTrue(($nodes[1]->body->value == $reverted_node->body->value), 'Node reverted correctly.'); | |
171 // Confirm the revision author is the user performing the revert. | |
172 $this->assertTrue($reverted_node->getRevisionUserId() == $this->loggedInUser->id(), 'Node revision author is user performing revert.'); | |
173 // And that its not the revision author. | |
174 $this->assertTrue($reverted_node->getRevisionUserId() != $nodes[1]->getRevisionUserId(), 'Node revision author is not original revision author.'); | |
175 | |
176 // Confirm that this is not the default version. | |
177 $node = node_revision_load($node->getRevisionId()); | |
178 $this->assertFalse($node->isDefaultRevision(), 'Third node revision is not the default one.'); | |
179 | |
180 // Confirm revisions delete properly. | |
181 $this->drupalPostForm("node/" . $node->id() . "/revisions/" . $nodes[1]->getRevisionId() . "/delete", [], t('Delete')); | |
182 $this->assertRaw(t('Revision from %revision-date of @type %title has been deleted.', [ | |
183 '%revision-date' => format_date($nodes[1]->getRevisionCreationTime()), | |
184 '@type' => 'Basic page', | |
185 '%title' => $nodes[1]->label(), | |
186 ]), 'Revision deleted.'); | |
187 $this->assertTrue(db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid and vid = :vid', [':nid' => $node->id(), ':vid' => $nodes[1]->getRevisionId()])->fetchField() == 0, 'Revision not found.'); | |
188 $this->assertTrue(db_query('SELECT COUNT(vid) FROM {node_field_revision} WHERE nid = :nid and vid = :vid', [':nid' => $node->id(), ':vid' => $nodes[1]->getRevisionId()])->fetchField() == 0, 'Field revision not found.'); | |
189 | |
190 // Set the revision timestamp to an older date to make sure that the | |
191 // confirmation message correctly displays the stored revision date. | |
192 $old_revision_date = REQUEST_TIME - 86400; | |
193 db_update('node_revision') | |
194 ->condition('vid', $nodes[2]->getRevisionId()) | |
195 ->fields([ | |
196 'revision_timestamp' => $old_revision_date, | |
197 ]) | |
198 ->execute(); | |
199 $this->drupalPostForm("node/" . $node->id() . "/revisions/" . $nodes[2]->getRevisionId() . "/revert", [], t('Revert')); | |
200 $this->assertRaw(t('@type %title has been reverted to the revision from %revision-date.', [ | |
201 '@type' => 'Basic page', | |
202 '%title' => $nodes[2]->label(), | |
203 '%revision-date' => format_date($old_revision_date), | |
204 ])); | |
205 | |
206 // Make a new revision and set it to not be default. | |
207 // This will create a new revision that is not "front facing". | |
208 $new_node_revision = clone $node; | |
209 $new_body = $this->randomMachineName(); | |
210 $new_node_revision->body->value = $new_body; | |
211 // Save this as a non-default revision. | |
212 $new_node_revision->setNewRevision(); | |
213 $new_node_revision->isDefaultRevision = FALSE; | |
214 $new_node_revision->save(); | |
215 | |
216 $this->drupalGet('node/' . $node->id()); | |
217 $this->assertNoText($new_body, 'Revision body text is not present on default version of node.'); | |
218 | |
219 // Verify that the new body text is present on the revision. | |
220 $this->drupalGet("node/" . $node->id() . "/revisions/" . $new_node_revision->getRevisionId() . "/view"); | |
221 $this->assertText($new_body, 'Revision body text is present when loading specific revision.'); | |
222 | |
223 // Verify that the non-default revision vid is greater than the default | |
224 // revision vid. | |
225 $default_revision = db_select('node', 'n') | |
226 ->fields('n', ['vid']) | |
227 ->condition('nid', $node->id()) | |
228 ->execute() | |
229 ->fetchCol(); | |
230 $default_revision_vid = $default_revision[0]; | |
231 $this->assertTrue($new_node_revision->getRevisionId() > $default_revision_vid, 'Revision vid is greater than default revision vid.'); | |
232 | |
233 // Create an 'EN' node with a revision log message. | |
234 $node = $this->drupalCreateNode(); | |
235 $node->title = 'Node title in EN'; | |
236 $node->revision_log = 'Simple revision message (EN)'; | |
237 $node->save(); | |
238 | |
239 $this->drupalGet("node/" . $node->id() . "/revisions"); | |
240 $this->assertResponse(403); | |
241 | |
242 // Create a new revision and new log message. | |
243 $node = Node::load($node->id()); | |
244 $node->body->value = 'New text (EN)'; | |
245 $node->revision_log = 'New revision message (EN)'; | |
246 $node->setNewRevision(); | |
247 $node->save(); | |
248 | |
249 // Check both revisions are shown on the node revisions overview page. | |
250 $this->drupalGet("node/" . $node->id() . "/revisions"); | |
251 $this->assertText('Simple revision message (EN)'); | |
252 $this->assertText('New revision message (EN)'); | |
253 | |
254 // Create an 'EN' node with a revision log message. | |
255 $node = $this->drupalCreateNode(); | |
256 $node->langcode = 'en'; | |
257 $node->title = 'Node title in EN'; | |
258 $node->revision_log = 'Simple revision message (EN)'; | |
259 $node->save(); | |
260 | |
261 $this->drupalGet("node/" . $node->id() . "/revisions"); | |
262 $this->assertResponse(403); | |
263 | |
264 // Add a translation in 'DE' and create a new revision and new log message. | |
265 $translation = $node->addTranslation('de'); | |
266 $translation->title->value = 'Node title in DE'; | |
267 $translation->body->value = 'New text (DE)'; | |
268 $translation->revision_log = 'New revision message (DE)'; | |
269 $translation->setNewRevision(); | |
270 $translation->save(); | |
271 | |
272 // View the revision UI in 'IT', only the original node revision is shown. | |
273 $this->drupalGet("it/node/" . $node->id() . "/revisions"); | |
274 $this->assertText('Simple revision message (EN)'); | |
275 $this->assertNoText('New revision message (DE)'); | |
276 | |
277 // View the revision UI in 'DE', only the translated node revision is shown. | |
278 $this->drupalGet("de/node/" . $node->id() . "/revisions"); | |
279 $this->assertNoText('Simple revision message (EN)'); | |
280 $this->assertText('New revision message (DE)'); | |
281 | |
282 // View the revision UI in 'EN', only the original node revision is shown. | |
283 $this->drupalGet("node/" . $node->id() . "/revisions"); | |
284 $this->assertText('Simple revision message (EN)'); | |
285 $this->assertNoText('New revision message (DE)'); | |
286 } | |
287 | |
288 /** | |
289 * Checks that revisions are correctly saved without log messages. | |
290 */ | |
291 public function testNodeRevisionWithoutLogMessage() { | |
292 $node_storage = $this->container->get('entity.manager')->getStorage('node'); | |
293 // Create a node with an initial log message. | |
294 $revision_log = $this->randomMachineName(10); | |
295 $node = $this->drupalCreateNode(['revision_log' => $revision_log]); | |
296 | |
297 // Save over the same revision and explicitly provide an empty log message | |
298 // (for example, to mimic the case of a node form submitted with no text in | |
299 // the "log message" field), and check that the original log message is | |
300 // preserved. | |
301 $new_title = $this->randomMachineName(10) . 'testNodeRevisionWithoutLogMessage1'; | |
302 | |
303 $node = clone $node; | |
304 $node->title = $new_title; | |
305 $node->revision_log = ''; | |
306 $node->setNewRevision(FALSE); | |
307 | |
308 $node->save(); | |
309 $this->drupalGet('node/' . $node->id()); | |
310 $this->assertText($new_title, 'New node title appears on the page.'); | |
311 $node_storage->resetCache([$node->id()]); | |
312 $node_revision = $node_storage->load($node->id()); | |
313 $this->assertEqual($node_revision->revision_log->value, $revision_log, 'After an existing node revision is re-saved without a log message, the original log message is preserved.'); | |
314 | |
315 // Create another node with an initial revision log message. | |
316 $node = $this->drupalCreateNode(['revision_log' => $revision_log]); | |
317 | |
318 // Save a new node revision without providing a log message, and check that | |
319 // this revision has an empty log message. | |
320 $new_title = $this->randomMachineName(10) . 'testNodeRevisionWithoutLogMessage2'; | |
321 | |
322 $node = clone $node; | |
323 $node->title = $new_title; | |
324 $node->setNewRevision(); | |
325 $node->revision_log = NULL; | |
326 | |
327 $node->save(); | |
328 $this->drupalGet('node/' . $node->id()); | |
329 $this->assertText($new_title, 'New node title appears on the page.'); | |
330 $node_storage->resetCache([$node->id()]); | |
331 $node_revision = $node_storage->load($node->id()); | |
332 $this->assertTrue(empty($node_revision->revision_log->value), 'After a new node revision is saved with an empty log message, the log message for the node is empty.'); | |
333 } | |
334 | |
335 /** | |
336 * Gets server-rendered contextual links for the given contextual links IDs. | |
337 * | |
338 * @param string[] $ids | |
339 * An array of contextual link IDs. | |
340 * @param string $current_path | |
341 * The Drupal path for the page for which the contextual links are rendered. | |
342 * | |
343 * @return string | |
344 * The decoded JSON response body. | |
345 */ | |
346 protected function renderContextualLinks(array $ids, $current_path) { | |
347 $post = []; | |
348 for ($i = 0; $i < count($ids); $i++) { | |
349 $post['ids[' . $i . ']'] = $ids[$i]; | |
350 } | |
351 $response = $this->drupalPost('contextual/render', 'application/json', $post, ['query' => ['destination' => $current_path]]); | |
352 | |
353 return Json::decode($response); | |
354 } | |
355 | |
356 /** | |
357 * Tests the revision translations are correctly reverted. | |
358 */ | |
359 public function testRevisionTranslationRevert() { | |
360 // Create a node and a few revisions. | |
361 $node = $this->drupalCreateNode(['langcode' => 'en']); | |
362 | |
363 $initial_revision_id = $node->getRevisionId(); | |
364 $initial_title = $node->label(); | |
365 $this->createRevisions($node, 2); | |
366 | |
367 // Translate the node and create a few translation revisions. | |
368 $translation = $node->addTranslation('it'); | |
369 $this->createRevisions($translation, 3); | |
370 $revert_id = $node->getRevisionId(); | |
371 $translated_title = $translation->label(); | |
372 $untranslatable_string = $node->untranslatable_string_field->value; | |
373 | |
374 // Create a new revision for the default translation in-between a series of | |
375 // translation revisions. | |
376 $this->createRevisions($node, 1); | |
377 $default_translation_title = $node->label(); | |
378 | |
379 // And create a few more translation revisions. | |
380 $this->createRevisions($translation, 2); | |
381 $translation_revision_id = $translation->getRevisionId(); | |
382 | |
383 // Now revert the a translation revision preceding the last default | |
384 // translation revision, and check that the desired value was reverted but | |
385 // the default translation value was preserved. | |
386 $revert_translation_url = Url::fromRoute('node.revision_revert_translation_confirm', [ | |
387 'node' => $node->id(), | |
388 'node_revision' => $revert_id, | |
389 'langcode' => 'it', | |
390 ]); | |
391 $this->drupalPostForm($revert_translation_url, [], t('Revert')); | |
392 /** @var \Drupal\node\NodeStorage $node_storage */ | |
393 $node_storage = $this->container->get('entity.manager')->getStorage('node'); | |
394 $node_storage->resetCache(); | |
395 /** @var \Drupal\node\NodeInterface $node */ | |
396 $node = $node_storage->load($node->id()); | |
397 $this->assertTrue($node->getRevisionId() > $translation_revision_id); | |
398 $this->assertEqual($node->label(), $default_translation_title); | |
399 $this->assertEqual($node->getTranslation('it')->label(), $translated_title); | |
400 $this->assertNotEqual($node->untranslatable_string_field->value, $untranslatable_string); | |
401 | |
402 $latest_revision_id = $translation->getRevisionId(); | |
403 | |
404 // Now revert the a translation revision preceding the last default | |
405 // translation revision again, and check that the desired value was reverted | |
406 // but the default translation value was preserved. But in addition the | |
407 // untranslated field will be reverted as well. | |
408 $this->drupalPostForm($revert_translation_url, ['revert_untranslated_fields' => TRUE], t('Revert')); | |
409 $node_storage->resetCache(); | |
410 /** @var \Drupal\node\NodeInterface $node */ | |
411 $node = $node_storage->load($node->id()); | |
412 $this->assertTrue($node->getRevisionId() > $latest_revision_id); | |
413 $this->assertEqual($node->label(), $default_translation_title); | |
414 $this->assertEqual($node->getTranslation('it')->label(), $translated_title); | |
415 $this->assertEqual($node->untranslatable_string_field->value, $untranslatable_string); | |
416 | |
417 $latest_revision_id = $translation->getRevisionId(); | |
418 | |
419 // Now revert the entity revision to the initial one where the translation | |
420 // didn't exist. | |
421 $revert_url = Url::fromRoute('node.revision_revert_confirm', [ | |
422 'node' => $node->id(), | |
423 'node_revision' => $initial_revision_id, | |
424 ]); | |
425 $this->drupalPostForm($revert_url, [], t('Revert')); | |
426 $node_storage->resetCache(); | |
427 /** @var \Drupal\node\NodeInterface $node */ | |
428 $node = $node_storage->load($node->id()); | |
429 $this->assertTrue($node->getRevisionId() > $latest_revision_id); | |
430 $this->assertEqual($node->label(), $initial_title); | |
431 $this->assertFalse($node->hasTranslation('it')); | |
432 } | |
433 | |
434 /** | |
435 * Creates a series of revisions for the specified node. | |
436 * | |
437 * @param \Drupal\node\NodeInterface $node | |
438 * The node object. | |
439 * @param $count | |
440 * The number of revisions to be created. | |
441 */ | |
442 protected function createRevisions(NodeInterface $node, $count) { | |
443 for ($i = 0; $i < $count; $i++) { | |
444 $node->title = $this->randomString(); | |
445 $node->untranslatable_string_field->value = $this->randomString(); | |
446 $node->setNewRevision(TRUE); | |
447 $node->save(); | |
448 } | |
449 } | |
450 | |
451 } |