Issue #2068333 by roderik, skipyT, pcambra, plach, Désiré, marcingy, peximo: Convert node SQL queries to the Entity Query API.

8.0.x
Nathaniel Catchpole 2014-05-12 13:57:33 +01:00
parent a78a106319
commit e67b313df4
7 changed files with 233 additions and 139 deletions

View File

@ -24,6 +24,7 @@ use Drupal\user\UserInterface;
* label = @Translation("Content"),
* bundle_label = @Translation("Content type"),
* controllers = {
* "storage" = "Drupal\node\NodeStorage",
* "view_builder" = "Drupal\node\NodeViewBuilder",
* "access" = "Drupal\node\NodeAccessController",
* "form" = {

View File

@ -0,0 +1,62 @@
<?php
/**
* @file
* Definition of Drupal\node\NodeStorageController.
*/
namespace Drupal\node;
use Drupal\Core\Database\Database;
use Drupal\Core\Entity\ContentEntityDatabaseStorage;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Language\Language;
/**
* Defines the controller class for nodes.
*
* This extends the base storage class, adding required special handling for
* node entities.
*/
class NodeStorage extends ContentEntityDatabaseStorage implements NodeStorageInterface {
/**
* {@inheritdoc}
*/
public function revisionIds(NodeInterface $node) {
return $this->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();
}
}

View File

@ -0,0 +1,60 @@
<?php
/**
* @file
* Contains \Drupal\node\NodeStorageControllerInterface.
*/
namespace Drupal\node;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines a common interface for node entity controller classes.
*/
interface NodeStorageInterface extends EntityStorageInterface {
/**
* Returns a list of node revision IDs for a specific node.
*
* @param \Drupal\node\NodeInterface
* The node entity.
*
* @return int[]
* Node revision IDs (in ascending order).
*/
public function revisionIds(NodeInterface $node);
/**
* Returns a list of revision IDs having a given user as node author.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The user entity.
*
* @return int[]
* Node revision IDs (in ascending order).
*/
public function userRevisionIds(AccountInterface $account);
/**
* Updates all nodes of one type to be of another type.
*
* @param string $old_type
* The current node type of the nodes.
* @param string $new_type
* The new node type of the nodes.
*
* @return int
* The number of nodes whose node type field was modified.
*/
public function updateType($old_type, $new_type);
/**
* Unsets the language for all nodes with the given language.
*
* @param $language
* The language object.
*/
public function clearRevisionsLanguage($language);
}

View File

@ -26,14 +26,17 @@ use Drupal\node\NodeInterface;
* @param bool $load
* (optional) TRUE if $nodes contains an array of node IDs to be loaded, FALSE
* if it contains fully loaded nodes. Defaults to FALSE.
* @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.
*/
function node_mass_update(array $nodes, array $updates, $langcode = NULL, $load = FALSE) {
function node_mass_update(array $nodes, array $updates, $langcode = NULL, $load = FALSE, $revisions = FALSE) {
// We use batch processing to prevent timeout when updating a large number
// of nodes.
if (count($nodes) > 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);

View File

@ -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.
*
* @todo Remove once https://drupal.org/node/2002180 is resolved. It's only used
* for validation, which will be done by EntityChangedConstraintValidator.
*/
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.
*/
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;
$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);
}
/**

View File

@ -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 != '') ? '<p class="revision-log">' . Xss::filter($revision->log) . '</p>' : ''),
'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 != '') ? '<p class="revision-log">' . Xss::filter($revision->log) . '</p>' : '');
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 != '') ? '<p class="revision-log">' . Xss::filter($revision->log->value) . '</p>' : ''),
'class' => array('revision-current'));
$row[] = array('data' => drupal_placeholder(t('current revision')), 'class' => array('revision-current'));
}
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 != '') ? '<p class="revision-log">' . Xss::filter($revision->log->value) . '</p>' : '');
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,
),
);
}
if ($delete_permission) {
$links['delete'] = array(
'title' => t('Delete'),
'href' => "node/" . $node->id() . "/revisions/" . $revision->vid . "/delete",
);
}
$row[] = array(
'data' => array(
'#type' => 'operations',
'#links' => $links,
),
);
$rows[] = $row;
}
$rows[] = $row;
}
$build['node_revisions_table'] = array(

View File

@ -143,9 +143,10 @@ class QuickEditLoadingTest extends WebTestBase {
$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 @@ class QuickEditLoadingTest extends WebTestBase {
$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 @@ class QuickEditLoadingTest extends WebTestBase {
$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 <em class="placeholder">Body</em> 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 <em class="placeholder">Body</em> field through in-place editing.', $revision->log->value, 'The second revision log message was correctly generated by Quick Edit module.');
}
}