comparison core/modules/node/src/Tests/NodeRevisionsTest.php @ 0:4c8ae668cc8c

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