Commit 477c06c4 authored by alexpott's avatar alexpott

Issue #1498674 by plach, das-peter, Schnitzel, dawehner, YesCT, attiks,...

Issue #1498674 by plach, das-peter, Schnitzel, dawehner, YesCT, attiks, Berdir, Gábor Hojtsy, Soul88, Carsten Müller: Refactor node properties to multilingual.
parent 1d266317
...@@ -34,20 +34,20 @@ ...@@ -34,20 +34,20 @@
* results that need to be presented on multiple pages, and the Tablesort * results that need to be presented on multiple pages, and the Tablesort
* Extender for generating appropriate queries for sortable tables. * Extender for generating appropriate queries for sortable tables.
* *
* For example, one might wish to return a list of the most recent 10 nodes * For example, one might wish to return a list of the most recent 10 rows
* authored by a given user. Instead of directly issuing the SQL query * authored by a given user. Instead of directly issuing the SQL query
* @code * @code
* SELECT n.nid, n.title, n.created FROM node n WHERE n.uid = $uid LIMIT 0, 10; * SELECT e.id, e.title, e.created FROM example e WHERE e.uid = $uid LIMIT 0, 10;
* @endcode * @endcode
* one would instead call the Drupal functions: * one would instead call the Drupal functions:
* @code * @code
* $result = db_query_range('SELECT n.nid, n.title, n.created * $result = db_query_range('SELECT e.id, e.title, e.created
* FROM {node} n WHERE n.uid = :uid', 0, 10, array(':uid' => $uid)); * FROM {example} e WHERE e.uid = :uid', 0, 10, array(':uid' => $uid));
* foreach ($result as $record) { * foreach ($result as $record) {
* // Perform operations on $record->title, etc. here. * // Perform operations on $record->title, etc. here.
* } * }
* @endcode * @endcode
* Curly braces are used around "node" to provide table prefixing via * Curly braces are used around "example" to provide table prefixing via
* DatabaseConnection::prefixTables(). The explicit use of a user ID is pulled * DatabaseConnection::prefixTables(). The explicit use of a user ID is pulled
* out into an argument passed to db_query() so that SQL injection attacks * out into an argument passed to db_query() so that SQL injection attacks
* from user input can be caught and nullified. The LIMIT syntax varies between * from user input can be caught and nullified. The LIMIT syntax varies between
...@@ -69,7 +69,7 @@ ...@@ -69,7 +69,7 @@
* *
* Named placeholders begin with a colon followed by a unique string. Example: * Named placeholders begin with a colon followed by a unique string. Example:
* @code * @code
* SELECT nid, title FROM {node} WHERE uid=:uid; * SELECT id, title FROM {example} WHERE uid=:uid;
* @endcode * @endcode
* *
* ":uid" is a placeholder that will be replaced with a literal value when * ":uid" is a placeholder that will be replaced with a literal value when
...@@ -81,7 +81,7 @@ ...@@ -81,7 +81,7 @@
* *
* Unnamed placeholders are simply a question mark. Example: * Unnamed placeholders are simply a question mark. Example:
* @code * @code
* SELECT nid, title FROM {node} WHERE uid=?; * SELECT id, title FROM {example} WHERE uid=?;
* @endcode * @endcode
* *
* In this case, the array of arguments must be an indexed array of values to * In this case, the array of arguments must be an indexed array of values to
...@@ -91,11 +91,11 @@ ...@@ -91,11 +91,11 @@
* running a LIKE query the SQL wildcard character, %, should be part of the * running a LIKE query the SQL wildcard character, %, should be part of the
* value, not the query itself. Thus, the following is incorrect: * value, not the query itself. Thus, the following is incorrect:
* @code * @code
* SELECT nid, title FROM {node} WHERE title LIKE :title%; * SELECT id, title FROM {example} WHERE title LIKE :title%;
* @endcode * @endcode
* It should instead read: * It should instead read:
* @code * @code
* SELECT nid, title FROM {node} WHERE title LIKE :title; * SELECT id, title FROM {example} WHERE title LIKE :title;
* @endcode * @endcode
* and the value for :title should include a % as appropriate. Again, note the * and the value for :title should include a % as appropriate. Again, note the
* lack of quotation marks around :title. Because the value is not inserted * lack of quotation marks around :title. Because the value is not inserted
...@@ -109,7 +109,7 @@ ...@@ -109,7 +109,7 @@
* object-oriented API for defining a query structurally. For example, rather * object-oriented API for defining a query structurally. For example, rather
* than: * than:
* @code * @code
* INSERT INTO node (nid, title, body) VALUES (1, 'my title', 'my body'); * INSERT INTO {example} (id, uid, path, name) VALUES (1, 2, 'home', 'Home path');
* @endcode * @endcode
* one would instead write: * one would instead write:
* @code * @code
......
...@@ -4921,26 +4921,26 @@ function _form_set_attributes(&$element, $class = array()) { ...@@ -4921,26 +4921,26 @@ function _form_set_attributes(&$element, $class = array()) {
* $context['message'] = check_plain($node->label()); * $context['message'] = check_plain($node->label());
* } * }
* *
* // More advanced example: multi-step operation - load all nodes, five by five * // A more advanced example is a multi-step operation that loads all rows,
* // five by five.
* function my_function_2(&$context) { * function my_function_2(&$context) {
* if (empty($context['sandbox'])) { * if (empty($context['sandbox'])) {
* $context['sandbox']['progress'] = 0; * $context['sandbox']['progress'] = 0;
* $context['sandbox']['current_node'] = 0; * $context['sandbox']['current_id'] = 0;
* $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT nid) FROM {node}')->fetchField(); * $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT id) FROM {example}')->fetchField();
* } * }
* $limit = 5; * $limit = 5;
* $result = db_select('node') * $result = db_select('example')
* ->fields('node', array('nid')) * ->fields('example', array('id'))
* ->condition('nid', $context['sandbox']['current_node'], '>') * ->condition('id', $context['sandbox']['current_id'], '>')
* ->orderBy('nid') * ->orderBy('id')
* ->range(0, $limit) * ->range(0, $limit)
* ->execute(); * ->execute();
* foreach ($result as $row) { * foreach ($result as $row) {
* $node = node_load($row->nid, TRUE); * $context['results'][] = $row->id . ' : ' . check_plain($row->title);
* $context['results'][] = $node->nid . ' : ' . check_plain($node->label());
* $context['sandbox']['progress']++; * $context['sandbox']['progress']++;
* $context['sandbox']['current_node'] = $node->nid; * $context['sandbox']['current_id'] = $row->id;
* $context['message'] = check_plain($node->label()); * $context['message'] = check_plain($row->title);
* } * }
* if ($context['sandbox']['progress'] != $context['sandbox']['max']) { * if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
* $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; * $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
......
...@@ -1468,9 +1468,12 @@ function menu_tree_collect_node_links(&$tree, &$node_links) { ...@@ -1468,9 +1468,12 @@ function menu_tree_collect_node_links(&$tree, &$node_links) {
function menu_tree_check_access(&$tree, $node_links = array()) { function menu_tree_check_access(&$tree, $node_links = array()) {
if ($node_links) { if ($node_links) {
$nids = array_keys($node_links); $nids = array_keys($node_links);
$select = db_select('node', 'n'); $select = db_select('node_field_data', 'n');
$select->addField('n', 'nid'); $select->addField('n', 'nid');
// @todo This should be actually filtering on the desired node status field
// language and just fall back to the default language.
$select->condition('n.status', 1); $select->condition('n.status', 1);
$select->condition('n.nid', $nids, 'IN'); $select->condition('n.nid', $nids, 'IN');
$select->addTag('node_access'); $select->addTag('node_access');
$nids = $select->execute()->fetchCol(); $nids = $select->execute()->fetchCol();
......
...@@ -433,20 +433,8 @@ function drupal_write_record($table, &$record, $primary_keys = array()) { ...@@ -433,20 +433,8 @@ function drupal_write_record($table, &$record, $primary_keys = array()) {
// Type cast to proper datatype, except when the value is NULL and the // Type cast to proper datatype, except when the value is NULL and the
// column allows this. // column allows this.
//
// MySQL PDO silently casts e.g. FALSE and '' to 0 when inserting the value
// into an integer column, but PostgreSQL PDO does not. Also type cast NULL
// when the column does not allow this.
if (isset($object->$field) || !empty($info['not null'])) { if (isset($object->$field) || !empty($info['not null'])) {
if ($info['type'] == 'int' || $info['type'] == 'serial') { $fields[$field] = drupal_schema_get_field_value($info, $fields[$field]);
$fields[$field] = (int) $fields[$field];
}
elseif ($info['type'] == 'float') {
$fields[$field] = (float) $fields[$field];
}
else {
$fields[$field] = (string) $fields[$field];
}
} }
} }
...@@ -521,6 +509,34 @@ function drupal_write_record($table, &$record, $primary_keys = array()) { ...@@ -521,6 +509,34 @@ function drupal_write_record($table, &$record, $primary_keys = array()) {
return $return; return $return;
} }
/**
* Typecasts values to proper datatypes.
*
* MySQL PDO silently casts, e.g. FALSE and '' to 0, when inserting the value
* into an integer column, but PostgreSQL PDO does not. Look up the schema
* information and use that to correctly typecast the value.
*
* @param array $info
* An array describing the schema field info.
* @param mixed $value
* The value to be converted.
*
* @return mixed
* The converted value.
*/
function drupal_schema_get_field_value(array $info, $value) {
if ($info['type'] == 'int' || $info['type'] == 'serial') {
$value = (int) $value;
}
elseif ($info['type'] == 'float') {
$value = (float) $value;
}
else {
$value = (string) $value;
}
return $value;
}
/** /**
* @} End of "addtogroup schemaapi". * @} End of "addtogroup schemaapi".
*/ */
...@@ -32,16 +32,16 @@ public function orderRandom() { ...@@ -32,16 +32,16 @@ public function orderRandom() {
* yet selected. * yet selected.
* *
* @code * @code
* $query = db_select('node', 'n'); * $query = db_select('example', 'e');
* $query->join('node_revision', 'nr', 'n.vid = nr.vid'); * $query->join('example_revision', 'er', 'e.vid = er.vid');
* $query * $query
* ->distinct() * ->distinct()
* ->fields('n') * ->fields('e')
* ->orderBy('timestamp'); * ->orderBy('timestamp');
* @endcode * @endcode
* *
* In this query, it is not possible (without relying on the schema) to know * In this query, it is not possible (without relying on the schema) to know
* whether timestamp belongs to node_revisions and needs to be added or * whether timestamp belongs to example_revision and needs to be added or
* belongs to node and is already selected. Queries like this will need to be * belongs to node and is already selected. Queries like this will need to be
* corrected in the original query by adding an explicit call to * corrected in the original query by adding an explicit call to
* SelectQuery::addField() or SelectQuery::fields(). * SelectQuery::addField() or SelectQuery::fields().
......
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
* The following keys are defined: * The following keys are defined:
* - 'description': A string in non-markup plain text describing this table * - 'description': A string in non-markup plain text describing this table
* and its purpose. References to other tables should be enclosed in * and its purpose. References to other tables should be enclosed in
* curly-brackets. For example, the node_revisions table * curly-brackets. For example, the node_field_revision table
* description field might contain "Stores per-revision title and * description field might contain "Stores per-revision title and
* body data for each {node}." * body data for each {node}."
* - 'fields': An associative array ('fieldname' => specification) * - 'fields': An associative array ('fieldname' => specification)
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
* and its purpose. References to other tables should be enclosed in * and its purpose. References to other tables should be enclosed in
* curly-brackets. For example, the node table vid field * curly-brackets. For example, the node table vid field
* description might contain "Always holds the largest (most * description might contain "Always holds the largest (most
* recent) {node_revision}.vid value for this nid." * recent) {node_field_revision}.vid value for this nid."
* - 'type': The generic datatype: 'char', 'varchar', 'text', 'blob', 'int', * - 'type': The generic datatype: 'char', 'varchar', 'text', 'blob', 'int',
* 'float', 'numeric', or 'serial'. Most types just map to the according * 'float', 'numeric', or 'serial'. Most types just map to the according
* database engine specific datatypes. Use 'serial' for auto incrementing * database engine specific datatypes. Use 'serial' for auto incrementing
...@@ -150,7 +150,7 @@ ...@@ -150,7 +150,7 @@
* ), * ),
* 'foreign keys' => array( * 'foreign keys' => array(
* 'node_revision' => array( * 'node_revision' => array(
* 'table' => 'node_revision', * 'table' => 'node_field_revision',
* 'columns' => array('vid' => 'vid'), * 'columns' => array('vid' => 'vid'),
* ), * ),
* 'node_author' => array( * 'node_author' => array(
......
...@@ -289,7 +289,7 @@ public function loadRevision($revision_id) { ...@@ -289,7 +289,7 @@ public function loadRevision($revision_id) {
// which attaches fields (if supported by the entity type) and calls the // which attaches fields (if supported by the entity type) and calls the
// entity type specific load callback, for example hook_node_load(). // entity type specific load callback, for example hook_node_load().
if (!empty($queried_entities)) { if (!empty($queried_entities)) {
$this->attachLoad($queried_entities, TRUE); $this->attachLoad($queried_entities, $revision_id);
} }
return reset($queried_entities); return reset($queried_entities);
} }
......
...@@ -328,6 +328,7 @@ public function getTranslation($langcode, $strict = TRUE) { ...@@ -328,6 +328,7 @@ public function getTranslation($langcode, $strict = TRUE) {
*/ */
public function getTranslationLanguages($include_default = TRUE) { public function getTranslationLanguages($include_default = TRUE) {
$translations = array(); $translations = array();
$definitions = $this->getPropertyDefinitions();
// Build an array with the translation langcodes set as keys. Empty // Build an array with the translation langcodes set as keys. Empty
// translations should not be included and must be skipped. // translations should not be included and must be skipped.
foreach ($this->getProperties() as $name => $property) { foreach ($this->getProperties() as $name => $property) {
...@@ -339,7 +340,7 @@ public function getTranslationLanguages($include_default = TRUE) { ...@@ -339,7 +340,7 @@ public function getTranslationLanguages($include_default = TRUE) {
foreach ($this->values[$name] as $langcode => $values) { foreach ($this->values[$name] as $langcode => $values) {
// If a value is there but the field object is empty, it has been // If a value is there but the field object is empty, it has been
// unset, so we need to skip the field also. // unset, so we need to skip the field also.
if ($values && !(isset($this->fields[$name][$langcode]) && $this->fields[$name][$langcode]->isEmpty())) { if ($values && !empty($definitions[$name]['translatable']) && !(isset($this->fields[$name][$langcode]) && $this->fields[$name][$langcode]->isEmpty())) {
$translations[$langcode] = TRUE; $translations[$langcode] = TRUE;
} }
} }
......
...@@ -37,7 +37,14 @@ public static function getInfo() { ...@@ -37,7 +37,14 @@ public static function getInfo() {
public function testBulkForm() { public function testBulkForm() {
$nodes = array(); $nodes = array();
for ($i = 0; $i < 10; $i++) { for ($i = 0; $i < 10; $i++) {
$nodes[] = $this->drupalCreateNode(array('sticky' => FALSE)); // Ensure nodes are sorted in the same order they are inserted in the
// array.
$timestamp = REQUEST_TIME - $i;
$nodes[] = $this->drupalCreateNode(array(
'sticky' => FALSE,
'created' => $timestamp,
'changed' => $timestamp,
));
} }
$this->drupalGet('test_bulk_form'); $this->drupalGet('test_bulk_form');
......
...@@ -56,7 +56,7 @@ display: ...@@ -56,7 +56,7 @@ display:
fields: fields:
title: title:
id: title id: title
table: node table: node_field_data
field: title field: title
label: '' label: ''
alter: alter:
...@@ -124,7 +124,7 @@ display: ...@@ -124,7 +124,7 @@ display:
filters: filters:
status: status:
value: '1' value: '1'
table: node table: node_field_data
field: status field: status
id: status id: status
expose: expose:
...@@ -134,7 +134,7 @@ display: ...@@ -134,7 +134,7 @@ display:
sorts: sorts:
created: created:
id: created id: created
table: node table: node_field_data
field: created field: created
order: DESC order: DESC
plugin_id: date plugin_id: date
......
...@@ -137,7 +137,7 @@ function getFeedEditObject($feed_url = NULL, array $values = array()) { ...@@ -137,7 +137,7 @@ function getFeedEditObject($feed_url = NULL, array $values = array()) {
*/ */
function getDefaultFeedItemCount() { function getDefaultFeedItemCount() {
// Our tests are based off of rss.xml, so let's find out how many elements should be related. // Our tests are based off of rss.xml, so let's find out how many elements should be related.
$feed_count = db_query_range('SELECT COUNT(*) FROM {node} n WHERE n.promote = 1 AND n.status = 1', 0, config('system.rss')->get('items.limit'))->fetchField(); $feed_count = db_query_range('SELECT COUNT(DISTINCT nid) FROM {node_field_data} n WHERE n.promote = 1 AND n.status = 1', 0, config('system.rss')->get('items.limit'))->fetchField();
return $feed_count > 10 ? 10 : $feed_count; return $feed_count > 10 ? 10 : $feed_count;
} }
......
...@@ -35,14 +35,12 @@ protected function preSaveRevision(\stdClass $record, EntityInterface $entity) { ...@@ -35,14 +35,12 @@ protected function preSaveRevision(\stdClass $record, EntityInterface $entity) {
$record->log = ''; $record->log = '';
} }
} }
elseif (!isset($record->log) || $record->log === '') { elseif (isset($entity->original) && (!isset($record->log) || $record->log === '')) {
// If we are updating an existing custom_block without adding a new // If we are updating an existing custom_block without adding a new
// revision, we need to make sure $entity->log is unset whenever it is // revision, we need to make sure $entity->log is reset whenever it is
// empty. As long as $entity->log is unset, drupal_write_record() will not // empty. Therefore, this code allows us to avoid clobbering an existing
// attempt to update the existing database column when re-saving the // log entry with an empty one.
// revision; therefore, this code allows us to avoid clobbering an $record->log = $entity->original->log->value;
// existing log entry with an empty one.
unset($record->log);
} }
} }
......
...@@ -268,35 +268,7 @@ function book_admin_paths() { ...@@ -268,35 +268,7 @@ function book_admin_paths() {
* An array of all books. * An array of all books.
*/ */
function book_get_books() { function book_get_books() {
$all_books = &drupal_static(__FUNCTION__); return Drupal::service('book.manager')->getAllBooks();
if (!isset($all_books)) {
$all_books = array();
$nids = db_query("SELECT DISTINCT(bid) FROM {book}")->fetchCol();
if ($nids) {
$query = db_select('book', 'b', array('fetch' => PDO::FETCH_ASSOC));
$query->join('node', 'n', 'b.nid = n.nid');
$query->join('menu_links', 'ml', 'b.mlid = ml.mlid');
$query->addField('n', 'type', 'type');
$query->addField('n', 'title', 'title');
$query->fields('b');
$query->fields('ml');
$query->condition('n.nid', $nids, 'IN');
$query->condition('n.status', 1);
$query->orderBy('ml.weight');
$query->orderBy('ml.link_title');
$query->addTag('node_access');
$result2 = $query->execute();
foreach ($result2 as $link) {
$link['href'] = $link['link_path'];
$link['options'] = unserialize($link['options']);
$all_books[$link['bid']] = $link;
}
}
}
return $all_books;
} }
/** /**
......
services: services:
book.manager: book.manager:
class: Drupal\book\BookManager class: Drupal\book\BookManager
arguments: ['@database'] arguments: ['@database', '@plugin.manager.entity']
...@@ -6,8 +6,9 @@ ...@@ -6,8 +6,9 @@
namespace Drupal\book; namespace Drupal\book;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Database\Connection; use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/** /**
* Book Manager Service. * Book Manager Service.
...@@ -21,6 +22,13 @@ class BookManager { ...@@ -21,6 +22,13 @@ class BookManager {
*/ */
protected $database; protected $database;
/**
* Entity manager Service Object.
*
* @var \Drupal\Core\Entity\EntityManager
*/
protected $entityManager;
/** /**
* Books Array. * Books Array.
* *
...@@ -31,8 +39,9 @@ class BookManager { ...@@ -31,8 +39,9 @@ class BookManager {
/** /**
* Constructs a BookManager object. * Constructs a BookManager object.
*/ */
public function __construct(Connection $database) { public function __construct(Connection $database, EntityManager $entityManager) {
$this->database = $database; $this->database = $database;
$this->entityManager = $entityManager;
} }
/** /**
...@@ -57,26 +66,32 @@ public function getAllBooks() { ...@@ -57,26 +66,32 @@ public function getAllBooks() {
protected function loadBooks() { protected function loadBooks() {
$this->books = array(); $this->books = array();
$nids = $this->database->query("SELECT DISTINCT(bid) FROM {book}")->fetchCol(); $nids = $this->database->query("SELECT DISTINCT(bid) FROM {book}")->fetchCol();
if ($nids) { if ($nids) {
$query = $this->database->select('book', 'b', array('fetch' => \PDO::FETCH_ASSOC)); $query = $this->database->select('book', 'b', array('fetch' => \PDO::FETCH_ASSOC));
$query->join('node', 'n', 'b.nid = n.nid');
$query->join('menu_links', 'ml', 'b.mlid = ml.mlid'); $query->join('menu_links', 'ml', 'b.mlid = ml.mlid');
$query->addField('n', 'type', 'type');
$query->addField('n', 'title', 'title');
$query->fields('b'); $query->fields('b');
$query->fields('ml'); $query->fields('ml');
$query->condition('n.nid', $nids, 'IN'); $query->condition('b.nid', $nids);
$query->condition('n.status', 1);
$query->orderBy('ml.weight'); $query->orderBy('ml.weight');
$query->orderBy('ml.link_title'); $query->orderBy('ml.link_title');
$query->addTag('node_access'); $query->addTag('node_access');