diff --git a/core/modules/node/lib/Drupal/node/Entity/Node.php b/core/modules/node/lib/Drupal/node/Entity/Node.php index 1fc6f033ec8e22092c0790d3960cf64046d16af7..1d21504e6afaacefa7b4db23d5e674c92c88660c 100644 --- a/core/modules/node/lib/Drupal/node/Entity/Node.php +++ b/core/modules/node/lib/Drupal/node/Entity/Node.php @@ -24,6 +24,7 @@ * label = @Translation("Content"), * bundle_label = @Translation("Content type"), * controllers = { + * "storage" = "Drupal\node\NodeStorage", * "view_builder" = "Drupal\node\NodeViewBuilder", * "access" = "Drupal\node\NodeAccessController", * "form" = { diff --git a/core/modules/node/lib/Drupal/node/NodeStorage.php b/core/modules/node/lib/Drupal/node/NodeStorage.php new file mode 100644 index 0000000000000000000000000000000000000000..8bb17fd4d4d1a069e84e04168120d89d0f6c3de2 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/NodeStorage.php @@ -0,0 +1,62 @@ +database->query( + 'SELECT vid FROM {node_revision} WHERE nid=:nid ORDER BY vid', + array(':nid' => $node->id()) + )->fetchCol(); + } + + /** + * {@inheritdoc} + */ + public function userRevisionIds(AccountInterface $account) { + return $this->database->query( + 'SELECT vid FROM {node_field_revision} WHERE uid = :uid ORDER BY vid', + array(':uid' => $account->id()) + )->fetchCol(); + } + + /** + * {@inheritdoc} + */ + public function updateType($old_type, $new_type) { + return $this->database->update('node') + ->fields(array('type' => $new_type)) + ->condition('type', $old_type) + ->execute(); + } + + /** + * {@inheritdoc} + */ + public function clearRevisionsLanguage($language) { + return $this->database->update('node_revision') + ->fields(array('langcode' => Language::LANGCODE_NOT_SPECIFIED)) + ->condition('langcode', $language->id) + ->execute(); + } +} diff --git a/core/modules/node/lib/Drupal/node/NodeStorageInterface.php b/core/modules/node/lib/Drupal/node/NodeStorageInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..db6830722e6291627e545e8edadeefc73259a8f5 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/NodeStorageInterface.php @@ -0,0 +1,60 @@ + 10) { $batch = array( 'operations' => array( - array('_node_mass_update_batch_process', array($nodes, $updates, $langcode, $load)) + array('_node_mass_update_batch_process', array($nodes, $updates, $langcode, $load, $revisions)) ), 'finished' => '_node_mass_update_batch_finished', 'title' => t('Processing'), @@ -48,10 +51,13 @@ function node_mass_update(array $nodes, array $updates, $langcode = NULL, $load batch_set($batch); } else { - if ($load) { + if ($load && !$revisions) { $nodes = entity_load_multiple('node', $nodes); } foreach ($nodes as $node) { + if ($load && $revisions) { + $node = entity_revision_load('node', $node); + } _node_mass_update_helper($node, $updates, $langcode); } drupal_set_message(t('The update has been performed.')); @@ -97,10 +103,13 @@ function _node_mass_update_helper(NodeInterface $node, array $updates, $langcode * @param bool $load * TRUE if $nodes contains an array of node IDs to be loaded, FALSE if it * contains fully loaded nodes. + * @param bool $revisions + * (optional) TRUE if $nodes contains an array of revision IDs instead of + * node IDs. Defaults to FALSE; will be ignored if $load is FALSE. * @param array $context * An array of contextual key/values. */ -function _node_mass_update_batch_process(array $nodes, array $updates, $load, array &$context) { +function _node_mass_update_batch_process(array $nodes, array $updates, $load, $revisions, array &$context) { if (!isset($context['sandbox']['progress'])) { $context['sandbox']['progress'] = 0; $context['sandbox']['max'] = count($nodes); @@ -113,7 +122,8 @@ function _node_mass_update_batch_process(array $nodes, array $updates, $load, ar // For each nid, load the node, reset the values, and save it. $node = array_shift($context['sandbox']['nodes']); if ($load) { - $node = entity_load('node', $node); + $node = $revisions ? + entity_revision_load('node', $node) : entity_load('node', $node); } $node = _node_mass_update_helper($node, $updates); diff --git a/core/modules/node/node.module b/core/modules/node/node.module index e749f86e49937f727effe2e53d6aceae81b6c1ad..25c55e4162fb7dfa9be282a0c4dadd1aab1260a6 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -494,10 +494,7 @@ function node_entity_extra_field_info() { * The number of nodes whose node type field was modified. */ function node_type_update_nodes($old_id, $new_id) { - return db_update('node') - ->fields(array('type' => $new_id)) - ->condition('type', $old_id) - ->execute(); + return \Drupal::entityManager()->getStorage('node')->updateType($old_id, $new_id); } /** @@ -801,31 +798,18 @@ function node_user_cancel($edit, $account, $method) { switch ($method) { case 'user_cancel_block_unpublish': // Unpublish nodes (current revisions). - module_load_include('inc', 'node', 'node.admin'); - $nodes = db_select('node_field_data', 'n') - ->distinct() - ->fields('n', array('nid')) + $nids = \Drupal::entityQuery('node') ->condition('uid', $account->id()) - ->execute() - ->fetchCol(); - node_mass_update($nodes, array('status' => 0), NULL, TRUE); + ->execute(); + module_load_include('inc', 'node', 'node.admin'); + node_mass_update($nids, array('status' => 0), NULL, TRUE); break; case 'user_cancel_reassign': - // Anonymize nodes (current revisions). + // Anonymize all of the nodes for this old account. module_load_include('inc', 'node', 'node.admin'); - $nodes = db_select('node_field_data', 'n') - ->distinct() - ->fields('n', array('nid')) - ->condition('uid', $account->id()) - ->execute() - ->fetchCol(); - node_mass_update($nodes, array('uid' => 0), NULL, TRUE); - // Anonymize old revisions. - db_update('node_field_revision') - ->fields(array('uid' => 0)) - ->condition('uid', $account->id()) - ->execute(); + $vids = \Drupal::entityManager()->getStorage('node')->userRevisionIds($account); + node_mass_update($vids, array('uid' => 0), NULL, TRUE, TRUE); break; } } @@ -836,15 +820,13 @@ function node_user_cancel($edit, $account, $method) { function node_user_predelete($account) { // Delete nodes (current revisions). // @todo Introduce node_mass_delete() or make node_mass_update() more flexible. - $nodes = db_select('node_field_data', 'n') - ->distinct() - ->fields('n', array('nid')) + $nids = \Drupal::entityQuery('node') ->condition('uid', $account->id()) - ->execute() - ->fetchCol(); - entity_delete_multiple('node', $nodes); + ->execute(); + entity_delete_multiple('node', $nids); // Delete old revisions. - $revisions = db_query('SELECT DISTINCT vid FROM {node_field_revision} WHERE uid = :uid', array(':uid' => $account->id()))->fetchCol(); + $storage_controller = \Drupal::entityManager()->getStorage('node'); + $revisions = $storage_controller->userRevisionIds($account); foreach ($revisions as $revision) { node_revision_delete($revision); } @@ -910,34 +892,13 @@ function node_page_title(NodeInterface $node) { * * @return string * A unix timestamp indicating the last time the node was changed. - */ -function node_last_changed($nid, $langcode = NULL) { - if (isset($langcode)) { - $result = db_query('SELECT changed FROM {node_field_data} WHERE nid = :nid AND langcode = :langcode', array(':nid' => $nid, ':langcode' => $langcode))->fetch(); - } - else { - $result = db_query('SELECT changed FROM {node_field_data} WHERE nid = :nid AND default_langcode = :default_langcode', array(':nid' => $nid, ':default_langcode' => 1))->fetch(); - } - return is_object($result) ? $result->changed : FALSE; -} - -/** - * Returns a list of all the existing revision numbers for the node passed in. * - * @param \Drupal\node\NodeInterface $node - * The node entity. - * - * @return - * An associative array keyed by node revision number. + * @todo Remove once https://drupal.org/node/2002180 is resolved. It's only used + * for validation, which will be done by EntityChangedConstraintValidator. */ -function node_revision_list(NodeInterface $node) { - $revisions = array(); - $result = db_query('SELECT nr.vid, nfr.title, nr.log, nr.revision_uid AS uid, n.vid AS current_vid, nr.revision_timestamp, u.name FROM {node_field_revision} nfr JOIN {node_revision} nr ON nr.vid = nfr.vid LEFT JOIN {node} n ON n.vid = nfr.vid INNER JOIN {users} u ON u.uid = nr.revision_uid WHERE nfr.nid = :nid AND nfr.default_langcode = 1 ORDER BY nfr.vid DESC', array(':nid' => $node->id())); - foreach ($result as $revision) { - $revisions[$revision->vid] = $revision; - } - - return $revisions; +function node_last_changed($nid, $langcode = NULL) { + $changed = \Drupal::entityManager()->getStorage('node')->loadUnchanged($nid)->getChangedTime(); + return $changed ? $changed : FALSE; } /** @@ -951,31 +912,31 @@ function node_revision_list(NodeInterface $node) { * visible to the current user. */ function node_get_recent($number = 10) { - $query = db_select('node_field_data', 'n'); + $account = \Drupal::currentUser(); + $query = \Drupal::entityQuery('node'); - if (!user_access('bypass node access')) { + if (!$account->hasPermission('bypass node access')) { // If the user is able to view their own unpublished nodes, allow them // to see these in addition to published nodes. Check that they actually // have some unpublished nodes to view before adding the condition. - if (user_access('view own unpublished content') && $own_unpublished = db_query('SELECT DISTINCT nid FROM {node_field_data} WHERE uid = :uid AND status = :status', array(':uid' => \Drupal::currentUser()->id(), ':status' => NODE_NOT_PUBLISHED))->fetchCol()) { - $query->condition(db_or() - ->condition('n.status', NODE_PUBLISHED) - ->condition('n.nid', $own_unpublished, 'IN') - ); + $access_query = \Drupal::entityQuery('node') + ->condition('uid', $account->id()) + ->condition('status', NODE_NOT_PUBLISHED); + if ($account->hasPermission('view own unpublished content') && ($own_unpublished = $access_query->execute())) { + $query->orConditionGroup() + ->condition('status', NODE_PUBLISHED) + ->condition('nid', $own_unpublished, 'IN'); } else { // If not, restrict the query to published nodes. - $query->condition('n.status', NODE_PUBLISHED); + $query->condition('status', NODE_PUBLISHED); } - } + } $nids = $query - ->distinct() - ->fields('n', array('nid')) - ->orderBy('n.changed', 'DESC') + ->sort('changed', 'DESC') ->range(0, $number) ->addTag('node_access') - ->execute() - ->fetchCol(); + ->execute(); $nodes = node_load_multiple($nids); @@ -1078,16 +1039,13 @@ function node_feed($nids = FALSE, $channel = array()) { $rss_config = \Drupal::config('system.rss'); if ($nids === FALSE) { - $nids = db_select('node_field_data', 'n') - ->distinct() - ->fields('n', array('nid')) - ->condition('n.promote', 1) - ->condition('n.status', 1) - ->orderBy('n.created', 'DESC') + $nids = \Drupal::entityQuery('node') + ->condition('status', 1) + ->condition('promote', 1) + ->sort('created', 'DESC') ->range(0, $rss_config->get('items.limit')) ->addTag('node_access') - ->execute() - ->fetchCol(); + ->execute(); } $item_length = $rss_config->get('items.view_mode'); @@ -1272,9 +1230,9 @@ function node_form_system_themes_admin_form_submit($form, &$form_state) { * the process above is followed except that hook_node_access() is not called on * each node for performance reasons and for proper functioning of the pager * system. When adding a node listing to your module, be sure to use a dynamic - * query created by db_select() and add a tag of "node_access". This will allow - * modules dealing with node access to ensure only nodes to which the user has - * access are retrieved, through the use of hook_query_TAG_alter(). + * entity query and add a tag of "node_access". This will allow modules dealing + * with node access to ensure only nodes to which the user has access are + * retrieved, through the use of hook_query_TAG_alter(). * * Note: Even a single module returning NODE_ACCESS_DENY from hook_node_access() * will block access to the node. Therefore, implementers should take care to @@ -1749,10 +1707,7 @@ function node_file_download_access($field, EntityInterface $entity, File $file) */ function node_language_entity_delete(LanguageEntity $language) { // On nodes with this language, unset the language. - db_update('node_revision') - ->fields(array('langcode' => '')) - ->condition('langcode', $language->id()) - ->execute(); + \Drupal::entityManager()->getStorage('node')->clearRevisionsLanguage($language); } /** diff --git a/core/modules/node/node.pages.inc b/core/modules/node/node.pages.inc index efc88fbaaa5007fd545a3342994dafda529b50e2..45ff09facf08c144405992d0145bb0a8fe9f9768 100644 --- a/core/modules/node/node.pages.inc +++ b/core/modules/node/node.pages.inc @@ -121,8 +121,6 @@ function node_revision_overview($node) { $header = array(t('Revision'), t('Operations')); - $revisions = node_revision_list($node); - $rows = array(); $type = $node->getType(); @@ -134,45 +132,49 @@ function node_revision_overview($node) { if ((user_access("delete $type revisions") || user_access('delete all revisions') || user_access('administer nodes')) && $node->access('delete')) { $delete_permission = TRUE; } - foreach ($revisions as $revision) { - $row = array(); - if ($revision->current_vid > 0) { - $username = array( - '#theme' => 'username', - '#account' => user_load($revision->uid), - ); - $row[] = array('data' => t('!date by !username', array('!date' => l(format_date($revision->revision_timestamp, 'short'), 'node/' . $node->id()), '!username' => drupal_render($username))) - . (($revision->log != '') ? '

' . Xss::filter($revision->log) . '

' : ''), - 'class' => array('revision-current')); - $row[] = array('data' => drupal_placeholder(t('current revision')), 'class' => array('revision-current')); - } - else { - $username = array( - '#theme' => 'username', - '#account' => user_load($revision->uid), - ); - $row[] = t('!date by !username', array('!date' => l(format_date($revision->revision_timestamp, 'short'), "node/" . $node->id() . "/revisions/" . $revision->vid . "/view"), '!username' => drupal_render($username))) - . (($revision->log != '') ? '

' . Xss::filter($revision->log) . '

' : ''); - if ($revert_permission) { - $links['revert'] = array( - 'title' => t('Revert'), - 'href' => "node/" . $node->id() . "/revisions/" . $revision->vid . "/revert", + $vids = \Drupal::entityManager()->getStorage('node')->revisionIds($node); + foreach (array_reverse($vids) as $vid) { + if ($revision = node_revision_load($vid)) { + + $row = array(); + if ($vid == $node->getRevisionId()) { + $username = array( + '#theme' => 'username', + '#account' => $revision->getOwner(), ); + $row[] = array('data' => t('!date by !username', array('!date' => l(format_date($revision->getRevisionCreationTime(), 'short'), 'node/' . $node->id()), '!username' => drupal_render($username))) + . (($revision->log->value != '') ? '

' . Xss::filter($revision->log->value) . '

' : ''), + 'class' => array('revision-current')); + $row[] = array('data' => drupal_placeholder(t('current revision')), 'class' => array('revision-current')); } - if ($delete_permission) { - $links['delete'] = array( - 'title' => t('Delete'), - 'href' => "node/" . $node->id() . "/revisions/" . $revision->vid . "/delete", + else { + $username = array( + '#theme' => 'username', + '#account' => $revision->getOwner(), + ); + $row[] = t('!date by !username', array('!date' => l(format_date($revision->getRevisionCreationTime(), 'short'), "node/" . $node->id() . "/revisions/" . $vid . "/view"), '!username' => drupal_render($username))) + . (($revision->log->value != '') ? '

' . Xss::filter($revision->log->value) . '

' : ''); + if ($revert_permission) { + $links['revert'] = array( + 'title' => t('Revert'), + 'href' => "node/" . $node->id() . "/revisions/" . $vid . "/revert", + ); + } + if ($delete_permission) { + $links['delete'] = array( + 'title' => t('Delete'), + 'href' => "node/" . $node->id() . "/revisions/" . $vid . "/delete", + ); + } + $row[] = array( + 'data' => array( + '#type' => 'operations', + '#links' => $links, + ), ); } - $row[] = array( - 'data' => array( - '#type' => 'operations', - '#links' => $links, - ), - ); + $rows[] = $row; } - $rows[] = $row; } $build['node_revisions_table'] = array( diff --git a/core/modules/quickedit/lib/Drupal/quickedit/Tests/QuickEditLoadingTest.php b/core/modules/quickedit/lib/Drupal/quickedit/Tests/QuickEditLoadingTest.php index b5122dda4a9d97eb44d6548353cf6a9f22d66e5a..220a14a4e4fa9f34c780b737d5972d7d008a52e8 100644 --- a/core/modules/quickedit/lib/Drupal/quickedit/Tests/QuickEditLoadingTest.php +++ b/core/modules/quickedit/lib/Drupal/quickedit/Tests/QuickEditLoadingTest.php @@ -143,9 +143,10 @@ public function testUserWithPermission() { $this->assertRaw('data-quickedit-field-id="node/1/body/und/full"'); // There should be only one revision so far. - $revisions = node_revision_list(node_load(1)); - $this->assertIdentical(1, count($revisions), 'The node has only one revision.'); - $original_log = $revisions[1]->log; + $node = node_load(1); + $vids = \Drupal::entityManager()->getStorage('node')->revisionIds($node); + $this->assertIdentical(1, count($vids), 'The node has only one revision.'); + $original_log = $node->log->value; // Retrieving the metadata should result in a 200 JSON response. $htmlPageDrupalSettings = $this->drupalSettings; @@ -236,9 +237,10 @@ public function testUserWithPermission() { $this->assertText('Fine thanks.'); // Ensure no new revision was created and the log message is unchanged. - $revisions = node_revision_list(node_load(1)); - $this->assertIdentical(1, count($revisions), 'The node has only one revision.'); - $this->assertIdentical($original_log, $revisions[1]->log, 'The revision log message is unchanged.'); + $node = node_load(1); + $vids = \Drupal::entityManager()->getStorage('node')->revisionIds($node); + $this->assertIdentical(1, count($vids), 'The node has only one revision.'); + $this->assertIdentical($original_log, $node->log->value, 'The revision log message is unchanged.'); // Now configure this node type to create new revisions automatically, // then again retrieve the field form, fill it, submit it (so it ends up @@ -284,10 +286,12 @@ public function testUserWithPermission() { $this->assertResponse(200); // Test that a revision was created with the correct log message. - $revisions = node_revision_list(node_load(1)); - $this->assertIdentical(2, count($revisions), 'The node has two revisions.'); - $this->assertIdentical($original_log, $revisions[1]->log, 'The first revision log message is unchanged.'); - $this->assertIdentical('Updated the Body field through in-place editing.', $revisions[2]->log, 'The second revision log message was correctly generated by Quick Edit module.'); + $vids = \Drupal::entityManager()->getStorage('node')->revisionIds(node_load(1)); + $this->assertIdentical(2, count($vids), 'The node has two revisions.'); + $revision = node_revision_load($vids[0]); + $this->assertIdentical($original_log, $revision->log->value, 'The first revision log message is unchanged.'); + $revision = node_revision_load($vids[1]); + $this->assertIdentical('Updated the Body field through in-place editing.', $revision->log->value, 'The second revision log message was correctly generated by Quick Edit module.'); } }