Browse Source

update core to 7.36

Bachir Soussi Chiadmi 9 years ago
parent
commit
802ec0c6f3
100 changed files with 1710 additions and 439 deletions
  1. 154 0
      CHANGELOG.txt
  2. 14 10
      MAINTAINERS.txt
  3. 26 3
      includes/ajax.inc
  4. 107 51
      includes/bootstrap.inc
  5. 10 6
      includes/cache.inc
  6. 193 59
      includes/common.inc
  7. 3 3
      includes/database/database.inc
  8. 4 0
      includes/database/mysql/database.inc
  9. 15 15
      includes/database/mysql/schema.inc
  10. 14 14
      includes/database/pgsql/schema.inc
  11. 1 1
      includes/database/query.inc
  12. 3 3
      includes/database/schema.inc
  13. 4 1
      includes/database/select.inc
  14. 14 14
      includes/database/sqlite/schema.inc
  15. 7 3
      includes/entity.inc
  16. 74 32
      includes/file.inc
  17. 20 0
      includes/file.mimetypes.inc
  18. 4 2
      includes/filetransfer/ssh.inc
  19. 79 16
      includes/form.inc
  20. 8 1
      includes/install.inc
  21. 1 1
      includes/language.inc
  22. 10 5
      includes/locale.inc
  23. 1 1
      includes/lock.inc
  24. 1 1
      includes/mail.inc
  25. 33 1
      includes/menu.inc
  26. 3 3
      includes/module.inc
  27. 5 1
      includes/password.inc
  28. 1 1
      includes/session.inc
  29. 3 4
      includes/tablesort.inc
  30. 79 4
      includes/theme.inc
  31. 9 5
      includes/unicode.inc
  32. 35 1
      includes/xmlrpc.inc
  33. 3 1
      includes/xmlrpcs.inc
  34. 21 1
      misc/ajax.js
  35. BIN
      misc/favicon.ico
  36. 1 1
      misc/tabledrag.js
  37. 5 1
      misc/tableselect.js
  38. BIN
      misc/throbber-active.gif
  39. BIN
      misc/throbber-inactive.png
  40. 6 0
      misc/vertical-tabs.js
  41. 1 1
      modules/aggregator/aggregator.fetcher.inc
  42. 3 3
      modules/aggregator/aggregator.info
  43. 3 3
      modules/aggregator/tests/aggregator_test.info
  44. 1 1
      modules/aggregator/tests/aggregator_test.module
  45. 1 1
      modules/block/block.admin.inc
  46. 3 3
      modules/block/block.info
  47. 17 1
      modules/block/block.install
  48. 0 0
      modules/block/block.module
  49. 2 2
      modules/block/block.test
  50. 3 3
      modules/block/tests/block_test.info
  51. 3 3
      modules/block/tests/themes/block_test_theme/block_test_theme.info
  52. 3 3
      modules/blog/blog.info
  53. 3 3
      modules/book/book.info
  54. 3 3
      modules/color/color.info
  55. 3 3
      modules/comment/comment.info
  56. 21 14
      modules/comment/comment.module
  57. 36 1
      modules/comment/comment.test
  58. 3 3
      modules/contact/contact.info
  59. 2 2
      modules/contact/contact.pages.inc
  60. 3 3
      modules/contextual/contextual.info
  61. 3 3
      modules/dashboard/dashboard.info
  62. 4 4
      modules/dashboard/dashboard.js
  63. 3 3
      modules/dblog/dblog.info
  64. 20 3
      modules/field/field.api.php
  65. 1 1
      modules/field/field.attach.inc
  66. 4 4
      modules/field/field.crud.inc
  67. 3 3
      modules/field/field.info
  68. 21 5
      modules/field/field.info.class.inc
  69. 5 1
      modules/field/field.info.inc
  70. 1 0
      modules/field/field.install
  71. 14 14
      modules/field/field.module
  72. 3 3
      modules/field/modules/field_sql_storage/field_sql_storage.info
  73. 51 4
      modules/field/modules/field_sql_storage/field_sql_storage.module
  74. 145 0
      modules/field/modules/field_sql_storage/field_sql_storage.test
  75. 3 3
      modules/field/modules/list/list.info
  76. 3 3
      modules/field/modules/list/tests/list_test.info
  77. 3 3
      modules/field/modules/number/number.info
  78. 3 3
      modules/field/modules/options/options.info
  79. 3 3
      modules/field/modules/text/text.info
  80. 16 12
      modules/field/modules/text/text.js
  81. 1 1
      modules/field/modules/text/text.module
  82. 62 1
      modules/field/tests/field.test
  83. 3 3
      modules/field/tests/field_test.info
  84. 15 0
      modules/field/tests/field_test.module
  85. 3 0
      modules/field_ui/field_ui.api.php
  86. 3 3
      modules/field_ui/field_ui.info
  87. 2 2
      modules/field_ui/field_ui.js
  88. 7 0
      modules/file/file.field.inc
  89. 3 3
      modules/file/file.info
  90. 33 8
      modules/file/file.module
  91. 156 0
      modules/file/tests/file.test
  92. 3 3
      modules/file/tests/file_module_test.info
  93. 3 3
      modules/filter/filter.info
  94. 1 3
      modules/filter/filter.module
  95. 1 1
      modules/filter/filter.pages.inc
  96. 9 0
      modules/filter/filter.test
  97. 3 3
      modules/forum/forum.info
  98. 2 2
      modules/forum/forum.module
  99. 3 3
      modules/help/help.info
  100. 3 3
      modules/image/image.info

+ 154 - 0
CHANGELOG.txt

@@ -1,4 +1,158 @@
 
+Drupal 7.36, 2015-04-01
+-----------------------
+- Added a 'file_public_schema' variable which allows modules that define
+  publicly-accessible streams in hook_stream_wrappers() to bypass file download
+  access checks when processing managed file upload fields.
+- Fixed a bug that caused database query tags not to be added to search-related
+  database queries under many circumstances, and which prevented the
+  corresponding hook_query_TAG_alter() implementations from being called.
+- Fixed the "for" attribute on managed file upload field labels to improve
+  accessibility (minor markup change).
+- Added a 'javascript_always_use_jquery' variable which can be set to FALSE by
+  sites that may not need jQuery loaded on all pages, and a 'requires_jquery'
+  option to drupal_add_js() which modules can set to FALSE when adding
+  JavaScript files that have no dependency on jQuery (API addition:
+  https://www.drupal.org/node/2462717).
+- Fixed incorrect foreign keys in the User module's role_permission and
+  users_roles database tables.
+- Changed permission descriptions throughout Drupal core to consistently link
+  to relevant administrative pages, regardless of whether the user viewing the
+  Permissions page can view the page being linked to (minor UI change).
+- Fixed the drupal_add_region_content() function so that it actually adds
+  content to the page.
+- Added an 'image_suppress_itok_output' variable to allow sites already using
+  the existing 'image_allow_insecure_derivatives' variable to also prevent
+  security tokens from appearing in image derivative URLs.
+- Fixed double-escaping of theme names in the Block module administrative
+  interface (minor string change).
+- Added basic support for Xdebug when running automated tests.
+- Fixed a bug which caused previewing a node to remove elements from the node
+  being edited. With this fix, calling node_preview() will no longer modify the
+  passed-in node object (minor API change).
+- Added a user_has_role() function to check whether a user has a particular
+  role (API addition: https://www.drupal.org/node/2462411).
+- Fixed installation failures when an opcode cache is enabled.
+- Fixed a bug in the Drupal 6 to Drupal 7 upgrade path which caused private
+  files to be inaccessible.
+- Fixed a bug in the Drupal 6 to Drupal 7 upgrade path which caused user
+  pictures to be lost.
+- Fixed missing language code in hook_field_attach_view_alter() when it is
+  invoked from field_view_field().
+- Stopped sending ETag and Last-Modified headers for uncached page requests,
+  since they break caching for certain Varnish and Nginx configurations.
+- Changed the Simpletest module to allow PSR-4 test classes to be used in
+  Drupal 7.
+- Fixed a fatal error that occurred when using the Comment module's "Unpublish
+  comment containing keyword(s)" action.
+- Changed the "lang" attribute on language links to "xml:lang" so it validates
+  as XHTML (minor markup change).
+- Prevented the form API from allowing arrays to be submitted for various form
+  elements, such as textfields, textareas, and password fields (API change:
+  https://www.drupal.org/node/2462723).
+- Fixed a bug in the Contact module which caused the global user object to have
+  the incorrect name and e-mail address during the remainder of the page
+  request after the contact form is submitted.
+- Numerous small bug fixes.
+- Numerous API documentation improvements.
+- Additional automated test coverage.
+
+Drupal 7.35, 2015-03-18
+----------------------
+- Fixed security issues (multiple vulnerabilities). See SA-CORE-2015-001.
+
+Drupal 7.34, 2014-11-19
+----------------------
+- Fixed security issues (multiple vulnerabilities). See SA-CORE-2014-006.
+
+Drupal 7.33, 2014-11-07
+-----------------------
+- Began storing the file modification time of each module and theme in the
+  {system} database table so that contributed modules can use it to identify
+  recently changed modules and themes (minor data structure change to the
+  return value of system_get_info() and other related functions).
+- Added a "Did you mean?" feature to the run-tests.sh script for running
+  automated tests from the command line, to help developers who are attempting
+  to run a particular test class or group.
+- Changed the date format used in various HTTP headers output by Drupal core
+  from RFC 1123 format to RFC 7231 format.
+- Added a "block_cache_bypass_node_grants" variable to allow sites which have
+  node access modules enabled to use the block cache if desired (API addition).
+- Made image derivative generation HTTP requests return a 404 error (rather
+  than a 500 error) when the source image does not exist.
+- Fixed a bug which caused user pictures to be removed from the user object
+  after saving, and resulted in data loss if the user account was subsequently
+  re-saved.
+- Fixed a bug in which field_has_data() did not return TRUE for fields that
+  only had data in older entity revisions, leading to loss of the field's data
+  when the field configuration was edited.
+- Fixed a bug which caused the Ajax progress throbber to appear misaligned in
+  many situatons (minor styling change).
+- Prevented the Bartik theme from lower-casing the "Permalink" link on
+  comments, for improved multilingual support (minor UI change).
+- Added a "preferred_menu_links" tag to the database query that is used by
+  menu_link_get_preferred() to find the preferred menu link for a given path,
+  to make it easier to alter.
+- Increased the maximum allowed length of block titles to 255 characters
+  (database schema change to the {block} table).
+- Removed the Field module's field_modules_uninstalled() function, since it did
+  not do anything when it was invoked.
+- Added a "theme_hook_original" variable to templates and theme functions and
+  an optional sitewide theme debug mode, to provide contextual information in
+  the page's HTML to theme developers. The theme debug mode is based on the one
+  used with Twig in Drupal 8 and can be accessed by setting the "theme_debug"
+  variable to TRUE (API addition).
+- Added an entity_view_mode_prepare() API function to allow entity-defining
+  modules to properly invoke hook_entity_view_mode_alter(), and used it
+  throughout Drupal core to fix bugs with the invocation of that hook (API
+  change: https://www.drupal.org/node/2369141).
+- Security improvement: Made the database API's orderBy() method sanitize the
+  sort direction ("ASC" or "DESC") for queries built with db_select(), so that
+  calling code does not have to.
+- Changed the RDF module to consistently output RDF metadata for nodes and
+  comments near where the node is rendered in the HTML (minor markup and data
+  structure change).
+- Added an HTML class to RDFa metatags throughout Drupal to prevent them from
+  accidentally affecting the site appearance (minor markup change).
+- Fixed a bug in the Unicode requirements check which prevented installing
+  Drupal on PHP 5.6.
+- Fixed a bug which caused drupal_get_bootstrap_phase() to abort the bootstrap
+  when called early in the page request.
+- Renamed the "Search result" view mode to "Search result highlighting input"
+  to better reflect how it is used (UI change).
+- Improved database queries generated by EntityFieldQuery in the case where
+  delta or language condition groups are used, to reduce the number of INNER
+  JOINs (this is a minor data structure change affecting code which implements
+  hook_query_alter() on these queries).
+- Removed special-case behavior for file uploads which allowed user #1 to
+  bypass maximum file size and user quota limits.
+- Numerous small bug fixes.
+- Numerous API documentation improvements.
+- Additional automated test coverage.
+
+Drupal 7.32, 2014-10-15
+----------------------
+- Fixed security issues (SQL injection). See SA-CORE-2014-005.
+
+Drupal 7.31, 2014-08-06
+----------------------
+- Fixed security issues (denial of service). See SA-CORE-2014-004.
+
+Drupal 7.30, 2014-07-24
+-----------------------
+- Fixed a regression introduced in Drupal 7.29 that caused files or images
+  attached to taxonomy terms to be deleted when the taxonomy term was edited
+  and resaved (and other related bugs with contributed and custom modules).
+- Added a warning on the permissions page to recommend restricting access to
+  the "View site reports" permission to trusted administrators. See
+  DRUPAL-PSA-2014-002.
+- Numerous API documentation improvements.
+- Additional automated test coverage.
+
+Drupal 7.29, 2014-07-16
+----------------------
+- Fixed security issues (multiple vulnerabilities). See SA-CORE-2014-003.
+
 Drupal 7.28, 2014-05-08
 -----------------------
 - Fixed a regression introduced in Drupal 7.27 that caused JavaScript to break

+ 14 - 10
MAINTAINERS.txt

@@ -27,7 +27,6 @@ Ajax system
 - Earl Miles 'merlinofchaos' http://drupal.org/user/26979
 
 Base system
-- Károly Négyesi 'chx' http://drupal.org/user/9446
 - Damien Tournoud 'DamZ' http://drupal.org/user/22211
 - Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23
 
@@ -39,7 +38,6 @@ Cache system
 - Nathaniel Catchpole 'catch' http://drupal.org/user/35733
 
 Cron system
-- Károly Négyesi 'chx' http://drupal.org/user/9446
 - Derek Wright 'dww' http://drupal.org/user/46549
 
 Database system
@@ -55,10 +53,8 @@ Database system
 
   - Sqlite driver
     - Damien Tournoud 'DamZ' http://drupal.org/user/22211
-    - Károly Négyesi 'chx' http://drupal.org/user/9446
 
 Database update system
-- Károly Négyesi 'chx' http://drupal.org/user/9446
 - Ashok Modi 'BTMash' http://drupal.org/user/60422
 
 Entity system
@@ -71,7 +67,6 @@ File system
 - Aaron Winborn 'aaron' http://drupal.org/user/33420
 
 Form system
-- Károly Négyesi 'chx' http://drupal.org/user/9446
 - Alex Bronstein 'effulgentsia' http://drupal.org/user/78040
 - Wolfgang Ziegler 'fago' http://drupal.org/user/16747
 - Daniel F. Kudwien 'sun' http://drupal.org/user/54136
@@ -105,7 +100,6 @@ Markup
 
 Menu system
 - Peter Wolanin 'pwolanin' http://drupal.org/user/49851
-- Károly Négyesi 'chx' http://drupal.org/user/9446
 
 Path system
 - Dave Reid 'davereid' http://drupal.org/user/53892
@@ -139,9 +133,6 @@ Accessibility
 Documentation
 - Jennifer Hodgdon 'jhodgdon' http://drupal.org/user/155601
 
-Security
-- Greg Knaddison 'greggles' http://drupal.org/user/36762
-
 Translations
 - Gerhard Killesreiter 'killes' http://drupal.org/user/83
 
@@ -154,6 +145,20 @@ Node Access
 - Ken Rickard 'agentrickard' http://drupal.org/user/20975
 - Jess Myrbo 'xjm' http://drupal.org/user/65776
 
+
+Security team
+-----------------
+
+To report a security issue, see: https://drupal.org/security-team/report-issue
+
+The Drupal security team provides Security Advisories for vulnerabilities,
+assists developers in resolving security issues, and provides security
+documentation. See http://drupal.org/security-team for more information. The
+security team lead is:
+
+- Michael Hess 'mlhess' https://drupal.org/user/102818
+
+
 Module maintainers
 ------------------
 
@@ -250,7 +255,6 @@ Shortcut module
 
 Simpletest module
 - Jimmy Berry 'boombatower' http://drupal.org/user/214218
-- Károly Négyesi 'chx' http://drupal.org/user/9446
 
 Statistics module
 - Tim Millwood 'timmillwood' http://drupal.org/user/227849

+ 26 - 3
includes/ajax.inc

@@ -211,7 +211,7 @@
  *
  * When returning an Ajax command array, it is often useful to have
  * status messages rendered along with other tasks in the command array.
- * In that case the the Ajax commands array may be constructed like this:
+ * In that case the Ajax commands array may be constructed like this:
  * @code
  *   $commands = array();
  *   $commands[] = ajax_command_replace(NULL, $output);
@@ -276,7 +276,7 @@ function ajax_render($commands = array()) {
 
   $extra_commands = array();
   if (!empty($styles)) {
-    $extra_commands[] = ajax_command_prepend('head', $styles);
+    $extra_commands[] = ajax_command_add_css($styles);
   }
   if (!empty($scripts_header)) {
     $extra_commands[] = ajax_command_prepend('head', $scripts_header);
@@ -292,7 +292,7 @@ function ajax_render($commands = array()) {
   $scripts = drupal_add_js();
   if (!empty($scripts['settings'])) {
     $settings = $scripts['settings'];
-    array_unshift($commands, ajax_command_settings(call_user_func_array('array_merge_recursive', $settings['data']), TRUE));
+    array_unshift($commands, ajax_command_settings(drupal_array_merge_deep_array($settings['data']), TRUE));
   }
 
   // Allow modules to alter any Ajax response.
@@ -1257,3 +1257,26 @@ function ajax_command_update_build_id($form) {
     'new' => $form['#build_id'],
   );
 }
+
+/**
+ * Creates a Drupal Ajax 'add_css' command.
+ *
+ * This method will add css via ajax in a cross-browser compatible way.
+ *
+ * This command is implemented by Drupal.ajax.prototype.commands.add_css()
+ * defined in misc/ajax.js.
+ *
+ * @param $styles
+ *   A string that contains the styles to be added.
+ *
+ * @return
+ *   An array suitable for use with the ajax_render() function.
+ *
+ * @see misc/ajax.js
+ */
+function ajax_command_add_css($styles) {
+  return array(
+    'command' => 'add_css',
+    'data' => $styles,
+  );
+}

+ 107 - 51
includes/bootstrap.inc

@@ -8,7 +8,7 @@
 /**
  * The current system version.
  */
-define('VERSION', '7.28');
+define('VERSION', '7.36');
 
 /**
  * Core API compatibility.
@@ -248,6 +248,15 @@ define('REGISTRY_WRITE_LOOKUP_CACHE', 2);
  */
 define('DRUPAL_PHP_FUNCTION_PATTERN', '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*');
 
+/**
+ * A RFC7231 Compliant date.
+ *
+ * http://tools.ietf.org/html/rfc7231#section-7.1.1.1
+ *
+ * Example: Sun, 06 Nov 1994 08:49:37 GMT
+ */
+define('DATE_RFC7231', 'D, d M Y H:i:s \G\M\T');
+
 /**
  * Provides a caching wrapper to be used in place of large array structures.
  *
@@ -520,9 +529,8 @@ function timer_stop($name) {
  * Returns the appropriate configuration directory.
  *
  * Returns the configuration path based on the site's hostname, port, and
- * pathname. Uses find_conf_path() to find the current configuration directory.
- * See default.settings.php for examples on how the URL is converted to a
- * directory.
+ * pathname. See default.settings.php for examples on how the URL is converted
+ * to a directory.
  *
  * @param bool $require_settings
  *   Only configuration directories with an existing settings.php file
@@ -700,7 +708,14 @@ function drupal_environment_initialize() {
  *  TRUE if only containing valid characters, or FALSE otherwise.
  */
 function drupal_valid_http_host($host) {
-  return preg_match('/^\[?(?:[a-zA-Z0-9-:\]_]+\.?)+$/', $host);
+  // Limit the length of the host name to 1000 bytes to prevent DoS attacks with
+  // long host names.
+  return strlen($host) <= 1000
+    // Limit the number of subdomains and port separators to prevent DoS attacks
+    // in conf_path().
+    && substr_count($host, '.') <= 100
+    && substr_count($host, ':') <= 100
+    && preg_match('/^\[?(?:[a-zA-Z0-9-:\]_]+\.?)+$/', $host);
 }
 
 /**
@@ -845,7 +860,7 @@ function drupal_get_filename($type, $name, $filename = NULL) {
     try {
       if (function_exists('db_query')) {
         $file = db_query("SELECT filename FROM {system} WHERE name = :name AND type = :type", array(':name' => $name, ':type' => $type))->fetchField();
-        if (file_exists(DRUPAL_ROOT . '/' . $file)) {
+        if ($file !== FALSE && file_exists(DRUPAL_ROOT . '/' . $file)) {
           $files[$type][$name] = $file;
         }
       }
@@ -1230,23 +1245,10 @@ function drupal_send_headers($default_headers = array(), $only_default = FALSE)
  * fresh page on every request. This prevents authenticated users from seeing
  * locally cached pages.
  *
- * Also give each page a unique ETag. This will force clients to include both
- * an If-Modified-Since header and an If-None-Match header when doing
- * conditional requests for the page (required by RFC 2616, section 13.3.4),
- * making the validation more robust. This is a workaround for a bug in Mozilla
- * Firefox that is triggered when Drupal's caching is enabled and the user
- * accesses Drupal via an HTTP proxy (see
- * https://bugzilla.mozilla.org/show_bug.cgi?id=269303): When an authenticated
- * user requests a page, and then logs out and requests the same page again,
- * Firefox may send a conditional request based on the page that was cached
- * locally when the user was logged in. If this page did not have an ETag
- * header, the request only contains an If-Modified-Since header. The date will
- * be recent, because with authenticated users the Last-Modified header always
- * refers to the time of the request. If the user accesses Drupal via a proxy
- * server, and the proxy already has a cached copy of the anonymous page with an
- * older Last-Modified date, the proxy may respond with 304 Not Modified, making
- * the client think that the anonymous and authenticated pageviews are
- * identical.
+ * ETag and Last-Modified headers are not set per default for authenticated
+ * users so that browsers do not send If-Modified-Since headers from
+ * authenticated user pages. drupal_serve_page_from_cache() will set appropriate
+ * ETag and Last-Modified headers for cached pages.
  *
  * @see drupal_page_set_cache()
  */
@@ -1259,9 +1261,7 @@ function drupal_page_header() {
 
   $default_headers = array(
     'Expires' => 'Sun, 19 Nov 1978 05:00:00 GMT',
-    'Last-Modified' => gmdate(DATE_RFC1123, REQUEST_TIME),
     'Cache-Control' => 'no-cache, must-revalidate, post-check=0, pre-check=0',
-    'ETag' => '"' . REQUEST_TIME . '"',
   );
   drupal_send_headers($default_headers);
 }
@@ -1329,7 +1329,7 @@ function drupal_serve_page_from_cache(stdClass $cache) {
     drupal_add_http_header($name, $value);
   }
 
-  $default_headers['Last-Modified'] = gmdate(DATE_RFC1123, $cache->created);
+  $default_headers['Last-Modified'] = gmdate(DATE_RFC7231, $cache->created);
 
   // HTTP/1.0 proxies does not support the Vary header, so prevent any caching
   // by sending an Expires date in the past. HTTP/1.1 clients ignores the
@@ -1552,12 +1552,13 @@ function format_string($string, array $args = array()) {
  * Also validates strings as UTF-8 to prevent cross site scripting attacks on
  * Internet Explorer 6.
  *
- * @param $text
+ * @param string $text
  *   The text to be checked or processed.
  *
- * @return
- *   An HTML safe version of $text, or an empty string if $text is not
- *   valid UTF-8.
+ * @return string
+ *   An HTML safe version of $text. If $text is not valid UTF-8, an empty string
+ *   is returned and, on PHP < 5.4, a warning may be issued depending on server
+ *   configuration (see @link https://bugs.php.net/bug.php?id=47494 @endlink).
  *
  * @see drupal_validate_utf8()
  * @ingroup sanitization
@@ -1642,14 +1643,14 @@ function request_uri() {
  *   information about the passed-in exception is used.
  * @param $variables
  *   Array of variables to replace in the message on display. Defaults to the
- *   return value of drupal_decode_exception().
+ *   return value of _drupal_decode_exception().
  * @param $severity
  *   The severity of the message, as per RFC 3164.
  * @param $link
  *   A link to associate with the message.
  *
  * @see watchdog()
- * @see drupal_decode_exception()
+ * @see _drupal_decode_exception()
  */
 function watchdog_exception($type, Exception $exception, $message = NULL, $variables = array(), $severity = WATCHDOG_ERROR, $link = NULL) {
 
@@ -2169,7 +2170,7 @@ function drupal_anonymous_user() {
  *   drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
  * @endcode
  *
- * @param $phase
+ * @param int $phase
  *   A constant telling which phase to bootstrap to. When you bootstrap to a
  *   particular phase, all earlier phases are run automatically. Possible
  *   values:
@@ -2182,11 +2183,11 @@ function drupal_anonymous_user() {
  *   - DRUPAL_BOOTSTRAP_LANGUAGE: Finds out the language of the page.
  *   - DRUPAL_BOOTSTRAP_FULL: Fully loads Drupal. Validates and fixes input
  *     data.
- * @param $new_phase
+ * @param boolean $new_phase
  *   A boolean, set to FALSE if calling drupal_bootstrap from inside a
  *   function called from drupal_bootstrap (recursion).
  *
- * @return
+ * @return int
  *   The most recently completed phase.
  */
 function drupal_bootstrap($phase = NULL, $new_phase = TRUE) {
@@ -2208,12 +2209,13 @@ function drupal_bootstrap($phase = NULL, $new_phase = TRUE) {
   // bootstrap state.
   static $stored_phase = -1;
 
-  // When not recursing, store the phase name so it's not forgotten while
-  // recursing.
-  if ($new_phase) {
-    $final_phase = $phase;
-  }
   if (isset($phase)) {
+    // When not recursing, store the phase name so it's not forgotten while
+    // recursing but take care of not going backwards.
+    if ($new_phase && $phase >= $stored_phase) {
+      $final_phase = $phase;
+    }
+
     // Call a phase if it has not been called before and is below the requested
     // phase.
     while ($phases && $phase > $stored_phase && $final_phase > $stored_phase) {
@@ -2479,6 +2481,26 @@ function _drupal_bootstrap_variables() {
   // Load bootstrap modules.
   require_once DRUPAL_ROOT . '/includes/module.inc';
   module_load_all(TRUE);
+
+  // Sanitize the destination parameter (which is often used for redirects) to
+  // prevent open redirect attacks leading to other domains. Sanitize both
+  // $_GET['destination'] and $_REQUEST['destination'] to protect code that
+  // relies on either, but do not sanitize $_POST to avoid interfering with
+  // unrelated form submissions. The sanitization happens here because
+  // url_is_external() requires the variable system to be available.
+  if (isset($_GET['destination']) || isset($_REQUEST['destination'])) {
+    require_once DRUPAL_ROOT . '/includes/common.inc';
+    // If the destination is an external URL, remove it.
+    if (isset($_GET['destination']) && url_is_external($_GET['destination'])) {
+      unset($_GET['destination']);
+      unset($_REQUEST['destination']);
+    }
+    // If there's still something in $_REQUEST['destination'] that didn't come
+    // from $_GET, check it too.
+    if (isset($_REQUEST['destination']) && (!isset($_GET['destination']) || $_REQUEST['destination'] != $_GET['destination']) && url_is_external($_REQUEST['destination'])) {
+      unset($_REQUEST['destination']);
+    }
+  }
 }
 
 /**
@@ -2501,7 +2523,7 @@ function _drupal_bootstrap_page_header() {
  * @see drupal_bootstrap()
  */
 function drupal_get_bootstrap_phase() {
-  return drupal_bootstrap();
+  return drupal_bootstrap(NULL, FALSE);
 }
 
 /**
@@ -2615,7 +2637,7 @@ function drupal_installation_attempted() {
  *
  * This would include implementations of hook_install(), which could run
  * during the Drupal installation phase, and might also be run during
- * non-installation time, such as while installing the module from the the
+ * non-installation time, such as while installing the module from the
  * module administration page.
  *
  * Example usage:
@@ -3144,10 +3166,13 @@ function _registry_check_code($type, $name = NULL) {
   // This function may get called when the default database is not active, but
   // there is no reason we'd ever want to not use the default database for
   // this query.
-  $file = Database::getConnection('default', 'default')->query("SELECT filename FROM {registry} WHERE name = :name AND type = :type", array(
-      ':name' => $name,
-      ':type' => $type,
-    ))
+  $file = Database::getConnection('default', 'default')
+    ->select('registry', 'r', array('target' => 'default'))
+    ->fields('r', array('filename'))
+    // Use LIKE here to make the query case-insensitive.
+    ->condition('r.name', db_like($name), 'LIKE')
+    ->condition('r.type', $type)
+    ->execute()
     ->fetchField();
 
   // Flag that we've run a lookup query and need to update the cache.
@@ -3321,11 +3346,9 @@ function registry_update() {
  * @param $default_value
  *   Optional default value.
  * @param $reset
- *   TRUE to reset a specific named variable, or all variables if $name is NULL.
- *   Resetting every variable should only be used, for example, for running
- *   unit tests with a clean environment. Should be used only though via
- *   function drupal_static_reset() and the return value should not be used in
- *   this case.
+ *   TRUE to reset one or all variables(s). This parameter is only used
+ *   internally and should not be passed in; use drupal_static_reset() instead.
+ *   (This function's return value should not be used when TRUE is passed in.)
  *
  * @return
  *   Returns a variable by reference.
@@ -3370,6 +3393,8 @@ function &drupal_static($name, $default_value = NULL, $reset = FALSE) {
  *
  * @param $name
  *   Name of the static variable to reset. Omit to reset all variables.
+ *   Resetting all variables should only be used, for example, for running unit
+ *   tests with a clean environment.
  */
 function drupal_static_reset($name = NULL) {
   drupal_static($name, NULL, TRUE);
@@ -3485,3 +3510,34 @@ function drupal_check_memory_limit($required, $memory_limit = NULL) {
   // - The memory limit is greater than the memory required for the operation.
   return ((!$memory_limit) || ($memory_limit == -1) || (parse_size($memory_limit) >= parse_size($required)));
 }
+
+/**
+ * Invalidates a PHP file from any active opcode caches.
+ *
+ * If the opcode cache does not support the invalidation of individual files,
+ * the entire cache will be flushed.
+ *
+ * @param string $filepath
+ *   The absolute path of the PHP file to invalidate.
+ */
+function drupal_clear_opcode_cache($filepath) {
+  if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50300) {
+    // Below PHP 5.3, clearstatcache does not accept any function parameters.
+    clearstatcache();
+  }
+  else {
+    clearstatcache(TRUE, $filepath);
+  }
+
+  // Zend OPcache.
+  if (function_exists('opcache_invalidate')) {
+    opcache_invalidate($filepath, TRUE);
+  }
+  // APC.
+  if (function_exists('apc_delete_file')) {
+    // apc_delete_file() throws a PHP warning in case the specified file was
+    // not compiled yet.
+    // @see http://php.net/apc-delete-file
+    @apc_delete_file($filepath);
+  }
+}

+ 10 - 6
includes/cache.inc

@@ -98,9 +98,11 @@ function cache_get_multiple(array &$cids, $bin = 'cache') {
  * @param $data
  *   The data to store in the cache. Complex data types will be automatically
  *   serialized before insertion. Strings will be stored as plain text and are
- *   not serialized.
+ *   not serialized. Some storage engines only allow objects up to a maximum of
+ *   1MB in size to be stored by default. When caching large arrays or similar,
+ *   take care to ensure $data does not exceed this size.
  * @param $bin
- *   The cache bin to store the data in. Valid core values are:
+ *   (optional) The cache bin to store the data in. Valid core values are:
  *   - cache: (default) Generic cache storage bin (used for theme registry,
  *     locale date, list of simpletest tests, etc.).
  *   - cache_block: Stores the content of various blocks.
@@ -119,7 +121,7 @@ function cache_get_multiple(array &$cids, $bin = 'cache') {
  *     the administrator panel.
  *   - cache_path: Stores the system paths that have an alias.
  * @param $expire
- *   One of the following values:
+ *   (optional) One of the following values:
  *   - CACHE_PERMANENT: Indicates that the item should never be removed unless
  *     explicitly told to using cache_clear_all() with a cache ID.
  *   - CACHE_TEMPORARY: Indicates that the item should be removed at the next
@@ -254,10 +256,12 @@ interface DrupalCacheInterface {
    *   The cache ID of the data to store.
    * @param $data
    *   The data to store in the cache. Complex data types will be automatically
-   *   serialized before insertion.
-   *   Strings will be stored as plain text and not serialized.
+   *   serialized before insertion. Strings will be stored as plain text and not
+   *   serialized. Some storage engines only allow objects up to a maximum of
+   *   1MB in size to be stored by default. When caching large arrays or
+   *   similar, take care to ensure $data does not exceed this size.
    * @param $expire
-   *   One of the following values:
+   *   (optional) One of the following values:
    *   - CACHE_PERMANENT: Indicates that the item should never be removed unless
    *     explicitly told to using cache_clear_all() with a cache ID.
    *   - CACHE_TEMPORARY: Indicates that the item should be removed at the next

+ 193 - 59
includes/common.inc

@@ -544,37 +544,32 @@ function drupal_get_destination() {
 }
 
 /**
- * Parses a system URL string into an associative array suitable for url().
+ * Parses a URL string into its path, query, and fragment components.
  *
- * This function should only be used for URLs that have been generated by the
- * system, such as via url(). It should not be used for URLs that come from
- * external sources, or URLs that link to external resources.
+ * This function splits both internal paths like @code node?b=c#d @endcode and
+ * external URLs like @code https://example.com/a?b=c#d @endcode into their
+ * component parts. See
+ * @link http://tools.ietf.org/html/rfc3986#section-3 RFC 3986 @endlink for an
+ * explanation of what the component parts are.
  *
- * The returned array contains a 'path' that may be passed separately to url().
- * For example:
- * @code
- *   $options = drupal_parse_url($_GET['destination']);
- *   $my_url = url($options['path'], $options);
- *   $my_link = l('Example link', $options['path'], $options);
- * @endcode
- *
- * This is required, because url() does not support relative URLs containing a
- * query string or fragment in its $path argument. Instead, any query string
- * needs to be parsed into an associative query parameter array in
- * $options['query'] and the fragment into $options['fragment'].
+ * Note that, unlike the RFC, when passed an external URL, this function
+ * groups the scheme, authority, and path together into the path component.
  *
- * @param $url
- *   The URL string to parse, f.e. $_GET['destination'].
+ * @param string $url
+ *   The internal path or external URL string to parse.
  *
- * @return
- *   An associative array containing the keys:
- *   - 'path': The path of the URL. If the given $url is external, this includes
- *     the scheme and host.
- *   - 'query': An array of query parameters of $url, if existent.
- *   - 'fragment': The fragment of $url, if existent.
+ * @return array
+ *   An associative array containing:
+ *   - path: The path component of $url. If $url is an external URL, this
+ *     includes the scheme, authority, and path.
+ *   - query: An array of query parameters from $url, if they exist.
+ *   - fragment: The fragment component from $url, if it exists.
  *
- * @see url()
  * @see drupal_goto()
+ * @see l()
+ * @see url()
+ * @see http://tools.ietf.org/html/rfc3986
+ *
  * @ingroup php_wrappers
  */
 function drupal_parse_url($url) {
@@ -990,9 +985,10 @@ function drupal_http_request($url, array $options = array()) {
   $response = preg_split("/\r\n|\n|\r/", $response);
 
   // Parse the response status line.
-  list($protocol, $code, $status_message) = explode(' ', trim(array_shift($response)), 3);
-  $result->protocol = $protocol;
-  $result->status_message = $status_message;
+  $response_status_array = _drupal_parse_response_status(trim(array_shift($response)));
+  $result->protocol = $response_status_array['http_version'];
+  $result->status_message = $response_status_array['reason_phrase'];
+  $code = $response_status_array['response_code'];
 
   $result->headers = array();
 
@@ -1083,12 +1079,43 @@ function drupal_http_request($url, array $options = array()) {
       }
       break;
     default:
-      $result->error = $status_message;
+      $result->error = $result->status_message;
   }
 
   return $result;
 }
 
+/**
+ * Splits an HTTP response status line into components.
+ *
+ * See the @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html status line definition @endlink
+ * in RFC 2616.
+ *
+ * @param string $respone
+ *   The response status line, for example 'HTTP/1.1 500 Internal Server Error'.
+ *
+ * @return array
+ *   Keyed array containing the component parts. If the response is malformed,
+ *   all possible parts will be extracted. 'reason_phrase' could be empty.
+ *   Possible keys:
+ *   - 'http_version'
+ *   - 'response_code'
+ *   - 'reason_phrase'
+ */
+function _drupal_parse_response_status($response) {
+  $response_array = explode(' ', trim($response), 3);
+  // Set up empty values.
+  $result = array(
+    'reason_phrase' => '',
+  );
+  $result['http_version'] = $response_array[0];
+  $result['response_code'] = $response_array[1];
+  if (isset($response_array[2])) {
+    $result['reason_phrase'] = $response_array[2];
+  }
+  return $result;
+}
+
 /**
  * Helper function for determining hosts excluded from needing a proxy.
  *
@@ -2187,14 +2214,20 @@ function url($path = NULL, array $options = array()) {
     'prefix' => ''
   );
 
+  // A duplicate of the code from url_is_external() to avoid needing another
+  // function call, since performance inside url() is critical.
   if (!isset($options['external'])) {
-    // Return an external link if $path contains an allowed absolute URL. Only
-    // call the slow drupal_strip_dangerous_protocols() if $path contains a ':'
-    // before any / ? or #. Note: we could use url_is_external($path) here, but
-    // that would require another function call, and performance inside url() is
-    // critical.
+    // Return an external link if $path contains an allowed absolute URL. Avoid
+    // calling drupal_strip_dangerous_protocols() if there is any slash (/),
+    // hash (#) or question_mark (?) before the colon (:) occurrence - if any -
+    // as this would clearly mean it is not a URL. If the path starts with 2
+    // slashes then it is always considered an external URL without an explicit
+    // protocol part.
     $colonpos = strpos($path, ':');
-    $options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $path);
+    $options['external'] = (strpos($path, '//') === 0)
+      || ($colonpos !== FALSE
+        && !preg_match('![/?#]!', substr($path, 0, $colonpos))
+        && drupal_strip_dangerous_protocols($path) == $path);
   }
 
   // Preserve the original path before altering or aliasing.
@@ -2232,6 +2265,11 @@ function url($path = NULL, array $options = array()) {
     return $path . $options['fragment'];
   }
 
+  // Strip leading slashes from internal paths to prevent them becoming external
+  // URLs without protocol. /example.com should not be turned into
+  // //example.com.
+  $path = ltrim($path, '/');
+
   global $base_url, $base_secure_url, $base_insecure_url;
 
   // The base_url might be rewritten from the language rewrite in domain mode.
@@ -2309,10 +2347,15 @@ function url($path = NULL, array $options = array()) {
  */
 function url_is_external($path) {
   $colonpos = strpos($path, ':');
-  // Avoid calling drupal_strip_dangerous_protocols() if there is any
-  // slash (/), hash (#) or question_mark (?) before the colon (:)
-  // occurrence - if any - as this would clearly mean it is not a URL.
-  return $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $path;
+  // Avoid calling drupal_strip_dangerous_protocols() if there is any slash (/),
+  // hash (#) or question_mark (?) before the colon (:) occurrence - if any - as
+  // this would clearly mean it is not a URL. If the path starts with 2 slashes
+  // then it is always considered an external URL without an explicit protocol
+  // part.
+  return (strpos($path, '//') === 0)
+    || ($colonpos !== FALSE
+      && !preg_match('![/?#]!', substr($path, 0, $colonpos))
+      && drupal_strip_dangerous_protocols($path) == $path);
 }
 
 /**
@@ -2609,7 +2652,10 @@ function drupal_deliver_html_page($page_callback_result) {
 
         // Keep old path for reference, and to allow forms to redirect to it.
         if (!isset($_GET['destination'])) {
-          $_GET['destination'] = $_GET['q'];
+          // Make sure that the current path is not interpreted as external URL.
+          if (!url_is_external($_GET['q'])) {
+            $_GET['destination'] = $_GET['q'];
+          }
         }
 
         $path = drupal_get_normal_path(variable_get('site_404', ''));
@@ -2638,7 +2684,10 @@ function drupal_deliver_html_page($page_callback_result) {
 
         // Keep old path for reference, and to allow forms to redirect to it.
         if (!isset($_GET['destination'])) {
-          $_GET['destination'] = $_GET['q'];
+          // Make sure that the current path is not interpreted as external URL.
+          if (!url_is_external($_GET['q'])) {
+            $_GET['destination'] = $_GET['q'];
+          }
         }
 
         $path = drupal_get_normal_path(variable_get('site_403', ''));
@@ -3447,7 +3496,11 @@ function drupal_pre_render_styles($elements) {
             $import_batch = array_slice($import, 0, 31);
             $import = array_slice($import, 31);
             $element = $style_element_defaults;
-            $element['#value'] = implode("\n", $import_batch);
+            // This simplifies the JavaScript regex, allowing each line
+            // (separated by \n) to be treated as a completely different string.
+            // This means that we can use ^ and $ on one line at a time, and not
+            // worry about style tags since they'll never match the regex.
+            $element['#value'] = "\n" . implode("\n", $import_batch) . "\n";
             $element['#attributes']['media'] = $group['media'];
             $element['#browsers'] = $group['browsers'];
             $elements[] = $element;
@@ -3773,7 +3826,7 @@ function _drupal_load_stylesheet($matches) {
   // Alter all internal url() paths. Leave external paths alone. We don't need
   // to normalize absolute paths here (i.e. remove folder/... segments) because
   // that will be done later.
-  return preg_replace('/url\(\s*([\'"]?)(?![a-z]+:|\/+)/i', 'url(\1'. $directory, $file);
+  return preg_replace('/url\(\s*([\'"]?)(?![a-z]+:|\/+)([^\'")]+)([\'"]?)\s*\)/i', 'url(\1' . $directory . '\2\3)', $file);
 }
 
 /**
@@ -4109,6 +4162,13 @@ function drupal_region_class($region) {
  *       else being the same, JavaScript added by a call to drupal_add_js() that
  *       happened later in the page request gets added to the page after one for
  *       which drupal_add_js() happened earlier in the page request.
+ *   - requires_jquery: Set this to FALSE if the JavaScript you are adding does
+ *     not have a dependency on jQuery. Defaults to TRUE, except for JavaScript
+ *     settings where it defaults to FALSE. This is used on sites that have the
+ *     'javascript_always_use_jquery' variable set to FALSE; on those sites, if
+ *     all the JavaScript added to the page by drupal_add_js() does not have a
+ *     dependency on jQuery, then for improved front-end performance Drupal
+ *     will not add jQuery and related libraries and settings to the page.
  *   - defer: If set to TRUE, the defer attribute is set on the <script>
  *     tag. Defaults to FALSE.
  *   - cache: If set to FALSE, the JavaScript file is loaded anew on every page
@@ -4126,6 +4186,14 @@ function drupal_region_class($region) {
  */
 function drupal_add_js($data = NULL, $options = NULL) {
   $javascript = &drupal_static(__FUNCTION__, array());
+  $jquery_added = &drupal_static(__FUNCTION__ . ':jquery_added', FALSE);
+
+  // If the $javascript variable has been reset with drupal_static_reset(),
+  // jQuery and related files will have been removed from the list, so set the
+  // variable back to FALSE to indicate they have not yet been added.
+  if (empty($javascript)) {
+    $jquery_added = FALSE;
+  }
 
   // Construct the options, taking the defaults into consideration.
   if (isset($options)) {
@@ -4136,6 +4204,9 @@ function drupal_add_js($data = NULL, $options = NULL) {
   else {
     $options = array();
   }
+  if (isset($options['type']) && $options['type'] == 'setting') {
+    $options += array('requires_jquery' => FALSE);
+  }
   $options += drupal_js_defaults($data);
 
   // Preprocess can only be set if caching is enabled.
@@ -4146,14 +4217,18 @@ function drupal_add_js($data = NULL, $options = NULL) {
   $options['weight'] += count($javascript) / 1000;
 
   if (isset($data)) {
-    // Add jquery.js and drupal.js, as well as the basePath setting, the
-    // first time a JavaScript file is added.
-    if (empty($javascript)) {
+    // Add jquery.js, drupal.js, and related files and settings if they have
+    // not been added yet. However, if the 'javascript_always_use_jquery'
+    // variable is set to FALSE (indicating that the site does not want jQuery
+    // automatically added on all pages) then only add it if a file or setting
+    // that requires jQuery is being added also.
+    if (!$jquery_added && (variable_get('javascript_always_use_jquery', TRUE) || $options['requires_jquery'])) {
+      $jquery_added = TRUE;
       // url() generates the prefix using hook_url_outbound_alter(). Instead of
       // running the hook_url_outbound_alter() again here, extract the prefix
       // from url().
       url('', array('prefix' => &$prefix));
-      $javascript = array(
+      $default_javascript = array(
         'settings' => array(
           'data' => array(
             array('basePath' => base_path()),
@@ -4172,11 +4247,13 @@ function drupal_add_js($data = NULL, $options = NULL) {
           'group' => JS_LIBRARY,
           'every_page' => TRUE,
           'weight' => -1,
+          'requires_jquery' => TRUE,
           'preprocess' => TRUE,
           'cache' => TRUE,
           'defer' => FALSE,
         ),
       );
+      $javascript = drupal_array_merge_deep($javascript, $default_javascript);
       // Register all required libraries.
       drupal_add_library('system', 'jquery', TRUE);
       drupal_add_library('system', 'jquery.once', TRUE);
@@ -4217,6 +4294,7 @@ function drupal_js_defaults($data = NULL) {
     'group' => JS_DEFAULT,
     'every_page' => FALSE,
     'weight' => 0,
+    'requires_jquery' => TRUE,
     'scope' => 'header',
     'cache' => TRUE,
     'defer' => FALSE,
@@ -4263,7 +4341,12 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
   if (!isset($javascript)) {
     $javascript = drupal_add_js();
   }
-  if (empty($javascript)) {
+
+  // If no JavaScript items have been added, or if the only JavaScript items
+  // that have been added are JavaScript settings (which don't do anything
+  // without any JavaScript code to use them), then no JavaScript code should
+  // be added to the page.
+  if (empty($javascript) || (isset($javascript['settings']) && count($javascript) == 1)) {
     return '';
   }
 
@@ -4417,8 +4500,8 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
  *
  * Libraries, JavaScript, CSS and other types of custom structures are attached
  * to elements using the #attached property. The #attached property is an
- * associative array, where the keys are the the attachment types and the values
- * are the attached data. For example:
+ * associative array, where the keys are the attachment types and the values are
+ * the attached data. For example:
  * @code
  * $build['#attached'] = array(
  *   'js' => array(drupal_get_path('module', 'taxonomy') . '/taxonomy.js'),
@@ -5260,8 +5343,6 @@ function drupal_cron_run() {
     foreach ($queues as $queue_name => $info) {
       DrupalQueue::get($queue_name)->createQueue();
     }
-    // Register shutdown callback.
-    drupal_register_shutdown_function('drupal_cron_cleanup');
 
     // Iterate through the modules calling their cron handlers (if any):
     foreach (module_implements('cron') as $module) {
@@ -5313,10 +5394,13 @@ function drupal_cron_run() {
 }
 
 /**
- * Shutdown function: Performs cron cleanup.
+ * DEPRECATED: Shutdown function: Performs cron cleanup.
+ *
+ * This function is deprecated because the 'cron_semaphore' variable it
+ * references no longer exists. It is therefore no longer used as a shutdown
+ * function by Drupal core.
  *
- * @see drupal_cron_run()
- * @see drupal_register_shutdown_function()
+ * @deprecated
  */
 function drupal_cron_cleanup() {
   // See if the semaphore is still locked.
@@ -6641,10 +6725,10 @@ function drupal_array_set_nested_value(array &$array, array $parents, $value, $f
  * $value = drupal_array_get_nested_value($form, $parents);
  * @endcode
  *
- * The return value will be NULL, regardless of whether the actual value is NULL
- * or whether the requested key does not exist. If it is required to know
- * whether the nested array key actually exists, pass a third argument that is
- * altered by reference:
+ * A return value of NULL is ambiguous, and can mean either that the requested
+ * key does not exist, or that the actual value is NULL. If it is required to
+ * know whether the nested array key actually exists, pass a third argument that
+ * is altered by reference:
  * @code
  * $key_exists = NULL;
  * $value = drupal_array_get_nested_value($form, $parents, $key_exists);
@@ -7903,6 +7987,56 @@ function entity_prepare_view($entity_type, $entities, $langcode = NULL) {
   }
 }
 
+/**
+ * Invoke hook_entity_view_mode_alter().
+ *
+ * If adding a new entity similar to nodes, comments or users, you should invoke
+ * this function during the ENTITY_build_content() or ENTITY_view_multiple()
+ * phases of rendering to allow other modules to alter the view mode during this
+ * phase. This function needs to be called before field_attach_prepare_view() to
+ * ensure that the correct content is loaded by field API.
+ *
+ * @param $entity_type
+ *   The type of entity, i.e. 'node', 'user'.
+ * @param $entities
+ *   The entity objects which are being prepared for view, keyed by object ID.
+ * @param $view_mode
+ *   The original view mode e.g. 'full', 'teaser'...
+ * @param $langcode
+ *   (optional) A language code to be used for rendering. Defaults to the global
+ *   content language of the current request.
+ * @return
+ *   An associative array with arrays of entities keyed by view mode.
+ *
+ * @see hook_entity_view_mode_alter()
+ */
+function entity_view_mode_prepare($entity_type, $entities, $view_mode, $langcode = NULL) {
+  if (!isset($langcode)) {
+    $langcode = $GLOBALS['language_content']->language;
+  }
+
+  // To ensure hooks are never run after field_attach_prepare_view() only
+  // process items without the entity_view_prepared flag.
+  $entities_by_view_mode = array();
+  foreach ($entities as $id => $entity) {
+    $entity_view_mode = $view_mode;
+    if (empty($entity->entity_view_prepared)) {
+
+      // Allow modules to change the view mode.
+      $context = array(
+        'entity_type' => $entity_type,
+        'entity' => $entity,
+        'langcode' => $langcode,
+      );
+      drupal_alter('entity_view_mode', $entity_view_mode, $context);
+    }
+
+    $entities_by_view_mode[$entity_view_mode][$id] = $entity;
+  }
+
+  return $entities_by_view_mode;
+}
+
 /**
  * Returns the URI elements of an entity.
  *

+ 3 - 3
includes/database/database.inc

@@ -736,7 +736,7 @@ abstract class DatabaseConnection extends PDO {
     // to expand it out into a comma-delimited set of placeholders.
     foreach (array_filter($args, 'is_array') as $key => $data) {
       $new_keys = array();
-      foreach ($data as $i => $value) {
+      foreach (array_values($data) as $i => $value) {
         // This assumes that there are no other placeholders that use the same
         // name.  For example, if the array placeholder is defined as :example
         // and there is already an :example_2 placeholder, this will generate
@@ -2832,7 +2832,7 @@ function db_drop_table($table) {
  *   will be set to the value of the key in all rows. This is most useful for
  *   creating NOT NULL columns with no default value in existing tables.
  * @param $keys_new
- *   Optional keys and indexes specification to be created on the table along
+ *   (optional) Keys and indexes specification to be created on the table along
  *   with adding the field. The format is the same as a table specification, but
  *   without the 'fields' element. If you are adding a type 'serial' field, you
  *   MUST specify at least one key or index including it in this array. See
@@ -3012,7 +3012,7 @@ function db_drop_index($table, $name) {
  * @param $spec
  *   The field specification for the new field.
  * @param $keys_new
- *   Optional keys and indexes specification to be created on the table along
+ *   (optional) Keys and indexes specification to be created on the table along
  *   with changing the field. The format is the same as a table specification
  *   but without the 'fields' element.
  */

+ 4 - 0
includes/database/mysql/database.inc

@@ -36,6 +36,10 @@ class DatabaseConnection_mysql extends DatabaseConnection {
       // Default to TCP connection on port 3306.
       $dsn = 'mysql:host=' . $connection_options['host'] . ';port=' . (empty($connection_options['port']) ? 3306 : $connection_options['port']);
     }
+    // Character set is added to dsn to ensure PDO uses the proper character
+    // set when escaping. This has security implications. See
+    // https://www.drupal.org/node/1201452 for further discussion.
+    $dsn .= ';charset=utf8';
     $dsn .= ';dbname=' . $connection_options['database'];
     // Allow PDO options to be overridden.
     $connection_options += array(

+ 15 - 15
includes/database/mysql/schema.inc

@@ -40,7 +40,7 @@ class DatabaseSchema_mysql extends DatabaseSchema {
     }
     else {
       $db_info = Database::getConnectionInfo();
-      $info['database'] = $db_info['default']['database'];
+      $info['database'] = $db_info[$this->connection->getTarget()]['database'];
       $info['table'] = $table;
     }
     return $info;
@@ -301,10 +301,10 @@ class DatabaseSchema_mysql extends DatabaseSchema {
 
   public function renameTable($table, $new_name) {
     if (!$this->tableExists($table)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot rename %table to %table_new: table %table doesn't exist.", array('%table' => $table, '%table_new' => $new_name)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot rename @table to @table_new: table @table doesn't exist.", array('@table' => $table, '@table_new' => $new_name)));
     }
     if ($this->tableExists($new_name)) {
-      throw new DatabaseSchemaObjectExistsException(t("Cannot rename %table to %table_new: table %table_new already exists.", array('%table' => $table, '%table_new' => $new_name)));
+      throw new DatabaseSchemaObjectExistsException(t("Cannot rename @table to @table_new: table @table_new already exists.", array('@table' => $table, '@table_new' => $new_name)));
     }
 
     $info = $this->getPrefixInfo($new_name);
@@ -322,10 +322,10 @@ class DatabaseSchema_mysql extends DatabaseSchema {
 
   public function addField($table, $field, $spec, $keys_new = array()) {
     if (!$this->tableExists($table)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add field %table.%field: table doesn't exist.", array('%field' => $field, '%table' => $table)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add field @table.@field: table doesn't exist.", array('@field' => $field, '@table' => $table)));
     }
     if ($this->fieldExists($table, $field)) {
-      throw new DatabaseSchemaObjectExistsException(t("Cannot add field %table.%field: field already exists.", array('%field' => $field, '%table' => $table)));
+      throw new DatabaseSchemaObjectExistsException(t("Cannot add field @table.@field: field already exists.", array('@field' => $field, '@table' => $table)));
     }
 
     $fixnull = FALSE;
@@ -361,7 +361,7 @@ class DatabaseSchema_mysql extends DatabaseSchema {
 
   public function fieldSetDefault($table, $field, $default) {
     if (!$this->fieldExists($table, $field)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field %table.%field: field doesn't exist.", array('%table' => $table, '%field' => $field)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
     }
 
     if (!isset($default)) {
@@ -376,7 +376,7 @@ class DatabaseSchema_mysql extends DatabaseSchema {
 
   public function fieldSetNoDefault($table, $field) {
     if (!$this->fieldExists($table, $field)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot remove default value of field %table.%field: field doesn't exist.", array('%table' => $table, '%field' => $field)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot remove default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
     }
 
     $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` DROP DEFAULT');
@@ -391,10 +391,10 @@ class DatabaseSchema_mysql extends DatabaseSchema {
 
   public function addPrimaryKey($table, $fields) {
     if (!$this->tableExists($table)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add primary key to table %table: table doesn't exist.", array('%table' => $table)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add primary key to table @table: table doesn't exist.", array('@table' => $table)));
     }
     if ($this->indexExists($table, 'PRIMARY')) {
-      throw new DatabaseSchemaObjectExistsException(t("Cannot add primary key to table %table: primary key already exists.", array('%table' => $table)));
+      throw new DatabaseSchemaObjectExistsException(t("Cannot add primary key to table @table: primary key already exists.", array('@table' => $table)));
     }
 
     $this->connection->query('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . $this->createKeySql($fields) . ')');
@@ -411,10 +411,10 @@ class DatabaseSchema_mysql extends DatabaseSchema {
 
   public function addUniqueKey($table, $name, $fields) {
     if (!$this->tableExists($table)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add unique key %name to table %table: table doesn't exist.", array('%table' => $table, '%name' => $name)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add unique key @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
     }
     if ($this->indexExists($table, $name)) {
-      throw new DatabaseSchemaObjectExistsException(t("Cannot add unique key %name to table %table: unique key already exists.", array('%table' => $table, '%name' => $name)));
+      throw new DatabaseSchemaObjectExistsException(t("Cannot add unique key @name to table @table: unique key already exists.", array('@table' => $table, '@name' => $name)));
     }
 
     $this->connection->query('ALTER TABLE {' . $table . '} ADD UNIQUE KEY `' . $name . '` (' . $this->createKeySql($fields) . ')');
@@ -431,10 +431,10 @@ class DatabaseSchema_mysql extends DatabaseSchema {
 
   public function addIndex($table, $name, $fields) {
     if (!$this->tableExists($table)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add index %name to table %table: table doesn't exist.", array('%table' => $table, '%name' => $name)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
     }
     if ($this->indexExists($table, $name)) {
-      throw new DatabaseSchemaObjectExistsException(t("Cannot add index %name to table %table: index already exists.", array('%table' => $table, '%name' => $name)));
+      throw new DatabaseSchemaObjectExistsException(t("Cannot add index @name to table @table: index already exists.", array('@table' => $table, '@name' => $name)));
     }
 
     $this->connection->query('ALTER TABLE {' . $table . '} ADD INDEX `' . $name . '` (' . $this->createKeySql($fields) . ')');
@@ -451,10 +451,10 @@ class DatabaseSchema_mysql extends DatabaseSchema {
 
   public function changeField($table, $field, $field_new, $spec, $keys_new = array()) {
     if (!$this->fieldExists($table, $field)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot change the definition of field %table.%name: field doesn't exist.", array('%table' => $table, '%name' => $field)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot change the definition of field @table.@name: field doesn't exist.", array('@table' => $table, '@name' => $field)));
     }
     if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
-      throw new DatabaseSchemaObjectExistsException(t("Cannot rename field %table.%name to %name_new: target field already exists.", array('%table' => $table, '%name' => $field, '%name_new' => $field_new)));
+      throw new DatabaseSchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", array('@table' => $table, '@name' => $field, '@name_new' => $field_new)));
     }
 
     $sql = 'ALTER TABLE {' . $table . '} CHANGE `' . $field . '` ' . $this->createFieldSql($field_new, $this->processField($spec));

+ 14 - 14
includes/database/pgsql/schema.inc

@@ -314,10 +314,10 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
 
   function renameTable($table, $new_name) {
     if (!$this->tableExists($table)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot rename %table to %table_new: table %table doesn't exist.", array('%table' => $table, '%table_new' => $new_name)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot rename @table to @table_new: table @table doesn't exist.", array('@table' => $table, '@table_new' => $new_name)));
     }
     if ($this->tableExists($new_name)) {
-      throw new DatabaseSchemaObjectExistsException(t("Cannot rename %table to %table_new: table %table_new already exists.", array('%table' => $table, '%table_new' => $new_name)));
+      throw new DatabaseSchemaObjectExistsException(t("Cannot rename @table to @table_new: table @table_new already exists.", array('@table' => $table, '@table_new' => $new_name)));
     }
 
     // Get the schema and tablename for the old table.
@@ -351,10 +351,10 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
 
   public function addField($table, $field, $spec, $new_keys = array()) {
     if (!$this->tableExists($table)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add field %table.%field: table doesn't exist.", array('%field' => $field, '%table' => $table)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add field @table.@field: table doesn't exist.", array('@field' => $field, '@table' => $table)));
     }
     if ($this->fieldExists($table, $field)) {
-      throw new DatabaseSchemaObjectExistsException(t("Cannot add field %table.%field: field already exists.", array('%field' => $field, '%table' => $table)));
+      throw new DatabaseSchemaObjectExistsException(t("Cannot add field @table.@field: field already exists.", array('@field' => $field, '@table' => $table)));
     }
 
     $fixnull = FALSE;
@@ -393,7 +393,7 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
 
   public function fieldSetDefault($table, $field, $default) {
     if (!$this->fieldExists($table, $field)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field %table.%field: field doesn't exist.", array('%table' => $table, '%field' => $field)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
     }
 
     if (!isset($default)) {
@@ -408,7 +408,7 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
 
   public function fieldSetNoDefault($table, $field) {
     if (!$this->fieldExists($table, $field)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot remove default value of field %table.%field: field doesn't exist.", array('%table' => $table, '%field' => $field)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot remove default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
     }
 
     $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" DROP DEFAULT');
@@ -435,10 +435,10 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
 
   public function addPrimaryKey($table, $fields) {
     if (!$this->tableExists($table)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add primary key to table %table: table doesn't exist.", array('%table' => $table)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add primary key to table @table: table doesn't exist.", array('@table' => $table)));
     }
     if ($this->constraintExists($table, 'pkey')) {
-      throw new DatabaseSchemaObjectExistsException(t("Cannot add primary key to table %table: primary key already exists.", array('%table' => $table)));
+      throw new DatabaseSchemaObjectExistsException(t("Cannot add primary key to table @table: primary key already exists.", array('@table' => $table)));
     }
 
     $this->connection->query('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . implode(',', $fields) . ')');
@@ -455,10 +455,10 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
 
   function addUniqueKey($table, $name, $fields) {
     if (!$this->tableExists($table)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add unique key %name to table %table: table doesn't exist.", array('%table' => $table, '%name' => $name)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add unique key @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
     }
     if ($this->constraintExists($table, $name . '_key')) {
-      throw new DatabaseSchemaObjectExistsException(t("Cannot add unique key %name to table %table: unique key already exists.", array('%table' => $table, '%name' => $name)));
+      throw new DatabaseSchemaObjectExistsException(t("Cannot add unique key @name to table @table: unique key already exists.", array('@table' => $table, '@name' => $name)));
     }
 
     $this->connection->query('ALTER TABLE {' . $table . '} ADD CONSTRAINT "' . $this->prefixNonTable($table, $name, 'key') . '" UNIQUE (' . implode(',', $fields) . ')');
@@ -475,10 +475,10 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
 
   public function addIndex($table, $name, $fields) {
     if (!$this->tableExists($table)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add index %name to table %table: table doesn't exist.", array('%table' => $table, '%name' => $name)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
     }
     if ($this->indexExists($table, $name)) {
-      throw new DatabaseSchemaObjectExistsException(t("Cannot add index %name to table %table: index already exists.", array('%table' => $table, '%name' => $name)));
+      throw new DatabaseSchemaObjectExistsException(t("Cannot add index @name to table @table: index already exists.", array('@table' => $table, '@name' => $name)));
     }
 
     $this->connection->query($this->_createIndexSql($table, $name, $fields));
@@ -495,10 +495,10 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
 
   public function changeField($table, $field, $field_new, $spec, $new_keys = array()) {
     if (!$this->fieldExists($table, $field)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot change the definition of field %table.%name: field doesn't exist.", array('%table' => $table, '%name' => $field)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot change the definition of field @table.@name: field doesn't exist.", array('@table' => $table, '@name' => $field)));
     }
     if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
-      throw new DatabaseSchemaObjectExistsException(t("Cannot rename field %table.%name to %name_new: target field already exists.", array('%table' => $table, '%name' => $field, '%name_new' => $field_new)));
+      throw new DatabaseSchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", array('@table' => $table, '@name' => $field, '@name_new' => $field_new)));
     }
 
     $spec = $this->processField($spec);

+ 1 - 1
includes/database/query.inc

@@ -1694,7 +1694,7 @@ class DatabaseCondition implements QueryConditionInterface, Countable {
    * Implements Countable::count().
    *
    * Returns the size of this conditional. The size of the conditional is the
-   * size of its conditional array minus one, because one element is the the
+   * size of its conditional array minus one, because one element is the
    * conjunction.
    */
   public function count() {

+ 3 - 3
includes/database/schema.inc

@@ -416,7 +416,7 @@ abstract class DatabaseSchema implements QueryPlaceholderInterface {
    *   This is most useful for creating NOT NULL columns with no default
    *   value in existing tables.
    * @param $keys_new
-   *   Optional keys and indexes specification to be created on the
+   *   (optional) Keys and indexes specification to be created on the
    *   table along with adding the field. The format is the same as a
    *   table specification but without the 'fields' element. If you are
    *   adding a type 'serial' field, you MUST specify at least one key
@@ -630,7 +630,7 @@ abstract class DatabaseSchema implements QueryPlaceholderInterface {
    * @param $spec
    *   The field specification for the new field.
    * @param $keys_new
-   *   Optional keys and indexes specification to be created on the
+   *   (optional) Keys and indexes specification to be created on the
    *   table along with changing the field. The format is the same as a
    *   table specification but without the 'fields' element.
    *
@@ -654,7 +654,7 @@ abstract class DatabaseSchema implements QueryPlaceholderInterface {
    */
   public function createTable($name, $table) {
     if ($this->tableExists($name)) {
-      throw new DatabaseSchemaObjectExistsException(t('Table %name already exists.', array('%name' => $name)));
+      throw new DatabaseSchemaObjectExistsException(t('Table @name already exists.', array('@name' => $name)));
     }
     $statements = $this->createTableSql($name, $table);
     foreach ($statements as $statement) {

+ 4 - 1
includes/database/select.inc

@@ -377,7 +377,8 @@ interface SelectQueryInterface extends QueryConditionInterface, QueryAlterableIn
    * @param $field
    *   The field on which to order.
    * @param $direction
-   *   The direction to sort. Legal values are "ASC" and "DESC".
+   *   The direction to sort. Legal values are "ASC" and "DESC". Any other value
+   *   will be converted to "ASC".
    * @return SelectQueryInterface
    *   The called object.
    */
@@ -1384,6 +1385,8 @@ class SelectQuery extends Query implements SelectQueryInterface {
   }
 
   public function orderBy($field, $direction = 'ASC') {
+    // Only allow ASC and DESC, default to ASC.
+    $direction = strtoupper($direction) == 'DESC' ? 'DESC' : 'ASC';
     $this->order[$field] = $direction;
     return $this;
   }

+ 14 - 14
includes/database/sqlite/schema.inc

@@ -232,10 +232,10 @@ class DatabaseSchema_sqlite extends DatabaseSchema {
 
   public function renameTable($table, $new_name) {
     if (!$this->tableExists($table)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot rename %table to %table_new: table %table doesn't exist.", array('%table' => $table, '%table_new' => $new_name)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot rename @table to @table_new: table @table doesn't exist.", array('@table' => $table, '@table_new' => $new_name)));
     }
     if ($this->tableExists($new_name)) {
-      throw new DatabaseSchemaObjectExistsException(t("Cannot rename %table to %table_new: table %table_new already exists.", array('%table' => $table, '%table_new' => $new_name)));
+      throw new DatabaseSchemaObjectExistsException(t("Cannot rename @table to @table_new: table @table_new already exists.", array('@table' => $table, '@table_new' => $new_name)));
     }
 
     $schema = $this->introspectSchema($table);
@@ -278,10 +278,10 @@ class DatabaseSchema_sqlite extends DatabaseSchema {
 
   public function addField($table, $field, $specification, $keys_new = array()) {
     if (!$this->tableExists($table)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add field %table.%field: table doesn't exist.", array('%field' => $field, '%table' => $table)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add field @table.@field: table doesn't exist.", array('@field' => $field, '@table' => $table)));
     }
     if ($this->fieldExists($table, $field)) {
-      throw new DatabaseSchemaObjectExistsException(t("Cannot add field %table.%field: field already exists.", array('%field' => $field, '%table' => $table)));
+      throw new DatabaseSchemaObjectExistsException(t("Cannot add field @table.@field: field already exists.", array('@field' => $field, '@table' => $table)));
     }
 
     // SQLite doesn't have a full-featured ALTER TABLE statement. It only
@@ -494,10 +494,10 @@ class DatabaseSchema_sqlite extends DatabaseSchema {
 
   public function changeField($table, $field, $field_new, $spec, $keys_new = array()) {
     if (!$this->fieldExists($table, $field)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot change the definition of field %table.%name: field doesn't exist.", array('%table' => $table, '%name' => $field)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot change the definition of field @table.@name: field doesn't exist.", array('@table' => $table, '@name' => $field)));
     }
     if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
-      throw new DatabaseSchemaObjectExistsException(t("Cannot rename field %table.%name to %name_new: target field already exists.", array('%table' => $table, '%name' => $field, '%name_new' => $field_new)));
+      throw new DatabaseSchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", array('@table' => $table, '@name' => $field, '@name_new' => $field_new)));
     }
 
     $old_schema = $this->introspectSchema($table);
@@ -559,10 +559,10 @@ class DatabaseSchema_sqlite extends DatabaseSchema {
 
   public function addIndex($table, $name, $fields) {
     if (!$this->tableExists($table)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add index %name to table %table: table doesn't exist.", array('%table' => $table, '%name' => $name)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
     }
     if ($this->indexExists($table, $name)) {
-      throw new DatabaseSchemaObjectExistsException(t("Cannot add index %name to table %table: index already exists.", array('%table' => $table, '%name' => $name)));
+      throw new DatabaseSchemaObjectExistsException(t("Cannot add index @name to table @table: index already exists.", array('@table' => $table, '@name' => $name)));
     }
 
     $schema['indexes'][$name] = $fields;
@@ -591,10 +591,10 @@ class DatabaseSchema_sqlite extends DatabaseSchema {
 
   public function addUniqueKey($table, $name, $fields) {
     if (!$this->tableExists($table)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add unique key %name to table %table: table doesn't exist.", array('%table' => $table, '%name' => $name)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add unique key @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
     }
     if ($this->indexExists($table, $name)) {
-      throw new DatabaseSchemaObjectExistsException(t("Cannot add unique key %name to table %table: unique key already exists.", array('%table' => $table, '%name' => $name)));
+      throw new DatabaseSchemaObjectExistsException(t("Cannot add unique key @name to table @table: unique key already exists.", array('@table' => $table, '@name' => $name)));
     }
 
     $schema['unique keys'][$name] = $fields;
@@ -617,14 +617,14 @@ class DatabaseSchema_sqlite extends DatabaseSchema {
 
   public function addPrimaryKey($table, $fields) {
     if (!$this->tableExists($table)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add primary key to table %table: table doesn't exist.", array('%table' => $table)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add primary key to table @table: table doesn't exist.", array('@table' => $table)));
     }
 
     $old_schema = $this->introspectSchema($table);
     $new_schema = $old_schema;
 
     if (!empty($new_schema['primary key'])) {
-      throw new DatabaseSchemaObjectExistsException(t("Cannot add primary key to table %table: primary key already exists.", array('%table' => $table)));
+      throw new DatabaseSchemaObjectExistsException(t("Cannot add primary key to table @table: primary key already exists.", array('@table' => $table)));
     }
 
     $new_schema['primary key'] = $fields;
@@ -646,7 +646,7 @@ class DatabaseSchema_sqlite extends DatabaseSchema {
 
   public function fieldSetDefault($table, $field, $default) {
     if (!$this->fieldExists($table, $field)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field %table.%field: field doesn't exist.", array('%table' => $table, '%field' => $field)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
     }
 
     $old_schema = $this->introspectSchema($table);
@@ -658,7 +658,7 @@ class DatabaseSchema_sqlite extends DatabaseSchema {
 
   public function fieldSetNoDefault($table, $field) {
     if (!$this->fieldExists($table, $field)) {
-      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot remove default value of field %table.%field: field doesn't exist.", array('%table' => $table, '%field' => $field)));
+      throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot remove default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
     }
 
     $old_schema = $this->introspectSchema($table);

+ 7 - 3
includes/entity.inc

@@ -28,7 +28,9 @@ interface DrupalEntityControllerInterface {
    * @param $ids
    *   An array of entity IDs, or FALSE to load all entities.
    * @param $conditions
-   *   An array of conditions in the form 'field' => $value.
+   *   An array of conditions. Keys are field names on the entity's base table.
+   *   Values will be compared for equality. All the comparisons will be ANDed
+   *   together. This parameter is deprecated; use an EntityFieldQuery instead.
    *
    * @return
    *   An array of entity objects indexed by their ids. When no results are
@@ -46,7 +48,7 @@ interface DrupalEntityControllerInterface {
 class DrupalDefaultEntityController implements DrupalEntityControllerInterface {
 
   /**
-   * Static cache of entities.
+   * Static cache of entities, keyed by entity ID.
    *
    * @var array
    */
@@ -236,7 +238,9 @@ class DrupalDefaultEntityController implements DrupalEntityControllerInterface {
    * @param $ids
    *   An array of entity IDs, or FALSE to load all entities.
    * @param $conditions
-   *   An array of conditions in the form 'field' => $value.
+   *   An array of conditions. Keys are field names on the entity's base table.
+   *   Values will be compared for equality. All the comparisons will be ANDed
+   *   together. This parameter is deprecated; use an EntityFieldQuery instead.
    * @param $revision_id
    *   The ID of the revision to load, or FALSE if this query is asking for the
    *   most current revision(s).

+ 74 - 32
includes/file.inc

@@ -1152,7 +1152,7 @@ function file_munge_filename($filename, $extensions, $alerts = TRUE) {
     // Remove any null bytes. See http://php.net/manual/security.filesystem.nullbytes.php
     $filename = str_replace(chr(0), '', $filename);
 
-    $whitelist = array_unique(explode(' ', trim($extensions)));
+    $whitelist = array_unique(explode(' ', strtolower(trim($extensions))));
 
     // Split the filename up by periods. The first part becomes the basename
     // the last part the final extension.
@@ -1165,7 +1165,7 @@ function file_munge_filename($filename, $extensions, $alerts = TRUE) {
     // of allowed extensions.
     foreach ($filename_parts as $filename_part) {
       $new_filename .= '.' . $filename_part;
-      if (!in_array($filename_part, $whitelist) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) {
+      if (!in_array(strtolower($filename_part), $whitelist) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) {
         $new_filename .= '_';
       }
     }
@@ -1559,7 +1559,7 @@ function file_save_upload($form_field_name, $validators = array(), $destination
     return FALSE;
   }
 
-  // Add in our check of the the file name length.
+  // Add in our check of the file name length.
   $validators['file_validate_name_length'] = array();
 
   // Call the validation functions specified by this function's caller.
@@ -1729,8 +1729,6 @@ function file_validate_extensions(stdClass $file, $extensions) {
 /**
  * Checks that the file's size is below certain limits.
  *
- * This check is not enforced for the user #1.
- *
  * @param $file
  *   A Drupal file object.
  * @param $file_limit
@@ -1748,20 +1746,17 @@ function file_validate_extensions(stdClass $file, $extensions) {
  */
 function file_validate_size(stdClass $file, $file_limit = 0, $user_limit = 0) {
   global $user;
-
   $errors = array();
 
-  // Bypass validation for uid  = 1.
-  if ($user->uid != 1) {
-    if ($file_limit && $file->filesize > $file_limit) {
-      $errors[] = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size($file->filesize), '%maxsize' => format_size($file_limit)));
-    }
+  if ($file_limit && $file->filesize > $file_limit) {
+    $errors[] = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size($file->filesize), '%maxsize' => format_size($file_limit)));
+  }
 
-    // Save a query by only calling file_space_used() when a limit is provided.
-    if ($user_limit && (file_space_used($user->uid) + $file->filesize) > $user_limit) {
-      $errors[] = t('The file is %filesize which would exceed your disk quota of %quota.', array('%filesize' => format_size($file->filesize), '%quota' => format_size($user_limit)));
-    }
+  // Save a query by only calling file_space_used() when a limit is provided.
+  if ($user_limit && (file_space_used($user->uid) + $file->filesize) > $user_limit) {
+    $errors[] = t('The file is %filesize which would exceed your disk quota of %quota.', array('%filesize' => format_size($file->filesize), '%quota' => format_size($user_limit)));
   }
+
   return $errors;
 }
 
@@ -1999,23 +1994,7 @@ function file_download() {
   $target = implode('/', $args);
   $uri = $scheme . '://' . $target;
   if (file_stream_wrapper_valid_scheme($scheme) && file_exists($uri)) {
-    // Let other modules provide headers and controls access to the file.
-    // module_invoke_all() uses array_merge_recursive() which merges header
-    // values into a new array. To avoid that and allow modules to override
-    // headers instead, use array_merge() to merge the returned arrays.
-    $headers = array();
-    foreach (module_implements('file_download') as $module) {
-      $function = $module . '_file_download';
-      $result = $function($uri);
-      if ($result == -1) {
-        // Throw away the headers received so far.
-        $headers = array();
-        break;
-      }
-      if (isset($result) && is_array($result)) {
-        $headers = array_merge($headers, $result);
-      }
-    }
+    $headers = file_download_headers($uri);
     if (count($headers)) {
       file_transfer($uri, $headers);
     }
@@ -2027,6 +2006,69 @@ function file_download() {
   drupal_exit();
 }
 
+/**
+ * Retrieves headers for a private file download.
+ *
+ * Calls all module implementations of hook_file_download() to retrieve headers
+ * for files by the module that originally provided the file. The presence of
+ * returned headers indicates the current user has access to the file.
+ *
+ * @param $uri
+ *   The URI for the file whose headers should be retrieved.
+ *
+ * @return
+ *   If access is allowed, headers for the file, suitable for passing to
+ *   file_transfer(). If access is not allowed, an empty array will be returned.
+ *
+ * @see file_transfer()
+ * @see file_download_access()
+ * @see hook_file_downlaod()
+ */
+function file_download_headers($uri) {
+  // Let other modules provide headers and control access to the file.
+  // module_invoke_all() uses array_merge_recursive() which merges header
+  // values into a new array. To avoid that and allow modules to override
+  // headers instead, use array_merge() to merge the returned arrays.
+  $headers = array();
+  foreach (module_implements('file_download') as $module) {
+    $function = $module . '_file_download';
+    $result = $function($uri);
+    if ($result == -1) {
+      // Throw away the headers received so far.
+      $headers = array();
+      break;
+    }
+    if (isset($result) && is_array($result)) {
+      $headers = array_merge($headers, $result);
+    }
+  }
+  return $headers;
+}
+
+/**
+ * Checks that the current user has access to a particular file.
+ *
+ * The return value of this function hinges on the return value from
+ * file_download_headers(), which is the function responsible for collecting
+ * access information through hook_file_download().
+ *
+ * If immediately transferring the file to the browser and the headers will
+ * need to be retrieved, the return value of file_download_headers() should be
+ * used to determine access directly, so that access checks will not be run
+ * twice.
+ *
+ * @param $uri
+ *   The URI for the file whose access should be retrieved.
+ *
+ * @return
+ *   Boolean TRUE if access is allowed. FALSE if access is not allowed.
+ *
+ * @see file_download_headers()
+ * @see hook_file_download()
+ */
+function file_download_access($uri) {
+  return count(file_download_headers($uri)) > 0;
+}
 
 /**
  * Finds all files that match a given mask in a given directory.

+ 20 - 0
includes/file.mimetypes.inc

@@ -43,6 +43,7 @@ function file_default_mimetype_mapping() {
       4 => 'application/cap',
       5 => 'application/cu-seeme',
       6 => 'application/dsptype',
+      350 => 'application/epub+zip',
       7 => 'application/hta',
       8 => 'application/java-archive',
       9 => 'application/java-serialized-object',
@@ -64,6 +65,7 @@ function file_default_mimetype_mapping() {
       25 => 'application/rss+xml',
       26 => 'application/rtf',
       27 => 'application/smil',
+      349 => 'application/vnd.amazon.ebook',
       28 => 'application/vnd.cinderella',
       29 => 'application/vnd.google-earth.kml+xml',
       30 => 'application/vnd.google-earth.kmz',
@@ -183,6 +185,8 @@ function file_default_mimetype_mapping() {
       144 => 'application/x-lzx',
       145 => 'application/x-maker',
       146 => 'application/x-mif',
+      351 => 'application/x-mobipocket-ebook',
+      352 => 'application/x-mobipocket-ebook',
       147 => 'application/x-ms-wmd',
       148 => 'application/x-ms-wmz',
       149 => 'application/x-msdos-program',
@@ -228,8 +232,10 @@ function file_default_mimetype_mapping() {
       188 => 'audio/mpeg',
       189 => 'audio/ogg',
       190 => 'audio/prs.sid',
+      356 => 'audio/webm',
       191 => 'audio/x-aiff',
       192 => 'audio/x-gsm',
+      354 => 'audio/x-matroska',
       193 => 'audio/x-mpegurl',
       194 => 'audio/x-ms-wax',
       195 => 'audio/x-ms-wma',
@@ -301,6 +307,7 @@ function file_default_mimetype_mapping() {
       261 => 'image/vnd.djvu',
       262 => 'image/vnd.microsoft.icon',
       263 => 'image/vnd.wap.wbmp',
+      355 => 'image/webp',
       264 => 'image/x-cmu-raster',
       265 => 'image/x-coreldraw',
       266 => 'image/x-coreldrawpattern',
@@ -337,6 +344,7 @@ function file_default_mimetype_mapping() {
       297 => 'text/vnd.sun.j2me.app-descriptor',
       298 => 'text/vnd.wap.wml',
       299 => 'text/vnd.wap.wmlscript',
+      358 => 'text/vtt',
       300 => 'text/x-bibtex',
       301 => 'text/x-boo',
       302 => 'text/x-c++hdr',
@@ -371,9 +379,11 @@ function file_default_mimetype_mapping() {
       331 => 'video/ogg',
       332 => 'video/quicktime',
       333 => 'video/vnd.mpegurl',
+      357 => 'video/webm',
       347 => 'video/x-flv',
       334 => 'video/x-la-asf',
       348 => 'video/x-m4v',
+      353 => 'video/x-matroska',
       335 => 'video/x-mng',
       336 => 'video/x-ms-asf',
       337 => 'video/x-ms-wm',
@@ -854,6 +864,16 @@ function file_default_mimetype_mapping() {
       'f4b' => 346,
       'flv' => 347,
       'm4v' => 348,
+      'azw' => 349,
+      'epub' => 350,
+      'mobi' => 351,
+      'prc' => 352,
+      'mkv' => 353,
+      'mka' => 354,
+      'webp' => 355,
+      'weba' => 356,
+      'webm' => 357,
+      'vtt' => 358,
     ),
   );
 }

+ 4 - 2
includes/filetransfer/ssh.inc

@@ -72,7 +72,8 @@ class FileTransferSSH extends FileTransfer implements FileTransferChmodInterface
         return TRUE;
       }
       return FALSE;
-    } else {
+    }
+    else {
       throw new FileTransferException('Cannot check @path.', NULL, array('@path' => $path));
     }
   }
@@ -85,7 +86,8 @@ class FileTransferSSH extends FileTransfer implements FileTransferChmodInterface
         return TRUE;
       }
       return FALSE;
-    } else {
+    }
+    else {
       throw new FileTransferException('Cannot check @path.', NULL, array('@path' => $path));
     }
   }

+ 79 - 16
includes/form.inc

@@ -938,7 +938,7 @@ function drupal_process_form($form_id, &$form, &$form_state) {
         // after the batch is processed.
       }
 
-      // Set a flag to indicate the the form has been processed and executed.
+      // Set a flag to indicate that the form has been processed and executed.
       $form_state['executed'] = TRUE;
 
       // Redirect the form based on values in $form_state.
@@ -2451,6 +2451,17 @@ function form_type_password_confirm_value($element, $input = FALSE) {
     $element += array('#default_value' => array());
     return $element['#default_value'] + array('pass1' => '', 'pass2' => '');
   }
+  $value = array('pass1' => '', 'pass2' => '');
+  // Throw out all invalid array keys; we only allow pass1 and pass2.
+  foreach ($value as $allowed_key => $default) {
+    // These should be strings, but allow other scalars since they might be
+    // valid input in programmatic form submissions. Any nested array values
+    // are ignored.
+    if (isset($input[$allowed_key]) && is_scalar($input[$allowed_key])) {
+      $value[$allowed_key] = (string) $input[$allowed_key];
+    }
+  }
+  return $value;
 }
 
 /**
@@ -2494,6 +2505,27 @@ function form_type_select_value($element, $input = FALSE) {
   }
 }
 
+/**
+ * Determines the value for a textarea form element.
+ *
+ * @param array $element
+ *   The form element whose value is being populated.
+ * @param mixed $input
+ *   The incoming input to populate the form element. If this is FALSE,
+ *   the element's default value should be returned.
+ *
+ * @return string
+ *   The data that will appear in the $element_state['values'] collection
+ *   for this element. Return nothing to use the default.
+ */
+function form_type_textarea_value($element, $input = FALSE) {
+  if ($input !== FALSE) {
+    // This should be a string, but allow other scalars since they might be
+    // valid input in programmatic form submissions.
+    return is_scalar($input) ? (string) $input : '';
+  }
+}
+
 /**
  * Determines the value for a textfield form element.
  *
@@ -2509,9 +2541,12 @@ function form_type_select_value($element, $input = FALSE) {
  */
 function form_type_textfield_value($element, $input = FALSE) {
   if ($input !== FALSE && $input !== NULL) {
-    // Equate $input to the form value to ensure it's marked for
-    // validation.
-    return str_replace(array("\r", "\n"), '', $input);
+    // This should be a string, but allow other scalars since they might be
+    // valid input in programmatic form submissions.
+    if (!is_scalar($input)) {
+      $input = '';
+    }
+    return str_replace(array("\r", "\n"), '', (string) $input);
   }
 }
 
@@ -2699,17 +2734,43 @@ function theme_select($variables) {
 }
 
 /**
- * Converts a select form element's options array into HTML.
- *
- * @param $element
- *   An associative array containing the properties of the element.
- * @param $choices
- *   Mixed: Either an associative array of items to list as choices, or an
- *   object with an 'option' member that is an associative array. This
- *   parameter is only used internally and should not be passed.
- *
- * @return
- *   An HTML string of options for the select form element.
+ * Converts an array of options into HTML, for use in select list form elements.
+ *
+ * This function calls itself recursively to obtain the values for each optgroup
+ * within the list of options and when the function encounters an object with
+ * an 'options' property inside $element['#options'].
+ *
+ * @param array $element
+ *   An associative array containing the following key-value pairs:
+ *   - #multiple: Optional Boolean indicating if the user may select more than
+ *     one item.
+ *   - #options: An associative array of options to render as HTML. Each array
+ *     value can be a string, an array, or an object with an 'option' property:
+ *     - A string or integer key whose value is a translated string is
+ *       interpreted as a single HTML option element. Do not use placeholders
+ *       that sanitize data: doing so will lead to double-escaping. Note that
+ *       the key will be visible in the HTML and could be modified by malicious
+ *       users, so don't put sensitive information in it.
+ *     - A translated string key whose value is an array indicates a group of
+ *       options. The translated string is used as the label attribute for the
+ *       optgroup. Do not use placeholders to sanitize data: doing so will lead
+ *       to double-escaping. The array should contain the options you wish to
+ *       group and should follow the syntax of $element['#options'].
+ *     - If the function encounters a string or integer key whose value is an
+ *       object with an 'option' property, the key is ignored, the contents of
+ *       the option property are interpreted as $element['#options'], and the
+ *       resulting HTML is added to the output.
+ *   - #value: Optional integer, string, or array representing which option(s)
+ *     to pre-select when the list is first displayed. The integer or string
+ *     must match the key of an option in the '#options' list. If '#multiple' is
+ *     TRUE, this can be an array of integers or strings.
+ * @param array|null $choices
+ *   (optional) Either an associative array of options in the same format as
+ *   $element['#options'] above, or NULL. This parameter is only used internally
+ *   and is not intended to be passed in to the initial function call.
+ *
+ * @return string
+ *   An HTML string of options and optgroups for use in a select form element.
  */
 function form_select_options($element, $choices = NULL) {
   if (!isset($choices)) {
@@ -2722,7 +2783,7 @@ function form_select_options($element, $choices = NULL) {
   $options = '';
   foreach ($choices as $key => $choice) {
     if (is_array($choice)) {
-      $options .= '<optgroup label="' . $key . '">';
+      $options .= '<optgroup label="' . check_plain($key) . '">';
       $options .= form_select_options($element, $choice);
       $options .= '</optgroup>';
     }
@@ -3285,6 +3346,8 @@ function form_process_container($element, &$form_state) {
  */
 function theme_container($variables) {
   $element = $variables['element'];
+  // Ensure #attributes is set.
+  $element += array('#attributes' => array());
 
   // Special handling for form elements.
   if (isset($element['#array_parents'])) {

+ 8 - 1
includes/install.inc

@@ -420,7 +420,7 @@ abstract class DatabaseTasks {
       }
     }
     if (!empty($message)) {
-      $message = '<p>In order for Drupal to work, and to continue with the installation process, you must resolve all issues reported below. For more help with configuring your database server, see the <a href="http://drupal.org/getting-started/install">installation handbook</a>. If you are unsure what any of this means you should probably contact your hosting provider.</p>' . $message;
+      $message = 'Resolve all issues below to continue the installation. For help configuring your database server, see the <a href="http://drupal.org/getting-started/install">installation handbook</a>, or contact your hosting provider.' . $message;
       throw new DatabaseTaskException($message);
     }
   }
@@ -653,6 +653,13 @@ function drupal_rewrite_settings($settings = array(), $prefix = '') {
     if ($fp && fwrite($fp, $buffer) === FALSE) {
       throw new Exception(st('Failed to modify %settings. Verify the file permissions.', array('%settings' => $settings_file)));
     }
+    else {
+      // The existing settings.php file might have been included already. In
+      // case an opcode cache is enabled, the rewritten contents of the file
+      // will not be reflected in this process. Ensure to invalidate the file
+      // in case an opcode cache is enabled.
+      drupal_clear_opcode_cache(DRUPAL_ROOT . '/' . $settings_file);
+    }
   }
   else {
     throw new Exception(st('Failed to open %settings. Verify the file permissions.', array('%settings' => $default_settings)));

+ 1 - 1
includes/language.inc

@@ -297,7 +297,7 @@ function language_negotiation_get_switch_links($type, $path) {
       // Add support for WCAG 2.0's Language of Parts to add language identifiers.
       // http://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-other-lang-id.html
       foreach ($result as $langcode => $link) {
-        $result[$langcode]['attributes']['lang'] = $langcode;
+        $result[$langcode]['attributes']['xml:lang'] = $langcode;
       }
 
       if (!empty($result)) {

+ 10 - 5
includes/locale.inc

@@ -398,7 +398,7 @@ function locale_language_switcher_session($type, $path) {
       $links[$langcode]['query'][$param] = $langcode;
     }
     else {
-      $links[$langcode]['attributes']['class'][] = ' session-active';
+      $links[$langcode]['attributes']['class'][] = 'session-active';
     }
   }
 
@@ -1931,7 +1931,7 @@ function _locale_translate_seek() {
       $groups[$string['group']],
       array('data' => check_plain(truncate_utf8($string['source'], 150, FALSE, TRUE)) . '<br /><small>' . $string['location'] . '</small>'),
       $string['context'],
-      array('data' => _locale_translate_language_list($string['languages'], $limit_language), 'align' => 'center'),
+      array('data' => _locale_translate_language_list($string, $limit_language), 'align' => 'center'),
       array('data' => l(t('edit'), "admin/config/regional/translate/edit/$lid", array('query' => drupal_get_destination())), 'class' => array('nowrap')),
       array('data' => l(t('delete'), "admin/config/regional/translate/delete/$lid", array('query' => drupal_get_destination())), 'class' => array('nowrap')),
     );
@@ -2126,16 +2126,21 @@ function _locale_rebuild_js($langcode = NULL) {
 /**
  * List languages in search result table
  */
-function _locale_translate_language_list($translation, $limit_language) {
+function _locale_translate_language_list($string, $limit_language) {
   // Add CSS.
   drupal_add_css(drupal_get_path('module', 'locale') . '/locale.css');
 
+  // Include both translated and not yet translated target languages in the
+  // list. The source language is English for built-in strings and the default
+  // language for other strings.
   $languages = language_list();
-  unset($languages['en']);
+  $default = language_default();
+  $omit = $string['group'] == 'default' ? 'en' : $default->language;
+  unset($languages[$omit]);
   $output = '';
   foreach ($languages as $langcode => $language) {
     if (!$limit_language || $limit_language == $langcode) {
-      $output .= (!empty($translation[$langcode])) ? $langcode . ' ' : "<em class=\"locale-untranslated\">$langcode</em> ";
+      $output .= (!empty($string['languages'][$langcode])) ? $langcode . ' ' : "<em class=\"locale-untranslated\">$langcode</em> ";
     }
   }
 

+ 1 - 1
includes/lock.inc

@@ -92,7 +92,7 @@ function _lock_id() {
  * Acquire (or renew) a lock, but do not block if it fails.
  *
  * @param $name
- *   The name of the lock.
+ *   The name of the lock. Limit of name's length is 255 characters.
  * @param $timeout
  *   A number of seconds (float) before the lock expires (minimum of 0.001).
  *

+ 1 - 1
includes/mail.inc

@@ -10,7 +10,7 @@
  *
  * $conf['mail_line_endings'] will override this setting.
  */
-define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER_SOFTWARE'], 'Win32') !== FALSE ? "\r\n" : "\n");
+define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || (isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Win32') !== FALSE) ? "\r\n" : "\n");
 
 /**
  * Composes and optionally sends an e-mail message.

+ 33 - 1
includes/menu.inc

@@ -456,7 +456,9 @@ function menu_get_item($path = NULL, $router_item = NULL) {
     // Rebuild if we know it's needed, or if the menu masks are missing which
     // occurs rarely, likely due to a race condition of multiple rebuilds.
     if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
-      menu_rebuild();
+      if (_menu_check_rebuild()) {
+        menu_rebuild();
+      }
     }
     $original_map = arg(NULL, $path);
 
@@ -2495,6 +2497,7 @@ function menu_link_get_preferred($path = NULL, $selected_menu = NULL) {
     $query->addField('ml', 'weight', 'link_weight');
     $query->fields('m');
     $query->condition('ml.link_path', $path_candidates, 'IN');
+    $query->addTag('preferred_menu_links');
 
     // Sort candidates by link path and menu name.
     $candidates = array();
@@ -2692,6 +2695,21 @@ function menu_reset_static_cache() {
   drupal_static_reset('menu_link_get_preferred');
 }
 
+/**
+ * Checks whether a menu_rebuild() is necessary.
+ */
+function _menu_check_rebuild() {
+  // To absolutely ensure that the menu rebuild is required, re-load the
+  // variables in case they were set by another process.
+  $variables = variable_initialize();
+  if (empty($variables['menu_rebuild_needed']) && !empty($variables['menu_masks'])) {
+    unset($GLOBALS['conf']['menu_rebuild_needed']);
+    $GLOBALS['conf']['menu_masks'] = $variables['menu_masks'];
+    return FALSE;
+  }
+  return TRUE;
+}
+
 /**
  * Populates the database tables used by various menu functions.
  *
@@ -2712,6 +2730,14 @@ function menu_rebuild() {
     // We choose to block here since otherwise the router item may not
     // be available in menu_execute_active_handler() resulting in a 404.
     lock_wait('menu_rebuild');
+
+    if (_menu_check_rebuild()) {
+      // If we get here and menu_masks was not set, then it is possible a menu
+      // is being reloaded, or that the process rebuilding the menu was unable
+      // to complete successfully. A missing menu_masks variable could result
+      // in a 404, so re-run the function.
+      return menu_rebuild();
+    }
     return FALSE;
   }
 
@@ -2736,6 +2762,12 @@ function menu_rebuild() {
     $transaction->rollback();
     watchdog_exception('menu', $e);
   }
+  // Explicitly commit the transaction now; this ensures that the database
+  // operations during the menu rebuild are committed before the lock is made
+  // available again, since locks may not always reside in the same database
+  // connection. The lock is acquired outside of the transaction so should also
+  // be released outside of it.
+  unset($transaction);
 
   lock_release('menu_rebuild');
   return TRUE;

+ 3 - 3
includes/module.inc

@@ -265,11 +265,11 @@ function _module_build_dependencies($files) {
 /**
  * Determines whether a given module exists.
  *
- * @param $module
+ * @param string $module
  *   The name of the module (without the .module extension).
  *
- * @return
- *   TRUE if the module is both installed and enabled.
+ * @return bool
+ *   TRUE if the module is both installed and enabled, FALSE otherwise.
  */
 function module_exists($module) {
   $list = module_list();

+ 5 - 1
includes/password.inc

@@ -140,7 +140,7 @@ function _password_enforce_log2_boundaries($count_log2) {
  * @param $algo
  *   The string name of a hashing algorithm usable by hash(), like 'sha256'.
  * @param $password
- *   The plain-text password to hash.
+ *   Plain-text password up to 512 bytes (128 to 512 UTF-8 characters) to hash.
  * @param $setting
  *   An existing hash or the output of _password_generate_salt().  Must be
  *   at least 12 characters (the settings and salt).
@@ -150,6 +150,10 @@ function _password_enforce_log2_boundaries($count_log2) {
  *   The return string will be truncated at DRUPAL_HASH_LENGTH characters max.
  */
 function _password_crypt($algo, $password, $setting) {
+  // Prevent DoS attacks by refusing to hash large passwords.
+  if (strlen($password) > 512) {
+    return FALSE;
+  }
   // The first 12 characters of an existing hash are its setting string.
   $setting = substr($setting, 0, 12);
 

+ 1 - 1
includes/session.inc

@@ -79,7 +79,7 @@ function _drupal_session_read($sid) {
   // Handle the case of first time visitors and clients that don't store
   // cookies (eg. web crawlers).
   $insecure_session_name = substr(session_name(), 1);
-  if (!isset($_COOKIE[session_name()]) && !isset($_COOKIE[$insecure_session_name])) {
+  if (empty($sid) || (!isset($_COOKIE[session_name()]) && !isset($_COOKIE[$insecure_session_name]))) {
     $user = drupal_anonymous_user();
     return '';
   }

+ 3 - 4
includes/tablesort.inc

@@ -46,10 +46,9 @@ class TableSort extends SelectQueryExtender {
       // Based on code from db_escape_table(), but this can also contain a dot.
       $field = preg_replace('/[^A-Za-z0-9_.]+/', '', $ts['sql']);
 
-      // Sort order can only be ASC or DESC.
-      $sort = drupal_strtoupper($ts['sort']);
-      $sort = in_array($sort, array('ASC', 'DESC')) ? $sort : '';
-      $this->orderBy($field, $sort);
+      // orderBy() will ensure that only ASC/DESC values are accepted, so we
+      // don't need to sanitize that here.
+      $this->orderBy($field, $ts['sort']);
     }
     return $this;
   }

+ 79 - 4
includes/theme.inc

@@ -1029,6 +1029,7 @@ function theme($hook, $variables = array()) {
     }
     $hook = $candidate;
   }
+  $theme_hook_original = $hook;
 
   // If there's no implementation, check for more generic fallbacks. If there's
   // still no implementation, log an error and return an empty string.
@@ -1090,6 +1091,8 @@ function theme($hook, $variables = array()) {
     $variables += array($info['render element'] => array());
   }
 
+  $variables['theme_hook_original'] = $theme_hook_original;
+
   // Invoke the variable processors, if any. The processors may specify
   // alternate suggestions for which hook's template/function to use. If the
   // hook is a suggestion of a base hook, invoke the variable processors of
@@ -1198,7 +1201,12 @@ function theme($hook, $variables = array()) {
     if (isset($info['path'])) {
       $template_file = $info['path'] . '/' . $template_file;
     }
-    $output = $render_function($template_file, $variables);
+    if (variable_get('theme_debug', FALSE)) {
+      $output = _theme_render_template_debug($render_function, $template_file, $variables, $extension);
+    }
+    else {
+      $output = $render_function($template_file, $variables);
+    }
   }
 
   // restore path_to_theme()
@@ -1520,6 +1528,72 @@ function theme_render_template($template_file, $variables) {
   return ob_get_clean();
 }
 
+/**
+ * Renders a template for any engine.
+ *
+ * Includes the possibility to get debug output by setting the
+ * theme_debug variable to TRUE.
+ *
+ * @param string $template_function
+ *   The function to call for rendering the template.
+ * @param string $template_file
+ *   The filename of the template to render.
+ * @param array $variables
+ *   A keyed array of variables that will appear in the output.
+ * @param string $extension
+ *   The extension used by the theme engine for template files.
+ *
+ * @return string
+ *   The output generated by the template including debug information.
+ */
+function _theme_render_template_debug($template_function, $template_file, $variables, $extension) {
+  $output = array(
+    'debug_prefix' => '',
+    'debug_info' => '',
+    'rendered_markup' => call_user_func($template_function, $template_file, $variables),
+    'debug_suffix' => '',
+  );
+  $output['debug_prefix'] .= "\n\n<!-- THEME DEBUG -->";
+  $output['debug_prefix'] .= "\n<!-- CALL: theme('" . check_plain($variables['theme_hook_original']) . "') -->";
+  // If there are theme suggestions, reverse the array so more specific
+  // suggestions are shown first.
+  if (!empty($variables['theme_hook_suggestions'])) {
+    $variables['theme_hook_suggestions'] = array_reverse($variables['theme_hook_suggestions']);
+  }
+  // Add debug output for directly called suggestions like
+  // '#theme' => 'comment__node__article'.
+  if (strpos($variables['theme_hook_original'], '__') !== FALSE) {
+    $derived_suggestions[] = $hook = $variables['theme_hook_original'];
+    while ($pos = strrpos($hook, '__')) {
+      $hook = substr($hook, 0, $pos);
+      $derived_suggestions[] = $hook;
+    }
+    // Get the value of the base hook (last derived suggestion) and append it
+    // to the end of all theme suggestions.
+    $base_hook = array_pop($derived_suggestions);
+    $variables['theme_hook_suggestions'] = array_merge($derived_suggestions, $variables['theme_hook_suggestions']);
+    $variables['theme_hook_suggestions'][] = $base_hook;
+  }
+  if (!empty($variables['theme_hook_suggestions'])) {
+    $current_template = basename($template_file);
+    $suggestions = $variables['theme_hook_suggestions'];
+    // Only add the original theme hook if it wasn't a directly called
+    // suggestion.
+    if (strpos($variables['theme_hook_original'], '__') === FALSE) {
+      $suggestions[] = $variables['theme_hook_original'];
+    }
+    foreach ($suggestions as &$suggestion) {
+      $template = strtr($suggestion, '_', '-') . $extension;
+      $prefix = ($template == $current_template) ? 'x' : '*';
+      $suggestion = $prefix . ' ' . $template;
+    }
+    $output['debug_info'] .= "\n<!-- FILE NAME SUGGESTIONS:\n   " . check_plain(implode("\n   ", $suggestions)) . "\n-->";
+  }
+  $output['debug_info'] .= "\n<!-- BEGIN OUTPUT from '" . check_plain($template_file) . "' -->\n";
+  $output['debug_suffix'] .= "\n<!-- END OUTPUT from '" . check_plain($template_file) . "' -->\n\n";
+  return implode('', $output);
+}
+
 /**
  * Enables a given list of themes.
  *
@@ -1617,7 +1691,7 @@ function theme_status_messages($variables) {
       $output .= " </ul>\n";
     }
     else {
-      $output .= $messages[0];
+      $output .= reset($messages);
     }
     $output .= "</div>\n";
   }
@@ -1690,8 +1764,6 @@ function theme_links($variables) {
   $output = '';
 
   if (count($links) > 0) {
-    $output = '';
-
     // Treat the heading first if it is present to prepend it to the
     // list of links.
     if (!empty($heading)) {
@@ -2550,6 +2622,9 @@ function template_preprocess_page(&$variables) {
     if (!isset($variables['page'][$region_key])) {
       $variables['page'][$region_key] = array();
     }
+    if ($region_content = drupal_get_region_content($region_key)) {
+      $variables['page'][$region_key][]['#markup'] = $region_content;
+    }
   }
 
   // Set up layout variable.

+ 9 - 5
includes/unicode.inc

@@ -116,11 +116,15 @@ function _unicode_check() {
   if (ini_get('mbstring.encoding_translation') != 0) {
     return array(UNICODE_ERROR, $t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.encoding_translation</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring')));
   }
-  if (ini_get('mbstring.http_input') != 'pass') {
-    return array(UNICODE_ERROR, $t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.http_input</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring')));
-  }
-  if (ini_get('mbstring.http_output') != 'pass') {
-    return array(UNICODE_ERROR, $t('Multibyte string output conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.http_output</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring')));
+  // mbstring.http_input and mbstring.http_output are deprecated and empty by
+  // default in PHP 5.6.
+  if (version_compare(PHP_VERSION, '5.6.0') == -1) {
+    if (ini_get('mbstring.http_input') != 'pass') {
+      return array(UNICODE_ERROR, $t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.http_input</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring')));
+    }
+    if (ini_get('mbstring.http_output') != 'pass') {
+      return array(UNICODE_ERROR, $t('Multibyte string output conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.http_output</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring')));
+    }
   }
 
   // Set appropriate configuration

+ 35 - 1
includes/xmlrpc.inc

@@ -178,7 +178,41 @@ function xmlrpc_message_parse($xmlrpc_message) {
   xml_set_element_handler($xmlrpc_message->_parser, 'xmlrpc_message_tag_open', 'xmlrpc_message_tag_close');
   xml_set_character_data_handler($xmlrpc_message->_parser, 'xmlrpc_message_cdata');
   xmlrpc_message_set($xmlrpc_message);
-  if (!xml_parse($xmlrpc_message->_parser, $xmlrpc_message->message)) {
+
+  // Strip XML declaration.
+  $header = preg_replace('/<\?xml.*?\?'.'>/s', '', substr($xmlrpc_message->message, 0, 100), 1);
+  $xml = trim(substr_replace($xmlrpc_message->message, $header, 0, 100));
+  if ($xml == '') {
+    return FALSE;
+  }
+  // Strip DTD.
+  $header = preg_replace('/^<!DOCTYPE[^>]*+>/i', '', substr($xml, 0, 200), 1);
+  $xml = trim(substr_replace($xml, $header, 0, 200));
+  if ($xml == '') {
+    return FALSE;
+  }
+  // Confirm the XML now starts with a valid root tag. A root tag can end in [> \t\r\n]
+  $root_tag = substr($xml, 0, strcspn(substr($xml, 0, 20), "> \t\r\n"));
+  // Reject a second DTD.
+  if (strtoupper($root_tag) == '<!DOCTYPE') {
+    return FALSE;
+  }
+  if (!in_array($root_tag, array('<methodCall', '<methodResponse', '<fault'))) {
+    return FALSE;
+  }
+  // Skip parsing if there is an unreasonably large number of tags.
+  try {
+    $dom = new DOMDocument();
+    @$dom->loadXML($xml);
+    if ($dom->getElementsByTagName('*')->length > variable_get('xmlrpc_message_maximum_tag_count', 30000)) {
+      return FALSE;
+    }
+  }
+  catch (Exception $e) {
+    return FALSE;
+  }
+
+  if (!xml_parse($xmlrpc_message->_parser, $xml)) {
     return FALSE;
   }
   xml_parser_free($xmlrpc_message->_parser);

+ 3 - 1
includes/xmlrpcs.inc

@@ -9,7 +9,9 @@
  * Invokes XML-RPC methods on this server.
  *
  * @param array $callbacks
- *   Array of external XML-RPC method names with the callbacks they map to.
+ *   Either an associative array of external XML-RPC method names as keys with
+ *   the callbacks they map to as values, or a more complex structure
+ *   describing XML-RPC callbacks as returned from hook_xmlrpc().
  */
 function xmlrpc_server($callbacks) {
   $xmlrpc_server = new stdClass();

+ 21 - 1
misc/ajax.js

@@ -348,7 +348,7 @@ Drupal.ajax.prototype.beforeSend = function (xmlhttprequest, options) {
     // this is only needed for IFRAME submissions.
     var v = $.fieldValue(this.element);
     if (v !== null) {
-      options.extraData[this.element.name] = v;
+      options.extraData[this.element.name] = Drupal.checkPlain(v);
     }
   }
 
@@ -618,6 +618,26 @@ Drupal.ajax.prototype.commands = {
       .filter(':odd').addClass('even');
   },
 
+  /**
+   * Command to add css.
+   *
+   * Uses the proprietary addImport method if available as browsers which
+   * support that method ignore @import statements in dynamically added
+   * stylesheets.
+   */
+  add_css: function (ajax, response, status) {
+    // Add the styles in the normal way.
+    $('head').prepend(response.data);
+    // Add imports in the styles using the addImport method if available.
+    var match, importMatch = /^@import url\("(.*)"\);$/igm;
+    if (document.styleSheets[0].addImport && importMatch.test(response.data)) {
+      importMatch.lastIndex = 0;
+      while (match = importMatch.exec(response.data)) {
+        document.styleSheets[0].addImport(match[1]);
+      }
+    }
+  },
+
   /**
    * Command to update a form's build ID.
    */

BIN
misc/favicon.ico


+ 1 - 1
misc/tabledrag.js

@@ -500,7 +500,7 @@ Drupal.tableDrag.prototype.dragRow = function (event, self) {
     if (self.indentEnabled) {
       var xDiff = self.currentMouseCoords.x - self.dragObject.indentMousePos.x;
       // Set the number of indentations the mouse has been moved left or right.
-      var indentDiff = Math.round(xDiff / self.indentAmount * self.rtl);
+      var indentDiff = Math.round(xDiff / self.indentAmount);
       // Indent the row with our estimated diff, which may be further
       // restricted according to the rows around this row.
       var indentChange = self.rowObject.indent(indentDiff);

+ 5 - 1
misc/tableselect.js

@@ -57,10 +57,14 @@ Drupal.tableSelect = function () {
     // Keep track of the last checked checkbox.
     lastChecked = e.target;
   });
+
+  // If all checkboxes are checked on page load, make sure the select-all one
+  // is checked too, otherwise keep unchecked.
+  updateSelectAll((checkboxes.length == $(checkboxes).filter(':checked').length));
 };
 
 Drupal.tableSelectRange = function (from, to, state) {
-  // We determine the looping mode based on the the order of from and to.
+  // We determine the looping mode based on the order of from and to.
   var mode = from.rowIndex > to.rowIndex ? 'previousSibling' : 'nextSibling';
 
   // Traverse through the sibling nodes.

BIN
misc/throbber-active.gif


BIN
misc/throbber-inactive.png


+ 6 - 0
misc/vertical-tabs.js

@@ -134,6 +134,8 @@ Drupal.verticalTab.prototype = {
   tabShow: function () {
     // Display the tab.
     this.item.show();
+    // Show the vertical tabs.
+    this.item.closest('.vertical-tabs').show();
     // Update .first marker for items. We need recurse from parent to retain the
     // actual DOM element order as jQuery implements sortOrder, but not as public
     // method.
@@ -164,6 +166,10 @@ Drupal.verticalTab.prototype = {
     if ($firstTab.length) {
       $firstTab.data('verticalTab').focus();
     }
+    // Hide the vertical tabs (if no tabs remain).
+    else {
+      this.item.closest('.vertical-tabs').hide();
+    }
     return this;
   }
 };

+ 1 - 1
modules/aggregator/aggregator.fetcher.inc

@@ -27,7 +27,7 @@ function aggregator_aggregator_fetch($feed) {
     $headers['If-None-Match'] = $feed->etag;
   }
   if ($feed->modified) {
-    $headers['If-Modified-Since'] = gmdate(DATE_RFC1123, $feed->modified);
+    $headers['If-Modified-Since'] = gmdate(DATE_RFC7231, $feed->modified);
   }
 
   // Request feed.

+ 3 - 3
modules/aggregator/aggregator.info

@@ -7,8 +7,8 @@ files[] = aggregator.test
 configure = admin/config/services/aggregator/settings
 stylesheets[all][] = aggregator.css
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 3 - 3
modules/aggregator/tests/aggregator_test.info

@@ -5,8 +5,8 @@ version = VERSION
 core = 7.x
 hidden = TRUE
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 1 - 1
modules/aggregator/tests/aggregator_test.module

@@ -32,7 +32,7 @@ function aggregator_test_feed($use_last_modified = FALSE, $use_etag = FALSE) {
   // Send appropriate response. We respond with a 304 not modified on either
   // etag or on last modified.
   if ($use_last_modified) {
-    drupal_add_http_header('Last-Modified', gmdate(DATE_RFC1123, $last_modified));
+    drupal_add_http_header('Last-Modified', gmdate(DATE_RFC7231, $last_modified));
   }
   if ($use_etag) {
     drupal_add_http_header('ETag', $etag);

+ 1 - 1
modules/block/block.admin.inc

@@ -272,7 +272,7 @@ function block_admin_configure($form, &$form_state, $module, $delta) {
   $form['settings']['title'] = array(
     '#type' => 'textfield',
     '#title' => t('Block title'),
-    '#maxlength' => 64,
+    '#maxlength' => 255,
     '#description' => $block->module == 'block' ? t('The title of the block as shown to the user.') : t('Override the default title for the block. Use <em>!placeholder</em> to display no title, or leave blank to use the default block title.', array('!placeholder' => '&lt;none&gt;')),
     '#default_value' => isset($block->title) ? $block->title : '',
     '#weight' => -19,

+ 3 - 3
modules/block/block.info

@@ -6,8 +6,8 @@ core = 7.x
 files[] = block.test
 configure = admin/structure/block
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 17 - 1
modules/block/block.install

@@ -79,7 +79,7 @@ function block_schema() {
       ),
       'title' => array(
         'type' => 'varchar',
-        'length' => 64,
+        'length' => 255,
         'not null' => TRUE,
         'default' => '',
         'description' => 'Custom title for the block. (Empty string will use block default title, <none> will remove the title, text will cause block to use specified title.)',
@@ -472,6 +472,22 @@ function block_update_7008() {
   db_drop_field('block', 'throttle');
 }
 
+/**
+ * Increase {block}.title length to 255 characters.
+ */
+function block_update_7009() {
+  db_change_field('block', 'title', 'title',
+    array(
+      'type' => 'varchar',
+      'length' => 255,
+      'not null' => TRUE,
+      'default' => '',
+      'description' => 'Custom title for the block. (Empty string will use block default title, <none> will remove the title, text will cause block to use specified title.)',
+      'translatable' => TRUE,
+    )
+  );
+}
+
 /**
  * @} End of "addtogroup updates-7.x-extra".
  */

File diff suppressed because it is too large
+ 0 - 0
modules/block/block.module


+ 2 - 2
modules/block/block.test

@@ -75,7 +75,7 @@ class BlockTestCase extends DrupalWebTestCase {
     $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField();
 
     // Check to see if the custom block was created by checking that it's in the database.
-    $this->assertNotNull($bid, 'Custom block found in database');
+    $this->assertTrue($bid, 'Custom block found in database');
 
     // Check that block_block_view() returns the correct title and content.
     $data = block_block_view($bid);
@@ -305,7 +305,7 @@ class BlockTestCase extends DrupalWebTestCase {
     ))->fetchField();
 
     // Check to see if the block was created by checking that it's in the database.
-    $this->assertNotNull($bid, 'Block found in database');
+    $this->assertTrue($bid, 'Block found in database');
 
     // Check whether the block can be moved to all available regions.
     foreach ($this->regions as $region) {

+ 3 - 3
modules/block/tests/block_test.info

@@ -5,8 +5,8 @@ version = VERSION
 core = 7.x
 hidden = TRUE
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 3 - 3
modules/block/tests/themes/block_test_theme/block_test_theme.info

@@ -13,8 +13,8 @@ regions[footer] = Footer
 regions[highlighted] = Highlighted
 regions[help] = Help
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 3 - 3
modules/blog/blog.info

@@ -5,8 +5,8 @@ version = VERSION
 core = 7.x
 files[] = blog.test
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 3 - 3
modules/book/book.info

@@ -7,8 +7,8 @@ files[] = book.test
 configure = admin/content/book/settings
 stylesheets[all][] = book.css
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 3 - 3
modules/color/color.info

@@ -5,8 +5,8 @@ version = VERSION
 core = 7.x
 files[] = color.test
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 3 - 3
modules/comment/comment.info

@@ -9,8 +9,8 @@ files[] = comment.test
 configure = admin/content/comment
 stylesheets[all][] = comment.css
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 21 - 14
modules/comment/comment.module

@@ -993,12 +993,7 @@ function comment_build_content($comment, $node, $view_mode = 'full', $langcode =
   $comment->content = array();
 
   // Allow modules to change the view mode.
-  $context = array(
-    'entity_type' => 'comment',
-    'entity' => $comment,
-    'langcode' => $langcode,
-  );
-  drupal_alter('entity_view_mode', $view_mode, $context);
+  $view_mode = key(entity_view_mode_prepare('comment', array($comment->cid => $comment), $view_mode, $langcode));
 
   // Build fields content.
   field_attach_prepare_view('comment', array($comment->cid => $comment), $view_mode, $langcode);
@@ -1108,17 +1103,26 @@ function comment_links($comment, $node) {
  *   An array in the format expected by drupal_render().
  */
 function comment_view_multiple($comments, $node, $view_mode = 'full', $weight = 0, $langcode = NULL) {
-  field_attach_prepare_view('comment', $comments, $view_mode, $langcode);
-  entity_prepare_view('comment', $comments, $langcode);
+  $build = array();
+  $entities_by_view_mode = entity_view_mode_prepare('comment', $comments, $view_mode, $langcode);
+  foreach ($entities_by_view_mode as $entity_view_mode => $entities) {
+    field_attach_prepare_view('comment', $entities, $entity_view_mode, $langcode);
+    entity_prepare_view('comment', $entities, $langcode);
+
+    foreach ($entities as $entity) {
+      $build[$entity->cid] = comment_view($entity, $node, $entity_view_mode, $langcode);
+    }
+  }
 
-  $build = array(
-    '#sorted' => TRUE,
-  );
   foreach ($comments as $comment) {
-    $build[$comment->cid] = comment_view($comment, $node, $view_mode, $langcode);
     $build[$comment->cid]['#weight'] = $weight;
     $weight++;
   }
+  // Sort here, to preserve the input order of the entities that were passed to
+  // this function.
+  uasort($build, 'element_sort');
+  $build['#sorted'] = TRUE;
+
   return $build;
 }
 
@@ -2603,7 +2607,7 @@ function comment_unpublish_action($comment, $context = array()) {
 /**
  * Unpublishes a comment if it contains certain keywords.
  *
- * @param $comment
+ * @param object $comment
  *   Comment object to modify.
  * @param array $context
  *   Array with components:
@@ -2615,10 +2619,13 @@ function comment_unpublish_action($comment, $context = array()) {
  * @see comment_unpublish_by_keyword_action_submit()
  */
 function comment_unpublish_by_keyword_action($comment, $context) {
+  $node = node_load($comment->nid);
+  $build = comment_view($comment, $node);
+  $text = drupal_render($build);
   foreach ($context['keywords'] as $keyword) {
-    $text = drupal_render($comment);
     if (strpos($text, $keyword) !== FALSE) {
       $comment->status = COMMENT_NOT_PUBLISHED;
+      comment_save($comment);
       watchdog('action', 'Unpublished comment %subject.', array('%subject' => $comment->subject));
       break;
     }

+ 36 - 1
modules/comment/comment.test

@@ -13,7 +13,7 @@ class CommentHelperCase extends DrupalWebTestCase {
   function setUp() {
     parent::setUp('comment', 'search');
     // Create users and test node.
-    $this->admin_user = $this->drupalCreateUser(array('administer content types', 'administer comments', 'administer blocks'));
+    $this->admin_user = $this->drupalCreateUser(array('administer content types', 'administer comments', 'administer blocks', 'administer actions'));
     $this->web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'create article content', 'edit own comments'));
     $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->web_user->uid));
   }
@@ -1973,6 +1973,41 @@ class CommentActionsTestCase extends CommentHelperCase {
     $this->clearWatchdog();
   }
 
+  /**
+   * Tests the unpublish comment by keyword action.
+   */
+  public function testCommentUnpublishByKeyword() {
+    $this->drupalLogin($this->admin_user);
+    $callback = 'comment_unpublish_by_keyword_action';
+    $hash = drupal_hash_base64($callback);
+    $comment_text = $keywords = $this->randomName();
+    $edit = array(
+      'actions_label' => $callback,
+      'keywords' => $keywords,
+    );
+
+    $this->drupalPost("admin/config/system/actions/configure/$hash", $edit, t('Save'));
+
+    $action = db_query("SELECT aid, type, callback, parameters, label FROM {actions} WHERE callback = :callback", array(':callback' => $callback))->fetchObject();
+
+    $this->assertTrue($action, 'The action could be loaded.');
+
+    $comment = $this->postComment($this->node, $comment_text, $this->randomName());
+
+    // Load the full comment so that status is available.
+    $comment = comment_load($comment->id);
+
+    $this->assertTrue($comment->status == COMMENT_PUBLISHED, 'The comment status was set to published.');
+
+    comment_unpublish_by_keyword_action($comment, array('keywords' => array($keywords)));
+
+    // We need to make sure that the comment has been saved with status
+    // unpublished.
+    $this->assertEqual(comment_load($comment->cid)->status, COMMENT_NOT_PUBLISHED, 'Comment was unpublished.');
+    $this->assertWatchdogMessage('Unpublished comment %subject.', array('%subject' => $comment->subject), 'Found watchdog message.');
+    $this->clearWatchdog();
+  }
+
   /**
    * Verify that a watchdog message has been entered.
    *

+ 3 - 3
modules/contact/contact.info

@@ -6,8 +6,8 @@ core = 7.x
 files[] = contact.test
 configure = admin/structure/contact
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 2 - 2
modules/contact/contact.pages.inc

@@ -134,7 +134,7 @@ function contact_site_form_submit($form, &$form_state) {
   global $user, $language;
 
   $values = $form_state['values'];
-  $values['sender'] = $user;
+  $values['sender'] = clone $user;
   $values['sender']->name = $values['name'];
   $values['sender']->mail = $values['mail'];
   $values['category'] = contact_load($values['cid']);
@@ -270,7 +270,7 @@ function contact_personal_form_submit($form, &$form_state) {
   global $user, $language;
 
   $values = $form_state['values'];
-  $values['sender'] = $user;
+  $values['sender'] = clone $user;
   $values['sender']->name = $values['name'];
   $values['sender']->mail = $values['mail'];
 

+ 3 - 3
modules/contextual/contextual.info

@@ -5,8 +5,8 @@ version = VERSION
 core = 7.x
 files[] = contextual.test
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 3 - 3
modules/dashboard/dashboard.info

@@ -7,8 +7,8 @@ files[] = dashboard.test
 dependencies[] = block
 configure = admin/dashboard/customize
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 4 - 4
modules/dashboard/dashboard.js

@@ -32,13 +32,13 @@ Drupal.behaviors.dashboard = {
           empty_text = Drupal.settings.dashboard.emptyRegionTextInactive;
         }
         // We need a placeholder.
-        if ($('.placeholder', this).length == 0) {
-          $(this).append('<div class="placeholder"></div>');
+        if ($('.dashboard-placeholder', this).length == 0) {
+          $(this).append('<div class="dashboard-placeholder"></div>');
         }
-        $('.placeholder', this).html(empty_text);
+        $('.dashboard-placeholder', this).html(empty_text);
       }
       else {
-        $('.placeholder', this).remove();
+        $('.dashboard-placeholder', this).remove();
       }
     });
   },

+ 3 - 3
modules/dblog/dblog.info

@@ -5,8 +5,8 @@ version = VERSION
 core = 7.x
 files[] = dblog.test
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 20 - 3
modules/field/field.api.php

@@ -1328,10 +1328,27 @@ function hook_field_attach_load($entity_type, $entities, $age, $options) {
  * @param $entity
  *   The entity with fields to validate.
  * @param array $errors
- *   An associative array of errors keyed by field_name, language, delta.
+ *   The array of errors (keyed by field name, language code, and delta) that
+ *   have already been reported for the entity. The function should add its
+ *   errors to this array. Each error is an associative array with the following
+ *   keys and values:
+ *   - error: An error code (should be a string prefixed with the module name).
+ *   - message: The human readable message to be displayed.
  */
 function hook_field_attach_validate($entity_type, $entity, &$errors) {
-  // @todo Needs function body.
+  // Make sure any images in article nodes have an alt text.
+  if ($entity_type == 'node' && $entity->type == 'article' && !empty($entity->field_image)) {
+    foreach ($entity->field_image as $langcode => $items) {
+      foreach ($items as $delta => $item) {
+        if (!empty($item['fid']) && empty($item['alt'])) {
+          $errors['field_image'][$langcode][$delta][] = array(
+            'error' => 'field_example_invalid',
+            'message' => t('All images in articles need to have an alternative text set.'),
+          );
+        }
+      }
+    }
+  }
 }
 
 /**
@@ -1880,7 +1897,7 @@ function hook_field_storage_write($entity_type, $entity, $op, $fields) {
       $items = (array) $entity->{$field_name}[$langcode];
       $delta_count = 0;
       foreach ($items as $delta => $item) {
-        // We now know we have someting to insert.
+        // We now know we have something to insert.
         $do_insert = TRUE;
         $record = array(
           'entity_type' => $entity_type,

+ 1 - 1
modules/field/field.attach.inc

@@ -318,7 +318,7 @@ function _field_invoke_multiple($op, $entity_type, $entities, &$a = NULL, &$b =
         // Unless a language suggestion is provided we iterate on all the
         // available languages.
         $available_languages = field_available_languages($entity_type, $field);
-        $language = !empty($options['language'][$id]) ? $options['language'][$id] : $options['language'];
+        $language = is_array($options['language']) && !empty($options['language'][$id]) ? $options['language'][$id] : $options['language'];
         $languages = _field_language_suggestion($available_languages, $language, $field_name);
         foreach ($languages as $langcode) {
           $grouped_items[$field_id][$langcode][$id] = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array();

+ 4 - 4
modules/field/field.crud.inc

@@ -60,11 +60,11 @@ function field_create_field($field) {
   }
   // Field type is required.
   if (empty($field['type'])) {
-    throw new FieldException('Attempt to create a field with no type.');
+    throw new FieldException(format_string('Attempt to create field @field_name with no type.', array('@field_name' => $field['field_name'])));
   }
   // Field name cannot contain invalid characters.
   if (!preg_match('/^[_a-z]+[_a-z0-9]*$/', $field['field_name'])) {
-    throw new FieldException('Attempt to create a field with invalid characters. Only lowercase alphanumeric characters and underscores are allowed, and only lowercase letters and underscore are allowed as the first character');
+    throw new FieldException(format_string('Attempt to create a field @field_name with invalid characters. Only lowercase alphanumeric characters and underscores are allowed, and only lowercase letters and underscore are allowed as the first character', array('@field_name' => $field['field_name'])));
   }
 
   // Field name cannot be longer than 32 characters. We use drupal_strlen()
@@ -540,9 +540,9 @@ function field_create_instance($instance) {
  *   // Fetch an instance info array.
  *   $instance_info = field_info_instance($entity_type, $field_name, $bundle_name);
  *   // Change a single property in the instance definition.
- *   $instance_info['definition']['required'] = TRUE;
+ *   $instance_info['required'] = TRUE;
  *   // Write the changed definition back.
- *   field_update_instance($instance_info['definition']);
+ *   field_update_instance($instance_info);
  *   @endcode
  *
  * @throws FieldException

+ 3 - 3
modules/field/field.info

@@ -11,8 +11,8 @@ dependencies[] = field_sql_storage
 required = TRUE
 stylesheets[all][] = theme/field.css
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 21 - 5
modules/field/field.info.class.inc

@@ -146,7 +146,10 @@ class FieldInfo {
 
     // Save in "static" and persistent caches.
     $this->fieldMap = $map;
-    cache_set('field_info:field_map', $map, 'cache_field');
+    if (lock_acquire('field_info:field_map')) {
+      cache_set('field_info:field_map', $map, 'cache_field');
+      lock_release('field_info:field_map');
+    }
 
     return $map;
   }
@@ -174,7 +177,10 @@ class FieldInfo {
       }
 
       // Store in persistent cache.
-      cache_set('field_info:fields', $this->fieldsById, 'cache_field');
+      if (lock_acquire('field_info:fields')) {
+        cache_set('field_info:fields', $this->fieldsById, 'cache_field');
+        lock_release('field_info:fields');
+      }
     }
 
     // Fill the name/ID map.
@@ -231,7 +237,10 @@ class FieldInfo {
         }
 
         // Store in persistent cache.
-        cache_set('field_info:instances', $this->bundleInstances, 'cache_field');
+        if (lock_acquire('field_info:instances')) {
+          cache_set('field_info:instances', $this->bundleInstances, 'cache_field');
+          lock_release('field_info:instances');
+        }
       }
 
       $this->loadedAllInstances = TRUE;
@@ -419,7 +428,11 @@ class FieldInfo {
     foreach ($instances as $instance) {
       $cache['fields'][] = $this->fieldsById[$instance['field_id']];
     }
-    cache_set("field_info:bundle:$entity_type:$bundle", $cache, 'cache_field');
+
+    if (lock_acquire("field_info:bundle:$entity_type:$bundle")) {
+      cache_set("field_info:bundle:$entity_type:$bundle", $cache, 'cache_field');
+      lock_release("field_info:bundle:$entity_type:$bundle");
+    }
 
     return $instances;
   }
@@ -460,7 +473,10 @@ class FieldInfo {
 
     // Store in the 'static' and persistent caches.
     $this->bundleExtraFields[$entity_type][$bundle] = $info;
-    cache_set("field_info:bundle_extra:$entity_type:$bundle", $info, 'cache_field');
+    if (lock_acquire("field_info:bundle_extra:$entity_type:$bundle")) {
+      cache_set("field_info:bundle_extra:$entity_type:$bundle", $info, 'cache_field');
+      lock_release("field_info:bundle_extra:$entity_type:$bundle");
+    }
 
     return $this->bundleExtraFields[$entity_type][$bundle];
   }

+ 5 - 1
modules/field/field.info.inc

@@ -223,7 +223,11 @@ function _field_info_collate_types($reset = FALSE) {
       }
       drupal_alter('field_storage_info', $info['storage types']);
 
-      cache_set("field_info_types:$langcode", $info, 'cache_field');
+      // Set the cache if we can acquire a lock.
+      if (lock_acquire("field_info_types:$langcode")) {
+        cache_set("field_info_types:$langcode", $info, 'cache_field');
+        lock_release("field_info_types:$langcode");
+      }
     }
   }
 

+ 1 - 0
modules/field/field.install

@@ -162,6 +162,7 @@ function field_schema() {
     ),
   );
   $schema['cache_field'] = drupal_get_schema_unprocessed('system', 'cache');
+  $schema['cache_field']['description'] = 'Cache table for the Field module to store already built field information.';
 
   return $schema;
 }

+ 14 - 14
modules/field/field.module

@@ -342,17 +342,6 @@ function field_cron() {
   field_purge_batch($limit);
 }
 
-/**
- * Implements hook_modules_uninstalled().
- */
-function field_modules_uninstalled($modules) {
-  module_load_include('inc', 'field', 'field.crud');
-  foreach ($modules as $module) {
-    // TODO D7: field_module_delete is not yet implemented
-    // field_module_delete($module);
-  }
-}
-
 /**
  * Implements hook_system_info_alter().
  *
@@ -905,6 +894,7 @@ function field_view_field($entity_type, $entity, $field_name, $display = array()
       'entity' => $entity,
       'view_mode' => '_custom',
       'display' => $display,
+      'language' => $langcode,
     );
     drupal_alter('field_attach_view', $result, $context);
 
@@ -947,20 +937,30 @@ function field_get_items($entity_type, $entity, $field_name, $langcode = NULL) {
  */
 function field_has_data($field) {
   $query = new EntityFieldQuery();
-  return (bool) $query
-    ->fieldCondition($field)
+  $query = $query->fieldCondition($field)
     ->range(0, 1)
     ->count()
     // Neutralize the 'entity_field_access' query tag added by
     // field_sql_storage_field_storage_query(). The result cannot depend on the
     // access grants of the current user.
-    ->addTag('DANGEROUS_ACCESS_CHECK_OPT_OUT')
+    ->addTag('DANGEROUS_ACCESS_CHECK_OPT_OUT');
+
+  return (bool) $query
+    ->execute() || (bool) $query
+    ->age(FIELD_LOAD_REVISION)
     ->execute();
 }
 
 /**
  * Determine whether the user has access to a given field.
  *
+ * This function does not determine whether access is granted to the entity
+ * itself, only the specific field. Callers are responsible for ensuring that
+ * entity access is also respected. For example, when checking field access for
+ * nodes, check node_access() before checking field_access(), and when checking
+ * field access for entities using the Entity API contributed module,
+ * check entity_access() before checking field_access().
+ *
  * @param $op
  *   The operation to be performed. Possible values:
  *   - 'edit'

+ 3 - 3
modules/field/modules/field_sql_storage/field_sql_storage.info

@@ -7,8 +7,8 @@ dependencies[] = field
 files[] = field_sql_storage.test
 required = TRUE
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 51 - 4
modules/field/modules/field_sql_storage/field_sql_storage.module

@@ -64,6 +64,49 @@ function _field_sql_storage_revision_tablename($field) {
   }
 }
 
+/**
+ * Generates a table alias for a field data table.
+ *
+ * The table alias is unique for each unique combination of field name
+ * (represented by $tablename), delta_group and language_group.
+ *
+ * @param $tablename
+ *   The name of the data table for this field.
+ * @param $field_key
+ *   The numeric key of this field in this query.
+ * @param $query
+ *   The EntityFieldQuery that is executed.
+ *
+ * @return
+ *   A string containing the generated table alias.
+ */
+function _field_sql_storage_tablealias($tablename, $field_key, EntityFieldQuery $query) {
+  // No conditions present: use a unique alias.
+  if (empty($query->fieldConditions[$field_key])) {
+    return $tablename . $field_key;
+  }
+
+  // Find the delta and language condition values and append them to the alias.
+  $condition = $query->fieldConditions[$field_key];
+  $alias = $tablename;
+  $has_group_conditions = FALSE;
+
+  foreach (array('delta', 'language') as $column) {
+    if (isset($condition[$column . '_group'])) {
+      $alias .= '_' . $column . '_' . $condition[$column . '_group'];
+      $has_group_conditions = TRUE;
+    }
+  }
+
+  // Return the alias when it has delta/language group conditions.
+  if ($has_group_conditions) {
+    return $alias;
+  }
+
+  // Return a unique alias in other cases.
+  return $tablename . $field_key;
+}
+
 /**
  * Generate a column name for a field data table.
  *
@@ -422,7 +465,7 @@ function field_sql_storage_field_storage_write($entity_type, $entity, $op, $fiel
       $items = (array) $entity->{$field_name}[$langcode];
       $delta_count = 0;
       foreach ($items as $delta => $item) {
-        // We now know we have someting to insert.
+        // We now know we have something to insert.
         $do_insert = TRUE;
         $record = array(
           'entity_type' => $entity_type,
@@ -504,17 +547,21 @@ function field_sql_storage_field_storage_query(EntityFieldQuery $query) {
     $id_key = 'revision_id';
   }
   $table_aliases = array();
+  $query_tables = NULL;
   // Add tables for the fields used.
   foreach ($query->fields as $key => $field) {
     $tablename = $tablename_function($field);
-    // Every field needs a new table.
-    $table_alias = $tablename . $key;
+    $table_alias = _field_sql_storage_tablealias($tablename, $key, $query);
     $table_aliases[$key] = $table_alias;
     if ($key) {
-      $select_query->join($tablename, $table_alias, "$table_alias.entity_type = $field_base_table.entity_type AND $table_alias.$id_key = $field_base_table.$id_key");
+      if (!isset($query_tables[$table_alias])) {
+        $select_query->join($tablename, $table_alias, "$table_alias.entity_type = $field_base_table.entity_type AND $table_alias.$id_key = $field_base_table.$id_key");
+      }
     }
     else {
       $select_query = db_select($tablename, $table_alias);
+      // Store a reference to the list of joined tables.
+      $query_tables =& $select_query->getTables();
       // Allow queries internal to the Field API to opt out of the access
       // check, for situations where the query's results should not depend on
       // the access grants for the current user.

+ 145 - 0
modules/field/modules/field_sql_storage/field_sql_storage.test

@@ -438,4 +438,149 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase {
     $this->assertEqual($foreign_key['table'], $foreign_key_name, 'Foreign key table name preserved in the schema');
     $this->assertEqual($foreign_key['columns'][$foreign_key_column], 'id', 'Foreign key column name preserved in the schema');
   }
+
+  /**
+   * Test handling multiple conditions on one column of a field.
+   *
+   * Tests both the result and the complexity of the query.
+   */
+  function testFieldSqlStorageMultipleConditionsSameColumn() {
+    $entity = field_test_create_stub_entity(NULL, NULL);
+    $entity->{$this->field_name}[LANGUAGE_NONE][0] = array('value' => 1);
+    field_test_entity_save($entity);
+
+    $entity = field_test_create_stub_entity(NULL, NULL);
+    $entity->{$this->field_name}[LANGUAGE_NONE][0] = array('value' => 2);
+    field_test_entity_save($entity);
+
+    $entity = field_test_create_stub_entity(NULL, NULL);
+    $entity->{$this->field_name}[LANGUAGE_NONE][0] = array('value' => 3);
+    field_test_entity_save($entity);
+
+    $query = new EntityFieldQuery();
+    // This tag causes field_test_query_store_global_test_query_alter() to be
+    // invoked so that the query can be tested.
+    $query->addTag('store_global_test_query');
+    $query->entityCondition('entity_type', 'test_entity');
+    $query->entityCondition('bundle', 'test_bundle');
+    $query->fieldCondition($this->field_name, 'value', 1, '<>', 0, LANGUAGE_NONE);
+    $query->fieldCondition($this->field_name, 'value', 2, '<>', 0, LANGUAGE_NONE);
+    $result = field_sql_storage_field_storage_query($query);
+
+    // Test the results.
+    $this->assertEqual(1, count($result), format_string('One result should be returned, got @count', array('@count' => count($result))));
+
+    // Test the complexity of the query.
+    $query = $GLOBALS['test_query'];
+    $this->assertNotNull($query, 'Precondition: the query should be available');
+    $tables = $query->getTables();
+    $this->assertEqual(1, count($tables), 'The query contains just one table.');
+
+    // Clean up.
+    unset($GLOBALS['test_query']);
+  }
+
+  /**
+   * Test handling multiple conditions on multiple columns of one field.
+   *
+   * Tests both the result and the complexity of the query.
+   */
+  function testFieldSqlStorageMultipleConditionsDifferentColumns() {
+    // Create the multi-column shape field
+    $field_name = strtolower($this->randomName());
+    $field = array('field_name' => $field_name, 'type' => 'shape', 'cardinality' => 4);
+    $field = field_create_field($field);
+    $instance = array(
+      'field_name' => $field_name,
+      'entity_type' => 'test_entity',
+      'bundle' => 'test_bundle'
+    );
+    $instance = field_create_instance($instance);
+
+    $entity = field_test_create_stub_entity(NULL, NULL);
+    $entity->{$field_name}[LANGUAGE_NONE][0] = array('shape' => 'A', 'color' => 'X');
+    field_test_entity_save($entity);
+
+    $entity = field_test_create_stub_entity(NULL, NULL);
+    $entity->{$field_name}[LANGUAGE_NONE][0] = array('shape' => 'B', 'color' => 'X');
+    field_test_entity_save($entity);
+
+    $entity = field_test_create_stub_entity(NULL, NULL);
+    $entity->{$field_name}[LANGUAGE_NONE][0] = array('shape' => 'A', 'color' => 'Y');
+    field_test_entity_save($entity);
+
+    $query = new EntityFieldQuery();
+    // This tag causes field_test_query_store_global_test_query_alter() to be
+    // invoked so that the query can be tested.
+    $query->addTag('store_global_test_query');
+    $query->entityCondition('entity_type', 'test_entity');
+    $query->entityCondition('bundle', 'test_bundle');
+    $query->fieldCondition($field_name, 'shape', 'B', '=', 'something', LANGUAGE_NONE);
+    $query->fieldCondition($field_name, 'color', 'X', '=', 'something', LANGUAGE_NONE);
+    $result = field_sql_storage_field_storage_query($query);
+
+    // Test the results.
+    $this->assertEqual(1, count($result), format_string('One result should be returned, got @count', array('@count' => count($result))));
+
+    // Test the complexity of the query.
+    $query = $GLOBALS['test_query'];
+    $this->assertNotNull($query, 'Precondition: the query should be available');
+    $tables = $query->getTables();
+    $this->assertEqual(1, count($tables), 'The query contains just one table.');
+
+    // Clean up.
+    unset($GLOBALS['test_query']);
+  }
+
+  /**
+   * Test handling multiple conditions on multiple columns of one field for multiple languages.
+   *
+   * Tests both the result and the complexity of the query.
+   */
+  function testFieldSqlStorageMultipleConditionsDifferentColumnsMultipleLanguages() {
+    field_test_entity_info_translatable('test_entity', TRUE);
+
+    // Create the multi-column shape field
+    $field_name = strtolower($this->randomName());
+    $field = array('field_name' => $field_name, 'type' => 'shape', 'cardinality' => 4, 'translatable' => TRUE);
+    $field = field_create_field($field);
+    $instance = array(
+      'field_name' => $field_name,
+      'entity_type' => 'test_entity',
+      'bundle' => 'test_bundle',
+      'settings' => array(
+        // Prevent warning from field_test_field_load().
+        'test_hook_field_load' => FALSE,
+      ),
+    );
+    $instance = field_create_instance($instance);
+
+    $entity = field_test_create_stub_entity(NULL, NULL);
+    $entity->{$field_name}[LANGUAGE_NONE][0] = array('shape' => 'A', 'color' => 'X');
+    $entity->{$field_name}['en'][0] = array('shape' => 'B', 'color' => 'Y');
+    field_test_entity_save($entity);
+    $entity = field_test_entity_test_load($entity->ftid);
+
+    $query = new EntityFieldQuery();
+    // This tag causes field_test_query_store_global_test_query_alter() to be
+    // invoked so that the query can be tested.
+    $query->addTag('store_global_test_query');
+    $query->entityCondition('entity_type', 'test_entity');
+    $query->entityCondition('bundle', 'test_bundle');
+    $query->fieldCondition($field_name, 'color', 'X', '=', NULL, LANGUAGE_NONE);
+    $query->fieldCondition($field_name, 'shape', 'B', '=', NULL, 'en');
+    $result = field_sql_storage_field_storage_query($query);
+
+    // Test the results.
+    $this->assertEqual(1, count($result), format_string('One result should be returned, got @count', array('@count' => count($result))));
+
+    // Test the complexity of the query.
+    $query = $GLOBALS['test_query'];
+    $this->assertNotNull($query, 'Precondition: the query should be available');
+    $tables = $query->getTables();
+    $this->assertEqual(2, count($tables), 'The query contains two tables.');
+
+    // Clean up.
+    unset($GLOBALS['test_query']);
+  }
 }

+ 3 - 3
modules/field/modules/list/list.info

@@ -7,8 +7,8 @@ dependencies[] = field
 dependencies[] = options
 files[] = tests/list.test
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 3 - 3
modules/field/modules/list/tests/list_test.info

@@ -5,8 +5,8 @@ package = Testing
 version = VERSION
 hidden = TRUE
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 3 - 3
modules/field/modules/number/number.info

@@ -6,8 +6,8 @@ core = 7.x
 dependencies[] = field
 files[] = number.test
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 3 - 3
modules/field/modules/options/options.info

@@ -6,8 +6,8 @@ core = 7.x
 dependencies[] = field
 files[] = options.test
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 3 - 3
modules/field/modules/text/text.info

@@ -7,8 +7,8 @@ dependencies[] = field
 files[] = text.test
 required = TRUE
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 16 - 12
modules/field/modules/text/text.js

@@ -12,9 +12,9 @@ Drupal.behaviors.textSummary = {
 
       $summaries.once('text-summary-wrapper').each(function(index) {
         var $summary = $(this);
-        var $summaryLabel = $summary.find('label');
+        var $summaryLabel = $summary.find('label').first();
         var $full = $widget.find('.text-full').eq(index).closest('.form-item');
-        var $fullLabel = $full.find('label');
+        var $fullLabel = $full.find('label').first();
 
         // Create a placeholder label when the field cardinality is
         // unlimited or greater than 1.
@@ -23,24 +23,28 @@ Drupal.behaviors.textSummary = {
         }
 
         // Setup the edit/hide summary link.
-        var $link = $('<span class="field-edit-link">(<a class="link-edit-summary" href="#">' + Drupal.t('Hide summary') + '</a>)</span>').toggle(
-          function () {
+        var $link = $('<span class="field-edit-link">(<a class="link-edit-summary" href="#">' + Drupal.t('Hide summary') + '</a>)</span>');
+        var $a = $link.find('a');
+        var toggleClick = true;
+        $link.bind('click', function (e) {
+          if (toggleClick) {
             $summary.hide();
-            $(this).find('a').html(Drupal.t('Edit summary')).end().appendTo($fullLabel);
-            return false;
-          },
-          function () {
+            $a.html(Drupal.t('Edit summary'));
+            $link.appendTo($fullLabel);
+          }
+          else {
             $summary.show();
-            $(this).find('a').html(Drupal.t('Hide summary')).end().appendTo($summaryLabel);
-            return false;
+            $a.html(Drupal.t('Hide summary'));
+            $link.appendTo($summaryLabel);
           }
-        ).appendTo($summaryLabel);
+          toggleClick = !toggleClick;
+          return false;
+        }).appendTo($summaryLabel);
 
         // If no summary is set, hide the summary field.
         if ($(this).find('.text-summary').val() == '') {
           $link.click();
         }
-        return;
       });
     });
   }

+ 1 - 1
modules/field/modules/text/text.module

@@ -245,7 +245,7 @@ function text_field_formatter_settings_summary($field, $instance, $view_mode) {
   $summary = '';
 
   if (strpos($display['type'], '_trimmed') !== FALSE) {
-    $summary = t('Trim length') . ': ' . $settings['trim_length'];
+    $summary = t('Trim length') . ': ' . check_plain($settings['trim_length']);
   }
 
   return $summary;

+ 62 - 1
modules/field/tests/field.test

@@ -484,6 +484,66 @@ class FieldAttachStorageTestCase extends FieldAttachTestCase {
     $this->assertEqual($entity->{$this->field_name}[$langcode], $values, 'Insert: missing field results in default value saved');
   }
 
+  /**
+   * Test field_has_data().
+   */
+  function testFieldHasData() {
+    $entity_type = 'test_entity';
+    $langcode = LANGUAGE_NONE;
+
+    $field_name = 'field_1';
+    $field = array('field_name' => $field_name, 'type' => 'test_field');
+    $field = field_create_field($field);
+
+    $this->assertFalse(field_has_data($field), "No data should be detected.");
+
+    $instance = array(
+      'field_name' => $field_name,
+      'entity_type' => 'test_entity',
+      'bundle' => 'test_bundle'
+    );
+    $instance = field_create_instance($instance);
+    $table = _field_sql_storage_tablename($field);
+    $revision_table = _field_sql_storage_revision_tablename($field);
+
+    $columns = array('entity_type', 'entity_id', 'revision_id', 'delta', 'language', $field_name . '_value');
+
+    $eid = 0;
+
+    // Insert values into the field revision table.
+    $query = db_insert($revision_table)->fields($columns);
+    $query->values(array($entity_type, $eid, 0, 0, $langcode, 1));
+    $query->execute();
+
+    $this->assertTrue(field_has_data($field), "Revision data only should be detected.");
+
+    $field_name = 'field_2';
+    $field = array('field_name' => $field_name, 'type' => 'test_field');
+    $field = field_create_field($field);
+
+    $this->assertFalse(field_has_data($field), "No data should be detected.");
+
+    $instance = array(
+      'field_name' => $field_name,
+      'entity_type' => 'test_entity',
+      'bundle' => 'test_bundle'
+    );
+    $instance = field_create_instance($instance);
+    $table = _field_sql_storage_tablename($field);
+    $revision_table = _field_sql_storage_revision_tablename($field);
+
+    $columns = array('entity_type', 'entity_id', 'revision_id', 'delta', 'language', $field_name . '_value');
+
+    $eid = 1;
+
+    // Insert values into the field table.
+    $query = db_insert($table)->fields($columns);
+    $query->values(array($entity_type, $eid, 0, 0, $langcode, 1));
+    $query->execute();
+
+    $this->assertTrue(field_has_data($field), "Values only in field table should be detected.");
+  }
+
   /**
    * Test field_attach_delete().
    */
@@ -2146,11 +2206,12 @@ class FieldDisplayAPITestCase extends FieldTestCase {
         'alter' => TRUE,
       ),
     );
-    $output = field_view_field('test_entity', $this->entity, $this->field_name, $display);
+    $output = field_view_field('test_entity', $this->entity, $this->field_name, $display, LANGUAGE_NONE);
     $this->drupalSetContent(drupal_render($output));
     $setting = $display['settings']['test_formatter_setting_multiple'];
     $this->assertNoText($this->label, 'Label was not displayed.');
     $this->assertText('field_test_field_attach_view_alter', 'Alter fired, display passed.');
+    $this->assertText('field language is ' . LANGUAGE_NONE, 'Language is placed onto the context.');
     $array = array();
     foreach ($this->values as $delta => $value) {
       $array[] = $delta . ':' . $value['value'];

+ 3 - 3
modules/field/tests/field_test.info

@@ -6,8 +6,8 @@ files[] = field_test.entity.inc
 version = VERSION
 hidden = TRUE
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 15 - 0
modules/field/tests/field_test.module

@@ -220,6 +220,10 @@ function field_test_field_attach_view_alter(&$output, $context) {
   if (!empty($context['display']['settings']['alter'])) {
     $output['test_field'][] = array('#markup' => 'field_test_field_attach_view_alter');
   }
+
+  if (isset($output['test_field'])) {
+    $output['test_field'][] = array('#markup' => 'field language is ' . $context['language']);
+  }
 }
 
 /**
@@ -267,3 +271,14 @@ function field_test_query_efq_table_prefixing_test_alter(&$query) {
   // exception if the EFQ does not properly prefix the base table.
   $query->join('test_entity','te2','%alias.ftid = test_entity.ftid');
 }
+
+/**
+ * Implements hook_query_TAG_alter() for tag 'store_global_test_query'.
+ */
+function field_test_query_store_global_test_query_alter($query) {
+  // Save the query in a global variable so that it can be examined by tests.
+  // This can be used by any test which needs to check a query, but see
+  // FieldSqlStorageTestCase::testFieldSqlStorageMultipleConditionsSameColumn()
+  // for an example.
+  $GLOBALS['test_query'] = $query;
+}

+ 3 - 0
modules/field_ui/field_ui.api.php

@@ -134,6 +134,9 @@ function hook_field_widget_settings_form($field, $instance) {
 /**
  * Specify the form elements for a formatter's settings.
  *
+ * This hook is only invoked if hook_field_formatter_settings_summary()
+ * returns a non-empty value.
+ *
  * @param $field
  *   The field structure being configured.
  * @param $instance

+ 3 - 3
modules/field_ui/field_ui.info

@@ -6,8 +6,8 @@ core = 7.x
 dependencies[] = field
 files[] = field_ui.test
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 2 - 2
modules/field_ui/field_ui.js

@@ -168,7 +168,7 @@ Drupal.fieldUIOverview = {
     var dragObject = this;
     var row = dragObject.rowObject.element;
     var rowHandler = $(row).data('fieldUIRowHandler');
-    if (rowHandler !== undefined) {
+    if (typeof rowHandler !== 'undefined') {
       var regionRow = $(row).prevAll('tr.region-message').get(0);
       var region = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
 
@@ -319,7 +319,7 @@ Drupal.fieldUIDisplayOverview.field.prototype = {
         if (currentValue == 'hidden') {
           // Restore the formatter back to the default formatter. Pseudo-fields do
           // not have default formatters, we just return to 'visible' for those.
-          var value = (this.defaultFormatter != undefined) ? this.defaultFormatter : 'visible';
+          var value = (typeof this.defaultFormatter !== 'undefined') ? this.defaultFormatter : this.$formatSelect.find('option').val();
         }
         break;
 

+ 7 - 0
modules/file/file.field.inc

@@ -92,6 +92,7 @@ function file_field_instance_settings_form($field, $instance) {
     '#description' => t('Separate extensions with a space or comma and do not include the leading dot.'),
     '#element_validate' => array('_file_generic_settings_extensions'),
     '#weight' => 1,
+    '#maxlength' => 256,
     // By making this field required, we prevent a potential security issue
     // that would allow files of any type to be uploaded.
     '#required' => TRUE,
@@ -251,6 +252,12 @@ function file_field_insert($entity_type, $entity, $field, $instance, $langcode,
  * Checks for files that have been removed from the object.
  */
 function file_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
+  // Check whether the field is defined on the object.
+  if (!isset($entity->{$field['field_name']})) {
+    // We cannot check for removed files if the field is not defined.
+    return;
+  }
+
   list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
 
   // On new revisions, all files are considered to be a new usage and no

+ 3 - 3
modules/file/file.info

@@ -6,8 +6,8 @@ core = 7.x
 dependencies[] = field
 files[] = tests/file.test
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 33 - 8
modules/file/file.module

@@ -357,6 +357,10 @@ function file_file_delete($file) {
  * support for a default value.
  */
 function file_managed_file_process($element, &$form_state, $form) {
+  // Append the '-upload' to the #id so the field label's 'for' attribute
+  // corresponds with the file element.
+  $original_id = $element['#id'];
+  $element['#id'] .= '-upload';
   $fid = isset($element['#value']['fid']) ? $element['#value']['fid'] : 0;
 
   // Set some default element properties.
@@ -366,7 +370,7 @@ function file_managed_file_process($element, &$form_state, $form) {
 
   $ajax_settings = array(
     'path' => 'file/ajax/' . implode('/', $element['#array_parents']) . '/' . $form['form_build_id']['#value'],
-    'wrapper' => $element['#id'] . '-ajax-wrapper',
+    'wrapper' => $original_id . '-ajax-wrapper',
     'effect' => 'fade',
     'progress' => array(
       'type' => $element['#progress_indicator'],
@@ -461,13 +465,13 @@ function file_managed_file_process($element, &$form_state, $form) {
     $element['upload']['#attached']['js'] = array(
       array(
         'type' => 'setting',
-        'data' => array('file' => array('elements' => array('#' . $element['#id'] . '-upload' => $extension_list)))
+        'data' => array('file' => array('elements' => array('#' . $element['#id'] => $extension_list)))
       )
     );
   }
 
   // Prefix and suffix used for Ajax replacement.
-  $element['#prefix'] = '<div id="' . $element['#id'] . '-ajax-wrapper">';
+  $element['#prefix'] = '<div id="' . $original_id . '-ajax-wrapper">';
   $element['#suffix'] = '</div>';
 
   return $element;
@@ -478,6 +482,7 @@ function file_managed_file_process($element, &$form_state, $form) {
  */
 function file_managed_file_value(&$element, $input = FALSE, $form_state = NULL) {
   $fid = 0;
+  $force_default = FALSE;
 
   // Find the current value of this field from the form state.
   $form_state_fid = $form_state['values'];
@@ -510,15 +515,35 @@ function file_managed_file_value(&$element, $input = FALSE, $form_state = NULL)
           $callback($element, $input, $form_state);
         }
       }
-      // Load file if the FID has changed to confirm it exists.
-      if (isset($input['fid']) && $file = file_load($input['fid'])) {
-        $fid = $file->fid;
+      // If a FID was submitted, load the file (and check access if it's not a
+      // public file) to confirm it exists and that the current user has access
+      // to it.
+      if (isset($input['fid']) && ($file = file_load($input['fid']))) {
+        // By default the public:// file scheme provided by Drupal core is the
+        // only one that allows files to be publicly accessible to everyone, so
+        // it is the only one for which the file access checks are bypassed.
+        // Other modules which provide publicly accessible streams of their own
+        // in hook_stream_wrappers() can add the corresponding scheme to the
+        // 'file_public_schema' variable to bypass file access checks for those
+        // as well. This should only be done for schemes that are completely
+        // publicly accessible, with no download restrictions; for security
+        // reasons all other schemes must go through the file_download_access()
+        // check.
+        if (in_array(file_uri_scheme($file->uri), variable_get('file_public_schema', array('public'))) || file_download_access($file->uri)) {
+          $fid = $file->fid;
+        }
+        // If the current user doesn't have access, don't let the file be
+        // changed.
+        else {
+          $force_default = TRUE;
+        }
       }
     }
   }
 
-  // If there is no input, set the default value.
-  else {
+  // If there is no input or if the default value was requested above, use the
+  // default value.
+  if ($input === FALSE || $force_default) {
     if ($element['#extended']) {
       $default_fid = isset($element['#default_value']['fid']) ? $element['#default_value']['fid'] : 0;
       $return = isset($element['#default_value']) ? $element['#default_value'] : array('fid' => 0);

+ 156 - 0
modules/file/tests/file.test

@@ -220,6 +220,128 @@ class FileFieldTestCase extends DrupalWebTestCase {
   }
 }
 
+/**
+ * Tests adding a file to a non-node entity.
+ */
+class FileTaxonomyTermTestCase extends DrupalWebTestCase {
+  protected $admin_user;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Taxonomy term file test',
+      'description' => 'Tests adding a file to a non-node entity.',
+      'group' => 'File',
+    );
+  }
+
+  public function setUp() {
+    $modules[] = 'file';
+    $modules[] = 'taxonomy';
+    parent::setUp($modules);
+    $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer taxonomy'));
+    $this->drupalLogin($this->admin_user);
+  }
+
+  /**
+   * Creates a file field and attaches it to the "Tags" taxonomy vocabulary.
+   *
+   * @param $name
+   *   The field name of the file field to create.
+   * @param $uri_scheme
+   *   The URI scheme to use for the file field (for example, "private" to
+   *   create a field that stores private files or "public" to create a field
+   *   that stores public files).
+   */
+  protected function createAttachFileField($name, $uri_scheme) {
+    $field = array(
+      'field_name' => $name,
+      'type' => 'file',
+      'settings' => array(
+        'uri_scheme' => $uri_scheme,
+      ),
+      'cardinality' => 1,
+    );
+    field_create_field($field);
+    // Attach an instance of it.
+    $instance = array(
+      'field_name' => $name,
+      'label' => 'File',
+      'entity_type' => 'taxonomy_term',
+      'bundle' => 'tags',
+      'required' => FALSE,
+      'settings' => array(),
+      'widget' => array(
+        'type' => 'file_generic',
+        'settings' => array(),
+      ),
+    );
+    field_create_instance($instance);
+  }
+
+  /**
+   * Tests that a public file can be attached to a taxonomy term.
+   *
+   * This is a regression test for https://www.drupal.org/node/2305017.
+   */
+  public function testTermFilePublic() {
+    $this->_testTermFile('public');
+  }
+
+  /**
+   * Tests that a private file can be attached to a taxonomy term.
+   *
+   * This is a regression test for https://www.drupal.org/node/2305017.
+   */
+  public function testTermFilePrivate() {
+    $this->_testTermFile('private');
+  }
+
+  /**
+   * Runs tests for attaching a file field to a taxonomy term.
+   *
+   * @param $uri_scheme
+   *   The URI scheme to use for the file field, either "public" or "private".
+   */
+  protected function _testTermFile($uri_scheme) {
+    $field_name = strtolower($this->randomName());
+    $this->createAttachFileField($field_name, $uri_scheme);
+    // Get a file to upload.
+    $file = current($this->drupalGetTestFiles('text'));
+    // Add a filesize property to files as would be read by file_load().
+    $file->filesize = filesize($file->uri);
+    $langcode = LANGUAGE_NONE;
+    $edit = array(
+      "name" => $this->randomName(),
+    );
+    // Attach a file to the term.
+    $edit['files[' . $field_name . '_' . $langcode . '_0]'] = drupal_realpath($file->uri);
+    $this->drupalPost("admin/structure/taxonomy/tags/add", $edit, t('Save'));
+    // Find the term ID we just created.
+    $tid = db_query_range('SELECT tid FROM {taxonomy_term_data} ORDER BY tid DESC', 0, 1)->fetchField();
+    $terms = entity_load('taxonomy_term', array($tid));
+    $term = $terms[$tid];
+    $fid = $term->{$field_name}[LANGUAGE_NONE][0]['fid'];
+    // Check that the uploaded file is present on the edit form.
+    $this->drupalGet("taxonomy/term/$tid/edit");
+    $file_input_name = $field_name . '[' . LANGUAGE_NONE . '][0][fid]';
+    $this->assertFieldByXpath('//input[@type="hidden" and @name="' . $file_input_name . '"]', $fid, 'File is attached on edit form.');
+    // Edit the term and change name without changing the file.
+    $edit = array(
+      "name" => $this->randomName(),
+    );
+    $this->drupalPost("taxonomy/term/$tid/edit", $edit, t('Save'));
+    // Check that the uploaded file is still present on the edit form.
+    $this->drupalGet("taxonomy/term/$tid/edit");
+    $file_input_name = $field_name . '[' . LANGUAGE_NONE . '][0][fid]';
+    $this->assertFieldByXpath('//input[@type="hidden" and @name="' . $file_input_name . '"]', $fid, 'File is attached on edit form.');
+    // Load term while resetting the cache.
+    $terms = entity_load('taxonomy_term', array($tid), array(), TRUE);
+    $term = $terms[$tid];
+    $this->assertTrue(!empty($term->{$field_name}[LANGUAGE_NONE]), 'Term has attached files.');
+    $this->assertEqual($term->{$field_name}[LANGUAGE_NONE][0]['fid'], $fid, 'Same File ID is attached to the term.');
+  }
+}
+
 /**
  * Tests the 'managed_file' element type.
  *
@@ -352,6 +474,15 @@ class FileFieldWidgetTestCase extends FileFieldTestCase {
       $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
       $this->assertFileExists($node_file, 'New file saved to disk on node creation.');
 
+      // Test that running field_attach_update() leaves the file intact.
+      $field = new stdClass();
+      $field->type = $type_name;
+      $field->nid = $nid;
+      field_attach_update('node', $field);
+      $node = node_load($nid);
+      $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
+      $this->assertFileExists($node_file, 'New file still saved to disk on field update.');
+
       // Ensure the file can be downloaded.
       $this->drupalGet(file_create_url($node_file->uri));
       $this->assertResponse(200, 'Confirmed that the generated URL is correct by downloading the shipped file.');
@@ -755,6 +886,7 @@ class FileFieldDisplayTestCase extends FileFieldTestCase {
     $field_settings = array(
       'display_field' => '1',
       'display_default' => '1',
+      'cardinality' => FIELD_CARDINALITY_UNLIMITED,
     );
     $instance_settings = array(
       'description_field' => '1',
@@ -795,6 +927,17 @@ class FileFieldDisplayTestCase extends FileFieldTestCase {
 
     $this->assertNoRaw($default_output, 'Field is hidden when "display" option is unchecked.');
 
+    // Test that fields appear as expected during the preview.
+    // Add a second file.
+    $name = 'files[' . $field_name . '_' . LANGUAGE_NONE . '_1]';
+    $edit[$name] = drupal_realpath($test_file->uri);
+
+    // Uncheck the display checkboxes and go to the preview.
+    $edit[$field_name . '[' . LANGUAGE_NONE . '][0][display]'] = FALSE;
+    $edit[$field_name . '[' . LANGUAGE_NONE . '][1][display]'] = FALSE;
+    $this->drupalPost('node/' . $nid . '/edit', $edit, t('Preview'));
+    $this->assertRaw($field_name . '[' . LANGUAGE_NONE . '][0][display]', 'First file appears as expected.');
+    $this->assertRaw($field_name . '[' . LANGUAGE_NONE . '][1][display]', 'Second file appears as expected.');
   }
 }
 
@@ -1167,5 +1310,18 @@ class FilePrivateTestCase extends FileFieldTestCase {
     // Ensure the file cannot be downloaded.
     $this->drupalGet(file_create_url($node_file->uri));
     $this->assertResponse(403, 'Confirmed that access is denied for the file without view field access permission.');
+
+    // Attempt to reuse the existing file when creating a new node, and confirm
+    // that access is still denied.
+    $edit = array();
+    $edit['title'] = $this->randomName(8);
+    $edit[$field_name . '[' . LANGUAGE_NONE . '][0][fid]'] = $node_file->fid;
+    $this->drupalPost('node/add/page', $edit, t('Save'));
+    $new_node = $this->drupalGetNodeByTitle($edit['title']);
+    $this->assertTrue(!empty($new_node), 'Node was created.');
+    $this->assertUrl('node/' . $new_node->nid);
+    $this->assertNoRaw($node_file->filename, 'File without view field access permission does not appear after attempting to attach it to a new node.');
+    $this->drupalGet(file_create_url($node_file->uri));
+    $this->assertResponse(403, 'Confirmed that access is denied for the file without view field access permission after attempting to attach it to a new node.');
   }
 }

+ 3 - 3
modules/file/tests/file_module_test.info

@@ -5,8 +5,8 @@ version = VERSION
 core = 7.x
 hidden = TRUE
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 3 - 3
modules/filter/filter.info

@@ -7,8 +7,8 @@ files[] = filter.test
 required = TRUE
 configure = admin/config/content/formats
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 1 - 3
modules/filter/filter.module

@@ -348,9 +348,7 @@ function filter_permission() {
   foreach (filter_formats() as $format) {
     $permission = filter_permission_name($format);
     if (!empty($permission)) {
-      // Only link to the text format configuration page if the user who is
-      // viewing this will have access to that page.
-      $format_name_replacement = user_access('administer filters') ? l($format->name, 'admin/config/content/formats/' . $format->format) : drupal_placeholder($format->name);
+      $format_name_replacement = l($format->name, 'admin/config/content/formats/' . $format->format);
       $perms[$permission] = array(
         'title' => t("Use the !text_format text format", array('!text_format' => $format_name_replacement,)),
         'description' => drupal_placeholder(t('Warning: This permission may have security implications depending on how the text format is configured.')),

+ 1 - 1
modules/filter/filter.pages.inc

@@ -68,7 +68,7 @@ function theme_filter_tips($variables) {
     foreach ($tips as $name => $tiplist) {
       if ($multiple) {
         $output .= '<div class="filter-type filter-' . drupal_html_class($name) . '">';
-        $output .= '<h3>' . $name . '</h3>';
+        $output .= '<h3>' . check_plain($name) . '</h3>';
       }
 
       if (count($tiplist) > 0) {

+ 9 - 0
modules/filter/filter.test

@@ -70,6 +70,15 @@ class FilterCRUDTestCase extends DrupalWebTestCase {
     $this->assertFalse($db_format->status, 'Database: Disabled text format is marked as disabled.');
     $formats = filter_formats();
     $this->assertTrue(!isset($formats[$format->format]), 'filter_formats: Disabled text format no longer exists.');
+
+    // Add a new format to check for Xss in format name.
+    $format = new stdClass();
+    $format->format = 'xss_format';
+    $format->name = '<script>alert(123)</script>';
+    filter_format_save($format);
+    user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array(filter_permission_name($format) => 1));
+    $this->drupalGet('filter/tips');
+    $this->assertNoRaw($format->name, 'Text format name contains no xss.');
   }
 
   /**

+ 3 - 3
modules/forum/forum.info

@@ -9,8 +9,8 @@ files[] = forum.test
 configure = admin/structure/forum
 stylesheets[all][] = forum.css
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 2 - 2
modules/forum/forum.module

@@ -263,10 +263,10 @@ function _forum_node_check_node_type($node) {
  * Implements hook_node_view().
  */
 function forum_node_view($node, $view_mode) {
-  $vid = variable_get('forum_nav_vocabulary', 0);
-  $vocabulary = taxonomy_vocabulary_load($vid);
   if (_forum_node_check_node_type($node)) {
     if ($view_mode == 'full' && node_is_page($node)) {
+      $vid = variable_get('forum_nav_vocabulary', 0);
+      $vocabulary = taxonomy_vocabulary_load($vid);
       // Breadcrumb navigation
       $breadcrumb[] = l(t('Home'), NULL);
       $breadcrumb[] = l($vocabulary->name, 'forum');

+ 3 - 3
modules/help/help.info

@@ -5,8 +5,8 @@ version = VERSION
 core = 7.x
 files[] = help.test
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

+ 3 - 3
modules/image/image.info

@@ -7,8 +7,8 @@ dependencies[] = file
 files[] = image.test
 configure = admin/config/media/image-styles
 
-; Information added by Drupal.org packaging script on 2014-05-08
-version = "7.28"
+; Information added by Drupal.org packaging script on 2015-04-02
+version = "7.36"
 project = "drupal"
-datestamp = "1399522731"
+datestamp = "1427943826"
 

Some files were not shown because too many files changed in this diff