diff -Naur drupal-7.18/.editorconfig drupal-7.66/.editorconfig
--- drupal-7.18/.editorconfig 1970-01-01 01:00:00.000000000 +0100
+++ drupal-7.66/.editorconfig 2019-04-17 22:20:46.000000000 +0200
@@ -0,0 +1,14 @@
+# Drupal editor configuration normalization
+# @see http://editorconfig.org/
+
+# This is the top-most .editorconfig file; do not search in parent directories.
+root = true
+
+# All files.
+[*]
+end_of_line = LF
+indent_style = space
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
diff -Naur drupal-7.18/.htaccess drupal-7.66/.htaccess
--- drupal-7.18/.htaccess 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/.htaccess 2019-04-17 22:20:46.000000000 +0200
@@ -3,8 +3,13 @@
#
# Protect files and directories from prying eyes.
-
, and
+ similar tags in a larger font size, so it is easier to read.
+- Fixed a bug in the Search module that caused exceptions to be thrown during
+ searches if the server was not configured to represent decimal points as a
+ period.
+- Fixed a regression in the Image module that made image_style_url() not work
+ when a relative path (rather than a complete file URI) was passed to it.
+- Added an optional feature to the Statistics module to allow node views to be
+ tracked by Ajax requests rather than during the server-side generation of the
+ page. This allows the node counter to work on sites that use external page
+ caches (string change and new administrative option:
+ https://drupal.org/node/2164069).
+- Added a link to the drupal.org documentation page for cron to the Cron
+ settings page (string change).
+- Added a 'drupal_anonymous_user_object' variable to allow the anonymous user
+ object returned by drupal_anonymous_user() to be overridden with a classed
+ object (API addition).
+- Changed the database API to allow inserts based on a SELECT * query to work
+ correctly.
+- Changed the database schema of the {file_managed} table to allow Drupal to
+ manage files larger than 4 GB.
+- Changed the File module's hook_field_load() implementation to prevent file
+ entity properties which have the same name as file or image field properties
+ from overwriting the field properties (minor API change).
+- Numerous small bug fixes.
+- Numerous API documentation improvements.
+- Additional automated test coverage.
+
+Drupal 7.24, 2013-11-20
+-----------------------
+- Fixed security issues (multiple vulnerabilities), see SA-CORE-2013-003.
+
+Drupal 7.23, 2013-08-07
+-----------------------
+- Fixed a fatal error on PostgreSQL databases when updating the Taxonomy module
+ from Drupal 6 to Drupal 7.
+- Fixed the default ordering of CSS files for sites using right-to-left
+ languages, to consistently place the right-to-left override file immediately
+ after the CSS it is overriding (API change: https://drupal.org/node/2058463).
+- Added a drupal_check_memory_limit() API function to allow the memory limit to
+ be checked consistently (API addition).
+- Changed the default web.config file for IIS servers to allow favicon.ico
+ files which are present in the filesystem to be accessed.
+- Fixed inconsistent support for the 'tel' protocol in Drupal's URL filtering
+ functions.
+- Performance improvement: Allowed all hooks to be included in the
+ module_implements() cache, even those that are only invoked on HTTP POST
+ requests.
+- Made the database system replace truncate queries with delete queries when
+ inside a transaction, to fix issues with PostgreSQL and other databases.
+- Fixed a bug which caused nested contextual links to display improperly.
+- Fixed a bug which prevented cached image derivatives from being flushed for
+ private files and other non-default file schemes.
+- Fixed drupal_render() to always return an empty string when there is no
+ output, rather than sometimes returning NULL (minor API change).
+- Added protection to cache_clear_all() to ensure that non-cache tables cannot
+ be truncated (API addition: a new isValidBin() method has been added to the
+ default database cache implementation).
+- Changed the default .htaccess file to support HTTP authorization in CGI
+ environments.
+- Changed the password reset form to pre-fill the username when requested via a
+ URL query parameter, and used this in the error message that appears after a
+ failed login attempt (minor data structure and behavior change).
+- Fixed broken support for foreign keys in the field API.
+- Fixed "No active batch" error when a user cancels their own account.
+- Added a description to the "access content overview" permission on the
+ permissions page (string change).
+- Added a drupal_array_diff_assoc_recursive() function to allow associative
+ arrays to be compared recursively (API addition).
+- Added human-readable labels to image styles, in addition to the existing
+ machine-readable name (API change: https://drupal.org/node/2058503).
+- Moved the drupal_get_hash_salt() function to bootstrap.inc and used it in
+ additional places in the code, for added security in the case where there is
+ no hash salt in settings.php.
+- Fixed a regression in Drupal 7.22 that caused internal server errors for
+ sites running on very old Apache 1.x web servers.
+- Numerous small bug fixes.
+- Numerous API documentation improvements.
+- Additional automated test coverage.
+
+Drupal 7.22, 2013-04-03
+-----------------------
+- Allowed the drupal_http_request() function to be overridden so that
+ additional HTTP request capabilities can be added by contributed modules.
+- Changed the Simpletest module to allow PSR-0 test classes to be used in
+ Drupal 7.
+- Removed an unnecessary "Content-Disposition" header from private file
+ downloads; it prevented many private files from being viewed inline in a web
+ browser.
+- Changed various field API functions to allow them to optionally act on a
+ single field within an entity (API addition: http://drupal.org/node/1825844).
+- Fixed a bug which prevented Drupal's file transfer functionality from working
+ on some PHP 5.4 systems.
+- Fixed incorrect log message when theme() is called for a theme hook that does
+ not exist (minor string change).
+- Fixed Drupal's token-replacement system to allow spaces in the token value.
+- Changed the default behavior after a user creates a node they do not have
+ access to view. The user will now be redirected to the front page rather than
+ an access denied page.
+- Fixed a bug which prevented empty HTTP headers (such as "0") from being set.
+ (Minor behavior change: Callers of drupal_add_http_header() must now set
+ FALSE explicitly to prevent a header from being sent at all; this was already
+ indicated in the function's documentation.)
+- Fixed OpenID errors when more than one module implements hook_openid(). The
+ behavior is now changed so that if more than one module tries to set the same
+ parameter, the last module's change takes effect.
+- Fixed a serious documentation bug: The $name variable in the
+ taxonomy-term.tpl.php theme template was incorrectly documented as being
+ sanitized when in fact it is not.
+- Fixed a bug which prevented Drupal 6 to Drupal 7 upgrades on sites which had
+ duplicate permission names in the User module's database tables.
+- Added an empty "datatype" attribute to taxonomy term and username links to
+ make the RDFa markup upward compatible with RDFa 1.1 (minor markup addition).
+- Fixed a bug which caused the denial-of-service protection added in Drupal
+ 7.20 to break certain valid image URLs that had an extra slash in them.
+- Fixed a bug with update queries in the SQLite database driver that prevented
+ Drupal from being installed with SQLite on PHP 5.4.
+- Fixed enforced dependencies errors updating to recent versions of Drupal 7 on
+ certain non-MySQL databases.
+- Refactored the Field module's caching behavior to obtain large improvements
+ in memory usage for sites with many fields and instances (API addition:
+ http://drupal.org/node/1915646).
+- Fixed entity argument not being passed to implementations of
+ hook_file_download_access_alter(). The fix adds an additional context
+ parameter that can be passed when calling drupal_alter() for any hook (API
+ change: http://drupal.org/node/1882722).
+- Fixed broken support for translatable comment fields (API change:
+ http://drupal.org/node/1874724).
+- Added an assertThemeOutput() method to Simpletest to allow tests to check
+ that themed output matches an expected HTML string (API addition).
+- Added a link to "Install another module" after a module has been successfully
+ downloaded via the Update Manager (UI change).
+- Added an optional "exclusive" flag to installation profile .info files which
+ allows Drupal distributions to force a profile to be selected during
+ installation (API addition: http://drupal.org/node/1961012).
+- Fixed a bug which caused the database API to not properly close database
+ connections.
+- Added a link to the URL for running cron from outside the site to the Cron
+ settings page (UI change).
+- Fixed a bug which prevented image styles from being reverted on PHP 5.4.
+- Made the default .htaccess rules protocol sensitive to improve security for
+ sites which use HTTPS and redirect between "www" and non-"www" versions of
+ the page.
+- Numerous small bug fixes.
+- Numerous API documentation improvements.
+- Additional automated test coverage.
+
+Drupal 7.21, 2013-03-06
+-----------------------
+- Allowed sites using the 'image_allow_insecure_derivatives' variable to still
+ have partial protection from the security issues fixed in Drupal 7.20.
+
+Drupal 7.20, 2013-02-20
+-----------------------
+- Fixed security issues (denial of service). See SA-CORE-2013-002.
+
+Drupal 7.19, 2013-01-16
+-----------------------
+- Fixed security issues (multiple vulnerabilities). See SA-CORE-2013-001.
+
Drupal 7.18, 2012-12-19
-----------------------
- Fixed security issues (multiple vulnerabilities). See SA-CORE-2012-004.
@@ -121,8 +854,8 @@
- Numerous API documentation improvements.
- Additional automated test coverage.
-Drupal 7.14 2012-05-02
-----------------------
+Drupal 7.14, 2012-05-02
+-----------------------
- Fixed "integrity constraint" fatal errors when rebuilding registry.
- Fixed custom logo and favicon functionality referencing incorrect paths.
- Fixed DB Case Sensitivity: Allow BINARY attribute in MySQL.
@@ -170,12 +903,12 @@
- system_update_7061() converts filepaths too aggressively.
- Trigger upgrade path: Node triggers removed when upgrading to 7-x from 6.25.
-Drupal 7.13 2012-05-02
-----------------------
+Drupal 7.13, 2012-05-02
+-----------------------
- Fixed security issues (Multiple vulnerabilities), see SA-CORE-2012-002.
Drupal 7.12, 2012-02-01
-----------------------
+-----------------------
- Fixed bug preventing custom menus from receiving an active trail.
- Fixed hook_field_delete() no longer invoked during field_purge_data().
- Fixed bug causing entity info cache to not be cleared with the rest of caches.
@@ -209,11 +942,11 @@
cache.
Drupal 7.11, 2012-02-01
-----------------------
+-----------------------
- Fixed security issues (Multiple vulnerabilities), see SA-CORE-2012-001.
Drupal 7.10, 2011-12-05
-----------------------
+-----------------------
- Fixed Content-Language HTTP header to not cause issues with Drush 5.x.
- Reduce memory usage of theme registry (performance).
- Fixed PECL upload progress bar for FileField
@@ -566,7 +1299,7 @@
requests.
Drupal 6.23-dev, xxxx-xx-xx (development release)
------------------------
+---------------------------
Drupal 6.22, 2011-05-25
-----------------------
@@ -576,25 +1309,25 @@
- Fixed a variety of other bugs.
Drupal 6.21, 2011-05-25
-----------------------
+-----------------------
- Fixed security issues (Cross site scripting), see SA-CORE-2011-001.
Drupal 6.20, 2010-12-15
-----------------------
+-----------------------
- Fixed a variety of small bugs, improved code documentation.
Drupal 6.19, 2010-08-11
-----------------------
+-----------------------
- Fixed a variety of small bugs, improved code documentation.
Drupal 6.18, 2010-08-11
-----------------------
+-----------------------
- Fixed security issues (OpenID authentication bypass, File download access
bypass, Comment unpublishing bypass, Actions cross site scripting),
see SA-CORE-2010-002.
Drupal 6.17, 2010-06-02
-----------------------
+-----------------------
- Improved PostgreSQL compatibility
- Better PHP 5.3 and PHP 4 compatibility
- Better browser compatibility of CSS and JS aggregation
@@ -603,7 +1336,7 @@
- Fixed a variety of other bugs.
Drupal 6.16, 2010-03-03
-----------------------
+-----------------------
- Fixed security issues (Installation cross site scripting, Open redirection,
Locale module cross site scripting, Blocked user session regeneration),
see SA-CORE-2010-001.
@@ -615,12 +1348,12 @@
- Fixed a variety of other bugs.
Drupal 6.15, 2009-12-16
-----------------------
+-----------------------
- Fixed security issues (Cross site scripting), see SA-CORE-2009-009.
- Fixed a variety of other bugs.
Drupal 6.14, 2009-09-16
-----------------------
+-----------------------
- Fixed security issues (OpenID association cross site request forgeries,
OpenID impersonation and File upload), see SA-CORE-2009-008.
- Changed the system modules page to not run all cache rebuilds; use the
@@ -629,18 +1362,18 @@
- Fixed a variety of small bugs.
Drupal 6.13, 2009-07-01
-----------------------
+-----------------------
- Fixed security issues (Cross site scripting, Input format access bypass and
Password leakage in URL), see SA-CORE-2009-007.
- Fixed a variety of small bugs.
Drupal 6.12, 2009-05-13
-----------------------
+-----------------------
- Fixed security issues (Cross site scripting), see SA-CORE-2009-006.
- Fixed a variety of small bugs.
Drupal 6.11, 2009-04-29
-----------------------
+-----------------------
- Fixed security issues (Cross site scripting and limited information
disclosure), see SA-CORE-2009-005
- Fixed performance issues with the menu router cache, the update
@@ -648,7 +1381,7 @@
- Fixed a variety of small bugs.
Drupal 6.10, 2009-02-25
-----------------------
+-----------------------
- Fixed a security issue, (Local file inclusion on Windows),
see SA-CORE-2009-003
- Fixed node_feed() so custom fields can show up in RSS feeds.
@@ -1044,7 +1777,7 @@
- fixed a security issue (SQL injection), see SA-2007-031
Drupal 4.7.8, 2007-10-17
-----------------------
+------------------------
- fixed a security issue (HTTP response splitting), see SA-2007-024
- fixed a security issue (Cross site scripting via uploads), see SA-2007-026
- fixed a security issue (API handling of unpublished comment), see SA-2007-030
@@ -1157,7 +1890,7 @@
- Fixed security issue (DoS), see SA-2007-002
Drupal 4.6.10, 2006-10-18
-------------------------
+-------------------------
- Fixed security issue (XSS), see SA-2006-024
- Fixed security issue (CSRF), see SA-2006-025
- Fixed security issue (Form action attribute injection), see SA-2006-026
diff -Naur drupal-7.18/COPYRIGHT.txt drupal-7.66/COPYRIGHT.txt
--- drupal-7.18/COPYRIGHT.txt 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/COPYRIGHT.txt 2019-04-17 22:20:46.000000000 +0200
@@ -1,4 +1,4 @@
-All Drupal code is Copyright 2001 - 2012 by the original authors.
+All Drupal code is Copyright 2001 - 2013 by the original authors.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff -Naur drupal-7.18/INSTALL.mysql.txt drupal-7.66/INSTALL.mysql.txt
--- drupal-7.18/INSTALL.mysql.txt 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/INSTALL.mysql.txt 2019-04-17 22:20:46.000000000 +0200
@@ -20,18 +20,21 @@
Again, you will be asked for the 'username' database password. At the MySQL
prompt, enter the following command:
- GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER
- ON databasename.*
+ GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER,
+ CREATE TEMPORARY TABLES ON databasename.*
TO 'username'@'localhost' IDENTIFIED BY 'password';
-where
+where:
'databasename' is the name of your database
- 'username@localhost' is the username of your MySQL account
+ 'username' is the username of your MySQL account
+ 'localhost' is the web server host where Drupal is installed
'password' is the password required for that username
-Note: Unless your database user has all of the privileges listed above, you will
-not be able to run Drupal.
+Note: Unless the database user/host combination for your Drupal installation
+has all of the privileges listed above (except possibly CREATE TEMPORARY TABLES,
+which is currently only used by Drupal core automated tests and some
+contributed modules), you will not be able to install or run Drupal.
If successful, MySQL will reply with:
diff -Naur drupal-7.18/INSTALL.txt drupal-7.66/INSTALL.txt
--- drupal-7.18/INSTALL.txt 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/INSTALL.txt 2019-04-17 22:20:46.000000000 +0200
@@ -20,8 +20,10 @@
- MySQL 5.0.15 (or greater) (http://www.mysql.com/).
- MariaDB 5.1.44 (or greater) (http://mariadb.org/). MariaDB is a fully
compatible drop-in replacement for MySQL.
+ - Percona Server 5.1.70 (or greater) (http://www.percona.com/). Percona
+ Server is a backwards-compatible replacement for MySQL.
- PostgreSQL 8.3 (or greater) (http://www.postgresql.org/).
- - SQLite 3.4.2 (or greater) (http://www.sqlite.org/).
+ - SQLite 3.3.7 (or greater) (http://www.sqlite.org/).
For more detailed information about Drupal requirements, including a list of
PHP extensions and configurations that are required, see "System requirements"
diff -Naur drupal-7.18/MAINTAINERS.txt drupal-7.66/MAINTAINERS.txt
--- drupal-7.18/MAINTAINERS.txt 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/MAINTAINERS.txt 2019-04-17 22:20:46.000000000 +0200
@@ -1,7 +1,8 @@
Drupal core is built and maintained by the Drupal project community. Everyone is
encouraged to submit issues and changes (patches) to improve Drupal, and to
-contribute in other ways -- see http://drupal.org/contribute to find out how.
+contribute in other ways -- see https://www.drupal.org/contribute to find out
+how.
Branch maintainers
------------------
@@ -9,150 +10,155 @@
The Drupal Core branch maintainers oversee the development of Drupal as a whole.
The branch maintainers for Drupal 7 are:
-- Dries Buytaert 'dries' http://drupal.org/user/1
-- Angela Byron 'webchick' http://drupal.org/user/24967
-- David Rothstein 'David_Rothstein' http://drupal.org/user/124982
+- Dries Buytaert 'dries' https://www.drupal.org/u/dries
+- Angela Byron 'webchick' https://www.drupal.org/u/webchick
+- Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx
+- David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein
+- Stefan Ruijsenaars 'stefan.r' https://www.drupal.org/u/stefanr-0
+- (provisional) Pol Dellaiera 'Pol' https://www.drupal.org/u/pol
Component maintainers
---------------------
The Drupal Core component maintainers oversee the development of Drupal
-subsystems. See http://drupal.org/contribute/core-maintainers for more
+subsystems. See https://www.drupal.org/contribute/core-maintainers for more
information on their responsibilities, and to find out how to become a component
maintainer. Current component maintainers for Drupal 7:
Ajax system
-- Alex Bronstein 'effulgentsia' http://drupal.org/user/78040
-- Earl Miles 'merlinofchaos' http://drupal.org/user/26979
+- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia
+- Earl Miles 'merlinofchaos' https://www.drupal.org/u/merlinofchaos
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
+- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud
+- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman
Batch system
-- Yves Chedemois 'yched' http://drupal.org/user/39567
+- Yves Chedemois 'yched' https://www.drupal.org/u/yched
Cache system
-- Damien Tournoud 'DamZ' http://drupal.org/user/22211
-- Nathaniel Catchpole 'catch' http://drupal.org/user/35733
+- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud
+- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch
Cron system
-- Károly Négyesi 'chx' http://drupal.org/user/9446
-- Derek Wright 'dww' http://drupal.org/user/46549
+- Derek Wright 'dww' https://www.drupal.org/u/dww
Database system
-- Larry Garfield 'Crell' http://drupal.org/user/26398
+- ?
- MySQL driver
- - Larry Garfield 'Crell' http://drupal.org/user/26398
- - David Strauss 'David Strauss' http://drupal.org/user/93254
+ - David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss
- PostgreSQL driver
- - Damien Tournoud 'DamZ' http://drupal.org/user/22211
- - Josh Waihi 'fiasco' http://drupal.org/user/188162
+ - Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud
+ - Josh Waihi 'fiasco' https://www.drupal.org/u/josh-waihi
- Sqlite driver
- - Damien Tournoud 'DamZ' http://drupal.org/user/22211
- - Károly Négyesi 'chx' http://drupal.org/user/9446
+ - Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud
Database update system
-- Károly Négyesi 'chx' http://drupal.org/user/9446
-- Ashok Modi 'BTMash' http://drupal.org/user/60422
+- Ashok Modi 'BTMash' https://www.drupal.org/u/btmash
Entity system
-- Wolfgang Ziegler 'fago' http://drupal.org/user/16747
-- Nathaniel Catchpole 'catch' http://drupal.org/user/35733
-- Franz Heinzmann 'Frando' http://drupal.org/user/21850
+- Wolfgang Ziegler 'fago' https://www.drupal.org/u/fago
+- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch
+- Franz Heinzmann 'Frando' https://www.drupal.org/u/frando
File system
-- Andrew Morton 'drewish' http://drupal.org/user/34869
-- Aaron Winborn 'aaron' http://drupal.org/user/33420
+- Andrew Morton 'drewish' https://www.drupal.org/u/drewish
+- Aaron Winborn 'aaron' https://www.drupal.org/u/aaron
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
-- Franz Heinzmann 'Frando' http://drupal.org/user/21850
+- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia
+- Wolfgang Ziegler 'fago' https://www.drupal.org/u/fago
+- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
+- Franz Heinzmann 'Frando' https://www.drupal.org/u/frando
Image system
-- Andrew Morton 'drewish' http://drupal.org/user/34869
-- Nathan Haug 'quicksketch' http://drupal.org/user/35821
+- Andrew Morton 'drewish' https://www.drupal.org/u/drewish
+- Nathan Haug 'quicksketch' https://www.drupal.org/u/quicksketch
Install system
-- David Rothstein 'David_Rothstein' http://drupal.org/user/124982
+- David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein
JavaScript
-- Théodore Biadala 'nod_' http://drupal.org/user/598310
-- Steve De Jonghe 'seutje' http://drupal.org/user/264148
-- Jesse Renée Beach 'jessebeach' http://drupal.org/user/748566
+- Théodore Biadala 'nod_' https://www.drupal.org/u/nod_
+- Steve De Jonghe 'seutje' https://www.drupal.org/u/seutje
Language system
-- Francesco Placella 'plach' http://drupal.org/user/183211
-- Daniel F. Kudwien 'sun' http://drupal.org/user/54136
+- Francesco Placella 'plach' https://www.drupal.org/u/plach
+- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
Lock system
-- Damien Tournoud 'DamZ' http://drupal.org/user/22211
+- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud
Mail system
- ?
Markup
-- Jacine Luisi 'Jacine' http://drupal.org/user/88931
-- Daniel F. Kudwien 'sun' http://drupal.org/user/54136
+- Jacine Luisi 'Jacine' https://www.drupal.org/u/jacine
+- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
Menu system
-- Peter Wolanin 'pwolanin' http://drupal.org/user/49851
-- Károly Négyesi 'chx' http://drupal.org/user/9446
+- Peter Wolanin 'pwolanin' https://www.drupal.org/u/pwolanin
Path system
-- Dave Reid 'davereid' http://drupal.org/user/53892
-- Nathaniel Catchpole 'catch' http://drupal.org/user/35733
+- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid
+- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch
Render system
-- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23
-- Alex Bronstein 'effulgentsia' http://drupal.org/user/78040
-- Franz Heinzmann 'Frando' http://drupal.org/user/21850
+- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman
+- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia
+- Franz Heinzmann 'Frando' https://www.drupal.org/u/frando
Theme system
-- Earl Miles 'merlinofchaos' http://drupal.org/user/26979
-- Alex Bronstein 'effulgentsia' http://drupal.org/user/78040
-- Joon Park 'dvessel' http://drupal.org/user/56782
-- John Albin Wilkins 'JohnAlbin' http://drupal.org/user/32095
+- Earl Miles 'merlinofchaos' https://www.drupal.org/u/merlinofchaos
+- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia
+- Joon Park 'dvessel' https://www.drupal.org/u/dvessel
+- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin
Token system
-- Dave Reid 'davereid' http://drupal.org/user/53892
+- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid
XML-RPC system
-- Frederic G. Marand 'fgm' http://drupal.org/user/27985
+- Frederic G. Marand 'fgm' https://www.drupal.org/u/fgm
Topic coordinators
------------------
Accessibility
-- Everett Zufelt 'Everett Zufelt' http://drupal.org/user/406552
-- Brandon Bowersox-Johnson 'bowersox' http://drupal.org/user/186415
+- Everett Zufelt 'Everett Zufelt' https://www.drupal.org/u/everett-zufelt
+- Brandon Bowersox-Johnson 'bowersox' https://www.drupal.org/u/bowersox
Documentation
-- Jennifer Hodgdon 'jhodgdon' http://drupal.org/user/155601
-
-Security
-- Greg Knaddison 'greggles' http://drupal.org/user/36762
+- Jennifer Hodgdon 'jhodgdon' https://www.drupal.org/u/jhodgdon
Translations
-- Gerhard Killesreiter 'killes' http://drupal.org/user/83
+- Gerhard Killesreiter 'killes' https://www.drupal.org/u/gerhard-killesreiter
User experience and usability
-- Roy Scholten 'yoroy' http://drupal.org/user/41502
-- Bojhan Somers 'Bojhan' http://drupal.org/user/87969
+- Roy Scholten 'yoroy' https://www.drupal.org/u/yoroy
+- Bojhan Somers 'Bojhan' https://www.drupal.org/u/bojhan
Node Access
-- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23
-- Ken Rickard 'agentrickard' http://drupal.org/user/20975
-- Jess Myrbo 'xjm' http://drupal.org/user/65776
+- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman
+- Ken Rickard 'agentrickard' https://www.drupal.org/u/agentrickard
+
+
+Security team
+-----------------
+
+To report a security issue, see: https://www.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 https://www.drupal.org/security-team for more information.
+The security team lead is:
+
+- Michael Hess 'mlhess' https://www.drupal.org/u/mlhess
+
Module maintainers
------------------
@@ -161,143 +167,141 @@
- ?
Block module
-- John Albin Wilkins 'JohnAlbin' http://drupal.org/user/32095
+- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin
Blog module
- ?
Book module
-- Peter Wolanin 'pwolanin' http://drupal.org/user/49851
+- Peter Wolanin 'pwolanin' https://www.drupal.org/u/pwolanin
Color module
- ?
Comment module
-- Nathaniel Catchpole 'catch' http://drupal.org/user/35733
+- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch
Contact module
-- Dave Reid 'davereid' http://drupal.org/user/53892
+- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid
Contextual module
-- Daniel F. Kudwien 'sun' http://drupal.org/user/54136
+- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
Dashboard module
- ?
Database logging module
-- Khalid Baheyeldin 'kbahey' http://drupal.org/user/4063
+- Khalid Baheyeldin 'kbahey' https://www.drupal.org/u/kbahey
Field module
-- Yves Chedemois 'yched' http://drupal.org/user/39567
-- Barry Jaspan 'bjaspan' http://drupal.org/user/46413
+- Yves Chedemois 'yched' https://www.drupal.org/u/yched
+- Barry Jaspan 'bjaspan' https://www.drupal.org/u/bjaspan
Field UI module
-- Yves Chedemois 'yched' http://drupal.org/user/39567
+- Yves Chedemois 'yched' https://www.drupal.org/u/yched
File module
-- Aaron Winborn 'aaron' http://drupal.org/user/33420
+- Aaron Winborn 'aaron' https://www.drupal.org/u/aaron
Filter module
-- Daniel F. Kudwien 'sun' http://drupal.org/user/54136
+- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
Forum module
-- Lee Rowlands 'larowlan' http://drupal.org/user/395439
+- Lee Rowlands 'larowlan' https://www.drupal.org/u/larowlan
Help module
- ?
Image module
-- Nathan Haug 'quicksketch' http://drupal.org/user/35821
+- Nathan Haug 'quicksketch' https://www.drupal.org/u/quicksketch
Locale module
-- Gábor Hojtsy 'Gábor Hojtsy' http://drupal.org/user/4166
+- Gábor Hojtsy 'Gábor Hojtsy' https://www.drupal.org/u/gábor-hojtsy
Menu module
- ?
Node module
-- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23
-- David Strauss 'David Strauss' http://drupal.org/user/93254
+- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman
+- David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss
OpenID module
-- Vojtech Kusy 'wojtha' http://drupal.org/user/56154
-- Christian Schmidt 'c960657' http://drupal.org/user/216078
-- Damien Tournoud 'DamZ' http://drupal.org/user/22211
+- Vojtech Kusy 'wojtha' https://www.drupal.org/u/wojtha
+- Christian Schmidt 'c960657' https://www.drupal.org/u/c960657
+- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud
Overlay module
-- Katherine Senzee 'ksenzee' http://drupal.org/user/139855
+- Katherine Senzee 'ksenzee' https://www.drupal.org/u/ksenzee
Path module
-- Dave Reid 'davereid' http://drupal.org/user/53892
+- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid
PHP module
- ?
Poll module
-- Andrei Mateescu 'amateescu' http://drupal.org/user/729614
+- Andrei Mateescu 'amateescu' https://www.drupal.org/u/amateescu
Profile module
- ?
RDF module
-- Stéphane Corlosquet 'scor' http://drupal.org/user/52142
+- Stéphane Corlosquet 'scor' https://www.drupal.org/u/scor
Search module
-- Doug Green 'douggreen' http://drupal.org/user/29191
+- Doug Green 'douggreen' https://www.drupal.org/u/douggreen
Shortcut module
-- David Rothstein 'David_Rothstein' http://drupal.org/user/124982
+- David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein
Simpletest module
-- Jimmy Berry 'boombatower' http://drupal.org/user/214218
-- Károly Négyesi 'chx' http://drupal.org/user/9446
+- Jimmy Berry 'boombatower' https://www.drupal.org/u/boombatower
Statistics module
-- Tim Millwood 'timmillwood' http://drupal.org/user/227849
+- Tim Millwood 'timmillwood' https://www.drupal.org/u/timmillwood
Syslog module
-- Khalid Baheyeldin 'kbahey' http://drupal.org/user/4063
+- Khalid Baheyeldin 'kbahey' https://www.drupal.org/u/kbahey
System module
- ?
Taxonomy module
-- Jess Myrbo 'xjm' http://drupal.org/user/65776
-- Nathaniel Catchpole 'catch' http://drupal.org/user/35733
-- Benjamin Doherty 'bangpound' http://drupal.org/user/100456
+- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch
+- Benjamin Doherty 'bangpound' https://www.drupal.org/u/bangpound
Toolbar module
- ?
Tracker module
-- David Strauss 'David Strauss' http://drupal.org/user/93254
+- David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss
Translation module
-- Francesco Placella 'plach' http://drupal.org/user/183211
+- Francesco Placella 'plach' https://www.drupal.org/u/plach
Trigger module
- ?
Update module
-- Derek Wright 'dww' http://drupal.org/user/46549
+- Derek Wright 'dww' https://www.drupal.org/u/dww
User module
-- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23
-- David Strauss 'David Strauss' http://drupal.org/user/93254
+- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman
+- David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss
Theme maintainers
-----------------
Bartik theme
-- Jen Simmons 'jensimmons' http://drupal.org/user/140882
-- Jeff Burns 'Jeff Burnz' http://drupal.org/user/61393
+- Jen Simmons 'jensimmons' https://www.drupal.org/u/jensimmons
+- Jeff Burns 'Jeff Burnz' https://www.drupal.org/u/jeff-burnz
Garland theme
-- John Albin Wilkins 'JohnAlbin' http://drupal.org/user/32095
+- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin
Seven theme
-- Jeff Burns 'Jeff Burnz' http://drupal.org/user/61393
+- Jeff Burns 'Jeff Burnz' https://www.drupal.org/u/jeff-burnz
Stark theme
-- John Albin Wilkins 'JohnAlbin' http://drupal.org/user/32095
+- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin
diff -Naur drupal-7.18/README.txt drupal-7.66/README.txt
--- drupal-7.18/README.txt 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/README.txt 2019-04-17 22:20:46.000000000 +0200
@@ -71,12 +71,12 @@
sites that were installed with that specific profile.
More about installation profiles and distributions:
-* Read about the difference between installation profiles and distributions:
- http://drupal.org/node/1089736
-* Download contributed installation profiles and distributions:
- http://drupal.org/project/distributions
-* Develop your own installation profile or distribution:
- http://drupal.org/developing/distributions
+ * Read about the difference between installation profiles and distributions:
+ http://drupal.org/node/1089736
+ * Download contributed installation profiles and distributions:
+ http://drupal.org/project/distributions
+ * Develop your own installation profile or distribution:
+ http://drupal.org/developing/distributions
APPEARANCE
----------
diff -Naur drupal-7.18/UPGRADE.txt drupal-7.66/UPGRADE.txt
--- drupal-7.18/UPGRADE.txt 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/UPGRADE.txt 2019-04-17 22:20:46.000000000 +0200
@@ -64,6 +64,9 @@
Sometimes an update includes changes to default.settings.php (this will be
noted in the release notes). If that's the case, follow these steps:
+ - Locate your settings.php file in the /sites/* directory. (Typically
+ sites/default.)
+
- Make a backup copy of your settings.php file, with a different file name.
- Make a copy of the new default.settings.php file, and name the copy
@@ -74,6 +77,13 @@
database information, and you will also want to copy in any other
customizations you have added.
+ You can find the release notes for your version at
+ https://www.drupal.org/project/drupal. At bottom of the project page under
+ "Downloads" use the link for your version of Drupal to view the release
+ notes. If your version is not listed, use the 'View all releases' link. From
+ this page you can scroll down or use the filter to find your version and its
+ release notes.
+
4. Download the latest Drupal 7.x release from http://drupal.org to a
directory outside of your web root. Extract the archive and copy the files
into your Drupal directory.
diff -Naur drupal-7.18/authorize.php drupal-7.66/authorize.php
--- drupal-7.18/authorize.php 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/authorize.php 2019-04-17 22:20:46.000000000 +0200
@@ -4,16 +4,16 @@
* @file
* Administrative script for running authorized file operations.
*
- * Using this script, the site owner (the user actually owning the files on
- * the webserver) can authorize certain file-related operations to proceed
- * with elevated privileges, for example to deploy and upgrade modules or
- * themes. Users should not visit this page directly, but instead use an
- * administrative user interface which knows how to redirect the user to this
- * script as part of a multistep process. This script actually performs the
- * selected operations without loading all of Drupal, to be able to more
- * gracefully recover from errors. Access to the script is controlled by a
- * global killswitch in settings.php ('allow_authorize_operations') and via
- * the 'administer software updates' permission.
+ * Using this script, the site owner (the user actually owning the files on the
+ * webserver) can authorize certain file-related operations to proceed with
+ * elevated privileges, for example to deploy and upgrade modules or themes.
+ * Users should not visit this page directly, but instead use an administrative
+ * user interface which knows how to redirect the user to this script as part of
+ * a multistep process. This script actually performs the selected operations
+ * without loading all of Drupal, to be able to more gracefully recover from
+ * errors. Access to the script is controlled by a global killswitch in
+ * settings.php ('allow_authorize_operations') and via the 'administer software
+ * updates' permission.
*
* There are helper functions for setting up an operation to run via this
* system in modules/system/system.module. For more information, see:
@@ -21,16 +21,17 @@
*/
/**
- * Root directory of Drupal installation.
+ * Defines the root directory of the Drupal installation.
*/
define('DRUPAL_ROOT', getcwd());
/**
- * Global flag to identify update.php and authorize.php runs, and so
- * avoid various unwanted operations, such as hook_init() and
- * hook_exit() invokes, css/js preprocessing and translation, and
- * solve some theming issues. This flag is checked on several places
- * in Drupal code (not just authorize.php).
+ * Global flag to identify update.php and authorize.php runs.
+ *
+ * Identifies update.php and authorize.php runs, avoiding unwanted operations
+ * such as hook_init() and hook_exit() invokes, css/js preprocessing and
+ * translation, and solves some theming issues. The flag is checked in other
+ * places in Drupal code (not just authorize.php).
*/
define('MAINTENANCE_MODE', 'update');
@@ -51,7 +52,7 @@
* have access to the 'administer software updates' permission.
*
* @return
- * TRUE if the current user can run authorize.php, otherwise FALSE.
+ * TRUE if the current user can run authorize.php, and FALSE if not.
*/
function authorize_access_allowed() {
return variable_get('allow_authorize_operations', TRUE) && user_access('administer software updates');
diff -Naur drupal-7.18/includes/ajax.inc drupal-7.66/includes/ajax.inc
--- drupal-7.18/includes/ajax.inc 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/includes/ajax.inc 2019-04-17 22:20:46.000000000 +0200
@@ -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);
@@ -230,6 +230,10 @@
* functions.
*/
function ajax_render($commands = array()) {
+ // Although ajax_deliver() does this, some contributed and custom modules
+ // render Ajax responses without using that delivery callback.
+ ajax_set_verification_header();
+
// Ajax responses aren't rendered with html.tpl.php, so we have to call
// drupal_get_css() and drupal_get_js() here, in order to have new files added
// during this request to be loaded by the page. We only want to send back
@@ -251,8 +255,8 @@
// reliably diffed with array_diff_key(), since the number can change
// due to factors unrelated to the inline content, so for now, we strip
// the inline items from Ajax responses, and can add support for them
- // when drupal_add_css() and drupal_add_js() are changed to using md5()
- // or some other hash of the inline content.
+ // when drupal_add_css() and drupal_add_js() are changed to use a hash
+ // of the inline content as the array key.
foreach ($items[$type] as $key => $item) {
if (is_numeric($key)) {
unset($items[$type][$key]);
@@ -276,7 +280,7 @@
$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 +296,7 @@
$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.
@@ -308,10 +312,11 @@
* pulls the form info from $_POST.
*
* @return
- * An array containing the $form and $form_state. Use the list() function
- * to break these apart:
+ * An array containing the $form, $form_state, $form_id, $form_build_id and an
+ * initial list of Ajax $commands. Use the list() function to break these
+ * apart:
* @code
- * list($form, $form_state, $form_id, $form_build_id) = ajax_get_form();
+ * list($form, $form_state, $form_id, $form_build_id, $commands) = ajax_get_form();
* @endcode
*/
function ajax_get_form() {
@@ -331,6 +336,17 @@
drupal_exit();
}
+ // When a page level cache is enabled, the form-build id might have been
+ // replaced from within form_get_cache. If this is the case, it is also
+ // necessary to update it in the browser by issuing an appropriate Ajax
+ // command.
+ $commands = array();
+ if (isset($form['#build_id_old']) && $form['#build_id_old'] != $form['#build_id']) {
+ // If the form build ID has changed, issue an Ajax command to update it.
+ $commands[] = ajax_command_update_build_id($form);
+ $form_build_id = $form['#build_id'];
+ }
+
// Since some of the submit handlers are run, redirects need to be disabled.
$form_state['no_redirect'] = TRUE;
@@ -345,7 +361,7 @@
$form_state['input'] = $_POST;
$form_id = $form['#form_id'];
- return array($form, $form_state, $form_id, $form_build_id);
+ return array($form, $form_state, $form_id, $form_build_id, $commands);
}
/**
@@ -366,7 +382,7 @@
* @see system_menu()
*/
function ajax_form_callback() {
- list($form, $form_state) = ajax_get_form();
+ list($form, $form_state, $form_id, $form_build_id, $commands) = ajax_get_form();
drupal_process_form($form['#form_id'], $form, $form_state);
// We need to return the part of the form (or some other content) that needs
@@ -378,8 +394,20 @@
if (!empty($form_state['triggering_element'])) {
$callback = $form_state['triggering_element']['#ajax']['callback'];
}
- if (!empty($callback) && function_exists($callback)) {
- return $callback($form, $form_state);
+ if (!empty($callback) && is_callable($callback)) {
+ $result = $callback($form, $form_state);
+
+ if (!(is_array($result) && isset($result['#type']) && $result['#type'] == 'ajax')) {
+ // Turn the response into a #type=ajax array if it isn't one already.
+ $result = array(
+ '#type' => 'ajax',
+ '#commands' => ajax_prepare_response($result),
+ );
+ }
+
+ $result['#commands'] = array_merge($commands, $result['#commands']);
+
+ return $result;
}
}
@@ -463,6 +491,9 @@
}
}
+ // Let ajax.js know that this response is safe to process.
+ ajax_set_verification_header();
+
// Print the response.
$commands = ajax_prepare_response($page_callback_result);
$json = ajax_render($commands);
@@ -553,6 +584,29 @@
}
/**
+ * Sets a response header for ajax.js to trust the response body.
+ *
+ * It is not safe to invoke Ajax commands within user-uploaded files, so this
+ * header protects against those being invoked.
+ *
+ * @see Drupal.ajax.options.success()
+ */
+function ajax_set_verification_header() {
+ $added = &drupal_static(__FUNCTION__);
+
+ // User-uploaded files cannot set any response headers, so a custom header is
+ // used to indicate to ajax.js that this response is safe. Note that most
+ // Ajax requests bound using the Form API will be protected by having the URL
+ // flagged as trusted in Drupal.settings, so this header is used only for
+ // things like custom markup that gets Ajax behaviors attached.
+ if (empty($added)) {
+ drupal_add_http_header('X-Drupal-Ajax-Token', '1');
+ // Avoid sending the header twice.
+ $added = TRUE;
+ }
+}
+
+/**
* Performs end-of-Ajax-request tasks.
*
* This function is the equivalent of drupal_page_footer(), but for Ajax
@@ -740,7 +794,12 @@
$element['#attached']['js'][] = array(
'type' => 'setting',
- 'data' => array('ajax' => array($element['#id'] => $settings)),
+ 'data' => array(
+ 'ajax' => array($element['#id'] => $settings),
+ 'urlIsAjaxTrusted' => array(
+ $settings['url'] => TRUE,
+ ),
+ ),
);
// Indicate that Ajax processing was successful.
@@ -836,7 +895,8 @@
* @return
* An array suitable for use with the ajax_render() function.
*
- * See @link http://docs.jquery.com/Manipulation/replaceWith#content jQuery replaceWith command @endlink
+ * See
+ * @link http://docs.jquery.com/Manipulation/replaceWith#content jQuery replaceWith command @endlink
*/
function ajax_command_replace($selector, $html, $settings = NULL) {
return array(
@@ -1209,3 +1269,49 @@
'selector' => $selector,
);
}
+
+/**
+ * Creates a Drupal Ajax 'update_build_id' command.
+ *
+ * This command updates the value of a hidden form_build_id input element on a
+ * form. It requires the form passed in to have keys for both the old build ID
+ * in #build_id_old and the new build ID in #build_id.
+ *
+ * The primary use case for this Ajax command is to serve a new build ID to a
+ * form served from the cache to an anonymous user, preventing one anonymous
+ * user from accessing the form state of another anonymous users on Ajax enabled
+ * forms.
+ *
+ * @param $form
+ * The form array representing the form whose build ID should be updated.
+ */
+function ajax_command_update_build_id($form) {
+ return array(
+ 'command' => 'updateBuildId',
+ 'old' => $form['#build_id_old'],
+ '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,
+ );
+}
diff -Naur drupal-7.18/includes/batch.inc drupal-7.66/includes/batch.inc
--- drupal-7.18/includes/batch.inc 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/includes/batch.inc 2019-04-17 22:20:46.000000000 +0200
@@ -460,10 +460,10 @@
if (isset($batch_set['file']) && is_file($batch_set['file'])) {
include_once DRUPAL_ROOT . '/' . $batch_set['file'];
}
- if (function_exists($batch_set['finished'])) {
+ if (is_callable($batch_set['finished'])) {
$queue = _batch_queue($batch_set);
$operations = $queue->getAllItems();
- $batch_set['finished']($batch_set['success'], $batch_set['results'], $operations, format_interval($batch_set['elapsed'] / 1000));
+ call_user_func($batch_set['finished'], $batch_set['success'], $batch_set['results'], $operations, format_interval($batch_set['elapsed'] / 1000));
}
}
}
diff -Naur drupal-7.18/includes/bootstrap.inc drupal-7.66/includes/bootstrap.inc
--- drupal-7.18/includes/bootstrap.inc 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/includes/bootstrap.inc 2019-04-17 22:20:46.000000000 +0200
@@ -8,7 +8,7 @@
/**
* The current system version.
*/
-define('VERSION', '7.18');
+define('VERSION', '7.66');
/**
* Core API compatibility.
@@ -218,12 +218,16 @@
define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
/**
- * Flag for drupal_set_title(); text is not sanitized, so run check_plain().
+ * Flag used to indicate that text is not sanitized, so run check_plain().
+ *
+ * @see drupal_set_title()
*/
define('CHECK_PLAIN', 0);
/**
- * Flag for drupal_set_title(); text has already been sanitized.
+ * Flag used to indicate that text has already been sanitized.
+ *
+ * @see drupal_set_title()
*/
define('PASS_THROUGH', -1);
@@ -240,11 +244,25 @@
/**
* Regular expression to match PHP function names.
*
- * @see http://php.net/manual/en/language.functions.php
+ * @see http://php.net/manual/language.functions.php
*/
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
+ *
+ * This constant was introduced in PHP 7.0.19 and PHP 7.1.5 but needs to be
+ * defined by Drupal for earlier PHP versions.
+ */
+if (!defined('DATE_RFC7231')) {
+ 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.
*
* This class should be extended by systems that need to cache large amounts
@@ -274,7 +292,7 @@
* error, and $var will be populated with the contents of $object['foo'], but
* that data will be passed by value, not reference. For more information on
* the PHP limitation, see the note in the official PHP documentation at·
- * http://php.net/manual/en/arrayaccess.offsetget.php on
+ * http://php.net/manual/arrayaccess.offsetget.php on
* ArrayAccess::offsetGet().
*
* By default, the class accounts for caches where calling functions might
@@ -380,11 +398,11 @@
* without necessarily writing back to the persistent cache at the end.
*
* @param $offset
- * The array offset that was request.
+ * The array offset that was requested.
* @param $persist
* Optional boolean to specify whether the offset should be persisted or
* not, defaults to TRUE. When called with $persist = FALSE the offset will
- * be unflagged so that it will not written at the end of the request.
+ * be unflagged so that it will not be written at the end of the request.
*/
protected function persist($offset, $persist = TRUE) {
$this->keysToPersist[$offset] = $persist;
@@ -516,9 +534,8 @@
* 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
@@ -679,13 +696,27 @@
ini_set('session.use_only_cookies', '1');
ini_set('session.use_trans_sid', '0');
// Don't send HTTP headers using PHP's session handler.
- ini_set('session.cache_limiter', 'none');
+ // An empty string is used here to disable the cache limiter.
+ ini_set('session.cache_limiter', '');
// Use httponly session cookies.
ini_set('session.cookie_httponly', '1');
// Set sane locale settings, to ensure consistent string, dates, times and
// numbers handling.
setlocale(LC_ALL, 'C');
+
+ // PHP's built-in phar:// stream wrapper is not sufficiently secure. Override
+ // it with a more secure one, which requires PHP 5.3.3. For lower versions,
+ // unregister the built-in one without replacing it. Sites needing phar
+ // support for lower PHP versions must implement hook_stream_wrappers() to
+ // register their desired implementation.
+ if (in_array('phar', stream_get_wrappers(), TRUE)) {
+ stream_wrapper_unregister('phar');
+ if (version_compare(PHP_VERSION, '5.3.3', '>=')) {
+ include_once DRUPAL_ROOT . '/includes/file.phar.inc';
+ file_register_phar_wrapper();
+ }
+ }
}
/**
@@ -695,7 +726,24 @@
* 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);
+}
+
+/**
+ * Checks whether an HTTPS request is being served.
+ *
+ * @return bool
+ * TRUE if the request is HTTPS, FALSE otherwise.
+ */
+function drupal_is_https() {
+ return isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on';
}
/**
@@ -711,12 +759,11 @@
if (file_exists(DRUPAL_ROOT . '/' . conf_path() . '/settings.php')) {
include_once DRUPAL_ROOT . '/' . conf_path() . '/settings.php';
}
- $is_https = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on';
+ $is_https = drupal_is_https();
if (isset($base_url)) {
// Parse fixed base URL from settings.php.
$parts = parse_url($base_url);
- $http_protocol = $parts['scheme'];
if (!isset($parts['path'])) {
$parts['path'] = '';
}
@@ -792,7 +839,7 @@
*
* This function plays a key role in allowing Drupal's resources (modules
* and themes) to be located in different places depending on a site's
- * configuration. For example, a module 'foo' may legally be be located
+ * configuration. For example, a module 'foo' may legally be located
* in any of these three places:
*
* modules/foo/foo.module
@@ -803,20 +850,27 @@
* the above, depending on where the module is located.
*
* @param $type
- * The type of the item (i.e. theme, theme_engine, module, profile).
+ * The type of the item (theme, theme_engine, module, profile).
* @param $name
* The name of the item for which the filename is requested.
* @param $filename
* The filename of the item if it is to be set explicitly rather
* than by consulting the database.
+ * @param bool $trigger_error
+ * Whether to trigger an error when a file is missing or has unexpectedly
+ * moved. This defaults to TRUE, but can be set to FALSE by calling code that
+ * merely wants to check whether an item exists in the filesystem.
*
* @return
- * The filename of the requested item.
- */
-function drupal_get_filename($type, $name, $filename = NULL) {
+ * The filename of the requested item or NULL if the item is not found.
+ */
+function drupal_get_filename($type, $name, $filename = NULL, $trigger_error = TRUE) {
+ // The $files static variable will hold the locations of all requested files.
+ // We can be sure that any file listed in this static variable actually
+ // exists as all additions have gone through a file_exists() check.
// The location of files will not change during the request, so do not use
// drupal_static().
- static $files = array(), $dirs = array();
+ static $files = array();
// Profiles are a special case: they have a fixed location and naming.
if ($type == 'profile') {
@@ -828,64 +882,296 @@
}
if (!empty($filename) && file_exists($filename)) {
+ // Prime the static cache with the provided filename.
$files[$type][$name] = $filename;
}
elseif (isset($files[$type][$name])) {
- // nothing
+ // This item had already been found earlier in the request, either through
+ // priming of the static cache (for example, in system_list()), through a
+ // lookup in the {system} table, or through a file scan (cached or not). Do
+ // nothing.
}
- // Verify that we have an active database connection, before querying
- // the database. This is required because this function is called both
- // before we have a database connection (i.e. during installation) and
- // when a database connection fails.
else {
+ // Look for the filename listed in the {system} table. Verify that we have
+ // an active database connection before doing so, since this function is
+ // called both before we have a database connection (i.e. during
+ // installation) and when a database connection fails.
+ $database_unavailable = TRUE;
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;
}
+ $database_unavailable = FALSE;
}
}
catch (Exception $e) {
// The database table may not exist because Drupal is not yet installed,
- // or the database might be down. We have a fallback for this case so we
- // hide the error completely.
+ // the database might be down, or we may have done a non-database cache
+ // flush while $conf['page_cache_without_database'] = TRUE and
+ // $conf['page_cache_invoke_hooks'] = TRUE. We have a fallback for these
+ // cases so we hide the error completely.
}
- // Fallback to searching the filesystem if the database could not find the
- // file or the file returned by the database is not found.
+ // Fall back to searching the filesystem if the database could not find the
+ // file or the file does not exist at the path returned by the database.
if (!isset($files[$type][$name])) {
- // We have a consistent directory naming: modules, themes...
- $dir = $type . 's';
- if ($type == 'theme_engine') {
- $dir = 'themes/engines';
- $extension = 'engine';
- }
- elseif ($type == 'theme') {
- $extension = 'info';
- }
- else {
- $extension = $type;
- }
+ $files[$type][$name] = _drupal_get_filename_fallback($type, $name, $trigger_error, $database_unavailable);
+ }
+ }
- if (!isset($dirs[$dir][$extension])) {
- $dirs[$dir][$extension] = TRUE;
- if (!function_exists('drupal_system_listing')) {
- require_once DRUPAL_ROOT . '/includes/common.inc';
- }
- // Scan the appropriate directories for all files with the requested
- // extension, not just the file we are currently looking for. This
- // prevents unnecessary scans from being repeated when this function is
- // called more than once in the same page request.
- $matches = drupal_system_listing("/^" . DRUPAL_PHP_FUNCTION_PATTERN . "\.$extension$/", $dir, 'name', 0);
- foreach ($matches as $matched_name => $file) {
- $files[$type][$matched_name] = $file->uri;
+ if (isset($files[$type][$name])) {
+ return $files[$type][$name];
+ }
+}
+
+/**
+ * Performs a cached file system scan as a fallback when searching for a file.
+ *
+ * This function looks for the requested file by triggering a file scan,
+ * caching the new location if the file has moved and caching the miss
+ * if the file is missing. If a file had been marked as missing in a previous
+ * file scan, or if it has been marked as moved and is still in the last known
+ * location, no new file scan will be performed.
+ *
+ * @param string $type
+ * The type of the item (theme, theme_engine, module, profile).
+ * @param string $name
+ * The name of the item for which the filename is requested.
+ * @param bool $trigger_error
+ * Whether to trigger an error when a file is missing or has unexpectedly
+ * moved.
+ * @param bool $database_unavailable
+ * Whether this function is being called because the Drupal database could
+ * not be queried for the file's location.
+ *
+ * @return
+ * The filename of the requested item or NULL if the item is not found.
+ *
+ * @see drupal_get_filename()
+ */
+function _drupal_get_filename_fallback($type, $name, $trigger_error, $database_unavailable) {
+ $file_scans = &_drupal_file_scan_cache();
+ $filename = NULL;
+
+ // If the cache indicates that the item is missing, or we can verify that the
+ // item exists in the location the cache says it exists in, use that.
+ if (isset($file_scans[$type][$name]) && ($file_scans[$type][$name] === FALSE || file_exists($file_scans[$type][$name]))) {
+ $filename = $file_scans[$type][$name];
+ }
+ // Otherwise, perform a new file scan to find the item.
+ else {
+ $filename = _drupal_get_filename_perform_file_scan($type, $name);
+ // Update the static cache, and mark the persistent cache for updating at
+ // the end of the page request. See drupal_file_scan_write_cache().
+ $file_scans[$type][$name] = $filename;
+ $file_scans['#write_cache'] = TRUE;
+ }
+
+ // If requested, trigger a user-level warning about the missing or
+ // unexpectedly moved file. If the database was unavailable, do not trigger a
+ // warning in the latter case, though, since if the {system} table could not
+ // be queried there is no way to know if the location found here was
+ // "unexpected" or not.
+ if ($trigger_error) {
+ $error_type = $filename === FALSE ? 'missing' : 'moved';
+ if ($error_type == 'missing' || !$database_unavailable) {
+ _drupal_get_filename_fallback_trigger_error($type, $name, $error_type);
+ }
+ }
+
+ // The cache stores FALSE for files that aren't found (to be able to
+ // distinguish them from files that have not yet been searched for), but
+ // drupal_get_filename() expects NULL for these instead, so convert to NULL
+ // before returning.
+ if ($filename === FALSE) {
+ $filename = NULL;
+ }
+ return $filename;
+}
+
+/**
+ * Returns the current list of cached file system scan results.
+ *
+ * @return
+ * An associative array tracking the most recent file scan results for all
+ * files that have had scans performed. The keys are the type and name of the
+ * item that was searched for, and the values can be either:
+ * - Boolean FALSE if the item was not found in the file system.
+ * - A string pointing to the location where the item was found.
+ */
+function &_drupal_file_scan_cache() {
+ $file_scans = &drupal_static(__FUNCTION__, array());
+
+ // The file scan results are stored in a persistent cache (in addition to the
+ // static cache) but because this function can be called before the
+ // persistent cache is available, we must merge any items that were found
+ // earlier in the page request into the results from the persistent cache.
+ if (!isset($file_scans['#cache_merge_done'])) {
+ try {
+ if (function_exists('cache_get')) {
+ $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap');
+ if (!empty($cache->data)) {
+ // File scan results from the current request should take precedence
+ // over the results from the persistent cache, since they are newer.
+ $file_scans = drupal_array_merge_deep($cache->data, $file_scans);
}
+ // Set a flag to indicate that the persistent cache does not need to be
+ // merged again.
+ $file_scans['#cache_merge_done'] = TRUE;
}
}
+ catch (Exception $e) {
+ // Hide the error.
+ }
}
- if (isset($files[$type][$name])) {
- return $files[$type][$name];
+ return $file_scans;
+}
+
+/**
+ * Performs a file system scan to search for a system resource.
+ *
+ * @param $type
+ * The type of the item (theme, theme_engine, module, profile).
+ * @param $name
+ * The name of the item for which the filename is requested.
+ *
+ * @return
+ * The filename of the requested item or FALSE if the item is not found.
+ *
+ * @see drupal_get_filename()
+ * @see _drupal_get_filename_fallback()
+ */
+function _drupal_get_filename_perform_file_scan($type, $name) {
+ // The location of files will not change during the request, so do not use
+ // drupal_static().
+ static $dirs = array(), $files = array();
+
+ // We have a consistent directory naming: modules, themes...
+ $dir = $type . 's';
+ if ($type == 'theme_engine') {
+ $dir = 'themes/engines';
+ $extension = 'engine';
+ }
+ elseif ($type == 'theme') {
+ $extension = 'info';
+ }
+ else {
+ $extension = $type;
+ }
+
+ // Check if we had already scanned this directory/extension combination.
+ if (!isset($dirs[$dir][$extension])) {
+ // Log that we have now scanned this directory/extension combination
+ // into a static variable so as to prevent unnecessary file scans.
+ $dirs[$dir][$extension] = TRUE;
+ if (!function_exists('drupal_system_listing')) {
+ require_once DRUPAL_ROOT . '/includes/common.inc';
+ }
+ // Scan the appropriate directories for all files with the requested
+ // extension, not just the file we are currently looking for. This
+ // prevents unnecessary scans from being repeated when this function is
+ // called more than once in the same page request.
+ $matches = drupal_system_listing("/^" . DRUPAL_PHP_FUNCTION_PATTERN . "\.$extension$/", $dir, 'name', 0);
+ foreach ($matches as $matched_name => $file) {
+ // Log the locations found in the file scan into a static variable.
+ $files[$type][$matched_name] = $file->uri;
+ }
+ }
+
+ // Return the results of the file system scan, or FALSE to indicate the file
+ // was not found.
+ return isset($files[$type][$name]) ? $files[$type][$name] : FALSE;
+}
+
+/**
+ * Triggers a user-level warning for missing or unexpectedly moved files.
+ *
+ * @param $type
+ * The type of the item (theme, theme_engine, module, profile).
+ * @param $name
+ * The name of the item for which the filename is requested.
+ * @param $error_type
+ * The type of the error ('missing' or 'moved').
+ *
+ * @see drupal_get_filename()
+ * @see _drupal_get_filename_fallback()
+ */
+function _drupal_get_filename_fallback_trigger_error($type, $name, $error_type) {
+ // Hide messages due to known bugs that will appear on a lot of sites.
+ // @todo Remove this in https://www.drupal.org/node/2383823
+ if (empty($name)) {
+ return;
+ }
+
+ // Make sure we only show any missing or moved file errors only once per
+ // request.
+ static $errors_triggered = array();
+ if (empty($errors_triggered[$type][$name][$error_type])) {
+ // Use _drupal_trigger_error_with_delayed_logging() here since these are
+ // triggered during low-level operations that cannot necessarily be
+ // interrupted by a watchdog() call.
+ if ($error_type == 'missing') {
+ _drupal_trigger_error_with_delayed_logging(format_string('The following @type is missing from the file system: %name. For information about how to fix this, see the documentation page.', array('@type' => $type, '%name' => $name, '@documentation' => 'https://www.drupal.org/node/2487215')), E_USER_WARNING);
+ }
+ elseif ($error_type == 'moved') {
+ _drupal_trigger_error_with_delayed_logging(format_string('The following @type has moved within the file system: %name. In order to fix this, clear caches or put the @type back in its original location. For more information, see the documentation page.', array('@type' => $type, '%name' => $name, '@documentation' => 'https://www.drupal.org/node/2487215')), E_USER_WARNING);
+ }
+ $errors_triggered[$type][$name][$error_type] = TRUE;
+ }
+}
+
+/**
+ * Invokes trigger_error() with logging delayed until the end of the request.
+ *
+ * This is an alternative to PHP's trigger_error() function which can be used
+ * during low-level Drupal core operations that need to avoid being interrupted
+ * by a watchdog() call.
+ *
+ * Normally, Drupal's error handler calls watchdog() in response to a
+ * trigger_error() call. However, this invokes hook_watchdog() which can run
+ * arbitrary code. If the trigger_error() happens in the middle of an
+ * operation such as a rebuild operation which should not be interrupted by
+ * arbitrary code, that could potentially break or trigger the rebuild again.
+ * This function protects against that by delaying the watchdog() call until
+ * the end of the current page request.
+ *
+ * This is an internal function which should only be called by low-level Drupal
+ * core functions. It may be removed in a future Drupal 7 release.
+ *
+ * @param string $error_msg
+ * The error message to trigger. As with trigger_error() itself, this is
+ * limited to 1024 bytes; additional characters beyond that will be removed.
+ * @param int $error_type
+ * (optional) The type of error. This should be one of the E_USER family of
+ * constants. As with trigger_error() itself, this defaults to E_USER_NOTICE
+ * if not provided.
+ *
+ * @see _drupal_log_error()
+ */
+function _drupal_trigger_error_with_delayed_logging($error_msg, $error_type = E_USER_NOTICE) {
+ $delay_logging = &drupal_static(__FUNCTION__, FALSE);
+ $delay_logging = TRUE;
+ trigger_error($error_msg, $error_type);
+ $delay_logging = FALSE;
+}
+
+/**
+ * Writes the file scan cache to the persistent cache.
+ *
+ * This cache stores all files marked as missing or moved after a file scan
+ * to prevent unnecessary file scans in subsequent requests. This cache is
+ * cleared in system_list_reset() (i.e. after a module/theme rebuild).
+ */
+function drupal_file_scan_write_cache() {
+ // Only write to the persistent cache if requested, and if we know that any
+ // data previously in the cache was successfully loaded and merged in by
+ // _drupal_file_scan_cache().
+ $file_scans = &_drupal_file_scan_cache();
+ if (isset($file_scans['#write_cache']) && isset($file_scans['#cache_merge_done'])) {
+ unset($file_scans['#write_cache']);
+ cache_set('_drupal_file_scan_cache', $file_scans, 'cache_bootstrap');
}
}
@@ -1036,7 +1322,7 @@
* Determines the cacheability of the current page.
*
* @param $allow_caching
- * Set to FALSE if you want to prevent this page to get cached.
+ * Set to FALSE if you want to prevent this page from being cached.
*
* @return
* TRUE if the current page can be cached, FALSE otherwise.
@@ -1186,10 +1472,11 @@
* Headers are set in drupal_add_http_header(). Default headers are not set
* if they have been replaced or unset using drupal_add_http_header().
*
- * @param $default_headers
- * An array of headers as name/value pairs.
- * @param $single
- * If TRUE and headers have already be sent, send only the specified header.
+ * @param array $default_headers
+ * (optional) An array of headers as name/value pairs.
+ * @param bool $only_default
+ * (optional) If TRUE and headers have already been sent, send only the
+ * specified headers.
*/
function drupal_send_headers($default_headers = array(), $only_default = FALSE) {
$headers_sent = &drupal_static(__FUNCTION__, FALSE);
@@ -1212,7 +1499,7 @@
header($_SERVER['SERVER_PROTOCOL'] . ' ' . $value);
}
// Skip headers that have been unset.
- elseif ($value) {
+ elseif ($value !== FALSE) {
header($header_names[$name_lower] . ': ' . $value);
}
}
@@ -1225,23 +1512,10 @@
* 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()
*/
@@ -1254,9 +1528,11 @@
$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 . '"',
+ 'Cache-Control' => 'no-cache, must-revalidate',
+ // Prevent browsers from sniffing a response and picking a MIME type
+ // different from the declared content-type, since that can lead to
+ // XSS and other vulnerabilities.
+ 'X-Content-Type-Options' => 'nosniff',
);
drupal_send_headers($default_headers);
}
@@ -1274,7 +1550,7 @@
*/
function drupal_serve_page_from_cache(stdClass $cache) {
// Negotiate whether to use compression.
- $page_compression = variable_get('page_compression', TRUE) && extension_loaded('zlib');
+ $page_compression = !empty($cache->data['page_compressed']);
$return_compressed = $page_compression && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE;
// Get headers set in hook_boot(). Keys are lower-case.
@@ -1324,7 +1600,7 @@
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
@@ -1405,6 +1681,7 @@
* more information, including recommendations on how to break up or not
* break up strings for translation.
*
+ * @section sec_translating_vars Translating Variables
* You should never use t() to translate variables, such as calling
* @code t($text); @endcode, unless the text that the variable holds has been
* passed through t() elsewhere (e.g., $text is one of several translated
@@ -1420,13 +1697,32 @@
* Basically, you can put variables like @name into your string, and t() will
* substitute their sanitized values at translation time. (See the
* Localization API pages referenced above and the documentation of
- * format_string() for details.) Translators can then rearrange the string as
- * necessary for the language (e.g., in Spanish, it might be "blog de @name").
+ * format_string() for details about how to define variables in your string.)
+ * Translators can then rearrange the string as necessary for the language
+ * (e.g., in Spanish, it might be "blog de @name").
*
+ * @section sec_alt_funcs_install Use During Installation Phase
* During the Drupal installation phase, some resources used by t() wil not be
* available to code that needs localization. See st() and get_t() for
* alternatives.
*
+ * @section sec_context String context
+ * Matching source strings are normally only translated once, and the same
+ * translation is used everywhere that has a matching string. However, in some
+ * cases, a certain English source string needs to have multiple translations.
+ * One example of this is the string "May", which could be used as either a
+ * full month name or a 3-letter abbreviated month. In other languages where
+ * the month name for May has more than 3 letters, you would need to provide
+ * two different translations (one for the full name and one abbreviated), and
+ * the correct form would need to be chosen, depending on how "May" is being
+ * used. To facilitate this, the "May" string should be provided with two
+ * different contexts in the $options parameter when calling t(). For example:
+ * @code
+ * t('May', array(), array('context' => 'Long month name')
+ * t('May', array(), array('context' => 'Abbreviated month name')
+ * @endcode
+ * See https://localize.drupal.org/node/2109 for more information.
+ *
* @param $string
* A string containing the English string to translate.
* @param $args
@@ -1437,8 +1733,9 @@
* An associative array of additional options, with the following elements:
* - 'langcode' (defaults to the current language): The language code to
* translate to a language other than what is used to display the page.
- * - 'context' (defaults to the empty context): The context the source string
- * belongs to.
+ * - 'context' (defaults to the empty context): A string giving the context
+ * that the source string belongs to. See @ref sec_context above for more
+ * information.
*
* @return
* The translated string.
@@ -1484,21 +1781,34 @@
}
/**
- * Replaces placeholders with sanitized values in a string.
+ * Formats a string for HTML display by replacing variable placeholders.
+ *
+ * This function replaces variable placeholders in a string with the requested
+ * values and escapes the values so they can be safely displayed as HTML. It
+ * should be used on any unknown text that is intended to be printed to an HTML
+ * page (especially text that may have come from untrusted users, since in that
+ * case it prevents cross-site scripting and other security problems).
+ *
+ * In most cases, you should use t() rather than calling this function
+ * directly, since it will translate the text (on non-English-only sites) in
+ * addition to formatting it.
*
* @param $string
* A string containing placeholders.
* @param $args
* An associative array of replacements to make. Occurrences in $string of
- * any key in $args are replaced with the corresponding value, after
- * sanitization. The sanitization function depends on the first character of
- * the key:
- * - !variable: Inserted as is. Use this for text that has already been
- * sanitized.
- * - @variable: Escaped to HTML using check_plain(). Use this for anything
- * displayed on a page on the site.
- * - %variable: Escaped as a placeholder for user-submitted content using
- * drupal_placeholder(), which shows up as emphasized text.
+ * any key in $args are replaced with the corresponding value, after optional
+ * sanitization and formatting. The type of sanitization and formatting
+ * depends on the first character of the key:
+ * - @variable: Escaped to HTML using check_plain(). Use this as the default
+ * choice for anything displayed on a page on the site.
+ * - %variable: Escaped to HTML and formatted using drupal_placeholder(),
+ * which makes it display as emphasized text.
+ * - !variable: Inserted as is, with no sanitization or formatting. Only use
+ * this for text that has already been prepared for HTML display (for
+ * example, user-supplied text that has already been run through
+ * check_plain() previously, or is expected to contain some limited HTML
+ * tags and has already been run through filter_xss() previously).
*
* @see t()
* @ingroup sanitization
@@ -1531,12 +1841,13 @@
* 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
@@ -1621,14 +1932,14 @@
* 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) {
@@ -1754,7 +2065,7 @@
* @see theme_status_messages()
*/
function drupal_set_message($message = NULL, $type = 'status', $repeat = TRUE) {
- if ($message) {
+ if ($message || $message === '0' || $message === 0) {
if (!isset($_SESSION['messages'][$type])) {
$_SESSION['messages'][$type] = array();
}
@@ -1913,6 +2224,33 @@
}
/**
+ * Returns a URL-safe, base64 encoded string of highly randomized bytes (over the full 8-bit range).
+ *
+ * @param $byte_count
+ * The number of random bytes to fetch and base64 encode.
+ *
+ * @return string
+ * The base64 encoded result will have a length of up to 4 * $byte_count.
+ */
+function drupal_random_key($byte_count = 32) {
+ return drupal_base64_encode(drupal_random_bytes($byte_count));
+}
+
+/**
+ * Returns a URL-safe, base64 encoded version of the supplied string.
+ *
+ * @param $string
+ * The string to convert to base64.
+ *
+ * @return string
+ */
+function drupal_base64_encode($string) {
+ $data = base64_encode($string);
+ // Modify the output so it's safe to use in URLs.
+ return strtr($data, array('+' => '-', '/' => '_', '=' => ''));
+}
+
+/**
* Returns a string of highly randomized bytes (over the full 8-bit range).
*
* This function is better than simply calling mt_rand() or any other built-in
@@ -1925,38 +2263,34 @@
*/
function drupal_random_bytes($count) {
// $random_state does not use drupal_static as it stores random bytes.
- static $random_state, $bytes, $php_compatible;
- // Initialize on the first call. The contents of $_SERVER includes a mix of
- // user-specific and system information that varies a little with each page.
- if (!isset($random_state)) {
- $random_state = print_r($_SERVER, TRUE);
- if (function_exists('getmypid')) {
- // Further initialize with the somewhat random PHP process ID.
- $random_state .= getmypid();
- }
- $bytes = '';
- }
- if (strlen($bytes) < $count) {
+ static $random_state, $bytes, $has_openssl;
+
+ $missing_bytes = $count - strlen($bytes);
+
+ if ($missing_bytes > 0) {
// PHP versions prior 5.3.4 experienced openssl_random_pseudo_bytes()
// locking on Windows and rendered it unusable.
- if (!isset($php_compatible)) {
- $php_compatible = version_compare(PHP_VERSION, '5.3.4', '>=');
+ if (!isset($has_openssl)) {
+ $has_openssl = version_compare(PHP_VERSION, '5.3.4', '>=') && function_exists('openssl_random_pseudo_bytes');
}
- // /dev/urandom is available on many *nix systems and is considered the
- // best commonly available pseudo-random source.
- if ($fh = @fopen('/dev/urandom', 'rb')) {
+
+ // openssl_random_pseudo_bytes() will find entropy in a system-dependent
+ // way.
+ if ($has_openssl) {
+ $bytes .= openssl_random_pseudo_bytes($missing_bytes);
+ }
+
+ // Else, read directly from /dev/urandom, which is available on many *nix
+ // systems and is considered cryptographically secure.
+ elseif ($fh = @fopen('/dev/urandom', 'rb')) {
// PHP only performs buffered reads, so in reality it will always read
// at least 4096 bytes. Thus, it costs nothing extra to read and store
// that much so as to speed any additional invocations.
- $bytes .= fread($fh, max(4096, $count));
+ $bytes .= fread($fh, max(4096, $missing_bytes));
fclose($fh);
}
- // openssl_random_pseudo_bytes() will find entropy in a system-dependent
- // way.
- elseif ($php_compatible && function_exists('openssl_random_pseudo_bytes')) {
- $bytes .= openssl_random_pseudo_bytes($count - strlen($bytes));
- }
- // If /dev/urandom is not available or returns no bytes, this loop will
+
+ // If we couldn't get enough entropy, this simple hash-based PRNG will
// generate a good set of pseudo-random bytes on any system.
// Note that it may be important that our $random_state is passed
// through hash() prior to being rolled into $output, that the two hash()
@@ -1964,9 +2298,23 @@
// the microtime() - is prepended rather than appended. This is to avoid
// directly leaking $random_state via the $output stream, which could
// allow for trivial prediction of further "random" numbers.
- while (strlen($bytes) < $count) {
- $random_state = hash('sha256', microtime() . mt_rand() . $random_state);
- $bytes .= hash('sha256', mt_rand() . $random_state, TRUE);
+ if (strlen($bytes) < $count) {
+ // Initialize on the first call. The contents of $_SERVER includes a mix of
+ // user-specific and system information that varies a little with each page.
+ if (!isset($random_state)) {
+ $random_state = print_r($_SERVER, TRUE);
+ if (function_exists('getmypid')) {
+ // Further initialize with the somewhat random PHP process ID.
+ $random_state .= getmypid();
+ }
+ $bytes = '';
+ }
+
+ do {
+ $random_state = hash('sha256', microtime() . mt_rand() . $random_state);
+ $bytes .= hash('sha256', mt_rand() . $random_state, TRUE);
+ }
+ while (strlen($bytes) < $count);
}
}
$output = substr($bytes, 0, $count);
@@ -1977,17 +2325,21 @@
/**
* Calculates a base-64 encoded, URL-safe sha-256 hmac.
*
- * @param $data
+ * @param string $data
* String to be validated with the hmac.
- * @param $key
+ * @param string $key
* A secret string key.
*
- * @return
+ * @return string
* A base-64 encoded sha-256 hmac, with + replaced with -, / with _ and
* any = padding characters removed.
*/
function drupal_hmac_base64($data, $key) {
- $hmac = base64_encode(hash_hmac('sha256', $data, $key, TRUE));
+ // Casting $data and $key to strings here is necessary to avoid empty string
+ // results of the hash function if they are not scalar values. As this
+ // function is used in security-critical contexts like token validation it is
+ // important that it never returns an empty string.
+ $hmac = base64_encode(hash_hmac('sha256', (string) $data, (string) $key, TRUE));
// Modify the hmac so it's safe to use in URLs.
return strtr($hmac, array('+' => '-', '/' => '_', '=' => ''));
}
@@ -2088,7 +2440,7 @@
* @return Object - the user object.
*/
function drupal_anonymous_user() {
- $user = new stdClass();
+ $user = variable_get('drupal_anonymous_user_object', new stdClass);
$user->uid = 0;
$user->hostname = ip_address();
$user->roles = array();
@@ -2107,7 +2459,7 @@
* 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:
@@ -2120,11 +2472,11 @@
* - 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) {
@@ -2146,12 +2498,13 @@
// 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) {
@@ -2219,6 +2572,19 @@
}
/**
+ * Gets a salt useful for hardening against SQL injection.
+ *
+ * @return
+ * A salt based on information in settings.php, not in the database.
+ */
+function drupal_get_hash_salt() {
+ global $drupal_hash_salt, $databases;
+ // If the $drupal_hash_salt variable is empty, a hash of the serialized
+ // database credentials is used as a fallback salt.
+ return empty($drupal_hash_salt) ? hash('sha256', serialize($databases)) : $drupal_hash_salt;
+}
+
+/**
* Provides custom PHP error handling.
*
* @param $error_level
@@ -2279,6 +2645,10 @@
timer_start('page');
// Initialize the configuration, including variables from settings.php.
drupal_settings_initialize();
+
+ // Sanitize unsafe keys from the request.
+ require_once DRUPAL_ROOT . '/includes/request-sanitizer.inc';
+ DrupalRequestSanitizer::sanitize();
}
/**
@@ -2387,6 +2757,9 @@
// the install or upgrade process.
spl_autoload_register('drupal_autoload_class');
spl_autoload_register('drupal_autoload_interface');
+ if (version_compare(PHP_VERSION, '5.4') >= 0) {
+ spl_autoload_register('drupal_autoload_trait');
+ }
}
/**
@@ -2404,6 +2777,31 @@
// 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']);
+ }
+ // Use the DrupalRequestSanitizer to ensure that the destination's query
+ // parameters are not dangerous.
+ if (isset($_GET['destination'])) {
+ DrupalRequestSanitizer::cleanDestination();
+ }
+ // 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']);
+ }
+ }
}
/**
@@ -2426,7 +2824,7 @@
* @see drupal_bootstrap()
*/
function drupal_get_bootstrap_phase() {
- return drupal_bootstrap();
+ return drupal_bootstrap(NULL, FALSE);
}
/**
@@ -2438,7 +2836,6 @@
* HMAC and timestamp.
*/
function drupal_valid_test_ua() {
- global $drupal_hash_salt;
// No reason to reset this.
static $test_prefix;
@@ -2452,7 +2849,7 @@
// We use the salt from settings.php to make the HMAC key, since
// the database is not yet initialized and we can't access any Drupal variables.
// The file properties add more entropy not easily accessible to others.
- $key = $drupal_hash_salt . filectime(__FILE__) . fileinode(__FILE__);
+ $key = drupal_get_hash_salt() . filectime(__FILE__) . fileinode(__FILE__);
$time_diff = REQUEST_TIME - $time;
// Since we are making a local request a 5 second time window is allowed,
// and the HMAC must match.
@@ -2470,14 +2867,13 @@
* Generates a user agent string with a HMAC and timestamp for simpletest.
*/
function drupal_generate_test_ua($prefix) {
- global $drupal_hash_salt;
static $key;
if (!isset($key)) {
// We use the salt from settings.php to make the HMAC key, since
// the database is not yet initialized and we can't access any Drupal variables.
// The file properties add more entropy not easily accessible to others.
- $key = $drupal_hash_salt . filectime(__FILE__) . fileinode(__FILE__);
+ $key = drupal_get_hash_salt() . filectime(__FILE__) . fileinode(__FILE__);
}
// Generate a moderately secure HMAC based on the database credentials.
$salt = uniqid('', TRUE);
@@ -2542,7 +2938,7 @@
*
* 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:
@@ -2684,10 +3080,14 @@
}
/**
- * Returns the default language used on the site
+ * Returns the default language, as an object, or one of its properties.
*
* @param $property
- * Optional property of the language object to return
+ * (optional) The property of the language object to return.
+ *
+ * @return
+ * Either the language object for the default language used on the site,
+ * or the property of that object named in the $property parameter.
*/
function language_default($property = NULL) {
$language = variable_get('language_default', (object) array('language' => 'en', 'name' => 'English', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => '', 'weight' => 0, 'javascript' => ''));
@@ -2839,8 +3239,15 @@
// Eliminate all trusted IPs.
$untrusted = array_diff($forwarded, $reverse_proxy_addresses);
- // The right-most IP is the most specific we can trust.
- $ip_address = array_pop($untrusted);
+ if (!empty($untrusted)) {
+ // The right-most IP is the most specific we can trust.
+ $ip_address = array_pop($untrusted);
+ }
+ else {
+ // All IP addresses in the forwarded array are configured proxy IPs
+ // (and thus trusted). We take the leftmost IP.
+ $ip_address = array_shift($forwarded);
+ }
}
}
}
@@ -2857,7 +3264,9 @@
* Gets the schema definition of a table, or the whole database schema.
*
* The returned schema will include any modifications made by any
- * module that implements hook_schema_alter().
+ * module that implements hook_schema_alter(). To get the schema without
+ * modifications, use drupal_get_schema_unprocessed().
+ *
*
* @param $table
* The name of the table. If not given, the schema of all tables is returned.
@@ -3013,6 +3422,22 @@
}
/**
+ * Confirms that a trait is available.
+ *
+ * This function is rarely called directly. Instead, it is registered as an
+ * spl_autoload() handler, and PHP calls it for us when necessary.
+ *
+ * @param string $trait
+ * The name of the trait to check or load.
+ *
+ * @return bool
+ * TRUE if the trait is currently available, FALSE otherwise.
+ */
+function drupal_autoload_trait($trait) {
+ return _registry_check_code('trait', $trait);
+}
+
+/**
* Checks for a resource in the registry.
*
* @param $type
@@ -3030,7 +3455,7 @@
function _registry_check_code($type, $name = NULL) {
static $lookup_cache, $cache_update_needed;
- if ($type == 'class' && class_exists($name) || $type == 'interface' && interface_exists($name)) {
+ if ($type == 'class' && class_exists($name) || $type == 'interface' && interface_exists($name) || $type == 'trait' && trait_exists($name)) {
return TRUE;
}
@@ -3063,7 +3488,7 @@
$cache_key = $type[0] . $name;
if (isset($lookup_cache[$cache_key])) {
if ($lookup_cache[$cache_key]) {
- require_once DRUPAL_ROOT . '/' . $lookup_cache[$cache_key];
+ include_once DRUPAL_ROOT . '/' . $lookup_cache[$cache_key];
}
return (bool) $lookup_cache[$cache_key];
}
@@ -3071,10 +3496,13 @@
// 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.
@@ -3085,7 +3513,7 @@
$lookup_cache[$cache_key] = $file;
if ($file) {
- require_once DRUPAL_ROOT . '/' . $file;
+ include_once DRUPAL_ROOT . '/' . $file;
return TRUE;
}
else {
@@ -3222,8 +3650,8 @@
* However, the above line of code does not work, because PHP only allows static
* variables to be initializied by literal values, and does not allow static
* variables to be assigned to references.
- * - http://php.net/manual/en/language.variables.scope.php#language.variables.scope.static
- * - http://php.net/manual/en/language.variables.scope.php#language.variables.scope.references
+ * - http://php.net/manual/language.variables.scope.php#language.variables.scope.static
+ * - http://php.net/manual/language.variables.scope.php#language.variables.scope.references
* The example below shows the syntax needed to work around both limitations.
* For benchmarks and more information, see http://drupal.org/node/619666.
*
@@ -3248,11 +3676,9 @@
* @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.
@@ -3297,6 +3723,8 @@
*
* @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);
@@ -3370,8 +3798,12 @@
chdir(DRUPAL_ROOT);
try {
- while (list($key, $callback) = each($callbacks)) {
+ // Manually iterate over the array instead of using a foreach loop.
+ // A foreach operates on a copy of the array, so any shutdown functions that
+ // were added from other shutdown functions would never be called.
+ while ($callback = current($callbacks)) {
call_user_func_array($callback['callback'], $callback['arguments']);
+ next($callbacks);
}
}
catch (Exception $exception) {
@@ -3383,3 +3815,63 @@
}
}
}
+
+/**
+ * Compares the memory required for an operation to the available memory.
+ *
+ * @param $required
+ * The memory required for the operation, expressed as a number of bytes with
+ * optional SI or IEC binary unit prefix (e.g. 2, 3K, 5MB, 10G, 6GiB, 8bytes,
+ * 9mbytes).
+ * @param $memory_limit
+ * (optional) The memory limit for the operation, expressed as a number of
+ * bytes with optional SI or IEC binary unit prefix (e.g. 2, 3K, 5MB, 10G,
+ * 6GiB, 8bytes, 9mbytes). If no value is passed, the current PHP
+ * memory_limit will be used. Defaults to NULL.
+ *
+ * @return
+ * TRUE if there is sufficient memory to allow the operation, or FALSE
+ * otherwise.
+ */
+function drupal_check_memory_limit($required, $memory_limit = NULL) {
+ if (!isset($memory_limit)) {
+ $memory_limit = ini_get('memory_limit');
+ }
+
+ // There is sufficient memory if:
+ // - No memory limit is set.
+ // - The memory limit is set to unlimited (-1).
+ // - 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);
+ }
+}
diff -Naur drupal-7.18/includes/cache.inc drupal-7.66/includes/cache.inc
--- drupal-7.18/includes/cache.inc 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/includes/cache.inc 2019-04-17 22:20:46.000000000 +0200
@@ -14,6 +14,7 @@
*
* @param $bin
* The cache bin for which the cache object should be returned.
+ *
* @return DrupalCacheInterface
* The cache object associated with the specified bin.
*
@@ -80,43 +81,15 @@
* same name. Other implementations might want to store several bins in data
* structures that get flushed together. While it is not a problem for most
* cache bins if the entries in them are flushed before their expire time, some
- * might break functionality or are extremely expensive to recalculate. These
- * will be marked with a (*). The other bins expired automatically by core.
- * Contributed modules can add additional bins and get them expired
- * automatically by implementing hook_flush_caches().
- *
- * - cache: Generic cache storage bin (used for variables, theme registry,
- * locale date, list of simpletest tests etc).
- *
- * - cache_block: Stores the content of various blocks.
- *
- * - cache field: Stores the field data belonging to a given object.
- *
- * - cache_filter: Stores filtered pieces of content.
- *
- * - cache_form(*): Stores multistep forms. Flushing this bin means that some
- * forms displayed to users lose their state and the data already submitted
- * to them.
- *
- * - cache_menu: Stores the structure of visible navigation menus per page.
- *
- * - cache_page: Stores generated pages for anonymous users. It is flushed
- * very often, whenever a page changes, at least for every ode and comment
- * submission. This is the only bin affected by the page cache setting on
- * the administrator panel.
- *
- * - cache path: Stores the system paths that have an alias.
- *
- * - cache update(*): Stores available releases. The update server (for
- * example, drupal.org) needs to produce the relevant XML for every project
- * installed on the current site. As this is different for (almost) every
- * site, it's very expensive to recalculate for the update server.
+ * might break functionality or are extremely expensive to recalculate. The
+ * other bins are expired automatically by core. Contributed modules can add
+ * additional bins and get them expired automatically by implementing
+ * hook_flush_caches().
*
* The reasons for having several bins are as follows:
- *
- * - smaller bins mean smaller database tables and allow for faster selects and
- * inserts
- * - we try to put fast changing cache items and rather static ones into
+ * - Smaller bins mean smaller database tables and allow for faster selects and
+ * inserts.
+ * - We try to put fast changing cache items and rather static ones into
* different bins. The effect is that only the fast changing bins will need a
* lot of writes to disk. The more static bins will also be better cacheable
* with MySQL's query cache.
@@ -125,15 +98,36 @@
* 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 are
+ * 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 'cache_block',
- * 'cache_bootstrap', 'cache_field', 'cache_filter', 'cache_form',
- * 'cache_menu', 'cache_page', 'cache_update' or 'cache' for the default
- * cache.
+ * (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.
+ * - cache_bootstrap: Stores the class registry, the system list of modules,
+ * the list of which modules implement which hooks, and the Drupal variable
+ * list.
+ * - cache_field: Stores the field data belonging to a given object.
+ * - cache_filter: Stores filtered pieces of content.
+ * - cache_form: Stores multistep forms. Flushing this bin means that some
+ * forms displayed to users lose their state and the data already submitted
+ * to them. This bin should not be flushed before its expired time.
+ * - cache_menu: Stores the structure of visible navigation menus per page.
+ * - cache_page: Stores generated pages for anonymous users. It is flushed
+ * very often, whenever a page changes, at least for every node and comment
+ * submission. This is the only bin affected by the page cache setting on
+ * the administrator panel.
+ * - cache_path: Stores the system paths that have an alias.
* @param $expire
- * One of the following values:
+ * (optional) Controls the maximum lifetime of this cache entry. Note that
+ * caches might be subject to clearing at any time, so this setting does not
+ * guarantee a minimum lifetime. With this in mind, the cache should not be
+ * used for data that must be kept during a cache clear, like sessions.
+ *
+ * Use 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
@@ -141,6 +135,7 @@
* - A Unix timestamp: Indicates that the item should be kept at least until
* the given time, after which it behaves like CACHE_TEMPORARY.
*
+ * @see _update_cache_set()
* @see cache_get()
*/
function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) {
@@ -150,18 +145,20 @@
/**
* Expires data from the cache.
*
- * If called without arguments, expirable entries will be cleared from the
- * cache_page and cache_block bins.
+ * If called with the arguments $cid and $bin set to NULL or omitted, then
+ * expirable entries will be cleared from the cache_page and cache_block bins,
+ * and the $wildcard argument is ignored.
*
* @param $cid
- * If set, the cache ID to delete. Otherwise, all cache entries that can
- * expire are deleted.
+ * If set, the cache ID or an array of cache IDs. Otherwise, all cache entries
+ * that can expire are deleted. The $wildcard argument will be ignored if set
+ * to NULL.
* @param $bin
* If set, the cache bin to delete from. Mandatory argument if $cid is set.
* @param $wildcard
- * If TRUE, cache IDs starting with $cid are deleted in addition to the
- * exact cache ID specified by $cid. If $wildcard is TRUE and $cid is '*',
- * the entire cache bin is emptied.
+ * If TRUE, the $cid argument must contain a string value and cache IDs
+ * starting with $cid are deleted in addition to the exact cache ID specified
+ * by $cid. If $wildcard is TRUE and $cid is '*', the entire cache is emptied.
*/
function cache_clear_all($cid = NULL, $bin = NULL, $wildcard = FALSE) {
if (!isset($cid) && !isset($bin)) {
@@ -230,13 +227,6 @@
* @see DrupalDatabaseCache
*/
interface DrupalCacheInterface {
- /**
- * Constructs a new cache interface.
- *
- * @param $bin
- * The cache bin for which the object is created.
- */
- function __construct($bin);
/**
* Returns data from the persistent cache.
@@ -272,10 +262,17 @@
* 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) Controls the maximum lifetime of this cache entry. Note that
+ * caches might be subject to clearing at any time, so this setting does not
+ * guarantee a minimum lifetime. With this in mind, the cache should not be
+ * used for data that must be kept during a cache clear, like sessions.
+ *
+ * Use 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
@@ -293,12 +290,14 @@
* cache_page and cache_block bins.
*
* @param $cid
- * If set, the cache ID to delete. Otherwise, all cache entries that can
- * expire are deleted.
+ * If set, the cache ID or an array of cache IDs. Otherwise, all cache
+ * entries that can expire are deleted. The $wildcard argument will be
+ * ignored if set to NULL.
* @param $wildcard
- * If set to TRUE, the $cid is treated as a substring
- * to match rather than a complete ID. The match is a right hand
- * match. If '*' is given as $cid, the bin $bin will be emptied.
+ * If TRUE, the $cid argument must contain a string value and cache IDs
+ * starting with $cid are deleted in addition to the exact cache ID
+ * specified by $cid. If $wildcard is TRUE and $cid is '*', the entire
+ * cache is emptied.
*/
function clear($cid = NULL, $wildcard = FALSE);
@@ -324,7 +323,10 @@
protected $bin;
/**
- * Constructs a new DrupalDatabaseCache object.
+ * Constructs a DrupalDatabaseCache object.
+ *
+ * @param $bin
+ * The cache bin for which the object is created.
*/
function __construct($bin) {
$this->bin = $bin;
@@ -518,7 +520,16 @@
else {
if ($wildcard) {
if ($cid == '*') {
- db_truncate($this->bin)->execute();
+ // Check if $this->bin is a cache table before truncating. Other
+ // cache_clear_all() operations throw a PDO error in this situation,
+ // so we don't need to verify them first. This ensures that non-cache
+ // tables cannot be truncated accidentally.
+ if ($this->isValidBin()) {
+ db_truncate($this->bin)->execute();
+ }
+ else {
+ throw new Exception(t('Invalid or missing cache bin specified: %bin', array('%bin' => $this->bin)));
+ }
}
else {
db_delete($this->bin)
@@ -555,4 +566,25 @@
->fetchField();
return empty($result);
}
+
+ /**
+ * Checks if $this->bin represents a valid cache table.
+ *
+ * This check is required to ensure that non-cache tables are not truncated
+ * accidentally when calling cache_clear_all().
+ *
+ * @return boolean
+ */
+ function isValidBin() {
+ if ($this->bin == 'cache' || substr($this->bin, 0, 6) == 'cache_') {
+ // Skip schema check for bins with standard table names.
+ return TRUE;
+ }
+ // These fields are required for any cache table.
+ $fields = array('cid', 'data', 'expire', 'created', 'serialized');
+ // Load the table schema.
+ $schema = drupal_get_schema($this->bin);
+ // Confirm that all fields are present.
+ return isset($schema['fields']) && !array_diff($fields, array_keys($schema['fields']));
+ }
}
diff -Naur drupal-7.18/includes/common.inc drupal-7.66/includes/common.inc
--- drupal-7.18/includes/common.inc 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/includes/common.inc 2019-04-17 22:20:46.000000000 +0200
@@ -281,7 +281,7 @@
/**
* Adds output to the HEAD tag of the HTML page.
*
- * This function can be called as long the headers aren't sent. Pass no
+ * This function can be called as long as the headers aren't sent. Pass no
* arguments (or NULL for both) to retrieve the currently stored elements.
*
* @param $data
@@ -458,7 +458,7 @@
$result = array();
if (!empty($query)) {
foreach (explode('&', $query) as $param) {
- $param = explode('=', $param);
+ $param = explode('=', $param, 2);
$result[$param[0]] = isset($param[1]) ? rawurldecode($param[1]) : '';
}
}
@@ -487,7 +487,7 @@
$params = array();
foreach ($query as $key => $value) {
- $key = ($parent ? $parent . '[' . rawurlencode($key) . ']' : rawurlencode($key));
+ $key = $parent ? $parent . rawurlencode('[' . $key . ']') : rawurlencode($key);
// Recurse into children.
if (is_array($value)) {
@@ -544,37 +544,32 @@
}
/**
- * 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
+ * Note that, unlike the RFC, when passed an external URL, this function
+ * groups the scheme, authority, and path together into the path component.
*
- * 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'].
+ * @param string $url
+ * The internal path or external URL string to parse.
*
- * @param $url
- * The URL string to parse, f.e. $_GET['destination'].
- *
- * @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) {
@@ -616,8 +611,9 @@
}
// The 'q' parameter contains the path of the current page if clean URLs are
// disabled. It overrides the 'path' of the URL when present, even if clean
- // URLs are enabled, due to how Apache rewriting rules work.
- if (isset($options['query']['q'])) {
+ // URLs are enabled, due to how Apache rewriting rules work. The path
+ // parameter must be a string.
+ if (isset($options['query']['q']) && is_string($options['query']['q'])) {
$options['path'] = $options['query']['q'];
unset($options['query']['q']);
}
@@ -641,7 +637,7 @@
}
/**
- * Sends the user to a different Drupal page.
+ * Sends the user to a different page.
*
* This issues an on-site HTTP redirect. The function makes sure the redirected
* URL is formatted correctly.
@@ -693,6 +689,13 @@
$options['fragment'] = $destination['fragment'];
}
+ // In some cases modules call drupal_goto(current_path()). We need to ensure
+ // that such a redirect is not to an external URL.
+ if ($path === current_path() && empty($options['external']) && url_is_external($path)) {
+ // Force url() to generate a non-external URL.
+ $options['external'] = FALSE;
+ }
+
drupal_alter('drupal_goto', $path, $options, $http_response_code);
// The 'Location' HTTP header must be absolute.
@@ -758,7 +761,8 @@
* - headers: An array containing request headers to send as name/value pairs.
* - method: A string containing the request method. Defaults to 'GET'.
* - data: A string containing the request body, formatted as
- * 'param=value¶m=value&...'. Defaults to NULL.
+ * 'param=value¶m=value&...'; to generate this, use http_build_query().
+ * Defaults to NULL.
* - max_redirects: An integer representing how many times a redirect
* may be followed. Defaults to 3.
* - timeout: A float representing the maximum number of seconds the function
@@ -783,8 +787,17 @@
* HTTP header names are case-insensitive (RFC 2616, section 4.2), so for
* easy access the array keys are returned in lower case.
* - data: A string containing the response body that was received.
+ *
+ * @see http_build_query()
*/
function drupal_http_request($url, array $options = array()) {
+ // Allow an alternate HTTP client library to replace Drupal's default
+ // implementation.
+ $override_function = variable_get('drupal_http_request_function', FALSE);
+ if (!empty($override_function) && function_exists($override_function)) {
+ return $override_function($url, $options);
+ }
+
$result = new stdClass();
// Parse the URL and make sure we can handle the schema.
@@ -854,8 +867,10 @@
// Make the socket connection to a proxy server.
$socket = 'tcp://' . $proxy_server . ':' . variable_get('proxy_port', 8080);
// The Host header still needs to match the real request.
- $options['headers']['Host'] = $uri['host'];
- $options['headers']['Host'] .= isset($uri['port']) && $uri['port'] != 80 ? ':' . $uri['port'] : '';
+ if (!isset($options['headers']['Host'])) {
+ $options['headers']['Host'] = $uri['host'];
+ $options['headers']['Host'] .= isset($uri['port']) && $uri['port'] != 80 ? ':' . $uri['port'] : '';
+ }
break;
case 'http':
@@ -865,14 +880,18 @@
// RFC 2616: "non-standard ports MUST, default ports MAY be included".
// We don't add the standard port to prevent from breaking rewrite rules
// checking the host that do not take into account the port number.
- $options['headers']['Host'] = $uri['host'] . ($port != 80 ? ':' . $port : '');
+ if (!isset($options['headers']['Host'])) {
+ $options['headers']['Host'] = $uri['host'] . ($port != 80 ? ':' . $port : '');
+ }
break;
case 'https':
// Note: Only works when PHP is compiled with OpenSSL support.
$port = isset($uri['port']) ? $uri['port'] : 443;
$socket = 'ssl://' . $uri['host'] . ':' . $port;
- $options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : '');
+ if (!isset($options['headers']['Host'])) {
+ $options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : '');
+ }
break;
default:
@@ -922,7 +941,7 @@
// If the server URL has a user then attempt to use basic authentication.
if (isset($uri['user'])) {
- $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (isset($uri['pass']) ? ':' . $uri['pass'] : ''));
+ $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (isset($uri['pass']) ? ':' . $uri['pass'] : ':'));
}
// If the database prefix is being used by SimpleTest to run the tests in a copied
@@ -983,9 +1002,10 @@
$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();
@@ -1054,6 +1074,12 @@
switch ($code) {
case 200: // OK
+ case 201: // Created
+ case 202: // Accepted
+ case 203: // Non-Authoritative Information
+ case 204: // No Content
+ case 205: // Reset Content
+ case 206: // Partial Content
case 304: // Not modified
break;
case 301: // Moved permanently
@@ -1068,6 +1094,11 @@
elseif ($options['max_redirects']) {
// Redirect to the new location.
$options['max_redirects']--;
+
+ // We need to unset the 'Host' header
+ // as we are redirecting to a new location.
+ unset($options['headers']['Host']);
+
$result = drupal_http_request($location, $options);
$result->redirect_code = $code;
}
@@ -1076,13 +1107,44 @@
}
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.
*
* @return
@@ -1127,7 +1189,7 @@
* @param $key
* The key for the item within $_FILES.
*
- * @see http://php.net/manual/en/features.file-upload.php#42280
+ * @see http://php.net/manual/features.file-upload.php#42280
*/
function _fix_gpc_magic_files(&$item, $key) {
if ($key != 'tmp_name') {
@@ -1167,7 +1229,8 @@
/**
* Verifies the syntax of the given e-mail address.
*
- * See @link http://tools.ietf.org/html/rfc5321 RFC 5321 @endlink for details.
+ * This uses the
+ * @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink
*
* @param $mail
* A string containing an e-mail address.
@@ -1418,7 +1481,6 @@
* valid UTF-8.
*
* @see drupal_validate_utf8()
- * @ingroup sanitization
*/
function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) {
// Only operate on valid UTF-8 strings. This is necessary to prevent cross
@@ -1488,7 +1550,7 @@
return '<';
}
- if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?|()$%', $string, $matches)) {
+ if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9\-]+)([^>]*)>?|()$%', $string, $matches)) {
// Seriously malformed.
return '';
}
@@ -1720,9 +1782,15 @@
* - 'key': element name
* - 'value': element contents
* - 'attributes': associative array of element attributes
+ * - 'encoded': TRUE if 'value' is already encoded
*
* In both cases, 'value' can be a simple string, or it can be another array
* with the same format as $array itself for nesting.
+ *
+ * If 'encoded' is TRUE it is up to the caller to ensure that 'value' is either
+ * entity-encoded or CDATA-escaped. Using this option is not recommended when
+ * working with untrusted user input, since failing to escape the data
+ * correctly has security implications.
*/
function format_xml_elements($array) {
$output = '';
@@ -1735,7 +1803,7 @@
}
if (isset($value['value']) && $value['value'] != '') {
- $output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : check_plain($value['value'])) . '' . $value['key'] . ">\n";
+ $output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : (!empty($value['encoded']) ? $value['value'] : check_plain($value['value']))) . '' . $value['key'] . ">\n";
}
else {
$output .= " />\n";
@@ -1942,7 +2010,7 @@
* get interpreted as date format characters.
* @param $timezone
* (optional) Time zone identifier, as described at
- * http://php.net/manual/en/timezones.php Defaults to the time zone used to
+ * http://php.net/manual/timezones.php Defaults to the time zone used to
* display the page.
* @param $langcode
* (optional) Language code to translate to. Defaults to the language used to
@@ -2078,6 +2146,9 @@
/**
* Format a username.
*
+ * This is also the label callback implementation of
+ * callback_entity_info_label() for user_entity_info().
+ *
* By default, the passed-in object's 'name' property is used if it exists, or
* else, the site-defined value for the 'anonymous' variable. However, a module
* may override this by implementing hook_username_alter(&$name, $account).
@@ -2177,14 +2248,11 @@
'prefix' => ''
);
+ // Determine whether this is an external link, but ensure that the current
+ // path is always treated as internal by default (to prevent external link
+ // injection vulnerabilities).
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.
- $colonpos = strpos($path, ':');
- $options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $path);
+ $options['external'] = $path === $_GET['q'] ? FALSE : url_is_external($path);
}
// Preserve the original path before altering or aliasing.
@@ -2222,6 +2290,11 @@
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.
@@ -2249,7 +2322,10 @@
$language = isset($options['language']) && isset($options['language']->language) ? $options['language']->language : '';
$alias = drupal_get_path_alias($original_path, $language);
if ($alias != $original_path) {
- $path = $alias;
+ // Strip leading slashes from internal path aliases to prevent them
+ // becoming external URLs without protocol. /example.com should not be
+ // turned into //example.com.
+ $path = ltrim($alias, '/');
}
}
@@ -2299,10 +2375,21 @@
*/
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;
+ // Some browsers treat \ as / so normalize to forward slashes.
+ $path = str_replace('\\', '/', $path);
+ // If the path starts with 2 slashes then it is always considered an external
+ // URL without an explicit protocol part.
+ return (strpos($path, '//') === 0)
+ // Leading control characters may be ignored or mishandled by browsers, so
+ // assume such a path may lead to an external location. The \p{C} character
+ // class matches all UTF-8 control, unassigned, and private characters.
+ || (preg_match('/^\p{C}/u', $path) !== 0)
+ // 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.
+ || ($colonpos !== FALSE
+ && !preg_match('![/?#]!', substr($path, 0, $colonpos))
+ && drupal_strip_dangerous_protocols($path) == $path);
}
/**
@@ -2379,6 +2466,14 @@
* internal links output by modules should be generated by this function if
* possible.
*
+ * However, for links enclosed in translatable text you should use t() and
+ * embed the HTML anchor tag directly in the translated string. For example:
+ * @code
+ * t('Visit the settings page', array('@url' => url('admin')));
+ * @endcode
+ * This keeps the context of the link title ('settings' in the example) for
+ * translators.
+ *
* @param string $text
* The translated link text for the anchor tag.
* @param string $path
@@ -2576,6 +2671,15 @@
global $language;
drupal_add_http_header('Content-Language', $language->language);
+ // By default, do not allow the site to be rendered in an iframe on another
+ // domain, but provide a variable to override this. If the code running for
+ // this page request already set the X-Frame-Options header earlier, don't
+ // overwrite it here.
+ $frame_options = variable_get('x_frame_options', 'SAMEORIGIN');
+ if ($frame_options && is_null(drupal_get_http_header('X-Frame-Options'))) {
+ drupal_add_http_header('X-Frame-Options', $frame_options);
+ }
+
// Menu status constants are integers; page content is a string or array.
if (is_int($page_callback_result)) {
// @todo: Break these up into separate functions?
@@ -2591,7 +2695,10 @@
// 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', ''));
@@ -2620,7 +2727,10 @@
// 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', ''));
@@ -2684,6 +2794,7 @@
_registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE);
drupal_cache_system_paths();
module_implements_write_cache();
+ drupal_file_scan_write_cache();
system_run_automated_cron();
}
@@ -2745,11 +2856,11 @@
* into script execution a call such as set_time_limit(20) is made, the
* script will run for a total of 45 seconds before timing out.
*
- * It also means that it is possible to decrease the total time limit if
- * the sum of the new time limit and the current time spent running the
- * script is inferior to the original time limit. It is inherent to the way
- * set_time_limit() works, it should rather be called with an appropriate
- * value every time you need to allocate a certain amount of time
+ * If the current time limit is not unlimited it is possible to decrease the
+ * total time limit if the sum of the new time limit and the current time spent
+ * running the script is inferior to the original time limit. It is inherent to
+ * the way set_time_limit() works, it should rather be called with an
+ * appropriate value every time you need to allocate a certain amount of time
* to execute a task than only once at the beginning of the script.
*
* Before calling set_time_limit(), we check if this function is available
@@ -2766,7 +2877,11 @@
*/
function drupal_set_time_limit($time_limit) {
if (function_exists('set_time_limit')) {
- @set_time_limit($time_limit);
+ $current = ini_get('max_execution_time');
+ // Do not set time limit if it is currently unlimited.
+ if ($current != 0) {
+ @set_time_limit($time_limit);
+ }
}
}
@@ -2779,7 +2894,7 @@
* The name of the item for which the path is requested.
*
* @return
- * The path to the requested item.
+ * The path to the requested item or an empty string if the item is not found.
*/
function drupal_get_path($type, $name) {
return dirname(drupal_get_filename($type, $name));
@@ -2947,6 +3062,13 @@
*/
function drupal_add_css($data = NULL, $options = NULL) {
$css = &drupal_static(__FUNCTION__, array());
+ $count = &drupal_static(__FUNCTION__ . '_count', 0);
+
+ // If the $css variable has been reset with drupal_static_reset(), there is
+ // no longer any CSS being tracked, so set the counter back to 0 also.
+ if (count($css) === 0) {
+ $count = 0;
+ }
// Construct the options, taking the defaults into consideration.
if (isset($options)) {
@@ -2982,7 +3104,8 @@
}
// Always add a tiny value to the weight, to conserve the insertion order.
- $options['weight'] += count($css) / 1000;
+ $options['weight'] += $count / 1000;
+ $count++;
// Add the data to the CSS array depending on the type.
switch ($options['type']) {
@@ -3429,7 +3552,11 @@
$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;
@@ -3654,17 +3781,23 @@
if ($basepath && !file_uri_scheme($file)) {
$file = $basepath . '/' . $file;
}
+ // Store the parent base path to restore it later.
+ $parent_base_path = $basepath;
+ // Set the current base path to process possible child imports.
$basepath = dirname($file);
// Load the CSS stylesheet. We suppress errors because themes may specify
// stylesheets in their .info file that don't exist in the theme's path,
// but are merely there to disable certain module CSS files.
+ $content = '';
if ($contents = @file_get_contents($file)) {
// Return the processed stylesheet.
- return drupal_load_stylesheet_content($contents, $_optimize);
+ $content = drupal_load_stylesheet_content($contents, $_optimize);
}
- return '';
+ // Restore the parent base path as the file and its childen are processed.
+ $basepath = $parent_base_path;
+ return $content;
}
/**
@@ -3681,7 +3814,7 @@
*/
function drupal_load_stylesheet_content($contents, $optimize = FALSE) {
// Remove multiple charset declarations for standards compliance (and fixing Safari problems).
- $contents = preg_replace('/^@charset\s+[\'"](\S*)\b[\'"];/i', '', $contents);
+ $contents = preg_replace('/^@charset\s+[\'"](\S*?)\b[\'"];/i', '', $contents);
if ($optimize) {
// Perform some safe CSS optimizations.
@@ -3700,7 +3833,7 @@
// Remove certain whitespace.
// There are different conditions for removing leading and trailing
// whitespace.
- // @see http://php.net/manual/en/regexp.reference.subpatterns.php
+ // @see http://php.net/manual/regexp.reference.subpatterns.php
$contents = preg_replace('<
# Strip leading and trailing whitespace.
\s*([@{};,])\s*
@@ -3725,7 +3858,7 @@
// Replaces @import commands with the actual stylesheet content.
// This happens recursively but omits external files.
- $contents = preg_replace_callback('/@import\s*(?:url\(\s*)?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\s*\)?\s*;/', '_drupal_load_stylesheet', $contents);
+ $contents = preg_replace_callback('/@import\s*(?:url\(\s*)?[\'"]?(?![a-z]+:)(?!\/\/)([^\'"\()]+)[\'"]?\s*\)?\s*;/', '_drupal_load_stylesheet', $contents);
return $contents;
}
@@ -3749,7 +3882,7 @@
// 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);
}
/**
@@ -3785,6 +3918,21 @@
* The cleaned identifier.
*/
function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_' => '-', '/' => '-', '[' => '-', ']' => '')) {
+ // Use the advanced drupal_static() pattern, since this is called very often.
+ static $drupal_static_fast;
+ if (!isset($drupal_static_fast)) {
+ $drupal_static_fast['allow_css_double_underscores'] = &drupal_static(__FUNCTION__ . ':allow_css_double_underscores');
+ }
+ $allow_css_double_underscores = &$drupal_static_fast['allow_css_double_underscores'];
+ if (!isset($allow_css_double_underscores)) {
+ $allow_css_double_underscores = variable_get('allow_css_double_underscores', FALSE);
+ }
+
+ // Preserve BEM-style double-underscores depending on custom setting.
+ if ($allow_css_double_underscores) {
+ $filter['__'] = '__';
+ }
+
// By default, we filter using Drupal's coding standards.
$identifier = strtr($identifier, $filter);
@@ -3814,7 +3962,14 @@
* The cleaned class name.
*/
function drupal_html_class($class) {
- return drupal_clean_css_identifier(drupal_strtolower($class));
+ // The output of this function will never change, so this uses a normal
+ // static instead of drupal_static().
+ static $classes = array();
+
+ if (!isset($classes[$class])) {
+ $classes[$class] = drupal_clean_css_identifier(drupal_strtolower($class));
+ }
+ return $classes[$class];
}
/**
@@ -3849,7 +4004,11 @@
// be merged with content already on the base page. The HTML IDs must be
// unique for the fully merged content. Therefore, initialize $seen_ids to
// take into account IDs that are already in use on the base page.
- $seen_ids_init = &drupal_static(__FUNCTION__ . ':init');
+ static $drupal_static_fast;
+ if (!isset($drupal_static_fast['seen_ids_init'])) {
+ $drupal_static_fast['seen_ids_init'] = &drupal_static(__FUNCTION__ . ':init');
+ }
+ $seen_ids_init = &$drupal_static_fast['seen_ids_init'];
if (!isset($seen_ids_init)) {
// Ideally, Drupal would provide an API to persist state information about
// prior page requests in the database, and we'd be able to add this
@@ -3869,7 +4028,16 @@
// requested id. $_POST['ajax_html_ids'] contains the ids as they were
// returned by this function, potentially with the appended counter, so
// we parse that to reconstruct the $seen_ids array.
- foreach ($_POST['ajax_html_ids'] as $seen_id) {
+ if (isset($_POST['ajax_html_ids'][0]) && strpos($_POST['ajax_html_ids'][0], ',') === FALSE) {
+ $ajax_html_ids = $_POST['ajax_html_ids'];
+ }
+ else {
+ // jquery.form.js may send the server a comma-separated string as the
+ // first element of an array (see http://drupal.org/node/1575060), so
+ // we need to convert it to an array in that case.
+ $ajax_html_ids = explode(',', $_POST['ajax_html_ids'][0]);
+ }
+ foreach ($ajax_html_ids as $seen_id) {
// We rely on '--' being used solely for separating a base id from the
// counter, which this function ensures when returning an id.
$parts = explode('--', $seen_id, 2);
@@ -3885,7 +4053,10 @@
}
}
}
- $seen_ids = &drupal_static(__FUNCTION__, $seen_ids_init);
+ if (!isset($drupal_static_fast['seen_ids'])) {
+ $drupal_static_fast['seen_ids'] = &drupal_static(__FUNCTION__, $seen_ids_init);
+ }
+ $seen_ids = &$drupal_static_fast['seen_ids'];
$id = strtr(drupal_strtolower($id), array(' ' => '-', '_' => '-', '[' => '-', ']' => ''));
@@ -4069,7 +4240,14 @@
* 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.
- * - defer: If set to TRUE, the defer attribute is set on the <script>
+ * - 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 Lorem ipsum",
+ 'variables' => NULL,
+ 'severity' => WATCHDOG_NOTICE,
+ 'link' => 'foo/bar',
+ 'request_uri' => 'http://example.com?dblog=1',
+ 'referer' => 'http://example.org?dblog=2',
+ 'ip' => '0.0.1.0',
+ 'timestamp' => REQUEST_TIME,
+ );
+ dblog_watchdog($log);
+
+ $wid = db_query('SELECT MAX(wid) FROM {watchdog}')->fetchField();
+ $this->drupalGet('admin/reports/event/' . $wid);
+ $this->assertResponse(200);
+ $this->assertNoRaw("");
+ $this->assertRaw("alert('foo'); Lorem ipsum");
+ }
+}
diff -Naur drupal-7.18/modules/field/field.api.php drupal-7.66/modules/field/field.api.php
--- drupal-7.18/modules/field/field.api.php 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/field.api.php 2019-04-17 22:20:46.000000000 +0200
@@ -1,4 +1,8 @@
$instance['widget']['type'],
'#default_value' => isset($items[$delta]) ? $items[$delta] : '',
);
- return $element;
+ return array('value' => $element);
}
/**
@@ -1238,7 +1260,7 @@
*/
/**
- * @ingroup field_attach
+ * @addtogroup field_attach
* @{
*/
@@ -1300,9 +1322,33 @@
* This hook is invoked after the field module has performed the operation.
*
* See field_attach_validate() for details and arguments.
+ *
+ * @param $entity_type
+ * The type of $entity; e.g., 'node' or 'user'.
+ * @param $entity
+ * The entity with fields to validate.
+ * @param array $errors
+ * 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.'),
+ );
+ }
+ }
+ }
+ }
}
/**
@@ -1504,6 +1550,8 @@
* - entity_type: The type of the entity to be displayed.
* - entity: The entity with fields to render.
* - langcode: The language code $entity has to be displayed in.
+ *
+ * @ingroup field_language
*/
function hook_field_language_alter(&$display_language, $context) {
// Do not apply core language fallback rules if they are disabled or if Locale
@@ -1525,6 +1573,8 @@
* An associative array containing:
* - entity_type: The type of the entity the field is attached to.
* - field: A field data structure.
+ *
+ * @ingroup field_language
*/
function hook_field_available_languages_alter(&$languages, $context) {
// Add an unavailable language.
@@ -1575,7 +1625,7 @@
* @param $entity_type
* The type of entity; for example, 'node' or 'user'.
* @param $bundle
- * The bundle that was just deleted.
+ * The name of the bundle that was just deleted.
* @param $instances
* An array of all instances that existed for the bundle before it was
* deleted.
@@ -1590,7 +1640,7 @@
}
/**
- * @} End of "defgroup field_attach".
+ * @} End of "addtogroup field_attach".
*/
/**
@@ -1735,11 +1785,14 @@
* loaded.
*/
function hook_field_storage_load($entity_type, $entities, $age, $fields, $options) {
- $field_info = field_info_field_by_ids();
$load_current = $age == FIELD_LOAD_CURRENT;
foreach ($fields as $field_id => $ids) {
- $field = $field_info[$field_id];
+ // By the time this hook runs, the relevant field definitions have been
+ // populated and cached in FieldInfo, so calling field_info_field_by_id()
+ // on each field individually is more efficient than loading all fields in
+ // memory upfront with field_info_field_by_ids().
+ $field = field_info_field_by_id($field_id);
$field_name = $field['field_name'];
$table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field);
@@ -1844,7 +1897,7 @@
$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,
@@ -2248,6 +2301,10 @@
}
/**
+ * @} End of "addtogroup field_storage
+ */
+
+/**
* Returns the maximum weight for the entity components handled by the module.
*
* Field API takes care of fields and 'extra_fields'. This hook is intended for
@@ -2260,9 +2317,12 @@
* @param $context
* The context for which the maximum weight is requested. Either 'form', or
* the name of a view mode.
+ *
* @return
* The maximum weight of the entity's components, or NULL if no components
* were found.
+ *
+ * @ingroup field_info
*/
function hook_field_info_max_weight($entity_type, $bundle, $context) {
$weights = array();
@@ -2275,6 +2335,11 @@
}
/**
+ * @addtogroup field_types
+ * @{
+ */
+
+/**
* Alters the display settings of a field before it gets displayed.
*
* Note that instead of hook_field_display_alter(), which is called for all
@@ -2341,6 +2406,10 @@
}
/**
+ * @} End of "addtogroup field_types
+ */
+
+/**
* Alters the display settings of pseudo-fields before an entity is displayed.
*
* This hook is called once per displayed entity. If the result of the hook
@@ -2355,6 +2424,8 @@
* - entity_type: The entity type; e.g., 'node' or 'user'.
* - bundle: The bundle name.
* - view_mode: The view mode, e.g. 'full', 'teaser'...
+ *
+ * @ingroup field_types
*/
function hook_field_extra_fields_display_alter(&$displays, $context) {
if ($context['entity_type'] == 'taxonomy_term' && $context['view_mode'] == 'full') {
@@ -2384,6 +2455,8 @@
* - instance: The instance of the field.
*
* @see hook_field_widget_properties_alter()
+ *
+ * @ingroup field_widget
*/
function hook_field_widget_properties_ENTITY_TYPE_alter(&$widget, $context) {
// Change a widget's type according to the time of day.
@@ -2395,10 +2468,6 @@
}
/**
- * @} End of "addtogroup field_storage".
- */
-
-/**
* @addtogroup field_crud
* @{
*/
@@ -2505,7 +2574,7 @@
*
* @param $instance
* The instance as it is post-update.
- * @param $prior_$instance
+ * @param $prior_instance
* The instance as it was pre-update.
*/
function hook_field_update_instance($instance, $prior_instance) {
@@ -2593,6 +2662,8 @@
*
* @param $field
* The field being purged.
+ *
+ * @ingroup field_storage
*/
function hook_field_storage_purge_field($field) {
$table_name = _field_sql_storage_tablename($field);
@@ -2610,6 +2681,8 @@
*
* @param $instance
* The instance being purged.
+ *
+ * @ingroup field_storage
*/
function hook_field_storage_purge_field_instance($instance) {
db_delete('my_module_field_instance_info')
@@ -2631,6 +2704,8 @@
* The (possibly deleted) field whose data is being purged.
* @param $instance
* The deleted field instance whose data is being purged.
+ *
+ * @ingroup field_storage
*/
function hook_field_storage_purge($entity_type, $entity, $field, $instance) {
list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
@@ -2670,6 +2745,8 @@
*
* @return
* TRUE if the operation is allowed, and FALSE if the operation is denied.
+ *
+ * @ingroup field_types
*/
function hook_field_access($op, $field, $entity_type, $entity, $account) {
if ($field['field_name'] == 'field_of_interest' && $op == 'edit') {
diff -Naur drupal-7.18/modules/field/field.attach.inc drupal-7.66/modules/field/field.attach.inc
--- drupal-7.18/modules/field/field.attach.inc 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/field.attach.inc 2019-04-17 22:20:46.000000000 +0200
@@ -283,7 +283,6 @@
'language' => NULL,
);
$options += $default_options;
- $field_info = field_info_field_by_ids();
$fields = array();
$grouped_instances = array();
@@ -307,7 +306,7 @@
foreach ($instances as $instance) {
$field_id = $instance['field_id'];
$field_name = $instance['field_name'];
- $field = $field_info[$field_id];
+ $field = field_info_field_by_id($field_id);
$function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op;
if (function_exists($function)) {
// Add the field to the list of fields to invoke the hook on.
@@ -319,7 +318,7 @@
// 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();
@@ -555,16 +554,23 @@
* @param $langcode
* The language the field values are going to be entered, if no language
* is provided the default site language will be used.
+ * @param array $options
+ * An associative array of additional options. See _field_invoke() for
+ * details.
*
* @see field_form_get_state()
* @see field_form_set_state()
*/
-function field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode = NULL) {
+function field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode = NULL, $options = array()) {
+ // Validate $options since this is a new parameter added after Drupal 7 was
+ // released.
+ $options = is_array($options) ? $options : array();
+
// Set #parents to 'top-level' by default.
$form += array('#parents' => array());
// If no language is provided use the default site language.
- $options = array('language' => field_valid_language($langcode));
+ $options['language'] = field_valid_language($langcode);
$form += (array) _field_invoke_default('form', $entity_type, $entity, $form, $form_state, $options);
// Add custom weight handling.
@@ -614,7 +620,6 @@
* non-deleted fields are operated on.
*/
function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $options = array()) {
- $field_info = field_info_field_by_ids();
$load_current = $age == FIELD_LOAD_CURRENT;
// Merge default options.
@@ -692,7 +697,7 @@
}
// Collect the storage backend if the field has not been loaded yet.
if (!isset($skip_fields[$field_id])) {
- $field = $field_info[$field_id];
+ $field = field_info_field_by_id($field_id);
$storages[$field['storage']['type']][$field_id][] = $load_current ? $id : $vid;
}
}
@@ -709,7 +714,7 @@
_field_invoke_multiple('load', $entity_type, $queried_entities, $age, $null, $options);
// Invoke hook_field_attach_load(): let other modules act on loading the
- // entitiy.
+ // entity.
module_invoke_all('field_attach_load', $entity_type, $queried_entities, $age, $options);
// Build cache data.
@@ -769,13 +774,21 @@
* If validation errors are found, a FieldValidationException is thrown. The
* 'errors' property contains the array of errors, keyed by field name,
* language and delta.
+ * @param array $options
+ * An associative array of additional options. See _field_invoke() for
+ * details.
*/
-function field_attach_validate($entity_type, $entity) {
+function field_attach_validate($entity_type, $entity, $options = array()) {
+ // Validate $options since this is a new parameter added after Drupal 7 was
+ // released.
+ $options = is_array($options) ? $options : array();
+
$errors = array();
// Check generic, field-type-agnostic errors first.
- _field_invoke_default('validate', $entity_type, $entity, $errors);
+ $null = NULL;
+ _field_invoke_default('validate', $entity_type, $entity, $errors, $null, $options);
// Check field-type specific errors.
- _field_invoke('validate', $entity_type, $entity, $errors);
+ _field_invoke('validate', $entity_type, $entity, $errors, $null, $options);
// Let other modules validate the entity.
// Avoid module_invoke_all() to let $errors be taken by reference.
@@ -817,14 +830,21 @@
* full form structure, or a sub-element of a larger form.
* @param $form_state
* An associative array containing the current state of the form.
+ * @param array $options
+ * An associative array of additional options. See _field_invoke() for
+ * details.
*/
-function field_attach_form_validate($entity_type, $entity, $form, &$form_state) {
+function field_attach_form_validate($entity_type, $entity, $form, &$form_state, $options = array()) {
+ // Validate $options since this is a new parameter added after Drupal 7 was
+ // released.
+ $options = is_array($options) ? $options : array();
+
// Extract field values from submitted values.
_field_invoke_default('extract_form_values', $entity_type, $entity, $form, $form_state);
// Perform field_level validation.
try {
- field_attach_validate($entity_type, $entity);
+ field_attach_validate($entity_type, $entity, $options);
}
catch (FieldValidationException $e) {
// Pass field-level validation errors back to widgets for accurate error
@@ -836,7 +856,7 @@
field_form_set_state($form['#parents'], $field_name, $langcode, $form_state, $field_state);
}
}
- _field_invoke_default('form_errors', $entity_type, $entity, $form, $form_state);
+ _field_invoke_default('form_errors', $entity_type, $entity, $form, $form_state, $options);
}
}
@@ -857,12 +877,19 @@
* full form structure, or a sub-element of a larger form.
* @param $form_state
* An associative array containing the current state of the form.
+ * @param array $options
+ * An associative array of additional options. See _field_invoke() for
+ * details.
*/
-function field_attach_submit($entity_type, $entity, $form, &$form_state) {
+function field_attach_submit($entity_type, $entity, $form, &$form_state, $options = array()) {
+ // Validate $options since this is a new parameter added after Drupal 7 was
+ // released.
+ $options = is_array($options) ? $options : array();
+
// Extract field values from submitted values.
- _field_invoke_default('extract_form_values', $entity_type, $entity, $form, $form_state);
+ _field_invoke_default('extract_form_values', $entity_type, $entity, $form, $form_state, $options);
- _field_invoke_default('submit', $entity_type, $entity, $form, $form_state);
+ _field_invoke_default('submit', $entity_type, $entity, $form, $form_state, $options);
// Let other modules act on submitting the entity.
// Avoid module_invoke_all() to let $form_state be taken by reference.
@@ -949,6 +976,12 @@
/**
* Save field data for an existing entity.
*
+ * When calling this function outside an entity save operation be sure to
+ * clear caches for the entity:
+ * @code
+ * entity_get_controller($entity_type)->resetCache(array($entity_id))
+ * @endcode
+ *
* @param $entity_type
* The type of $entity; e.g. 'node' or 'user'.
* @param $entity
@@ -1093,9 +1126,16 @@
* @param $langcode
* (Optional) The language the field values are to be shown in. If no language
* is provided the current language is used.
+ * @param array $options
+ * An associative array of additional options. See _field_invoke() for
+ * details.
*/
-function field_attach_prepare_view($entity_type, $entities, $view_mode, $langcode = NULL) {
- $options = array('language' => array());
+function field_attach_prepare_view($entity_type, $entities, $view_mode, $langcode = NULL, $options = array()) {
+ // Validate $options since this is a new parameter added after Drupal 7 was
+ // released.
+ $options = is_array($options) ? $options : array();
+
+ $options['language'] = array();
// To ensure hooks are only run once per entity, only process items without
// the _field_view_prepared flag.
@@ -1167,14 +1207,21 @@
* @param $langcode
* The language the field values are to be shown in. If no language is
* provided the current language is used.
+ * @param array $options
+ * An associative array of additional options. See _field_invoke() for
+ * details.
* @return
* A renderable array for the field values.
*/
-function field_attach_view($entity_type, $entity, $view_mode, $langcode = NULL) {
+function field_attach_view($entity_type, $entity, $view_mode, $langcode = NULL, $options = array()) {
+ // Validate $options since this is a new parameter added after Drupal 7 was
+ // released.
+ $options = is_array($options) ? $options : array();
+
// Determine the actual language to display for each field, given the
// languages available in the field data.
$display_language = field_language($entity_type, $entity, NULL, $langcode);
- $options = array('language' => $display_language);
+ $options['language'] = $display_language;
// Invoke field_default_view().
$null = NULL;
diff -Naur drupal-7.18/modules/field/field.crud.inc drupal-7.66/modules/field/field.crud.inc
--- drupal-7.18/modules/field/field.crud.inc 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/field.crud.inc 2019-04-17 22:20:46.000000000 +0200
@@ -60,11 +60,11 @@
}
// 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()
@@ -189,7 +189,7 @@
}
// Clear caches
- field_cache_clear(TRUE);
+ field_cache_clear();
// Invoke external hooks after the cache is cleared for API consistency.
module_invoke_all('field_create_field', $field);
@@ -244,9 +244,11 @@
// $prior_field may no longer be right.
module_load_install($field['module']);
$schema = (array) module_invoke($field['module'], 'field_schema', $field);
- $schema += array('columns' => array(), 'indexes' => array());
+ $schema += array('columns' => array(), 'indexes' => array(), 'foreign keys' => array());
// 'columns' are hardcoded in the field type.
$field['columns'] = $schema['columns'];
+ // 'foreign keys' are hardcoded in the field type.
+ $field['foreign keys'] = $schema['foreign keys'];
// 'indexes' can be both hardcoded in the field type, and specified in the
// incoming $field definition.
$field += array(
@@ -286,7 +288,7 @@
drupal_write_record('field_config', $field, $primary_key);
// Clear caches
- field_cache_clear(TRUE);
+ field_cache_clear();
// Invoke external hooks after the cache is cleared for API consistency.
module_invoke_all('field_update_field', $field, $prior_field, $has_data);
@@ -319,7 +321,11 @@
* Reads in fields that match an array of conditions.
*
* @param array $params
- * An array of conditions to match against.
+ * An array of conditions to match against. Keys are columns from the
+ * 'field_config' table, values are conditions to match. Additionally,
+ * conditions on the 'entity_type' and 'bundle' columns from the
+ * 'field_config_instance' table are supported (select fields having an
+ * instance on a given bundle).
* @param array $include_additional
* The default behavior of this function is to not return fields that
* are inactive or have been deleted. Setting
@@ -337,8 +343,21 @@
// Turn the conditions into a query.
foreach ($params as $key => $value) {
+ // Allow filtering on the 'entity_type' and 'bundle' columns of the
+ // field_config_instance table.
+ if ($key == 'entity_type' || $key == 'bundle') {
+ if (empty($fci_join)) {
+ $fci_join = $query->join('field_config_instance', 'fci', 'fc.id = fci.field_id');
+ }
+ $key = 'fci.' . $key;
+ }
+ else {
+ $key = 'fc.' . $key;
+ }
+
$query->condition($key, $value);
}
+
if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) {
$query
->condition('fc.active', 1)
@@ -411,7 +430,7 @@
->execute();
// Clear the cache.
- field_cache_clear(TRUE);
+ field_cache_clear();
module_invoke_all('field_delete_field', $field);
}
@@ -505,17 +524,30 @@
* Updates an instance of a field.
*
* @param $instance
- * An associative array representing an instance structure. The required
- * keys and values are:
+ * An associative array representing an instance structure. The following
+ * required array elements specify which field instance is being updated:
* - entity_type: The type of the entity the field is attached to.
* - bundle: The bundle this field belongs to.
* - field_name: The name of an existing field.
- * Read-only_id properties are assigned automatically. Any other
- * properties specified in $instance overwrite the existing values for
- * the instance.
+ * The other array elements represent properties of the instance, and all
+ * properties must be specified or their default values will be used (except
+ * internal-use properties, which are assigned automatically). To avoid
+ * losing the previously stored properties of the instance when making a
+ * change, first load the instance with field_info_instance(), then override
+ * the values you want to override, and finally save using this function.
+ * Example:
+ * @code
+ * // 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['required'] = TRUE;
+ * // Write the changed definition back.
+ * field_update_instance($instance_info);
+ * @endcode
*
* @throws FieldException
*
+ * @see field_info_instance()
* @see field_create_instance()
*/
function field_update_instance($instance) {
diff -Naur drupal-7.18/modules/field/field.info drupal-7.66/modules/field/field.info
--- drupal-7.18/modules/field/field.info 2012-12-19 20:06:43.000000000 +0100
+++ drupal-7.66/modules/field/field.info 2019-04-17 22:39:36.000000000 +0200
@@ -5,13 +5,13 @@
core = 7.x
files[] = field.module
files[] = field.attach.inc
+files[] = field.info.class.inc
files[] = tests/field.test
dependencies[] = field_sql_storage
required = TRUE
stylesheets[all][] = theme/field.css
-; Information added by drupal.org packaging script on 2012-12-19
-version = "7.18"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1355944003"
-
+datestamp = "1555533576"
diff -Naur drupal-7.18/modules/field/field.info.class.inc drupal-7.66/modules/field/field.info.class.inc
--- drupal-7.18/modules/field/field.info.class.inc 1970-01-01 01:00:00.000000000 +0100
+++ drupal-7.66/modules/field/field.info.class.inc 2019-04-17 22:20:46.000000000 +0200
@@ -0,0 +1,686 @@
+fieldMap = NULL;
+
+ $this->fieldsById = array();
+ $this->fieldIdsByName = array();
+ $this->loadedAllFields = FALSE;
+ $this->unknownFields = array();
+
+ $this->bundleInstances = array();
+ $this->loadedAllInstances = FALSE;
+ $this->emptyBundles = array();
+
+ $this->bundleExtraFields = array();
+
+ cache_clear_all('field_info:', 'cache_field', TRUE);
+ }
+
+ /**
+ * Collects a lightweight map of fields across bundles.
+ *
+ * @return
+ * An array keyed by field name. Each value is an array with two entries:
+ * - type: The field type.
+ * - bundles: The bundles in which the field appears, as an array with
+ * entity types as keys and the array of bundle names as values.
+ */
+ public function getFieldMap() {
+ // Read from the "static" cache.
+ if ($this->fieldMap !== NULL) {
+ return $this->fieldMap;
+ }
+
+ // Read from persistent cache.
+ if ($cached = cache_get('field_info:field_map', 'cache_field')) {
+ $map = $cached->data;
+
+ // Save in "static" cache.
+ $this->fieldMap = $map;
+
+ return $map;
+ }
+
+ $map = array();
+
+ $query = db_query('SELECT fc.type, fci.field_name, fci.entity_type, fci.bundle FROM {field_config_instance} fci INNER JOIN {field_config} fc ON fc.id = fci.field_id WHERE fc.active = 1 AND fc.storage_active = 1 AND fc.deleted = 0 AND fci.deleted = 0');
+ foreach ($query as $row) {
+ $map[$row->field_name]['bundles'][$row->entity_type][] = $row->bundle;
+ $map[$row->field_name]['type'] = $row->type;
+ }
+
+ // Save in "static" and persistent caches.
+ $this->fieldMap = $map;
+ if (lock_acquire('field_info:field_map')) {
+ cache_set('field_info:field_map', $map, 'cache_field');
+ lock_release('field_info:field_map');
+ }
+
+ return $map;
+ }
+
+ /**
+ * Returns all active fields, including deleted ones.
+ *
+ * @return
+ * An array of field definitions, keyed by field ID.
+ */
+ public function getFields() {
+ // Read from the "static" cache.
+ if ($this->loadedAllFields) {
+ return $this->fieldsById;
+ }
+
+ // Read from persistent cache.
+ if ($cached = cache_get('field_info:fields', 'cache_field')) {
+ $this->fieldsById = $cached->data;
+ }
+ else {
+ // Collect and prepare fields.
+ foreach (field_read_fields(array(), array('include_deleted' => TRUE)) as $field) {
+ $this->fieldsById[$field['id']] = $this->prepareField($field);
+ }
+
+ // Store in persistent cache.
+ 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.
+ foreach ($this->fieldsById as $field) {
+ if (!$field['deleted']) {
+ $this->fieldIdsByName[$field['field_name']] = $field['id'];
+ }
+ }
+
+ $this->loadedAllFields = TRUE;
+
+ return $this->fieldsById;
+ }
+
+ /**
+ * Retrieves all active, non-deleted instances definitions.
+ *
+ * @param $entity_type
+ * (optional) The entity type.
+ *
+ * @return
+ * If $entity_type is not set, all instances keyed by entity type and bundle
+ * name. If $entity_type is set, all instances for that entity type, keyed
+ * by bundle name.
+ */
+ public function getInstances($entity_type = NULL) {
+ // If the full list is not present in "static" cache yet.
+ if (!$this->loadedAllInstances) {
+
+ // Read from persistent cache.
+ if ($cached = cache_get('field_info:instances', 'cache_field')) {
+ $this->bundleInstances = $cached->data;
+ }
+ else {
+ // Collect and prepare instances.
+
+ // We also need to populate the static field cache, since it will not
+ // be set by subsequent getBundleInstances() calls.
+ $this->getFields();
+
+ // Initialize empty arrays for all existing entity types and bundles.
+ // This is not strictly needed, but is done to preserve the behavior of
+ // field_info_instances() before http://drupal.org/node/1915646.
+ foreach (field_info_bundles() as $existing_entity_type => $bundles) {
+ foreach ($bundles as $bundle => $bundle_info) {
+ $this->bundleInstances[$existing_entity_type][$bundle] = array();
+ }
+ }
+
+ foreach (field_read_instances() as $instance) {
+ $field = $this->getField($instance['field_name']);
+ $instance = $this->prepareInstance($instance, $field['type']);
+ $this->bundleInstances[$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance;
+ }
+
+ // Store in persistent cache.
+ if (lock_acquire('field_info:instances')) {
+ cache_set('field_info:instances', $this->bundleInstances, 'cache_field');
+ lock_release('field_info:instances');
+ }
+ }
+
+ $this->loadedAllInstances = TRUE;
+ }
+
+ if (isset($entity_type)) {
+ return isset($this->bundleInstances[$entity_type]) ? $this->bundleInstances[$entity_type] : array();
+ }
+ else {
+ return $this->bundleInstances;
+ }
+ }
+
+ /**
+ * Returns a field definition from a field name.
+ *
+ * This method only retrieves active, non-deleted fields.
+ *
+ * @param $field_name
+ * The field name.
+ *
+ * @return
+ * The field definition, or NULL if no field was found.
+ */
+ public function getField($field_name) {
+ // Read from the "static" cache.
+ if (isset($this->fieldIdsByName[$field_name])) {
+ $field_id = $this->fieldIdsByName[$field_name];
+ return $this->fieldsById[$field_id];
+ }
+ if (isset($this->unknownFields[$field_name])) {
+ return;
+ }
+
+ // Do not check the (large) persistent cache, but read the definition.
+
+ // Cache miss: read from definition.
+ if ($field = field_read_field($field_name)) {
+ $field = $this->prepareField($field);
+
+ // Save in the "static" cache.
+ $this->fieldsById[$field['id']] = $field;
+ $this->fieldIdsByName[$field['field_name']] = $field['id'];
+
+ return $field;
+ }
+ else {
+ $this->unknownFields[$field_name] = TRUE;
+ }
+ }
+
+ /**
+ * Returns a field definition from a field ID.
+ *
+ * This method only retrieves active fields, deleted or not.
+ *
+ * @param $field_id
+ * The field ID.
+ *
+ * @return
+ * The field definition, or NULL if no field was found.
+ */
+ public function getFieldById($field_id) {
+ // Read from the "static" cache.
+ if (isset($this->fieldsById[$field_id])) {
+ return $this->fieldsById[$field_id];
+ }
+ if (isset($this->unknownFields[$field_id])) {
+ return;
+ }
+
+ // No persistent cache, fields are only persistently cached as part of a
+ // bundle.
+
+ // Cache miss: read from definition.
+ if ($fields = field_read_fields(array('id' => $field_id), array('include_deleted' => TRUE))) {
+ $field = current($fields);
+ $field = $this->prepareField($field);
+
+ // Store in the static cache.
+ $this->fieldsById[$field['id']] = $field;
+ if (!$field['deleted']) {
+ $this->fieldIdsByName[$field['field_name']] = $field['id'];
+ }
+
+ return $field;
+ }
+ else {
+ $this->unknownFields[$field_id] = TRUE;
+ }
+ }
+
+ /**
+ * Retrieves the instances for a bundle.
+ *
+ * The function also populates the corresponding field definitions in the
+ * "static" cache.
+ *
+ * @param $entity_type
+ * The entity type.
+ * @param $bundle
+ * The bundle name.
+ *
+ * @return
+ * The array of instance definitions, keyed by field name.
+ */
+ public function getBundleInstances($entity_type, $bundle) {
+ // Read from the "static" cache.
+ if (isset($this->bundleInstances[$entity_type][$bundle])) {
+ return $this->bundleInstances[$entity_type][$bundle];
+ }
+ if (isset($this->emptyBundles[$entity_type][$bundle])) {
+ return array();
+ }
+
+ // Read from the persistent cache.
+ if ($cached = cache_get("field_info:bundle:$entity_type:$bundle", 'cache_field')) {
+ $info = $cached->data;
+
+ // Extract the field definitions and save them in the "static" cache.
+ foreach ($info['fields'] as $field) {
+ if (!isset($this->fieldsById[$field['id']])) {
+ $this->fieldsById[$field['id']] = $field;
+ if (!$field['deleted']) {
+ $this->fieldIdsByName[$field['field_name']] = $field['id'];
+ }
+ }
+ }
+ unset($info['fields']);
+
+ // Store the instance definitions in the "static" cache'. Empty (or
+ // non-existent) bundles are stored separately, so that they do not
+ // pollute the global list returned by getInstances().
+ if ($info['instances']) {
+ $this->bundleInstances[$entity_type][$bundle] = $info['instances'];
+ }
+ else {
+ $this->emptyBundles[$entity_type][$bundle] = TRUE;
+ }
+
+ return $info['instances'];
+ }
+
+ // Cache miss: collect from the definitions.
+
+ $instances = array();
+
+ // Collect the fields in the bundle.
+ $params = array('entity_type' => $entity_type, 'bundle' => $bundle);
+ $fields = field_read_fields($params);
+
+ // This iterates on non-deleted instances, so deleted fields are kept out of
+ // the persistent caches.
+ foreach (field_read_instances($params) as $instance) {
+ $field = $fields[$instance['field_name']];
+
+ $instance = $this->prepareInstance($instance, $field['type']);
+ $instances[$field['field_name']] = $instance;
+
+ // If the field is not in our global "static" list yet, add it.
+ if (!isset($this->fieldsById[$field['id']])) {
+ $field = $this->prepareField($field);
+
+ $this->fieldsById[$field['id']] = $field;
+ $this->fieldIdsByName[$field['field_name']] = $field['id'];
+ }
+ }
+
+ // Store in the 'static' cache'. Empty (or non-existent) bundles are stored
+ // separately, so that they do not pollute the global list returned by
+ // getInstances().
+ if ($instances) {
+ $this->bundleInstances[$entity_type][$bundle] = $instances;
+ }
+ else {
+ $this->emptyBundles[$entity_type][$bundle] = TRUE;
+ }
+
+ // The persistent cache additionally contains the definitions of the fields
+ // involved in the bundle.
+ $cache = array(
+ 'instances' => $instances,
+ 'fields' => array()
+ );
+ foreach ($instances as $instance) {
+ $cache['fields'][] = $this->fieldsById[$instance['field_id']];
+ }
+
+ 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;
+ }
+
+ /**
+ * Retrieves the "extra fields" for a bundle.
+ *
+ * @param $entity_type
+ * The entity type.
+ * @param $bundle
+ * The bundle name.
+ *
+ * @return
+ * The array of extra fields.
+ */
+ public function getBundleExtraFields($entity_type, $bundle) {
+ // Read from the "static" cache.
+ if (isset($this->bundleExtraFields[$entity_type][$bundle])) {
+ return $this->bundleExtraFields[$entity_type][$bundle];
+ }
+
+ // Read from the persistent cache.
+ if ($cached = cache_get("field_info:bundle_extra:$entity_type:$bundle", 'cache_field')) {
+ $this->bundleExtraFields[$entity_type][$bundle] = $cached->data;
+ return $this->bundleExtraFields[$entity_type][$bundle];
+ }
+
+ // Cache miss: read from hook_field_extra_fields(). Note: given the current
+ // shape of the hook, we have no other way than collecting extra fields on
+ // all bundles.
+ $info = array();
+ $extra = module_invoke_all('field_extra_fields');
+ drupal_alter('field_extra_fields', $extra);
+ // Merge in saved settings.
+ if (isset($extra[$entity_type][$bundle])) {
+ $info = $this->prepareExtraFields($extra[$entity_type][$bundle], $entity_type, $bundle);
+ }
+
+ // Store in the 'static' and persistent caches.
+ $this->bundleExtraFields[$entity_type][$bundle] = $info;
+ 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];
+ }
+
+ /**
+ * Prepares a field definition for the current run-time context.
+ *
+ * @param $field
+ * The raw field structure as read from the database.
+ *
+ * @return
+ * The field definition completed for the current runtime context.
+ */
+ public function prepareField($field) {
+ // Make sure all expected field settings are present.
+ $field['settings'] += field_info_field_settings($field['type']);
+ $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']);
+
+ // Add storage details.
+ $details = (array) module_invoke($field['storage']['module'], 'field_storage_details', $field);
+ drupal_alter('field_storage_details', $details, $field);
+ $field['storage']['details'] = $details;
+
+ // Populate the list of bundles using the field.
+ $field['bundles'] = array();
+ if (!$field['deleted']) {
+ $map = $this->getFieldMap();
+ if (isset($map[$field['field_name']])) {
+ $field['bundles'] = $map[$field['field_name']]['bundles'];
+ }
+ }
+
+ return $field;
+ }
+
+ /**
+ * Prepares an instance definition for the current run-time context.
+ *
+ * @param $instance
+ * The raw instance structure as read from the database.
+ * @param $field_type
+ * The field type.
+ *
+ * @return
+ * The field instance array completed for the current runtime context.
+ */
+ public function prepareInstance($instance, $field_type) {
+ // Make sure all expected instance settings are present.
+ $instance['settings'] += field_info_instance_settings($field_type);
+
+ // Set a default value for the instance.
+ if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT && !isset($instance['default_value'])) {
+ $instance['default_value'] = NULL;
+ }
+
+ // Prepare widget settings.
+ $instance['widget'] = $this->prepareInstanceWidget($instance['widget'], $field_type);
+
+ // Prepare display settings.
+ foreach ($instance['display'] as $view_mode => $display) {
+ $instance['display'][$view_mode] = $this->prepareInstanceDisplay($display, $field_type);
+ }
+
+ // Fall back to 'hidden' for view modes configured to use custom display
+ // settings, and for which the instance has no explicit settings.
+ $entity_info = entity_get_info($instance['entity_type']);
+ $view_modes = array_merge(array('default'), array_keys($entity_info['view modes']));
+ $view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']);
+ foreach ($view_modes as $view_mode) {
+ if ($view_mode == 'default' || !empty($view_mode_settings[$view_mode]['custom_settings'])) {
+ if (!isset($instance['display'][$view_mode])) {
+ $instance['display'][$view_mode] = array(
+ 'type' => 'hidden',
+ 'label' => 'above',
+ 'settings' => array(),
+ 'weight' => 0,
+ );
+ }
+ }
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Prepares widget properties for the current run-time context.
+ *
+ * @param $widget
+ * Widget specifications as found in $instance['widget'].
+ * @param $field_type
+ * The field type.
+ *
+ * @return
+ * The widget properties completed for the current runtime context.
+ */
+ public function prepareInstanceWidget($widget, $field_type) {
+ $field_type_info = field_info_field_types($field_type);
+
+ // Fill in default values.
+ $widget += array(
+ 'type' => $field_type_info['default_widget'],
+ 'settings' => array(),
+ 'weight' => 0,
+ );
+
+ $widget_type_info = field_info_widget_types($widget['type']);
+ // Fall back to default formatter if formatter type is not available.
+ if (!$widget_type_info) {
+ $widget['type'] = $field_type_info['default_widget'];
+ $widget_type_info = field_info_widget_types($widget['type']);
+ }
+ $widget['module'] = $widget_type_info['module'];
+ // Fill in default settings for the widget.
+ $widget['settings'] += field_info_widget_settings($widget['type']);
+
+ return $widget;
+ }
+
+ /**
+ * Adapts display specifications to the current run-time context.
+ *
+ * @param $display
+ * Display specifications as found in $instance['display']['a_view_mode'].
+ * @param $field_type
+ * The field type.
+ *
+ * @return
+ * The display properties completed for the current runtime context.
+ */
+ public function prepareInstanceDisplay($display, $field_type) {
+ $field_type_info = field_info_field_types($field_type);
+
+ // Fill in default values.
+ $display += array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'weight' => 0,
+ );
+ if (empty($display['type'])) {
+ $display['type'] = $field_type_info['default_formatter'];
+ }
+ if ($display['type'] != 'hidden') {
+ $formatter_type_info = field_info_formatter_types($display['type']);
+ // Fall back to default formatter if formatter type is not available.
+ if (!$formatter_type_info) {
+ $display['type'] = $field_type_info['default_formatter'];
+ $formatter_type_info = field_info_formatter_types($display['type']);
+ }
+ $display['module'] = $formatter_type_info['module'];
+ // Fill in default settings for the formatter.
+ $display['settings'] += field_info_formatter_settings($display['type']);
+ }
+
+ return $display;
+ }
+
+ /**
+ * Prepares 'extra fields' for the current run-time context.
+ *
+ * @param $extra_fields
+ * The array of extra fields, as collected in hook_field_extra_fields().
+ * @param $entity_type
+ * The entity type.
+ * @param $bundle
+ * The bundle name.
+ *
+ * @return
+ * The list of extra fields completed for the current runtime context.
+ */
+ public function prepareExtraFields($extra_fields, $entity_type, $bundle) {
+ $entity_type_info = entity_get_info($entity_type);
+ $bundle_settings = field_bundle_settings($entity_type, $bundle);
+ $extra_fields += array('form' => array(), 'display' => array());
+
+ $result = array();
+ // Extra fields in forms.
+ foreach ($extra_fields['form'] as $name => $field_data) {
+ $settings = isset($bundle_settings['extra_fields']['form'][$name]) ? $bundle_settings['extra_fields']['form'][$name] : array();
+ if (isset($settings['weight'])) {
+ $field_data['weight'] = $settings['weight'];
+ }
+ $result['form'][$name] = $field_data;
+ }
+
+ // Extra fields in displayed entities.
+ $data = $extra_fields['display'];
+ foreach ($extra_fields['display'] as $name => $field_data) {
+ $settings = isset($bundle_settings['extra_fields']['display'][$name]) ? $bundle_settings['extra_fields']['display'][$name] : array();
+ $view_modes = array_merge(array('default'), array_keys($entity_type_info['view modes']));
+ foreach ($view_modes as $view_mode) {
+ if (isset($settings[$view_mode])) {
+ $field_data['display'][$view_mode] = $settings[$view_mode];
+ }
+ else {
+ $field_data['display'][$view_mode] = array(
+ 'weight' => $field_data['weight'],
+ 'visible' => TRUE,
+ );
+ }
+ }
+ unset($field_data['weight']);
+ $result['display'][$name] = $field_data;
+ }
+
+ return $result;
+ }
+}
diff -Naur drupal-7.18/modules/field/field.info.inc drupal-7.66/modules/field/field.info.inc
--- drupal-7.18/modules/field/field.info.inc 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/field.info.inc 2019-04-17 22:20:46.000000000 +0200
@@ -6,6 +6,32 @@
*/
/**
+ * Retrieves the FieldInfo object for the current request.
+ *
+ * @return FieldInfo
+ * An instance of the FieldInfo class.
+ */
+function _field_info_field_cache() {
+ // Use the advanced drupal_static() pattern, since this is called very often.
+ static $drupal_static_fast;
+
+ if (!isset($drupal_static_fast)) {
+ $drupal_static_fast['field_info_field_cache'] = &drupal_static(__FUNCTION__);
+ }
+ $field_info = &$drupal_static_fast['field_info_field_cache'];
+
+ if (!isset($field_info)) {
+ // @todo The registry should save the need for an explicit include, but not
+ // a couple upgrade tests (DisabledNodeTypeTestCase,
+ // FilterFormatUpgradePathTestCase...) break in a strange way without it.
+ include_once dirname(__FILE__) . '/field.info.class.inc';
+ $field_info = new FieldInfo();
+ }
+
+ return $field_info;
+}
+
+/**
* @defgroup field_info Field Info API
* @{
* Obtain information about Field API configuration.
@@ -34,7 +60,50 @@
entity_info_cache_clear();
_field_info_collate_types(TRUE);
- _field_info_collate_fields(TRUE);
+ _field_info_field_cache()->flush();
+}
+
+/**
+ * Collates all information on existing fields and instances.
+ *
+ * Deprecated. This function is kept to ensure backwards compatibility, but has
+ * a serious performance impact, and should be absolutely avoided.
+ * See http://drupal.org/node/1915646.
+ *
+ * Use the regular field_info_*() API functions to access the information, or
+ * field_info_cache_clear() to clear the cached data.
+ */
+function _field_info_collate_fields($reset = FALSE) {
+ if ($reset) {
+ _field_info_field_cache()->flush();
+ return;
+ }
+
+ $cache = _field_info_field_cache();
+
+ // Collect fields, and build the array of IDs keyed by field_name.
+ $fields = $cache->getFields();
+ $field_ids = array();
+ foreach ($fields as $id => $field) {
+ if (!$field['deleted']) {
+ $field_ids[$field['field_name']] = $id;
+ }
+ }
+
+ // Collect extra fields for all entity types.
+ $extra_fields = array();
+ foreach (field_info_bundles() as $entity_type => $bundles) {
+ foreach ($bundles as $bundle => $info) {
+ $extra_fields[$entity_type][$bundle] = $cache->getBundleExtraFields($entity_type, $bundle);
+ }
+ }
+
+ return array(
+ 'fields' => $fields,
+ 'field_ids' => $field_ids,
+ 'instances' => $cache->getInstances(),
+ 'extra_fields' => $extra_fields,
+ );
}
/**
@@ -154,96 +223,11 @@
}
drupal_alter('field_storage_info', $info['storage types']);
- cache_set("field_info_types:$langcode", $info, 'cache_field');
- }
- }
-
- return $info;
-}
-
-/**
- * Collates all information on existing fields and instances.
- *
- * @param $reset
- * If TRUE, clear the cache. The information will be rebuilt from the
- * database next time it is needed. Defaults to FALSE.
- *
- * @return
- * If $reset is TRUE, nothing.
- * If $reset is FALSE, an array containing the following elements:
- * - fields: Array of existing fields, keyed by field ID. This element
- * lists deleted and non-deleted fields, but not inactive ones.
- * Each field has an additional element, 'bundles', which is an array
- * of all non-deleted instances of that field.
- * - field_ids: Array of field IDs, keyed by field name. This element
- * only lists non-deleted, active fields.
- * - instances: Array of existing instances, keyed by entity type, bundle
- * name and field name. This element only lists non-deleted instances
- * whose field is active.
- */
-function _field_info_collate_fields($reset = FALSE) {
- static $info;
-
- if ($reset) {
- $info = NULL;
- cache_clear_all('field_info_fields', 'cache_field');
- return;
- }
-
- if (!isset($info)) {
- if ($cached = cache_get('field_info_fields', 'cache_field')) {
- $info = $cached->data;
- }
- else {
- $definitions = array(
- 'field_ids' => field_read_fields(array(), array('include_deleted' => 1)),
- 'instances' => field_read_instances(),
- );
-
- // Populate 'fields' with all fields, keyed by ID.
- $info['fields'] = array();
- foreach ($definitions['field_ids'] as $key => $field) {
- $info['fields'][$key] = $definitions['field_ids'][$key] = _field_info_prepare_field($field);
- }
-
- // Build an array of field IDs for non-deleted fields, keyed by name.
- $info['field_ids'] = array();
- foreach ($info['fields'] as $key => $field) {
- if (!$field['deleted']) {
- $info['field_ids'][$field['field_name']] = $key;
- }
+ // 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");
}
-
- // Populate 'instances'. Only non-deleted instances are considered.
- $info['instances'] = array();
- foreach (field_info_bundles() as $entity_type => $bundles) {
- foreach ($bundles as $bundle => $bundle_info) {
- $info['instances'][$entity_type][$bundle] = array();
- }
- }
- foreach ($definitions['instances'] as $instance) {
- $field = $info['fields'][$instance['field_id']];
- $instance = _field_info_prepare_instance($instance, $field);
- $info['instances'][$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance;
- // Enrich field definitions with the list of bundles where they have
- // instances. NOTE: Deleted fields in $info['field_ids'] are not
- // enriched because all of their instances are deleted, too, and
- // are thus not in $definitions['instances'].
- $info['fields'][$instance['field_id']]['bundles'][$instance['entity_type']][] = $instance['bundle'];
- }
-
- // Populate 'extra_fields'.
- $extra = module_invoke_all('field_extra_fields');
- drupal_alter('field_extra_fields', $extra);
- // Merge in saved settings.
- foreach ($extra as $entity_type => $bundles) {
- foreach ($bundles as $bundle => $extra_fields) {
- $extra_fields = _field_info_prepare_extra_fields($extra_fields, $entity_type, $bundle);
- $info['extra_fields'][$entity_type][$bundle] = $extra_fields;
- }
- }
-
- cache_set('field_info_fields', $info, 'cache_field');
}
}
@@ -253,190 +237,66 @@
/**
* Prepares a field definition for the current run-time context.
*
- * Since the field was last saved or updated, new field settings can be
- * expected.
+ * The functionality has moved to the FieldInfo class. This function is kept as
+ * a backwards-compatibility layer. See http://drupal.org/node/1915646.
*
- * @param $field
- * The raw field structure as read from the database.
+ * @see FieldInfo::prepareField()
*/
function _field_info_prepare_field($field) {
- // Make sure all expected field settings are present.
- $field['settings'] += field_info_field_settings($field['type']);
- $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']);
-
- // Add storage details.
- $details = (array) module_invoke($field['storage']['module'], 'field_storage_details', $field);
- drupal_alter('field_storage_details', $details, $field, $instance);
- $field['storage']['details'] = $details;
-
- // Initialize the 'bundles' list.
- $field['bundles'] = array();
-
- return $field;
+ $cache = _field_info_field_cache();
+ return $cache->prepareField($field);
}
/**
* Prepares an instance definition for the current run-time context.
*
- * Since the instance was last saved or updated, a number of things might have
- * changed: widgets or formatters disabled, new settings expected, new view
- * modes added...
- *
- * @param $instance
- * The raw instance structure as read from the database.
- * @param $field
- * The field structure for the instance.
+ * The functionality has moved to the FieldInfo class. This function is kept as
+ * a backwards-compatibility layer. See http://drupal.org/node/1915646.
*
- * @return
- * Field instance array.
+ * @see FieldInfo::prepareInstance()
*/
function _field_info_prepare_instance($instance, $field) {
- // Make sure all expected instance settings are present.
- $instance['settings'] += field_info_instance_settings($field['type']);
-
- // Set a default value for the instance.
- if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT && !isset($instance['default_value'])) {
- $instance['default_value'] = NULL;
- }
-
- $instance['widget'] = _field_info_prepare_instance_widget($field, $instance['widget']);
-
- foreach ($instance['display'] as $view_mode => $display) {
- $instance['display'][$view_mode] = _field_info_prepare_instance_display($field, $display);
- }
-
- // Fallback to 'hidden' for view modes configured to use custom display
- // settings, and for which the instance has no explicit settings.
- $entity_info = entity_get_info($instance['entity_type']);
- $view_modes = array_merge(array('default'), array_keys($entity_info['view modes']));
- $view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']);
- foreach ($view_modes as $view_mode) {
- if ($view_mode == 'default' || !empty($view_mode_settings[$view_mode]['custom_settings'])) {
- if (!isset($instance['display'][$view_mode])) {
- $instance['display'][$view_mode] = array(
- 'type' => 'hidden',
- 'label' => 'above',
- 'settings' => array(),
- 'weight' => 0,
- );
- }
- }
- }
-
- return $instance;
+ $cache = _field_info_field_cache();
+ return $cache->prepareInstance($instance, $field['type']);
}
/**
* Adapts display specifications to the current run-time context.
*
- * @param $field
- * The field structure for the instance.
- * @param $display
- * Display specifications as found in
- * $instance['display']['some_view_mode'].
+ * The functionality has moved to the FieldInfo class. This function is kept as
+ * a backwards-compatibility layer. See http://drupal.org/node/1915646.
+ *
+ * @see FieldInfo::prepareInstanceDisplay()
*/
function _field_info_prepare_instance_display($field, $display) {
- $field_type = field_info_field_types($field['type']);
-
- // Fill in default values.
- $display += array(
- 'label' => 'above',
- 'type' => $field_type['default_formatter'],
- 'settings' => array(),
- 'weight' => 0,
- );
- if ($display['type'] != 'hidden') {
- $formatter_type = field_info_formatter_types($display['type']);
- // Fallback to default formatter if formatter type is not available.
- if (!$formatter_type) {
- $display['type'] = $field_type['default_formatter'];
- $formatter_type = field_info_formatter_types($display['type']);
- }
- $display['module'] = $formatter_type['module'];
- // Fill in default settings for the formatter.
- $display['settings'] += field_info_formatter_settings($display['type']);
- }
-
- return $display;
+ $cache = _field_info_field_cache();
+ return $cache->prepareInstanceDisplay($display, $field['type']);
}
/**
* Prepares widget specifications for the current run-time context.
*
- * @param $field
- * The field structure for the instance.
- * @param $widget
- * Widget specifications as found in $instance['widget'].
+ * The functionality has moved to the FieldInfo class. This function is kept as
+ * a backwards-compatibility layer. See http://drupal.org/node/1915646.
+ *
+ * @see FieldInfo::prepareInstanceWidget()
*/
function _field_info_prepare_instance_widget($field, $widget) {
- $field_type = field_info_field_types($field['type']);
-
- // Fill in default values.
- $widget += array(
- 'type' => $field_type['default_widget'],
- 'settings' => array(),
- 'weight' => 0,
- );
-
- $widget_type = field_info_widget_types($widget['type']);
- // Fallback to default formatter if formatter type is not available.
- if (!$widget_type) {
- $widget['type'] = $field_type['default_widget'];
- $widget_type = field_info_widget_types($widget['type']);
- }
- $widget['module'] = $widget_type['module'];
- // Fill in default settings for the widget.
- $widget['settings'] += field_info_widget_settings($widget['type']);
-
- return $widget;
+ $cache = _field_info_field_cache();
+ return $cache->prepareInstanceWidget($widget, $field['type']);
}
/**
* Prepares 'extra fields' for the current run-time context.
*
- * @param $extra_fields
- * The array of extra fields, as collected in hook_field_extra_fields().
- * @param $entity_type
- * The entity type.
- * @param $bundle
- * The bundle name.
+ * The functionality has moved to the FieldInfo class. This function is kept as
+ * a backwards-compatibility layer. See http://drupal.org/node/1915646.
+ *
+ * @see FieldInfo::prepareExtraFields()
*/
function _field_info_prepare_extra_fields($extra_fields, $entity_type, $bundle) {
- $entity_type_info = entity_get_info($entity_type);
- $bundle_settings = field_bundle_settings($entity_type, $bundle);
- $extra_fields += array('form' => array(), 'display' => array());
-
- $result = array();
- // Extra fields in forms.
- foreach ($extra_fields['form'] as $name => $field_data) {
- $settings = isset($bundle_settings['extra_fields']['form'][$name]) ? $bundle_settings['extra_fields']['form'][$name] : array();
- if (isset($settings['weight'])) {
- $field_data['weight'] = $settings['weight'];
- }
- $result['form'][$name] = $field_data;
- }
-
- // Extra fields in displayed entities.
- $data = $extra_fields['display'];
- foreach ($extra_fields['display'] as $name => $field_data) {
- $settings = isset($bundle_settings['extra_fields']['display'][$name]) ? $bundle_settings['extra_fields']['display'][$name] : array();
- $view_modes = array_merge(array('default'), array_keys($entity_type_info['view modes']));
- foreach ($view_modes as $view_mode) {
- if (isset($settings[$view_mode])) {
- $field_data['display'][$view_mode] = $settings[$view_mode];
- }
- else {
- $field_data['display'][$view_mode] = array(
- 'weight' => $field_data['weight'],
- 'visible' => TRUE,
- );
- }
- }
- unset($field_data['weight']);
- $result['display'][$name] = $field_data;
- }
-
- return $result;
+ $cache = _field_info_field_cache();
+ return $cache->prepareExtraFields($extra_fields, $entity_type, $bundle);
}
/**
@@ -584,21 +444,61 @@
}
/**
+ * Returns a lightweight map of fields across bundles.
+ *
+ * The function only returns active, non deleted fields.
+ *
+ * @return
+ * An array keyed by field name. Each value is an array with two entries:
+ * - type: The field type.
+ * - bundles: The bundles in which the field appears, as an array with entity
+ * types as keys and the array of bundle names as values.
+ * Example:
+ * @code
+ * array(
+ * 'body' => array(
+ * 'bundles' => array(
+ * 'node' => array('page', 'article'),
+ * ),
+ * 'type' => 'text_with_summary',
+ * ),
+ * );
+ * @endcode
+ */
+function field_info_field_map() {
+ $cache = _field_info_field_cache();
+ return $cache->getFieldMap();
+}
+
+/**
* Returns all field definitions.
*
+ * Use of this function should be avoided when possible, since it loads and
+ * statically caches a potentially large array of information. Use
+ * field_info_field_map() instead.
+ *
+ * When iterating over the fields present in a given bundle after a call to
+ * field_info_instances($entity_type, $bundle), it is recommended to use
+ * field_info_field() on each individual field instead.
+ *
* @return
* An array of field definitions, keyed by field name. Each field has an
* additional property, 'bundles', which is an array of all the bundles to
* which this field belongs keyed by entity type.
+ *
+ * @see field_info_field_map()
*/
function field_info_fields() {
+ $cache = _field_info_field_cache();
+ $info = $cache->getFields();
+
$fields = array();
- $info = _field_info_collate_fields();
- foreach ($info['fields'] as $key => $field) {
+ foreach ($info as $key => $field) {
if (!$field['deleted']) {
$fields[$field['field_name']] = $field;
}
}
+
return $fields;
}
@@ -620,10 +520,8 @@
* @see field_info_field_by_id()
*/
function field_info_field($field_name) {
- $info = _field_info_collate_fields();
- if (isset($info['field_ids'][$field_name])) {
- return $info['fields'][$info['field_ids'][$field_name]];
- }
+ $cache = _field_info_field_cache();
+ return $cache->getField($field_name);
}
/**
@@ -641,17 +539,19 @@
* @see field_info_field()
*/
function field_info_field_by_id($field_id) {
- $info = _field_info_collate_fields();
- if (isset($info['fields'][$field_id])) {
- return $info['fields'][$field_id];
- }
+ $cache = _field_info_field_cache();
+ return $cache->getFieldById($field_id);
}
/**
* Returns the same data as field_info_field_by_id() for every field.
*
- * This function is typically used when handling all fields of some entities
- * to avoid thousands of calls to field_info_field_by_id().
+ * Use of this function should be avoided when possible, since it loads and
+ * statically caches a potentially large array of information.
+ *
+ * When iterating over the fields present in a given bundle after a call to
+ * field_info_instances($entity_type, $bundle), it is recommended to use
+ * field_info_field() on each individual field instead.
*
* @return
* An array, each key is a field ID and the values are field arrays as
@@ -662,41 +562,57 @@
* @see field_info_field_by_id()
*/
function field_info_field_by_ids() {
- $info = _field_info_collate_fields();
- return $info['fields'];
+ $cache = _field_info_field_cache();
+ return $cache->getFields();
}
/**
* Retrieves information about field instances.
*
+ * Use of this function to retrieve instances across separate bundles (i.e.
+ * when the $bundle parameter is NULL) should be avoided when possible, since
+ * it loads and statically caches a potentially large array of information. Use
+ * field_info_field_map() instead.
+ *
+ * When retrieving the instances of a specific bundle (i.e. when both
+ * $entity_type and $bundle_name are provided), the function also populates a
+ * static cache with the corresponding field definitions, allowing fast
+ * retrieval of field_info_field() later in the request.
+ *
* @param $entity_type
- * The entity type for which to return instances.
+ * (optional) The entity type for which to return instances.
* @param $bundle_name
- * The bundle name for which to return instances.
+ * (optional) The bundle name for which to return instances. If $entity_type
+ * is NULL, the $bundle_name parameter is ignored.
*
* @return
* If $entity_type is not set, return all instances keyed by entity type and
* bundle name. If $entity_type is set, return all instances for that entity
* type, keyed by bundle name. If $entity_type and $bundle_name are set, return
* all instances for that bundle.
+ *
+ * @see field_info_field_map()
*/
function field_info_instances($entity_type = NULL, $bundle_name = NULL) {
- $info = _field_info_collate_fields();
+ $cache = _field_info_field_cache();
- if (isset($entity_type) && isset($bundle_name)) {
- return isset($info['instances'][$entity_type][$bundle_name]) ? $info['instances'][$entity_type][$bundle_name] : array();
- }
- elseif (isset($entity_type)) {
- return isset($info['instances'][$entity_type]) ? $info['instances'][$entity_type] : array();
+ if (!isset($entity_type)) {
+ return $cache->getInstances();
}
- else {
- return $info['instances'];
+ if (!isset($bundle_name)) {
+ return $cache->getInstances($entity_type);
}
+
+ return $cache->getBundleInstances($entity_type, $bundle_name);
}
/**
* Returns an array of instance data for a specific field and bundle.
*
+ * The function populates a static cache with all fields and instances used in
+ * the bundle, allowing fast retrieval of field_info_field() or
+ * field_info_instance() later in the request.
+ *
* @param $entity_type
* The entity type for the instance.
* @param $field_name
@@ -709,9 +625,10 @@
* NULL if the instance does not exist.
*/
function field_info_instance($entity_type, $field_name, $bundle_name) {
- $info = _field_info_collate_fields();
- if (isset($info['instances'][$entity_type][$bundle_name][$field_name])) {
- return $info['instances'][$entity_type][$bundle_name][$field_name];
+ $cache = _field_info_field_cache();
+ $info = $cache->getBundleInstances($entity_type, $bundle_name);
+ if (isset($info[$field_name])) {
+ return $info[$field_name];
}
}
@@ -769,11 +686,10 @@
* The array of pseudo-field elements in the bundle.
*/
function field_info_extra_fields($entity_type, $bundle, $context) {
- $info = _field_info_collate_fields();
- if (isset($info['extra_fields'][$entity_type][$bundle][$context])) {
- return $info['extra_fields'][$entity_type][$bundle][$context];
- }
- return array();
+ $cache = _field_info_field_cache();
+ $info = $cache->getBundleExtraFields($entity_type, $bundle);
+
+ return isset($info[$context]) ? $info[$context] : array();
}
/**
diff -Naur drupal-7.18/modules/field/field.install drupal-7.66/modules/field/field.install
--- drupal-7.18/modules/field/field.install 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/field.install 2019-04-17 22:20:46.000000000 +0200
@@ -128,7 +128,7 @@
'not null' => TRUE,
'default' => ''
),
- 'entity_type' => array(
+ 'entity_type' => array(
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
@@ -162,6 +162,7 @@
),
);
$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;
}
@@ -460,5 +461,33 @@
}
/**
+ * Add the FieldInfo class to the class registry.
+ */
+function field_update_7003() {
+ // Empty update to force a rebuild of the registry.
+}
+
+/**
+ * Grant the new "administer fields" permission to trusted users.
+ */
+function field_update_7004() {
+ // Assign the permission to anyone that already has a trusted core permission
+ // that would have previously let them administer fields on an entity type.
+ $rids = array();
+ $permissions = array(
+ 'administer site configuration',
+ 'administer content types',
+ 'administer users',
+ );
+ foreach ($permissions as $permission) {
+ $rids = array_merge($rids, array_keys(user_roles(FALSE, $permission)));
+ }
+ $rids = array_unique($rids);
+ foreach ($rids as $rid) {
+ _update_7000_user_role_grant_permissions($rid, array('administer fields'), 'field');
+ }
+}
+
+/**
* @} End of "addtogroup updates-7.x-extra".
*/
diff -Naur drupal-7.18/modules/field/field.module drupal-7.66/modules/field/field.module
--- drupal-7.18/modules/field/field.module 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/field.module 2019-04-17 22:20:46.000000000 +0200
@@ -317,6 +317,21 @@
}
/**
+ * Implements hook_permission().
+ */
+function field_permission() {
+ return array(
+ 'administer fields' => array(
+ 'title' => t('Administer fields'),
+ 'description' => t('Additional permissions are required based on what the fields are attached to (for example, administer content types to manage fields attached to content).', array(
+ '@url' => '#module-node',
+ )),
+ 'restrict access' => TRUE,
+ ),
+ );
+}
+
+/**
* Implements hook_theme().
*/
function field_theme() {
@@ -343,17 +358,6 @@
}
/**
- * 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().
*
* Goes through a list of all modules that provide a field type, and makes them
@@ -819,9 +823,9 @@
*
* This function can be used by third-party modules that need to output an
* isolated field.
- * - Do not use inside node (or other entities) templates, use
+ * - Do not use inside node (or any other entity) templates; use
* render($content[FIELD_NAME]) instead.
- * - Do not use to display all fields in an entity, use
+ * - Do not use to display all fields in an entity; use
* field_attach_prepare_view() and field_attach_view() instead.
* - The field_view_value() function can be used to output a single formatted
* field value, without label or wrapping field markup.
@@ -873,7 +877,8 @@
if ($field = field_info_field($field_name)) {
if (is_array($display)) {
// When using custom display settings, fill in default values.
- $display = _field_info_prepare_instance_display($field, $display);
+ $cache = _field_info_field_cache();
+ $display = $cache->prepareInstanceDisplay($display, $field["type"]);
}
// Hook invocations are done through the _field_invoke() functions in
@@ -904,6 +909,7 @@
'entity' => $entity,
'view_mode' => '_custom',
'display' => $display,
+ 'language' => $langcode,
);
drupal_alter('field_attach_view', $result, $context);
@@ -946,20 +952,30 @@
*/
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'
@@ -1197,7 +1213,7 @@
* Use element_validate_integer_positive() instead.
*
* @deprecated
- * @see element_validate_number_positive()
+ * @see element_validate_integer_positive()
*/
function _element_validate_integer_positive($element, &$form_state) {
element_validate_integer_positive($element, $form_state);
diff -Naur drupal-7.18/modules/field/modules/field_sql_storage/field_sql_storage.info drupal-7.66/modules/field/modules/field_sql_storage/field_sql_storage.info
--- drupal-7.18/modules/field/modules/field_sql_storage/field_sql_storage.info 2012-12-19 20:06:43.000000000 +0100
+++ drupal-7.66/modules/field/modules/field_sql_storage/field_sql_storage.info 2019-04-17 22:39:36.000000000 +0200
@@ -7,8 +7,7 @@
files[] = field_sql_storage.test
required = TRUE
-; Information added by drupal.org packaging script on 2012-12-19
-version = "7.18"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1355944003"
-
+datestamp = "1555533576"
diff -Naur drupal-7.18/modules/field/modules/field_sql_storage/field_sql_storage.module drupal-7.66/modules/field/modules/field_sql_storage/field_sql_storage.module
--- drupal-7.18/modules/field/modules/field_sql_storage/field_sql_storage.module 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/modules/field_sql_storage/field_sql_storage.module 2019-04-17 22:20:46.000000000 +0200
@@ -65,6 +65,49 @@
}
/**
+ * 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.
*
* @param $name
@@ -180,7 +223,17 @@
foreach ($field['indexes'] as $index_name => $columns) {
$real_name = _field_sql_storage_indexname($field['field_name'], $index_name);
foreach ($columns as $column_name) {
- $current['indexes'][$real_name][] = _field_sql_storage_columnname($field['field_name'], $column_name);
+ // Indexes can be specified as either a column name or an array with
+ // column name and length. Allow for either case.
+ if (is_array($column_name)) {
+ $current['indexes'][$real_name][] = array(
+ _field_sql_storage_columnname($field['field_name'], $column_name[0]),
+ $column_name[1],
+ );
+ }
+ else {
+ $current['indexes'][$real_name][] = _field_sql_storage_columnname($field['field_name'], $column_name);
+ }
}
}
@@ -188,7 +241,7 @@
foreach ($field['foreign keys'] as $specifier => $specification) {
$real_name = _field_sql_storage_indexname($field['field_name'], $specifier);
$current['foreign keys'][$real_name]['table'] = $specification['table'];
- foreach ($specification['columns'] as $column => $referenced) {
+ foreach ($specification['columns'] as $column_name => $referenced) {
$sql_storage_column = _field_sql_storage_columnname($field['field_name'], $column_name);
$current['foreign keys'][$real_name]['columns'][$sql_storage_column] = $referenced;
}
@@ -289,7 +342,17 @@
$real_name = _field_sql_storage_indexname($field['field_name'], $name);
$real_columns = array();
foreach ($columns as $column_name) {
- $real_columns[] = _field_sql_storage_columnname($field['field_name'], $column_name);
+ // Indexes can be specified as either a column name or an array with
+ // column name and length. Allow for either case.
+ if (is_array($column_name)) {
+ $real_columns[] = array(
+ _field_sql_storage_columnname($field['field_name'], $column_name[0]),
+ $column_name[1],
+ );
+ }
+ else {
+ $real_columns[] = _field_sql_storage_columnname($field['field_name'], $column_name);
+ }
}
db_add_index($table, $real_name, $real_columns);
db_add_index($revision_table, $real_name, $real_columns);
@@ -324,11 +387,14 @@
* Implements hook_field_storage_load().
*/
function field_sql_storage_field_storage_load($entity_type, $entities, $age, $fields, $options) {
- $field_info = field_info_field_by_ids();
$load_current = $age == FIELD_LOAD_CURRENT;
foreach ($fields as $field_id => $ids) {
- $field = $field_info[$field_id];
+ // By the time this hook runs, the relevant field definitions have been
+ // populated and cached in FieldInfo, so calling field_info_field_by_id()
+ // on each field individually is more efficient than loading all fields in
+ // memory upfront with field_info_field_by_ids().
+ $field = field_info_field_by_id($field_id);
$field_name = $field['field_name'];
$table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field);
@@ -419,7 +485,7 @@
$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,
@@ -501,17 +567,21 @@
$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.
diff -Naur drupal-7.18/modules/field/modules/field_sql_storage/field_sql_storage.test drupal-7.66/modules/field/modules/field_sql_storage/field_sql_storage.test
--- drupal-7.18/modules/field/modules/field_sql_storage/field_sql_storage.test 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/modules/field_sql_storage/field_sql_storage.test 2019-04-17 22:20:46.000000000 +0200
@@ -126,7 +126,7 @@
$rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
foreach ($values as $delta => $value) {
if ($delta < $this->field['cardinality']) {
- $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Value $delta is inserted correctly"));
+ $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], format_string("Value %delta is inserted correctly", array('%delta' => $delta)));
}
else {
$this->assertFalse(array_key_exists($delta, $rows), "No extraneous value gets inserted.");
@@ -145,7 +145,7 @@
$rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
foreach ($values as $delta => $value) {
if ($delta < $this->field['cardinality']) {
- $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Value $delta is updated correctly"));
+ $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], format_string("Value %delta is updated correctly", array('%delta' => $delta)));
}
else {
$this->assertFalse(array_key_exists($delta, $rows), "No extraneous value gets updated.");
@@ -175,7 +175,7 @@
$rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
foreach ($values as $delta => $value) {
if ($delta < $this->field['cardinality']) {
- $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Update with no field_name entry leaves value $delta untouched"));
+ $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], format_string("Update with no field_name entry leaves value %delta untouched", array('%delta' => $delta)));
}
}
@@ -183,7 +183,7 @@
$entity->{$this->field_name} = NULL;
field_attach_update($entity_type, $entity);
$rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
- $this->assertEqual(count($rows), 0, t("Update with an empty field_name entry empties the field."));
+ $this->assertEqual(count($rows), 0, "Update with an empty field_name entry empties the field.");
}
/**
@@ -326,7 +326,7 @@
// Ensure that the field tables are still there.
foreach (_field_sql_storage_schema($prior_field) as $table_name => $table_info) {
- $this->assertTrue(db_table_exists($table_name), t('Table %table exists.', array('%table' => $table_name)));
+ $this->assertTrue(db_table_exists($table_name), format_string('Table %table exists.', array('%table' => $table_name)));
}
}
@@ -345,8 +345,8 @@
// Verify the indexes we will create do not exist yet.
foreach ($tables as $table) {
- $this->assertFalse(Database::getConnection()->schema()->indexExists($table, 'value'), t("No index named value exists in $table"));
- $this->assertFalse(Database::getConnection()->schema()->indexExists($table, 'value_format'), t("No index named value_format exists in $table"));
+ $this->assertFalse(Database::getConnection()->schema()->indexExists($table, 'value'), format_string("No index named value exists in %table", array('%table' => $table)));
+ $this->assertFalse(Database::getConnection()->schema()->indexExists($table, 'value_format'), format_string("No index named value_format exists in %table", array('%table' => $table)));
}
// Add data so the table cannot be dropped.
@@ -355,24 +355,24 @@
field_attach_insert('test_entity', $entity);
// Add an index
- $field = array('field_name' => $field_name, 'indexes' => array('value' => array('value')));
+ $field = array('field_name' => $field_name, 'indexes' => array('value' => array(array('value', 255))));
field_update_field($field);
foreach ($tables as $table) {
- $this->assertTrue(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value"), t("Index on value created in $table"));
+ $this->assertTrue(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value"), format_string("Index on value created in %table", array('%table' => $table)));
}
// Add a different index, removing the existing custom one.
- $field = array('field_name' => $field_name, 'indexes' => array('value_format' => array('value', 'format')));
+ $field = array('field_name' => $field_name, 'indexes' => array('value_format' => array(array('value', 127), array('format', 127))));
field_update_field($field);
foreach ($tables as $table) {
- $this->assertTrue(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value_format"), t("Index on value_format created in $table"));
- $this->assertFalse(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value"), t("Index on value removed in $table"));
+ $this->assertTrue(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value_format"), format_string("Index on value_format created in %table", array('%table' => $table)));
+ $this->assertFalse(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value"), format_string("Index on value removed in %table", array('%table' => $table)));
}
// Verify that the tables were not dropped.
$entity = field_test_create_stub_entity(0, 0, $instance['bundle']);
field_attach_load('test_entity', array(0 => $entity));
- $this->assertEqual($entity->{$field_name}[LANGUAGE_NONE][0]['value'], 'field data', t("Index changes performed without dropping the tables"));
+ $this->assertEqual($entity->{$field_name}[LANGUAGE_NONE][0]['value'], 'field data', "Index changes performed without dropping the tables");
}
/**
@@ -387,19 +387,19 @@
$instance = field_info_instance($this->instance['entity_type'], $this->instance['field_name'], $this->instance['bundle']);
// The storage details are indexed by a storage engine type.
- $this->assertTrue(array_key_exists('sql', $field['storage']['details']), t('The storage type is SQL.'));
+ $this->assertTrue(array_key_exists('sql', $field['storage']['details']), 'The storage type is SQL.');
// The SQL details are indexed by table name.
$details = $field['storage']['details']['sql'];
- $this->assertTrue(array_key_exists($current, $details[FIELD_LOAD_CURRENT]), t('Table name is available in the instance array.'));
- $this->assertTrue(array_key_exists($revision, $details[FIELD_LOAD_REVISION]), t('Revision table name is available in the instance array.'));
+ $this->assertTrue(array_key_exists($current, $details[FIELD_LOAD_CURRENT]), 'Table name is available in the instance array.');
+ $this->assertTrue(array_key_exists($revision, $details[FIELD_LOAD_REVISION]), 'Revision table name is available in the instance array.');
// Test current and revision storage details together because the columns
// are the same.
foreach ((array) $this->field['columns'] as $column_name => $attributes) {
$storage_column_name = _field_sql_storage_columnname($this->field['field_name'], $column_name);
- $this->assertEqual($details[FIELD_LOAD_CURRENT][$current][$column_name], $storage_column_name, t('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => $current)));
- $this->assertEqual($details[FIELD_LOAD_REVISION][$revision][$column_name], $storage_column_name, t('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => $revision)));
+ $this->assertEqual($details[FIELD_LOAD_CURRENT][$current][$column_name], $storage_column_name, format_string('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => $current)));
+ $this->assertEqual($details[FIELD_LOAD_REVISION][$revision][$column_name], $storage_column_name, format_string('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => $revision)));
}
}
@@ -407,21 +407,180 @@
* Test foreign key support.
*/
function testFieldSqlStorageForeignKeys() {
- // Create a decimal field.
+ // Create a 'shape' field, with a configurable foreign key (see
+ // field_test_field_schema()).
$field_name = 'testfield';
- $field = array('field_name' => $field_name, 'type' => 'text');
- $field = field_create_field($field);
- // Retrieve the field and instance with field_info and verify the foreign
- // keys are in place.
+ $foreign_key_name = 'shape';
+ $field = array('field_name' => $field_name, 'type' => 'shape', 'settings' => array('foreign_key_name' => $foreign_key_name));
+ field_create_field($field);
+
+ // Retrieve the field definition and check that the foreign key is in place.
+ $field = field_info_field($field_name);
+ $this->assertEqual($field['foreign keys'][$foreign_key_name]['table'], $foreign_key_name, 'Foreign key table name preserved through CRUD');
+ $this->assertEqual($field['foreign keys'][$foreign_key_name]['columns'][$foreign_key_name], 'id', 'Foreign key column name preserved through CRUD');
+
+ // Update the field settings, it should update the foreign key definition
+ // too.
+ $foreign_key_name = 'color';
+ $field['settings']['foreign_key_name'] = $foreign_key_name;
+ field_update_field($field);
+
+ // Retrieve the field definition and check that the foreign key is in place.
$field = field_info_field($field_name);
- $this->assertEqual($field['foreign keys']['format']['table'], 'filter_format', t('Foreign key table name preserved through CRUD'));
- $this->assertEqual($field['foreign keys']['format']['columns']['format'], 'format', t('Foreign key column name preserved through CRUD'));
+ $this->assertEqual($field['foreign keys'][$foreign_key_name]['table'], $foreign_key_name, 'Foreign key table name modified after update');
+ $this->assertEqual($field['foreign keys'][$foreign_key_name]['columns'][$foreign_key_name], 'id', 'Foreign key column name modified after update');
+
// Now grab the SQL schema and verify that too.
- $schema = drupal_get_schema(_field_sql_storage_tablename($field));
- $this->assertEqual(count($schema['foreign keys']), 1, t("There is 1 foreign key in the schema"));
+ $schema = drupal_get_schema(_field_sql_storage_tablename($field), TRUE);
+ $this->assertEqual(count($schema['foreign keys']), 1, 'There is 1 foreign key in the schema');
$foreign_key = reset($schema['foreign keys']);
- $filter_column = _field_sql_storage_columnname($field['field_name'], 'format');
- $this->assertEqual($foreign_key['table'], 'filter_format', t('Foreign key table name preserved in the schema'));
- $this->assertEqual($foreign_key['columns'][$filter_column], 'format', t('Foreign key column name preserved in the schema'));
+ $foreign_key_column = _field_sql_storage_columnname($field['field_name'], $foreign_key_name);
+ $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']);
}
}
diff -Naur drupal-7.18/modules/field/modules/list/list.info drupal-7.66/modules/field/modules/list/list.info
--- drupal-7.18/modules/field/modules/list/list.info 2012-12-19 20:06:43.000000000 +0100
+++ drupal-7.66/modules/field/modules/list/list.info 2019-04-17 22:39:36.000000000 +0200
@@ -7,8 +7,7 @@
dependencies[] = options
files[] = tests/list.test
-; Information added by drupal.org packaging script on 2012-12-19
-version = "7.18"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1355944003"
-
+datestamp = "1555533576"
diff -Naur drupal-7.18/modules/field/modules/list/list.install drupal-7.66/modules/field/modules/list/list.install
--- drupal-7.18/modules/field/modules/list/list.install 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/modules/list/list.install 2019-04-17 22:20:46.000000000 +0200
@@ -61,7 +61,7 @@
// Additionally, float keys need to be disambiguated ('.5' is '0.5').
if ($field['type'] == 'list_number' && !empty($allowed_values)) {
- $keys = array_map(create_function('$a', 'return (string) (float) $a;'), array_keys($allowed_values));
+ $keys = array_map('_list_update_7001_float_string_cast', array_keys($allowed_values));
$allowed_values = array_combine($keys, array_values($allowed_values));
}
@@ -89,6 +89,13 @@
}
/**
+ * Helper callback function to cast the array element.
+ */
+function _list_update_7001_float_string_cast($element) {
+ return (string) (float) $element;
+}
+
+/**
* Helper function for list_update_7001: extract allowed values from a string.
*
* This reproduces the parsing logic in use before D7 RC2.
diff -Naur drupal-7.18/modules/field/modules/list/tests/list.test drupal-7.66/modules/field/modules/list/tests/list.test
--- drupal-7.18/modules/field/modules/list/tests/list.test 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/modules/list/tests/list.test 2019-04-17 22:20:46.000000000 +0200
@@ -51,9 +51,9 @@
// All three options appear.
$entity = field_test_create_stub_entity();
$form = drupal_get_form('field_test_entity_form', $entity);
- $this->assertTrue(!empty($form[$this->field_name][$langcode][1]), t('Option 1 exists'));
- $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), t('Option 2 exists'));
- $this->assertTrue(!empty($form[$this->field_name][$langcode][3]), t('Option 3 exists'));
+ $this->assertTrue(!empty($form[$this->field_name][$langcode][1]), 'Option 1 exists');
+ $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), 'Option 2 exists');
+ $this->assertTrue(!empty($form[$this->field_name][$langcode][3]), 'Option 3 exists');
// Use one of the values in an actual entity, and check that this value
// cannot be removed from the list.
@@ -77,19 +77,19 @@
field_update_field($this->field);
$entity = field_test_create_stub_entity();
$form = drupal_get_form('field_test_entity_form', $entity);
- $this->assertTrue(empty($form[$this->field_name][$langcode][1]), t('Option 1 does not exist'));
- $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), t('Option 2 exists'));
- $this->assertTrue(empty($form[$this->field_name][$langcode][3]), t('Option 3 does not exist'));
+ $this->assertTrue(empty($form[$this->field_name][$langcode][1]), 'Option 1 does not exist');
+ $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), 'Option 2 exists');
+ $this->assertTrue(empty($form[$this->field_name][$langcode][3]), 'Option 3 does not exist');
// Completely new options appear.
$this->field['settings']['allowed_values'] = array(10 => 'Update', 20 => 'Twenty');
field_update_field($this->field);
$form = drupal_get_form('field_test_entity_form', $entity);
- $this->assertTrue(empty($form[$this->field_name][$langcode][1]), t('Option 1 does not exist'));
- $this->assertTrue(empty($form[$this->field_name][$langcode][2]), t('Option 2 does not exist'));
- $this->assertTrue(empty($form[$this->field_name][$langcode][3]), t('Option 3 does not exist'));
- $this->assertTrue(!empty($form[$this->field_name][$langcode][10]), t('Option 10 exists'));
- $this->assertTrue(!empty($form[$this->field_name][$langcode][20]), t('Option 20 exists'));
+ $this->assertTrue(empty($form[$this->field_name][$langcode][1]), 'Option 1 does not exist');
+ $this->assertTrue(empty($form[$this->field_name][$langcode][2]), 'Option 2 does not exist');
+ $this->assertTrue(empty($form[$this->field_name][$langcode][3]), 'Option 3 does not exist');
+ $this->assertTrue(!empty($form[$this->field_name][$langcode][10]), 'Option 10 exists');
+ $this->assertTrue(!empty($form[$this->field_name][$langcode][20]), 'Option 20 exists');
// Options are reset when a new field with the same name is created.
field_delete_field($this->field_name);
@@ -107,9 +107,9 @@
$this->instance = field_create_instance($this->instance);
$entity = field_test_create_stub_entity();
$form = drupal_get_form('field_test_entity_form', $entity);
- $this->assertTrue(!empty($form[$this->field_name][$langcode][1]), t('Option 1 exists'));
- $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), t('Option 2 exists'));
- $this->assertTrue(!empty($form[$this->field_name][$langcode][3]), t('Option 3 exists'));
+ $this->assertTrue(!empty($form[$this->field_name][$langcode][1]), 'Option 1 exists');
+ $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), 'Option 2 exists');
+ $this->assertTrue(!empty($form[$this->field_name][$langcode][3]), 'Option 3 exists');
}
}
@@ -212,7 +212,7 @@
parent::setUp('field_test', 'field_ui');
// Create test user.
- $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy'));
+ $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy', 'administer fields'));
$this->drupalLogin($admin_user);
// Create content type, with underscores.
@@ -233,20 +233,20 @@
// Flat list of textual values.
$string = "Zero\nOne";
$array = array('0' => 'Zero', '1' => 'One');
- $this->assertAllowedValuesInput($string, $array, t('Unkeyed lists are accepted.'));
+ $this->assertAllowedValuesInput($string, $array, 'Unkeyed lists are accepted.');
// Explicit integer keys.
$string = "0|Zero\n2|Two";
$array = array('0' => 'Zero', '2' => 'Two');
- $this->assertAllowedValuesInput($string, $array, t('Integer keys are accepted.'));
+ $this->assertAllowedValuesInput($string, $array, 'Integer keys are accepted.');
// Check that values can be added and removed.
$string = "0|Zero\n1|One";
$array = array('0' => 'Zero', '1' => 'One');
- $this->assertAllowedValuesInput($string, $array, t('Values can be added and removed.'));
+ $this->assertAllowedValuesInput($string, $array, 'Values can be added and removed.');
// Non-integer keys.
- $this->assertAllowedValuesInput("1.1|One", 'keys must be integers', t('Non integer keys are rejected.'));
- $this->assertAllowedValuesInput("abc|abc", 'keys must be integers', t('Non integer keys are rejected.'));
+ $this->assertAllowedValuesInput("1.1|One", 'keys must be integers', 'Non integer keys are rejected.');
+ $this->assertAllowedValuesInput("abc|abc", 'keys must be integers', 'Non integer keys are rejected.');
// Mixed list of keyed and unkeyed values.
- $this->assertAllowedValuesInput("Zero\n1|One", 'invalid input', t('Mixed lists are rejected.'));
+ $this->assertAllowedValuesInput("Zero\n1|One", 'invalid input', 'Mixed lists are rejected.');
// Create a node with actual data for the field.
$settings = array(
@@ -256,22 +256,22 @@
$node = $this->drupalCreateNode($settings);
// Check that a flat list of values is rejected once the field has data.
- $this->assertAllowedValuesInput( "Zero\nOne", 'invalid input', t('Unkeyed lists are rejected once the field has data.'));
+ $this->assertAllowedValuesInput( "Zero\nOne", 'invalid input', 'Unkeyed lists are rejected once the field has data.');
// Check that values can be added but values in use cannot be removed.
$string = "0|Zero\n1|One\n2|Two";
$array = array('0' => 'Zero', '1' => 'One', '2' => 'Two');
- $this->assertAllowedValuesInput($string, $array, t('Values can be added.'));
+ $this->assertAllowedValuesInput($string, $array, 'Values can be added.');
$string = "0|Zero\n1|One";
$array = array('0' => 'Zero', '1' => 'One');
- $this->assertAllowedValuesInput($string, $array, t('Values not in use can be removed.'));
- $this->assertAllowedValuesInput("0|Zero", 'some values are being removed while currently in use', t('Values in use cannot be removed.'));
+ $this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.');
+ $this->assertAllowedValuesInput("0|Zero", 'some values are being removed while currently in use', 'Values in use cannot be removed.');
// Delete the node, remove the value.
node_delete($node->nid);
$string = "0|Zero";
$array = array('0' => 'Zero');
- $this->assertAllowedValuesInput($string, $array, t('Values not in use can be removed.'));
+ $this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.');
}
/**
@@ -284,19 +284,19 @@
// Flat list of textual values.
$string = "Zero\nOne";
$array = array('0' => 'Zero', '1' => 'One');
- $this->assertAllowedValuesInput($string, $array, t('Unkeyed lists are accepted.'));
+ $this->assertAllowedValuesInput($string, $array, 'Unkeyed lists are accepted.');
// Explicit numeric keys.
$string = "0|Zero\n.5|Point five";
$array = array('0' => 'Zero', '0.5' => 'Point five');
- $this->assertAllowedValuesInput($string, $array, t('Integer keys are accepted.'));
+ $this->assertAllowedValuesInput($string, $array, 'Integer keys are accepted.');
// Check that values can be added and removed.
$string = "0|Zero\n.5|Point five\n1.0|One";
$array = array('0' => 'Zero', '0.5' => 'Point five', '1' => 'One');
- $this->assertAllowedValuesInput($string, $array, t('Values can be added and removed.'));
+ $this->assertAllowedValuesInput($string, $array, 'Values can be added and removed.');
// Non-numeric keys.
- $this->assertAllowedValuesInput("abc|abc\n", 'each key must be a valid integer or decimal', t('Non numeric keys are rejected.'));
+ $this->assertAllowedValuesInput("abc|abc\n", 'each key must be a valid integer or decimal', 'Non numeric keys are rejected.');
// Mixed list of keyed and unkeyed values.
- $this->assertAllowedValuesInput("Zero\n1|One\n", 'invalid input', t('Mixed lists are rejected.'));
+ $this->assertAllowedValuesInput("Zero\n1|One\n", 'invalid input', 'Mixed lists are rejected.');
// Create a node with actual data for the field.
$settings = array(
@@ -306,22 +306,22 @@
$node = $this->drupalCreateNode($settings);
// Check that a flat list of values is rejected once the field has data.
- $this->assertAllowedValuesInput("Zero\nOne", 'invalid input', t('Unkeyed lists are rejected once the field has data.'));
+ $this->assertAllowedValuesInput("Zero\nOne", 'invalid input', 'Unkeyed lists are rejected once the field has data.');
// Check that values can be added but values in use cannot be removed.
$string = "0|Zero\n.5|Point five\n2|Two";
$array = array('0' => 'Zero', '0.5' => 'Point five', '2' => 'Two');
- $this->assertAllowedValuesInput($string, $array, t('Values can be added.'));
+ $this->assertAllowedValuesInput($string, $array, 'Values can be added.');
$string = "0|Zero\n.5|Point five";
$array = array('0' => 'Zero', '0.5' => 'Point five');
- $this->assertAllowedValuesInput($string, $array, t('Values not in use can be removed.'));
- $this->assertAllowedValuesInput("0|Zero", 'some values are being removed while currently in use', t('Values in use cannot be removed.'));
+ $this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.');
+ $this->assertAllowedValuesInput("0|Zero", 'some values are being removed while currently in use', 'Values in use cannot be removed.');
// Delete the node, remove the value.
node_delete($node->nid);
$string = "0|Zero";
$array = array('0' => 'Zero');
- $this->assertAllowedValuesInput($string, $array, t('Values not in use can be removed.'));
+ $this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.');
}
/**
@@ -334,21 +334,21 @@
// Flat list of textual values.
$string = "Zero\nOne";
$array = array('Zero' => 'Zero', 'One' => 'One');
- $this->assertAllowedValuesInput($string, $array, t('Unkeyed lists are accepted.'));
+ $this->assertAllowedValuesInput($string, $array, 'Unkeyed lists are accepted.');
// Explicit keys.
$string = "zero|Zero\none|One";
$array = array('zero' => 'Zero', 'one' => 'One');
- $this->assertAllowedValuesInput($string, $array, t('Explicit keys are accepted.'));
+ $this->assertAllowedValuesInput($string, $array, 'Explicit keys are accepted.');
// Check that values can be added and removed.
$string = "zero|Zero\ntwo|Two";
$array = array('zero' => 'Zero', 'two' => 'Two');
- $this->assertAllowedValuesInput($string, $array, t('Values can be added and removed.'));
+ $this->assertAllowedValuesInput($string, $array, 'Values can be added and removed.');
// Mixed list of keyed and unkeyed values.
$string = "zero|Zero\nOne\n";
$array = array('zero' => 'Zero', 'One' => 'One');
- $this->assertAllowedValuesInput($string, $array, t('Mixed lists are accepted.'));
+ $this->assertAllowedValuesInput($string, $array, 'Mixed lists are accepted.');
// Overly long keys.
- $this->assertAllowedValuesInput("zero|Zero\n" . $this->randomName(256) . "|One", 'each key must be a string at most 255 characters long', t('Overly long keys are rejected.'));
+ $this->assertAllowedValuesInput("zero|Zero\n" . $this->randomName(256) . "|One", 'each key must be a string at most 255 characters long', 'Overly long keys are rejected.');
// Create a node with actual data for the field.
$settings = array(
@@ -361,22 +361,22 @@
// data.
$string = "Zero\nOne";
$array = array('Zero' => 'Zero', 'One' => 'One');
- $this->assertAllowedValuesInput($string, $array, t('Unkeyed lists are still accepted once the field has data.'));
+ $this->assertAllowedValuesInput($string, $array, 'Unkeyed lists are still accepted once the field has data.');
// Check that values can be added but values in use cannot be removed.
$string = "Zero\nOne\nTwo";
$array = array('Zero' => 'Zero', 'One' => 'One', 'Two' => 'Two');
- $this->assertAllowedValuesInput($string, $array, t('Values can be added.'));
+ $this->assertAllowedValuesInput($string, $array, 'Values can be added.');
$string = "Zero\nOne";
$array = array('Zero' => 'Zero', 'One' => 'One');
- $this->assertAllowedValuesInput($string, $array, t('Values not in use can be removed.'));
- $this->assertAllowedValuesInput("Zero", 'some values are being removed while currently in use', t('Values in use cannot be removed.'));
+ $this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.');
+ $this->assertAllowedValuesInput("Zero", 'some values are being removed while currently in use', 'Values in use cannot be removed.');
// Delete the node, remove the value.
node_delete($node->nid);
$string = "Zero";
$array = array('Zero' => 'Zero');
- $this->assertAllowedValuesInput($string, $array, t('Values not in use can be removed.'));
+ $this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.');
}
/**
@@ -395,15 +395,15 @@
'off' => $off,
);
$this->drupalPost($this->admin_path, $edit, t('Save settings'));
- $this->assertText("Saved field_list_boolean configuration.", t("The 'On' and 'Off' form fields work for boolean fields."));
+ $this->assertText("Saved field_list_boolean configuration.", "The 'On' and 'Off' form fields work for boolean fields.");
// Test the allowed_values on the field settings form.
$this->drupalGet($this->admin_path);
- $this->assertFieldByName('on', $on, t("The 'On' value is stored correctly."));
- $this->assertFieldByName('off', $off, t("The 'Off' value is stored correctly."));
+ $this->assertFieldByName('on', $on, "The 'On' value is stored correctly.");
+ $this->assertFieldByName('off', $off, "The 'Off' value is stored correctly.");
$field = field_info_field($this->field_name);
- $this->assertEqual($field['settings']['allowed_values'], $allowed_values, t('The allowed value is correct'));
- $this->assertFalse(isset($field['settings']['on']), t('The on value is not saved into settings'));
- $this->assertFalse(isset($field['settings']['off']), t('The off value is not saved into settings'));
+ $this->assertEqual($field['settings']['allowed_values'], $allowed_values, 'The allowed value is correct');
+ $this->assertFalse(isset($field['settings']['on']), 'The on value is not saved into settings');
+ $this->assertFalse(isset($field['settings']['off']), 'The off value is not saved into settings');
}
/**
diff -Naur drupal-7.18/modules/field/modules/list/tests/list_test.info drupal-7.66/modules/field/modules/list/tests/list_test.info
--- drupal-7.18/modules/field/modules/list/tests/list_test.info 2012-12-19 20:06:43.000000000 +0100
+++ drupal-7.66/modules/field/modules/list/tests/list_test.info 2019-04-17 22:39:36.000000000 +0200
@@ -5,8 +5,7 @@
version = VERSION
hidden = TRUE
-; Information added by drupal.org packaging script on 2012-12-19
-version = "7.18"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1355944003"
-
+datestamp = "1555533576"
diff -Naur drupal-7.18/modules/field/modules/number/number.info drupal-7.66/modules/field/modules/number/number.info
--- drupal-7.18/modules/field/modules/number/number.info 2012-12-19 20:06:43.000000000 +0100
+++ drupal-7.66/modules/field/modules/number/number.info 2019-04-17 22:39:36.000000000 +0200
@@ -6,8 +6,7 @@
dependencies[] = field
files[] = number.test
-; Information added by drupal.org packaging script on 2012-12-19
-version = "7.18"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1355944003"
-
+datestamp = "1555533576"
diff -Naur drupal-7.18/modules/field/modules/number/number.module drupal-7.66/modules/field/modules/number/number.module
--- drupal-7.18/modules/field/modules/number/number.module 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/modules/number/number.module 2019-04-17 22:20:46.000000000 +0200
@@ -164,6 +164,15 @@
}
}
}
+ if ($field['type'] == 'number_float') {
+ // Remove the decimal point from float values with decimal
+ // point but no decimal numbers.
+ foreach ($items as $delta => $item) {
+ if (isset($item['value'])) {
+ $items[$delta]['value'] = floatval($item['value']);
+ }
+ }
+ }
}
/**
@@ -188,7 +197,7 @@
'label' => t('Default'),
'field types' => array('number_integer'),
'settings' => array(
- 'thousand_separator' => ' ',
+ 'thousand_separator' => '',
// The 'decimal_separator' and 'scale' settings are not configurable
// through the UI, and will therefore keep their default values. They
// are only present so that the 'number_integer' and 'number_decimal'
@@ -202,7 +211,7 @@
'label' => t('Default'),
'field types' => array('number_decimal', 'number_float'),
'settings' => array(
- 'thousand_separator' => ' ',
+ 'thousand_separator' => '',
'decimal_separator' => '.',
'scale' => 2,
'prefix_suffix' => TRUE,
@@ -222,6 +231,8 @@
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
+ $element = array();
+
if ($display['type'] == 'number_decimal' || $display['type'] == 'number_integer') {
$options = array(
'' => t(''),
diff -Naur drupal-7.18/modules/field/modules/number/number.test drupal-7.66/modules/field/modules/number/number.test
--- drupal-7.18/modules/field/modules/number/number.test 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/modules/number/number.test 2019-04-17 22:20:46.000000000 +0200
@@ -23,7 +23,7 @@
function setUp() {
parent::setUp('field_test');
- $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content', 'administer content types'));
+ $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content', 'administer content types', 'administer fields'));
$this->drupalLogin($this->web_user);
}
@@ -58,7 +58,7 @@
// Display creation form.
$this->drupalGet('test-entity/add/test-bundle');
$langcode = LANGUAGE_NONE;
- $this->assertFieldByName("{$this->field['field_name']}[$langcode][0][value]", '', t('Widget is displayed'));
+ $this->assertFieldByName("{$this->field['field_name']}[$langcode][0][value]", '', 'Widget is displayed');
// Submit a signed decimal value within the allowed precision and scale.
$value = '-1234.5678';
@@ -68,8 +68,8 @@
$this->drupalPost(NULL, $edit, t('Save'));
preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match);
$id = $match[1];
- $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), t('Entity was created'));
- $this->assertRaw(round($value, 2), t('Value is displayed.'));
+ $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created');
+ $this->assertRaw($value, 'Value is displayed.');
// Try to create entries with more than one decimal separator; assert fail.
$wrong_entries = array(
@@ -89,7 +89,7 @@
$this->assertText(
t('There should only be one decimal separator (@separator)',
array('@separator' => $this->field['settings']['decimal_separator'])),
- t('Correctly failed to save decimal value with more than one decimal point.')
+ 'Correctly failed to save decimal value with more than one decimal point.'
);
}
@@ -152,4 +152,50 @@
);
$this->drupalPost(NULL, $edit, t('Save'));
}
+
+ /**
+ * Test number_float field.
+ */
+ function testNumberFloatField() {
+ $this->field = array(
+ 'field_name' => drupal_strtolower($this->randomName()),
+ 'type' => 'number_float',
+ 'settings' => array(
+ 'precision' => 8, 'scale' => 4, 'decimal_separator' => '.',
+ )
+ );
+ field_create_field($this->field);
+ $this->instance = array(
+ 'field_name' => $this->field['field_name'],
+ 'entity_type' => 'test_entity',
+ 'bundle' => 'test_bundle',
+ 'widget' => array(
+ 'type' => 'number',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'number_float',
+ ),
+ ),
+ );
+ field_create_instance($this->instance);
+
+ $langcode = LANGUAGE_NONE;
+ $value = array(
+ '9.' => '9',
+ '.' => '0',
+ '123.55' => '123.55',
+ '.55' => '0.55',
+ '-0.55' => '-0.55',
+ );
+ foreach($value as $key => $value) {
+ $edit = array(
+ "{$this->field['field_name']}[$langcode][0][value]" => $key,
+ );
+ $this->drupalPost('test-entity/add/test-bundle', $edit, t('Save'));
+ $this->assertNoText("PDOException");
+ $this->assertRaw($value, 'Correct value is displayed.');
+ }
+ }
+
}
diff -Naur drupal-7.18/modules/field/modules/options/options.info drupal-7.66/modules/field/modules/options/options.info
--- drupal-7.18/modules/field/modules/options/options.info 2012-12-19 20:06:43.000000000 +0100
+++ drupal-7.66/modules/field/modules/options/options.info 2019-04-17 22:39:36.000000000 +0200
@@ -6,8 +6,7 @@
dependencies[] = field
files[] = options.test
-; Information added by drupal.org packaging script on 2012-12-19
-version = "7.18"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1355944003"
-
+datestamp = "1555533576"
diff -Naur drupal-7.18/modules/field/modules/options/options.module drupal-7.66/modules/field/modules/options/options.module
--- drupal-7.18/modules/field/modules/options/options.module 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/modules/options/options.module 2019-04-17 22:20:46.000000000 +0200
@@ -185,6 +185,7 @@
$base = array(
'filter_xss' => FALSE,
'strip_tags' => FALSE,
+ 'strip_tags_and_unescape' => FALSE,
'empty_option' => FALSE,
'optgroups' => FALSE,
);
@@ -195,7 +196,7 @@
case 'select':
$properties = array(
// Select boxes do not support any HTML tag.
- 'strip_tags' => TRUE,
+ 'strip_tags_and_unescape' => TRUE,
'optgroups' => TRUE,
);
if ($multiple) {
@@ -271,9 +272,16 @@
_options_prepare_options($options[$value], $properties);
}
else {
+ // The 'strip_tags' option is deprecated. Use 'strip_tags_and_unescape'
+ // when plain text is required (and where the output will be run through
+ // check_plain() before being inserted back into HTML) or 'filter_xss'
+ // when HTML is required.
if ($properties['strip_tags']) {
$options[$value] = strip_tags($label);
}
+ if ($properties['strip_tags_and_unescape']) {
+ $options[$value] = decode_entities(strip_tags($label));
+ }
if ($properties['filter_xss']) {
$options[$value] = field_filter_xss($label);
}
diff -Naur drupal-7.18/modules/field/modules/options/options.test drupal-7.66/modules/field/modules/options/options.test
--- drupal-7.18/modules/field/modules/options/options.test 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/modules/options/options.test 2019-04-17 22:20:46.000000000 +0200
@@ -1,7 +1,7 @@
'list_integer',
'cardinality' => 1,
'settings' => array(
- // Make sure that 0 works as an option.
- 'allowed_values' => array(0 => 'Zero', 1 => 'One', 2 => 'Some & unescaped markup'),
+ 'allowed_values' => array(
+ // Make sure that 0 works as an option.
+ 0 => 'Zero',
+ 1 => 'One',
+ // Make sure that option text is properly sanitized.
+ 2 => 'Some & unescaped markup',
+ // Make sure that HTML entities in option text are not double-encoded.
+ 3 => 'Some HTML encoded markup with < & >',
+ ),
),
);
$this->card_1 = field_create_field($this->card_1);
@@ -35,8 +42,13 @@
'type' => 'list_integer',
'cardinality' => 2,
'settings' => array(
- // Make sure that 0 works as an option.
- 'allowed_values' => array(0 => 'Zero', 1 => 'One', 2 => 'Some & unescaped markup'),
+ 'allowed_values' => array(
+ // Make sure that 0 works as an option.
+ 0 => 'Zero',
+ 1 => 'One',
+ // Make sure that option text is properly sanitized.
+ 2 => 'Some & unescaped markup',
+ ),
),
);
$this->card_2 = field_create_field($this->card_2);
@@ -47,14 +59,18 @@
'type' => 'list_boolean',
'cardinality' => 1,
'settings' => array(
- // Make sure that 0 works as a 'on' value'.
- 'allowed_values' => array(1 => 'Zero', 0 => 'Some & unescaped markup'),
+ 'allowed_values' => array(
+ // Make sure that 1 works as a 'on' value'.
+ 1 => 'Zero',
+ // Make sure that option text is properly sanitized.
+ 0 => 'Some & unescaped markup',
+ ),
),
);
$this->bool = field_create_field($this->bool);
// Create a web user.
- $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content'));
+ $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content', 'administer fields'));
$this->drupalLogin($this->web_user);
}
@@ -85,7 +101,7 @@
$this->assertNoFieldChecked("edit-card-1-$langcode-0");
$this->assertNoFieldChecked("edit-card-1-$langcode-1");
$this->assertNoFieldChecked("edit-card-1-$langcode-2");
- $this->assertRaw('Some dangerous & unescaped markup', t('Option text was properly filtered.'));
+ $this->assertRaw('Some dangerous & unescaped markup', 'Option text was properly filtered.');
// Select first option.
$edit = array("card_1[$langcode]" => 0);
@@ -139,7 +155,7 @@
$this->assertNoFieldChecked("edit-card-2-$langcode-0");
$this->assertNoFieldChecked("edit-card-2-$langcode-1");
$this->assertNoFieldChecked("edit-card-2-$langcode-2");
- $this->assertRaw('Some dangerous & unescaped markup', t('Option text was properly filtered.'));
+ $this->assertRaw('Some dangerous & unescaped markup', 'Option text was properly filtered.');
// Submit form: select first and third options.
$edit = array(
@@ -178,7 +194,7 @@
"card_2[$langcode][2]" => TRUE,
);
$this->drupalPost(NULL, $edit, t('Save'));
- $this->assertText('this field cannot hold more than 2 values', t('Validation error was displayed.'));
+ $this->assertText('this field cannot hold more than 2 values', 'Validation error was displayed.');
// Submit form: uncheck all options.
$edit = array(
@@ -225,19 +241,20 @@
// Display form.
$this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit');
// A required field without any value has a "none" option.
- $this->assertTrue($this->xpath('//select[@id=:id]//option[@value="_none" and text()=:label]', array(':id' => 'edit-card-1-' . $langcode, ':label' => t('- Select a value -'))), t('A required select list has a "Select a value" choice.'));
+ $this->assertTrue($this->xpath('//select[@id=:id]//option[@value="_none" and text()=:label]', array(':id' => 'edit-card-1-' . $langcode, ':label' => t('- Select a value -'))), 'A required select list has a "Select a value" choice.');
// With no field data, nothing is selected.
$this->assertNoOptionSelected("edit-card-1-$langcode", '_none');
$this->assertNoOptionSelected("edit-card-1-$langcode", 0);
$this->assertNoOptionSelected("edit-card-1-$langcode", 1);
$this->assertNoOptionSelected("edit-card-1-$langcode", 2);
- $this->assertRaw('Some dangerous & unescaped markup', t('Option text was properly filtered.'));
+ $this->assertRaw('Some dangerous & unescaped markup', 'Option text was properly filtered.');
+ $this->assertRaw('Some HTML encoded markup with < & >', 'HTML entities in option text were properly handled and not double-encoded');
// Submit form: select invalid 'none' option.
$edit = array("card_1[$langcode]" => '_none');
$this->drupalPost(NULL, $edit, t('Save'));
- $this->assertRaw(t('!title field is required.', array('!title' => $instance['field_name'])), t('Cannot save a required field when selecting "none" from the select list.'));
+ $this->assertRaw(t('!title field is required.', array('!title' => $instance['field_name'])), 'Cannot save a required field when selecting "none" from the select list.');
// Submit form: select first option.
$edit = array("card_1[$langcode]" => 0);
@@ -247,7 +264,7 @@
// Display form: check that the right options are selected.
$this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit');
// A required field with a value has no 'none' option.
- $this->assertFalse($this->xpath('//select[@id=:id]//option[@value="_none"]', array(':id' => 'edit-card-1-' . $langcode)), t('A required select list with an actual value has no "none" choice.'));
+ $this->assertFalse($this->xpath('//select[@id=:id]//option[@value="_none"]', array(':id' => 'edit-card-1-' . $langcode)), 'A required select list with an actual value has no "none" choice.');
$this->assertOptionSelected("edit-card-1-$langcode", 0);
$this->assertNoOptionSelected("edit-card-1-$langcode", 1);
$this->assertNoOptionSelected("edit-card-1-$langcode", 2);
@@ -259,7 +276,7 @@
// Display form.
$this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit');
// A non-required field has a 'none' option.
- $this->assertTrue($this->xpath('//select[@id=:id]//option[@value="_none" and text()=:label]', array(':id' => 'edit-card-1-' . $langcode, ':label' => t('- None -'))), t('A non-required select list has a "None" choice.'));
+ $this->assertTrue($this->xpath('//select[@id=:id]//option[@value="_none" and text()=:label]', array(':id' => 'edit-card-1-' . $langcode, ':label' => t('- None -'))), 'A non-required select list has a "None" choice.');
// Submit form: Unselect the option.
$edit = array("card_1[$langcode]" => '_none');
$this->drupalPost('test-entity/manage/' . $entity->ftid . '/edit', $edit, t('Save'));
@@ -276,8 +293,8 @@
$this->assertNoOptionSelected("edit-card-1-$langcode", 0);
$this->assertNoOptionSelected("edit-card-1-$langcode", 1);
$this->assertNoOptionSelected("edit-card-1-$langcode", 2);
- $this->assertRaw('Some dangerous & unescaped markup', t('Option text was properly filtered.'));
- $this->assertRaw('Group 1', t('Option groups are displayed.'));
+ $this->assertRaw('Some dangerous & unescaped markup', 'Option text was properly filtered.');
+ $this->assertRaw('Group 1', 'Option groups are displayed.');
// Submit form: select first option.
$edit = array("card_1[$langcode]" => 0);
@@ -323,7 +340,7 @@
$this->assertNoOptionSelected("edit-card-2-$langcode", 0);
$this->assertNoOptionSelected("edit-card-2-$langcode", 1);
$this->assertNoOptionSelected("edit-card-2-$langcode", 2);
- $this->assertRaw('Some dangerous & unescaped markup', t('Option text was properly filtered.'));
+ $this->assertRaw('Some dangerous & unescaped markup', 'Option text was properly filtered.');
// Submit form: select first and third options.
$edit = array("card_2[$langcode][]" => array(0 => 0, 2 => 2));
@@ -350,7 +367,7 @@
// Submit form: select the three options while the field accepts only 2.
$edit = array("card_2[$langcode][]" => array(0 => 0, 1 => 1, 2 => 2));
$this->drupalPost(NULL, $edit, t('Save'));
- $this->assertText('this field cannot hold more than 2 values', t('Validation error was displayed.'));
+ $this->assertText('this field cannot hold more than 2 values', 'Validation error was displayed.');
// Submit form: uncheck all options.
$edit = array("card_2[$langcode][]" => array());
@@ -359,7 +376,7 @@
// Test the 'None' option.
- // Check that the 'none' option has no efect if actual options are selected
+ // Check that the 'none' option has no effect if actual options are selected
// as well.
$edit = array("card_2[$langcode][]" => array('_none' => '_none', 0 => 0));
$this->drupalPost('test-entity/manage/' . $entity->ftid . '/edit', $edit, t('Save'));
@@ -374,7 +391,7 @@
$instance['required'] = TRUE;
field_update_instance($instance);
$this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit');
- $this->assertFalse($this->xpath('//select[@id=:id]//option[@value=""]', array(':id' => 'edit-card-2-' . $langcode)), t('A required select list does not have an empty key.'));
+ $this->assertFalse($this->xpath('//select[@id=:id]//option[@value=""]', array(':id' => 'edit-card-2-' . $langcode)), 'A required select list does not have an empty key.');
// We do not have to test that a required select list with one option is
// auto-selected because the browser does it for us.
@@ -393,8 +410,8 @@
$this->assertNoOptionSelected("edit-card-2-$langcode", 0);
$this->assertNoOptionSelected("edit-card-2-$langcode", 1);
$this->assertNoOptionSelected("edit-card-2-$langcode", 2);
- $this->assertRaw('Some dangerous & unescaped markup', t('Option text was properly filtered.'));
- $this->assertRaw('Group 1', t('Option groups are displayed.'));
+ $this->assertRaw('Some dangerous & unescaped markup', 'Option text was properly filtered.');
+ $this->assertRaw('Group 1', 'Option groups are displayed.');
// Submit form: select first option.
$edit = array("card_2[$langcode][]" => array(0 => 0));
@@ -438,7 +455,7 @@
// Display form: with no field data, option is unchecked.
$this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit');
$this->assertNoFieldChecked("edit-bool-$langcode");
- $this->assertRaw('Some dangerous & unescaped markup', t('Option text was properly filtered.'));
+ $this->assertRaw('Some dangerous & unescaped markup', 'Option text was properly filtered.');
// Submit form: check the option.
$edit = array("bool[$langcode]" => TRUE);
@@ -459,7 +476,7 @@
$this->assertNoFieldChecked("edit-bool-$langcode");
// Create admin user.
- $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy'));
+ $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy', 'administer fields'));
$this->drupalLogin($admin_user);
// Create a test field instance.
@@ -483,13 +500,13 @@
$this->assertText(
'Use field label instead of the "On value" as label ',
- t('Display setting checkbox available.')
+ 'Display setting checkbox available.'
);
$this->assertFieldByXPath(
'*//label[@for="edit-' . $this->bool['field_name'] . '-und" and text()="MyOnValue "]',
TRUE,
- t('Default case shows "On value"')
+ 'Default case shows "On value"'
);
// Enable setting
@@ -502,16 +519,16 @@
$this->drupalGet($fieldEditUrl);
$this->assertText(
'Use field label instead of the "On value" as label ',
- t('Display setting checkbox is available')
+ 'Display setting checkbox is available'
);
$this->assertFieldChecked(
'edit-instance-widget-settings-display-label',
- t('Display settings checkbox checked')
+ 'Display settings checkbox checked'
);
$this->assertFieldByXPath(
'*//label[@for="edit-' . $this->bool['field_name'] . '-und" and text()="' . $this->bool['field_name'] . ' "]',
TRUE,
- t('Display label changes label of the checkbox')
+ 'Display label changes label of the checkbox'
);
}
}
diff -Naur drupal-7.18/modules/field/modules/text/text.info drupal-7.66/modules/field/modules/text/text.info
--- drupal-7.18/modules/field/modules/text/text.info 2012-12-19 20:06:43.000000000 +0100
+++ drupal-7.66/modules/field/modules/text/text.info 2019-04-17 22:39:36.000000000 +0200
@@ -7,8 +7,7 @@
files[] = text.test
required = TRUE
-; Information added by drupal.org packaging script on 2012-12-19
-version = "7.18"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1355944003"
-
+datestamp = "1555533576"
diff -Naur drupal-7.18/modules/field/modules/text/text.js drupal-7.66/modules/field/modules/text/text.js
--- drupal-7.18/modules/field/modules/text/text.js 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/modules/text/text.js 2019-04-17 22:20:46.000000000 +0200
@@ -12,9 +12,9 @@
$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 @@
}
// Setup the edit/hide summary link.
- var $link = $('(' + Drupal.t('Hide summary') + ')').toggle(
- function () {
+ var $link = $('(' + Drupal.t('Hide summary') + ')');
+ 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;
});
});
}
diff -Naur drupal-7.18/modules/field/modules/text/text.module drupal-7.66/modules/field/modules/text/text.module
--- drupal-7.18/modules/field/modules/text/text.module 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/modules/text/text.module 2019-04-17 22:20:46.000000000 +0200
@@ -223,11 +223,13 @@
if (strpos($display['type'], '_trimmed') !== FALSE) {
$element['trim_length'] = array(
- '#title' => t('Trim length'),
+ '#title' => t('Trimmed limit'),
'#type' => 'textfield',
+ '#field_suffix' => t('characters'),
'#size' => 10,
'#default_value' => $settings['trim_length'],
'#element_validate' => array('element_validate_integer_positive'),
+ '#description' => t('If the summary is not set, the trimmed %label field will be shorter than this character limit.', array('%label' => $instance['label'])),
'#required' => TRUE,
);
}
@@ -245,7 +247,7 @@
$summary = '';
if (strpos($display['type'], '_trimmed') !== FALSE) {
- $summary = t('Trim length') . ': ' . $settings['trim_length'];
+ $summary = t('Trimmed limit: @trim_length characters', array('@trim_length' => $settings['trim_length']));
}
return $summary;
diff -Naur drupal-7.18/modules/field/modules/text/text.test drupal-7.66/modules/field/modules/text/text.test
--- drupal-7.18/modules/field/modules/text/text.test 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/modules/text/text.test 2019-04-17 22:20:46.000000000 +0200
@@ -110,8 +110,8 @@
// Display creation form.
$this->drupalGet('test-entity/add/test-bundle');
- $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', t('Widget is displayed'));
- $this->assertNoFieldByName("{$this->field_name}[$langcode][0][format]", '1', t('Format selector is not displayed'));
+ $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', 'Widget is displayed');
+ $this->assertNoFieldByName("{$this->field_name}[$langcode][0][format]", '1', 'Format selector is not displayed');
// Submit with some value.
$value = $this->randomName();
@@ -121,7 +121,7 @@
$this->drupalPost(NULL, $edit, t('Save'));
preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match);
$id = $match[1];
- $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), t('Entity was created'));
+ $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created');
// Display the entity.
$entity = field_test_entity_test_load($id);
@@ -179,8 +179,8 @@
// Display the creation form. Since the user only has access to one format,
// no format selector will be displayed.
$this->drupalGet('test-entity/add/test-bundle');
- $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', t('Widget is displayed'));
- $this->assertNoFieldByName("{$this->field_name}[$langcode][0][format]", '', t('Format selector is not displayed'));
+ $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', 'Widget is displayed');
+ $this->assertNoFieldByName("{$this->field_name}[$langcode][0][format]", '', 'Format selector is not displayed');
// Submit with data that should be filtered.
$value = '' . $this->randomName() . '';
@@ -190,14 +190,14 @@
$this->drupalPost(NULL, $edit, t('Save'));
preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match);
$id = $match[1];
- $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), t('Entity was created'));
+ $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created');
// Display the entity.
$entity = field_test_entity_test_load($id);
$entity->content = field_attach_view($entity_type, $entity, 'full');
$this->content = drupal_render($entity->content);
- $this->assertNoRaw($value, t('HTML tags are not displayed.'));
- $this->assertRaw(check_plain($value), t('Escaped HTML is displayed correctly.'));
+ $this->assertNoRaw($value, 'HTML tags are not displayed.');
+ $this->assertRaw(check_plain($value), 'Escaped HTML is displayed correctly.');
// Create a new text format that does not escape HTML, and grant the user
// access to it.
@@ -219,21 +219,21 @@
// Display edition form.
// We should now have a 'text format' selector.
$this->drupalGet('test-entity/manage/' . $id . '/edit');
- $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", NULL, t('Widget is displayed'));
- $this->assertFieldByName("{$this->field_name}[$langcode][0][format]", NULL, t('Format selector is displayed'));
+ $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", NULL, 'Widget is displayed');
+ $this->assertFieldByName("{$this->field_name}[$langcode][0][format]", NULL, 'Format selector is displayed');
// Edit and change the text format to the new one that was created.
$edit = array(
"{$this->field_name}[$langcode][0][format]" => $format_id,
);
$this->drupalPost(NULL, $edit, t('Save'));
- $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), t('Entity was updated'));
+ $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), 'Entity was updated');
// Display the entity.
$entity = field_test_entity_test_load($id);
$entity->content = field_attach_view($entity_type, $entity, 'full');
$this->content = drupal_render($entity->content);
- $this->assertRaw($value, t('Value is displayed unfiltered'));
+ $this->assertRaw($value, 'Value is displayed unfiltered');
}
}
@@ -383,7 +383,7 @@
*/
function callTextSummary($text, $expected, $format = NULL, $size = NULL) {
$summary = text_summary($text, $format, $size);
- $this->assertIdentical($summary, $expected, t('Generated summary "@summary" matches expected "@expected".', array('@summary' => $summary, '@expected' => $expected)));
+ $this->assertIdentical($summary, $expected, format_string('Generated summary "@summary" matches expected "@expected".', array('@summary' => $summary, '@expected' => $expected)));
}
/**
@@ -401,7 +401,7 @@
$this->drupalPost('node/add/article', $edit, t('Save'));
$node = $this->drupalGetNodeByTitle($edit['title']);
- $this->assertIdentical($node->body['und'][0]['summary'], $summary, t('Article with with summary and no body has been submitted.'));
+ $this->assertIdentical($node->body['und'][0]['summary'], $summary, 'Article with with summary and no body has been submitted.');
}
}
@@ -424,6 +424,7 @@
'administer content types',
'access administration pages',
'bypass node access',
+ 'administer fields',
filter_permission_name($full_html_format),
));
$this->translator = $this->drupalCreateUser(array('create article content', 'edit own article content', 'translate content'));
@@ -436,7 +437,7 @@
// Set "Article" content type to use multilingual support with translation.
$edit = array('language_content_type' => 2);
$this->drupalPost('admin/structure/types/manage/article', $edit, t('Save content type'));
- $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Article')), t('Article content type has been updated.'));
+ $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Article')), 'Article content type has been updated.');
}
/**
@@ -464,7 +465,7 @@
$node = $this->drupalGetNodeByTitle($edit['title']);
$this->drupalGet("node/$node->nid/translate");
$this->clickLink(t('add translation'));
- $this->assertFieldByXPath("//textarea[@name='body[$langcode][0][value]']", $body, t('The textfield widget is populated.'));
+ $this->assertFieldByXPath("//textarea[@name='body[$langcode][0][value]']", $body, 'The textfield widget is populated.');
}
/**
@@ -476,7 +477,7 @@
$edit = array('field[cardinality]' => -1);
$this->drupalPost('admin/structure/types/manage/article/fields/body', $edit, t('Save settings'));
$this->drupalGet('node/add/article');
- $this->assertFieldByXPath("//input[@name='body_add_more']", t('Add another item'), t('Body field cardinality set to multiple.'));
+ $this->assertFieldByXPath("//input[@name='body_add_more']", t('Add another item'), 'Body field cardinality set to multiple.');
$body = array(
$this->randomName(),
@@ -501,7 +502,7 @@
"body[$langcode][$delta][format]" => array_shift($formats),
);
$this->drupalPost('node/1/edit', $edit, t('Save'));
- $this->assertText($body[$delta], t('The body field with delta @delta has been saved.', array('@delta' => $delta)));
+ $this->assertText($body[$delta], format_string('The body field with delta @delta has been saved.', array('@delta' => $delta)));
}
// Login as translator.
@@ -511,7 +512,7 @@
$node = $this->drupalGetNodeByTitle($title);
$this->drupalGet("node/$node->nid/translate");
$this->clickLink(t('add translation'));
- $this->assertNoText($body[0], t('The body field with delta @delta is hidden.', array('@delta' => 0)));
- $this->assertText($body[1], t('The body field with delta @delta is shown.', array('@delta' => 1)));
+ $this->assertNoText($body[0], format_string('The body field with delta @delta is hidden.', array('@delta' => 0)));
+ $this->assertText($body[1], format_string('The body field with delta @delta is shown.', array('@delta' => 1)));
}
}
diff -Naur drupal-7.18/modules/field/tests/field.test drupal-7.66/modules/field/tests/field.test
--- drupal-7.18/modules/field/tests/field.test 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/tests/field.test 2019-04-17 22:20:46.000000000 +0200
@@ -64,9 +64,9 @@
$e = clone $entity;
field_attach_load('test_entity', array($e->ftid => $e));
$values = isset($e->{$field_name}[$langcode]) ? $e->{$field_name}[$langcode] : array();
- $this->assertEqual(count($values), count($expected_values), t('Expected number of values were saved.'));
+ $this->assertEqual(count($values), count($expected_values), 'Expected number of values were saved.');
foreach ($expected_values as $key => $value) {
- $this->assertEqual($values[$key][$column], $value, t('Value @value was saved correctly.', array('@value' => $value)));
+ $this->assertEqual($values[$key][$column], $value, format_string('Value @value was saved correctly.', array('@value' => $value)));
}
}
}
@@ -85,12 +85,28 @@
}
parent::setUp($modules);
- $this->field_name = drupal_strtolower($this->randomName() . '_field_name');
- $this->field = array('field_name' => $this->field_name, 'type' => 'test_field', 'cardinality' => 4);
- $this->field = field_create_field($this->field);
- $this->field_id = $this->field['id'];
- $this->instance = array(
- 'field_name' => $this->field_name,
+ $this->createFieldWithInstance();
+ }
+
+ /**
+ * Create a field and an instance of it.
+ *
+ * @param string $suffix
+ * (optional) A string that should only contain characters that are valid in
+ * PHP variable names as well.
+ */
+ function createFieldWithInstance($suffix = '') {
+ $field_name = 'field_name' . $suffix;
+ $field = 'field' . $suffix;
+ $field_id = 'field_id' . $suffix;
+ $instance = 'instance' . $suffix;
+
+ $this->$field_name = drupal_strtolower($this->randomName() . '_field_name' . $suffix);
+ $this->$field = array('field_name' => $this->$field_name, 'type' => 'test_field', 'cardinality' => 4);
+ $this->$field = field_create_field($this->$field);
+ $this->$field_id = $this->{$field}['id'];
+ $this->$instance = array(
+ 'field_name' => $this->$field_name,
'entity_type' => 'test_entity',
'bundle' => 'test_bundle',
'label' => $this->randomName() . '_label',
@@ -107,7 +123,7 @@
)
)
);
- field_create_instance($this->instance);
+ field_create_instance($this->$instance);
}
}
@@ -166,12 +182,12 @@
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
field_attach_load($entity_type, array(0 => $entity));
// Number of values per field loaded equals the field cardinality.
- $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], t('Current revision: expected number of values'));
+ $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], 'Current revision: expected number of values');
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
// The field value loaded matches the one inserted or updated.
- $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['value'] , $values[$current_revision][$delta]['value'], t('Current revision: expected value %delta was found.', array('%delta' => $delta)));
+ $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['value'] , $values[$current_revision][$delta]['value'], format_string('Current revision: expected value %delta was found.', array('%delta' => $delta)));
// The value added in hook_field_load() is found.
- $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['additional_key'], 'additional_value', t('Current revision: extra information for value %delta was found', array('%delta' => $delta)));
+ $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['additional_key'], 'additional_value', format_string('Current revision: extra information for value %delta was found', array('%delta' => $delta)));
}
// Confirm each revision loads the correct data.
@@ -179,12 +195,12 @@
$entity = field_test_create_stub_entity(0, $revision_id, $this->instance['bundle']);
field_attach_load_revision($entity_type, array(0 => $entity));
// Number of values per field loaded equals the field cardinality.
- $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], t('Revision %revision_id: expected number of values.', array('%revision_id' => $revision_id)));
+ $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], format_string('Revision %revision_id: expected number of values.', array('%revision_id' => $revision_id)));
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
// The field value loaded matches the one inserted or updated.
- $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['value'], $values[$revision_id][$delta]['value'], t('Revision %revision_id: expected value %delta was found.', array('%revision_id' => $revision_id, '%delta' => $delta)));
+ $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['value'], $values[$revision_id][$delta]['value'], format_string('Revision %revision_id: expected value %delta was found.', array('%revision_id' => $revision_id, '%delta' => $delta)));
// The value added in hook_field_load() is found.
- $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['additional_key'], 'additional_value', t('Revision %revision_id: extra information for value %delta was found', array('%revision_id' => $revision_id, '%delta' => $delta)));
+ $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['additional_key'], 'additional_value', format_string('Revision %revision_id: extra information for value %delta was found', array('%revision_id' => $revision_id, '%delta' => $delta)));
}
}
}
@@ -250,19 +266,19 @@
$instances = field_info_instances($entity_type, $bundles[$index]);
foreach ($instances as $field_name => $instance) {
// The field value loaded matches the one inserted.
- $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], $values[$index][$field_name], t('Entity %index: expected value was found.', array('%index' => $index)));
+ $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], $values[$index][$field_name], format_string('Entity %index: expected value was found.', array('%index' => $index)));
// The value added in hook_field_load() is found.
- $this->assertEqual($entity->{$field_name}[$langcode][0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => $index)));
+ $this->assertEqual($entity->{$field_name}[$langcode][0]['additional_key'], 'additional_value', format_string('Entity %index: extra information was found', array('%index' => $index)));
}
}
// Check that the single-field load option works.
$entity = field_test_create_stub_entity(1, 1, $bundles[1]);
field_attach_load($entity_type, array(1 => $entity), FIELD_LOAD_CURRENT, array('field_id' => $field_ids[1]));
- $this->assertEqual($entity->{$field_names[1]}[$langcode][0]['value'], $values[1][$field_names[1]], t('Entity %index: expected value was found.', array('%index' => 1)));
- $this->assertEqual($entity->{$field_names[1]}[$langcode][0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => 1)));
- $this->assert(!isset($entity->{$field_names[2]}), t('Entity %index: field %field_name is not loaded.', array('%index' => 2, '%field_name' => $field_names[2])));
- $this->assert(!isset($entity->{$field_names[3]}), t('Entity %index: field %field_name is not loaded.', array('%index' => 3, '%field_name' => $field_names[3])));
+ $this->assertEqual($entity->{$field_names[1]}[$langcode][0]['value'], $values[1][$field_names[1]], format_string('Entity %index: expected value was found.', array('%index' => 1)));
+ $this->assertEqual($entity->{$field_names[1]}[$langcode][0]['additional_key'], 'additional_value', format_string('Entity %index: extra information was found', array('%index' => 1)));
+ $this->assert(!isset($entity->{$field_names[2]}), format_string('Entity %index: field %field_name is not loaded.', array('%index' => 2, '%field_name' => $field_names[2])));
+ $this->assert(!isset($entity->{$field_names[3]}), format_string('Entity %index: field %field_name is not loaded.', array('%index' => 3, '%field_name' => $field_names[3])));
}
/**
@@ -312,7 +328,7 @@
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
foreach ($fields as $field) {
- $this->assertEqual($values[$field['field_name']], $entity->{$field['field_name']}[$langcode], t('%storage storage: expected values were found.', array('%storage' => $field['storage']['type'])));
+ $this->assertEqual($values[$field['field_name']], $entity->{$field['field_name']}[$langcode], format_string('%storage storage: expected values were found.', array('%storage' => $field['storage']['type'])));
}
}
@@ -341,20 +357,20 @@
$instance = field_info_instance($instance['entity_type'], $instance['field_name'], $instance['bundle']);
// The storage details are indexed by a storage engine type.
- $this->assertTrue(array_key_exists('drupal_variables', $field['storage']['details']), t('The storage type is Drupal variables.'));
+ $this->assertTrue(array_key_exists('drupal_variables', $field['storage']['details']), 'The storage type is Drupal variables.');
$details = $field['storage']['details']['drupal_variables'];
// The field_test storage details are indexed by variable name. The details
// are altered, so moon and mars are correct for this test.
- $this->assertTrue(array_key_exists('moon', $details[FIELD_LOAD_CURRENT]), t('Moon is available in the instance array.'));
- $this->assertTrue(array_key_exists('mars', $details[FIELD_LOAD_REVISION]), t('Mars is available in the instance array.'));
+ $this->assertTrue(array_key_exists('moon', $details[FIELD_LOAD_CURRENT]), 'Moon is available in the instance array.');
+ $this->assertTrue(array_key_exists('mars', $details[FIELD_LOAD_REVISION]), 'Mars is available in the instance array.');
// Test current and revision storage details together because the columns
// are the same.
foreach ((array) $field['columns'] as $column_name => $attributes) {
- $this->assertEqual($details[FIELD_LOAD_CURRENT]['moon'][$column_name], $column_name, t('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'moon[FIELD_LOAD_CURRENT]')));
- $this->assertEqual($details[FIELD_LOAD_REVISION]['mars'][$column_name], $column_name, t('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'mars[FIELD_LOAD_REVISION]')));
+ $this->assertEqual($details[FIELD_LOAD_CURRENT]['moon'][$column_name], $column_name, format_string('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'moon[FIELD_LOAD_CURRENT]')));
+ $this->assertEqual($details[FIELD_LOAD_REVISION]['mars'][$column_name], $column_name, format_string('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'mars[FIELD_LOAD_REVISION]')));
}
}
@@ -372,7 +388,7 @@
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
- $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: missing field results in no value saved'));
+ $this->assertTrue(empty($entity->{$this->field_name}), 'Insert: missing field results in no value saved');
// Insert: Field is NULL.
field_cache_clear();
@@ -382,7 +398,7 @@
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
- $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: NULL field results in no value saved'));
+ $this->assertTrue(empty($entity->{$this->field_name}), 'Insert: NULL field results in no value saved');
// Add some real data.
field_cache_clear();
@@ -393,7 +409,7 @@
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
- $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Field data saved'));
+ $this->assertEqual($entity->{$this->field_name}[$langcode], $values, 'Field data saved');
// Update: Field is missing. Data should survive.
field_cache_clear();
@@ -402,7 +418,7 @@
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
- $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Update: missing field leaves existing values in place'));
+ $this->assertEqual($entity->{$this->field_name}[$langcode], $values, 'Update: missing field leaves existing values in place');
// Update: Field is NULL. Data should be wiped.
field_cache_clear();
@@ -412,7 +428,7 @@
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
- $this->assertTrue(empty($entity->{$this->field_name}), t('Update: NULL field removes existing values'));
+ $this->assertTrue(empty($entity->{$this->field_name}), 'Update: NULL field removes existing values');
// Re-add some data.
field_cache_clear();
@@ -423,7 +439,7 @@
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
- $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Field data saved'));
+ $this->assertEqual($entity->{$this->field_name}[$langcode], $values, 'Field data saved');
// Update: Field is empty array. Data should be wiped.
field_cache_clear();
@@ -433,7 +449,7 @@
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
- $this->assertTrue(empty($entity->{$this->field_name}), t('Update: empty array removes existing values'));
+ $this->assertTrue(empty($entity->{$this->field_name}), 'Update: empty array removes existing values');
}
/**
@@ -455,7 +471,7 @@
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
- $this->assertTrue(empty($entity->{$this->field_name}[$langcode]), t('Insert: NULL field results in no value saved'));
+ $this->assertTrue(empty($entity->{$this->field_name}[$langcode]), 'Insert: NULL field results in no value saved');
// Insert: Field is missing.
field_cache_clear();
@@ -465,7 +481,67 @@
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
$values = field_test_default_value($entity_type, $entity, $this->field, $this->instance);
- $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Insert: missing field results in default value saved'));
+ $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.");
}
/**
@@ -520,7 +596,7 @@
}
$read = field_test_create_stub_entity(0, 2, $this->instance['bundle']);
field_attach_load($entity_type, array(0 => $read));
- $this->assertIdentical($read->{$this->field_name}, array(), t('The test entity current revision is deleted.'));
+ $this->assertIdentical($read->{$this->field_name}, array(), 'The test entity current revision is deleted.');
}
/**
@@ -641,13 +717,18 @@
* Test field_attach_view() and field_attach_prepare_view().
*/
function testFieldAttachView() {
+ $this->createFieldWithInstance('_2');
+
$entity_type = 'test_entity';
$entity_init = field_test_create_stub_entity();
$langcode = LANGUAGE_NONE;
+ $options = array('field_name' => $this->field_name_2);
// Populate values to be displayed.
$values = $this->_generateTestFieldValues($this->field['cardinality']);
$entity_init->{$this->field_name}[$langcode] = $values;
+ $values_2 = $this->_generateTestFieldValues($this->field_2['cardinality']);
+ $entity_init->{$this->field_name_2}[$langcode] = $values_2;
// Simple formatter, label displayed.
$entity = clone($entity_init);
@@ -662,15 +743,47 @@
),
);
field_update_instance($this->instance);
+ $formatter_setting_2 = $this->randomName();
+ $this->instance_2['display'] = array(
+ 'full' => array(
+ 'label' => 'above',
+ 'type' => 'field_test_default',
+ 'settings' => array(
+ 'test_formatter_setting' => $formatter_setting_2,
+ )
+ ),
+ );
+ field_update_instance($this->instance_2);
+ // View all fields.
field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full');
$entity->content = field_attach_view($entity_type, $entity, 'full');
$output = drupal_render($entity->content);
$this->content = $output;
- $this->assertRaw($this->instance['label'], "Label is displayed.");
+ $this->assertRaw($this->instance['label'], "First field's label is displayed.");
foreach ($values as $delta => $value) {
$this->content = $output;
$this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied.");
}
+ $this->assertRaw($this->instance_2['label'], "Second field's label is displayed.");
+ foreach ($values_2 as $delta => $value) {
+ $this->content = $output;
+ $this->assertRaw("$formatter_setting_2|{$value['value']}", "Value $delta is displayed, formatter settings are applied.");
+ }
+ // View single field (the second field).
+ field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full', $langcode, $options);
+ $entity->content = field_attach_view($entity_type, $entity, 'full', $langcode, $options);
+ $output = drupal_render($entity->content);
+ $this->content = $output;
+ $this->assertNoRaw($this->instance['label'], "First field's label is not displayed.");
+ foreach ($values as $delta => $value) {
+ $this->content = $output;
+ $this->assertNoRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied.");
+ }
+ $this->assertRaw($this->instance_2['label'], "Second field's label is displayed.");
+ foreach ($values_2 as $delta => $value) {
+ $this->content = $output;
+ $this->assertRaw("$formatter_setting_2|{$value['value']}", "Value $delta is displayed, formatter settings are applied.");
+ }
// Label hidden.
$entity = clone($entity_init);
@@ -697,7 +810,7 @@
$this->content = $output;
$this->assertNoRaw($this->instance['label'], "Hidden field: label is not displayed.");
foreach ($values as $delta => $value) {
- $this->assertNoRaw($value['value'], "Hidden field: value $delta is not displayed.");
+ $this->assertNoRaw("$formatter_setting|{$value['value']}", "Hidden field: value $delta is not displayed.");
}
// Multiple formatter.
@@ -759,7 +872,7 @@
break;
}
}
- $this->assertTrue($result, t('Variable $@field_name correctly populated.', array('@field_name' => $this->field_name)));
+ $this->assertTrue($result, format_string('Variable $@field_name correctly populated.', array('@field_name' => $this->field_name)));
}
/**
@@ -825,18 +938,18 @@
$cid = "field:$entity_type:{$entity_init->ftid}";
// Check that no initial cache entry is present.
- $this->assertFalse(cache_get($cid, 'cache_field'), t('Non-cached: no initial cache entry'));
+ $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no initial cache entry');
// Save, and check that no cache entry is present.
$entity = clone($entity_init);
$entity->{$this->field_name}[$langcode] = $values;
field_attach_insert($entity_type, $entity);
- $this->assertFalse(cache_get($cid, 'cache_field'), t('Non-cached: no cache entry on insert'));
+ $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no cache entry on insert');
// Load, and check that no cache entry is present.
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
- $this->assertFalse(cache_get($cid, 'cache_field'), t('Non-cached: no cache entry on load'));
+ $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no cache entry on load');
// Cacheable entity type.
@@ -847,38 +960,38 @@
field_create_instance($instance);
// Check that no initial cache entry is present.
- $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no initial cache entry'));
+ $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no initial cache entry');
// Save, and check that no cache entry is present.
$entity = clone($entity_init);
$entity->{$this->field_name}[$langcode] = $values;
field_attach_insert($entity_type, $entity);
- $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on insert'));
+ $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on insert');
// Load a single field, and check that no cache entry is present.
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity), FIELD_LOAD_CURRENT, array('field_id' => $this->field_id));
$cache = cache_get($cid, 'cache_field');
- $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on loading a single field'));
+ $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on loading a single field');
// Load, and check that a cache entry is present with the expected values.
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
$cache = cache_get($cid, 'cache_field');
- $this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load'));
+ $this->assertEqual($cache->data[$this->field_name][$langcode], $values, 'Cached: correct cache entry on load');
// Update with different values, and check that the cache entry is wiped.
$values = $this->_generateTestFieldValues($this->field['cardinality']);
$entity = clone($entity_init);
$entity->{$this->field_name}[$langcode] = $values;
field_attach_update($entity_type, $entity);
- $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on update'));
+ $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on update');
// Load, and check that a cache entry is present with the expected values.
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
$cache = cache_get($cid, 'cache_field');
- $this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load'));
+ $this->assertEqual($cache->data[$this->field_name][$langcode], $values, 'Cached: correct cache entry on load');
// Create a new revision, and check that the cache entry is wiped.
$entity_init = field_test_create_stub_entity(1, 2, $this->instance['bundle']);
@@ -887,17 +1000,17 @@
$entity->{$this->field_name}[$langcode] = $values;
field_attach_update($entity_type, $entity);
$cache = cache_get($cid, 'cache_field');
- $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on new revision creation'));
+ $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on new revision creation');
// Load, and check that a cache entry is present with the expected values.
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
$cache = cache_get($cid, 'cache_field');
- $this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load'));
+ $this->assertEqual($cache->data[$this->field_name][$langcode], $values, 'Cached: correct cache entry on load');
// Delete, and check that the cache entry is wiped.
field_attach_delete($entity_type, $entity);
- $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry after delete'));
+ $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry after delete');
}
/**
@@ -907,11 +1020,13 @@
* hook_field_validate.
*/
function testFieldAttachValidate() {
+ $this->createFieldWithInstance('_2');
+
$entity_type = 'test_entity';
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
$langcode = LANGUAGE_NONE;
- // Set up values to generate errors
+ // Set up all but one values of the first field to generate errors.
$values = array();
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
$values[$delta]['value'] = -1;
@@ -920,6 +1035,14 @@
$values[1]['value'] = 1;
$entity->{$this->field_name}[$langcode] = $values;
+ // Set up all values of the second field to generate errors.
+ $values_2 = array();
+ for ($delta = 0; $delta < $this->field_2['cardinality']; $delta++) {
+ $values_2[$delta]['value'] = -1;
+ }
+ $entity->{$this->field_name_2}[$langcode] = $values_2;
+
+ // Validate all fields.
try {
field_attach_validate($entity_type, $entity);
}
@@ -929,26 +1052,57 @@
foreach ($values as $delta => $value) {
if ($value['value'] != 1) {
- $this->assertIdentical($errors[$this->field_name][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on value $delta");
- $this->assertEqual(count($errors[$this->field_name][$langcode][$delta]), 1, "Only one error set on value $delta");
+ $this->assertIdentical($errors[$this->field_name][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on first field's value $delta");
+ $this->assertEqual(count($errors[$this->field_name][$langcode][$delta]), 1, "Only one error set on first field's value $delta");
unset($errors[$this->field_name][$langcode][$delta]);
}
else {
- $this->assertFalse(isset($errors[$this->field_name][$langcode][$delta]), "No error set on value $delta");
+ $this->assertFalse(isset($errors[$this->field_name][$langcode][$delta]), "No error set on first field's value $delta");
}
}
- $this->assertEqual(count($errors[$this->field_name][$langcode]), 0, 'No extraneous errors set');
+ foreach ($values_2 as $delta => $value) {
+ $this->assertIdentical($errors[$this->field_name_2][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on second field's value $delta");
+ $this->assertEqual(count($errors[$this->field_name_2][$langcode][$delta]), 1, "Only one error set on second field's value $delta");
+ unset($errors[$this->field_name_2][$langcode][$delta]);
+ }
+ $this->assertEqual(count($errors[$this->field_name][$langcode]), 0, 'No extraneous errors set for first field');
+ $this->assertEqual(count($errors[$this->field_name_2][$langcode]), 0, 'No extraneous errors set for second field');
+
+ // Validate a single field.
+ $options = array('field_name' => $this->field_name_2);
+ try {
+ field_attach_validate($entity_type, $entity, $options);
+ }
+ catch (FieldValidationException $e) {
+ $errors = $e->errors;
+ }
+
+ foreach ($values_2 as $delta => $value) {
+ $this->assertIdentical($errors[$this->field_name_2][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on second field's value $delta");
+ $this->assertEqual(count($errors[$this->field_name_2][$langcode][$delta]), 1, "Only one error set on second field's value $delta");
+ unset($errors[$this->field_name_2][$langcode][$delta]);
+ }
+ $this->assertFalse(isset($errors[$this->field_name]), 'No validation errors are set for the first field, despite it having errors');
+ $this->assertEqual(count($errors[$this->field_name_2][$langcode]), 0, 'No extraneous errors set for second field');
// Check that cardinality is validated.
- $entity->{$this->field_name}[$langcode] = $this->_generateTestFieldValues($this->field['cardinality'] + 1);
+ $entity->{$this->field_name_2}[$langcode] = $this->_generateTestFieldValues($this->field_2['cardinality'] + 1);
+ // When validating all fields.
try {
field_attach_validate($entity_type, $entity);
}
catch (FieldValidationException $e) {
$errors = $e->errors;
}
- $this->assertEqual($errors[$this->field_name][$langcode][0][0]['error'], 'field_cardinality', t('Cardinality validation failed.'));
-
+ $this->assertEqual($errors[$this->field_name_2][$langcode][0][0]['error'], 'field_cardinality', 'Cardinality validation failed.');
+ // When validating a single field (the second field).
+ try {
+ field_attach_validate($entity_type, $entity, $options);
+ }
+ catch (FieldValidationException $e) {
+ $errors = $e->errors;
+ }
+ $this->assertEqual($errors[$this->field_name_2][$langcode][0][0]['error'], 'field_cardinality', 'Cardinality validation failed.');
}
/**
@@ -958,34 +1112,59 @@
* widgets show up.
*/
function testFieldAttachForm() {
+ $this->createFieldWithInstance('_2');
+
$entity_type = 'test_entity';
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+ $langcode = LANGUAGE_NONE;
+ // When generating form for all fields.
$form = array();
$form_state = form_state_defaults();
field_attach_form($entity_type, $entity, $form, $form_state);
- $langcode = LANGUAGE_NONE;
- $this->assertEqual($form[$this->field_name][$langcode]['#title'], $this->instance['label'], "Form title is {$this->instance['label']}");
+ $this->assertEqual($form[$this->field_name][$langcode]['#title'], $this->instance['label'], "First field's form title is {$this->instance['label']}");
+ $this->assertEqual($form[$this->field_name_2][$langcode]['#title'], $this->instance_2['label'], "Second field's form title is {$this->instance_2['label']}");
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
// field_test_widget uses 'textfield'
- $this->assertEqual($form[$this->field_name][$langcode][$delta]['value']['#type'], 'textfield', "Form delta $delta widget is textfield");
- }
+ $this->assertEqual($form[$this->field_name][$langcode][$delta]['value']['#type'], 'textfield', "First field's form delta $delta widget is textfield");
+ }
+ for ($delta = 0; $delta < $this->field_2['cardinality']; $delta++) {
+ // field_test_widget uses 'textfield'
+ $this->assertEqual($form[$this->field_name_2][$langcode][$delta]['value']['#type'], 'textfield', "Second field's form delta $delta widget is textfield");
+ }
+
+ // When generating form for a single field (the second field).
+ $options = array('field_name' => $this->field_name_2);
+ $form = array();
+ $form_state = form_state_defaults();
+ field_attach_form($entity_type, $entity, $form, $form_state, NULL, $options);
+
+ $this->assertFalse(isset($form[$this->field_name]), 'The first field does not exist in the form');
+ $this->assertEqual($form[$this->field_name_2][$langcode]['#title'], $this->instance_2['label'], "Second field's form title is {$this->instance_2['label']}");
+ for ($delta = 0; $delta < $this->field_2['cardinality']; $delta++) {
+ // field_test_widget uses 'textfield'
+ $this->assertEqual($form[$this->field_name_2][$langcode][$delta]['value']['#type'], 'textfield', "Second field's form delta $delta widget is textfield");
+ }
}
/**
* Test field_attach_submit().
*/
function testFieldAttachSubmit() {
+ $this->createFieldWithInstance('_2');
+
$entity_type = 'test_entity';
- $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+ $entity_init = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+ $langcode = LANGUAGE_NONE;
- // Build the form.
+ // Build the form for all fields.
$form = array();
$form_state = form_state_defaults();
- field_attach_form($entity_type, $entity, $form, $form_state);
+ field_attach_form($entity_type, $entity_init, $form, $form_state);
// Simulate incoming values.
+ // First field.
$values = array();
$weights = array();
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
@@ -999,22 +1178,59 @@
}
// Leave an empty value. 'field_test' fields are empty if empty().
$values[1]['value'] = 0;
-
- $langcode = LANGUAGE_NONE;
+ // Second field.
+ $values_2 = array();
+ $weights_2 = array();
+ for ($delta = 0; $delta < $this->field_2['cardinality']; $delta++) {
+ $values_2[$delta]['value'] = mt_rand(1, 127);
+ // Assign random weight.
+ do {
+ $weight = mt_rand(0, $this->field_2['cardinality']);
+ } while (in_array($weight, $weights_2));
+ $weights_2[$delta] = $weight;
+ $values_2[$delta]['_weight'] = $weight;
+ }
+ // Leave an empty value. 'field_test' fields are empty if empty().
+ $values_2[1]['value'] = 0;
// Pretend the form has been built.
drupal_prepare_form('field_test_entity_form', $form, $form_state);
drupal_process_form('field_test_entity_form', $form, $form_state);
$form_state['values'][$this->field_name][$langcode] = $values;
+ $form_state['values'][$this->field_name_2][$langcode] = $values_2;
+
+ // Call field_attach_submit() for all fields.
+ $entity = clone($entity_init);
field_attach_submit($entity_type, $entity, $form, $form_state);
asort($weights);
+ asort($weights_2);
$expected_values = array();
+ $expected_values_2 = array();
foreach ($weights as $key => $value) {
if ($key != 1) {
$expected_values[] = array('value' => $values[$key]['value']);
}
}
$this->assertIdentical($entity->{$this->field_name}[$langcode], $expected_values, 'Submit filters empty values');
+ foreach ($weights_2 as $key => $value) {
+ if ($key != 1) {
+ $expected_values_2[] = array('value' => $values_2[$key]['value']);
+ }
+ }
+ $this->assertIdentical($entity->{$this->field_name_2}[$langcode], $expected_values_2, 'Submit filters empty values');
+
+ // Call field_attach_submit() for a single field (the second field).
+ $options = array('field_name' => $this->field_name_2);
+ $entity = clone($entity_init);
+ field_attach_submit($entity_type, $entity, $form, $form_state, $options);
+ $expected_values_2 = array();
+ foreach ($weights_2 as $key => $value) {
+ if ($key != 1) {
+ $expected_values_2[] = array('value' => $values_2[$key]['value']);
+ }
+ }
+ $this->assertFalse(isset($entity->{$this->field_name}), 'The first field does not exist in the entity object');
+ $this->assertIdentical($entity->{$this->field_name_2}[$langcode], $expected_values_2, 'Submit filters empty values');
}
}
@@ -1046,42 +1262,42 @@
$info = field_info_field_types();
foreach ($field_test_info as $t_key => $field_type) {
foreach ($field_type as $key => $val) {
- $this->assertEqual($info[$t_key][$key], $val, t("Field type $t_key key $key is $val"));
+ $this->assertEqual($info[$t_key][$key], $val, format_string('Field type %t_key key %key is %value', array('%t_key' => $t_key, '%key' => $key, '%value' => print_r($val, TRUE))));
}
- $this->assertEqual($info[$t_key]['module'], 'field_test', t("Field type field_test module appears"));
+ $this->assertEqual($info[$t_key]['module'], 'field_test', "Field type field_test module appears");
}
$formatter_info = field_test_field_formatter_info();
$info = field_info_formatter_types();
foreach ($formatter_info as $f_key => $formatter) {
foreach ($formatter as $key => $val) {
- $this->assertEqual($info[$f_key][$key], $val, t("Formatter type $f_key key $key is $val"));
+ $this->assertEqual($info[$f_key][$key], $val, format_string('Formatter type %f_key key %key is %value', array('%f_key' => $f_key, '%key' => $key, '%value' => print_r($val, TRUE))));
}
- $this->assertEqual($info[$f_key]['module'], 'field_test', t("Formatter type field_test module appears"));
+ $this->assertEqual($info[$f_key]['module'], 'field_test', "Formatter type field_test module appears");
}
$widget_info = field_test_field_widget_info();
$info = field_info_widget_types();
foreach ($widget_info as $w_key => $widget) {
foreach ($widget as $key => $val) {
- $this->assertEqual($info[$w_key][$key], $val, t("Widget type $w_key key $key is $val"));
+ $this->assertEqual($info[$w_key][$key], $val, format_string('Widget type %w_key key %key is %value', array('%w_key' => $w_key, '%key' => $key, '%value' => print_r($val, TRUE))));
}
- $this->assertEqual($info[$w_key]['module'], 'field_test', t("Widget type field_test module appears"));
+ $this->assertEqual($info[$w_key]['module'], 'field_test', "Widget type field_test module appears");
}
$storage_info = field_test_field_storage_info();
$info = field_info_storage_types();
foreach ($storage_info as $s_key => $storage) {
foreach ($storage as $key => $val) {
- $this->assertEqual($info[$s_key][$key], $val, t("Storage type $s_key key $key is $val"));
+ $this->assertEqual($info[$s_key][$key], $val, format_string('Storage type %s_key key %key is %value', array('%s_key' => $s_key, '%key' => $key, '%value' => print_r($val, TRUE))));
}
- $this->assertEqual($info[$s_key]['module'], 'field_test', t("Storage type field_test module appears"));
+ $this->assertEqual($info[$s_key]['module'], 'field_test', "Storage type field_test module appears");
}
// Verify that no unexpected instances exist.
$instances = field_info_instances('test_entity');
$expected = array('test_bundle' => array());
- $this->assertIdentical($instances, $expected, "field_info_instances('test_entity') returns " . var_export($expected, TRUE) . '.');
+ $this->assertIdentical($instances, $expected, format_string("field_info_instances('test_entity') returns %expected.", array('%expected' => var_export($expected, TRUE))));
$instances = field_info_instances('test_entity', 'test_bundle');
$this->assertIdentical($instances, array(), "field_info_instances('test_entity', 'test_bundle') returns an empty array.");
@@ -1093,16 +1309,16 @@
);
field_create_field($field);
$fields = field_info_fields();
- $this->assertEqual(count($fields), count($core_fields) + 1, t('One new field exists'));
- $this->assertEqual($fields[$field['field_name']]['field_name'], $field['field_name'], t('info fields contains field name'));
- $this->assertEqual($fields[$field['field_name']]['type'], $field['type'], t('info fields contains field type'));
- $this->assertEqual($fields[$field['field_name']]['module'], 'field_test', t('info fields contains field module'));
+ $this->assertEqual(count($fields), count($core_fields) + 1, 'One new field exists');
+ $this->assertEqual($fields[$field['field_name']]['field_name'], $field['field_name'], 'info fields contains field name');
+ $this->assertEqual($fields[$field['field_name']]['type'], $field['type'], 'info fields contains field type');
+ $this->assertEqual($fields[$field['field_name']]['module'], 'field_test', 'info fields contains field module');
$settings = array('test_field_setting' => 'dummy test string');
foreach ($settings as $key => $val) {
- $this->assertEqual($fields[$field['field_name']]['settings'][$key], $val, t("Field setting $key has correct default value $val"));
+ $this->assertEqual($fields[$field['field_name']]['settings'][$key], $val, format_string('Field setting %key has correct default value %value', array('%key' => $key, '%value' => $val)));
}
- $this->assertEqual($fields[$field['field_name']]['cardinality'], 1, t('info fields contains cardinality 1'));
- $this->assertEqual($fields[$field['field_name']]['active'], 1, t('info fields contains active 1'));
+ $this->assertEqual($fields[$field['field_name']]['cardinality'], 1, 'info fields contains cardinality 1');
+ $this->assertEqual($fields[$field['field_name']]['active'], 1, 'info fields contains active 1');
// Create an instance, verify that it shows up
$instance = array(
@@ -1124,7 +1340,7 @@
$this->assertEqual(count($instances), 1, format_string('One instance shows up in info when attached to a bundle on a @label.', array(
'@label' => $info['label']
)));
- $this->assertTrue($instance < $instances[$instance['field_name']], t('Instance appears in info correctly'));
+ $this->assertTrue($instance < $instances[$instance['field_name']], 'Instance appears in info correctly');
// Test a valid entity type but an invalid bundle.
$instances = field_info_instances('test_entity', 'invalid_bundle');
@@ -1141,9 +1357,19 @@
// Test with an entity type that has no bundles.
$instances = field_info_instances('user');
$expected = array('user' => array());
- $this->assertIdentical($instances, $expected, "field_info_instances('user') returns " . var_export($expected, TRUE) . '.');
+ $this->assertIdentical($instances, $expected, format_string("field_info_instances('user') returns %expected.", array('%expected' => var_export($expected, TRUE))));
$instances = field_info_instances('user', 'user');
$this->assertIdentical($instances, array(), "field_info_instances('user', 'user') returns an empty array.");
+
+ // Test that querying for invalid entity types does not add entries in the
+ // list returned by field_info_instances().
+ field_info_cache_clear();
+ field_info_instances('invalid_entity', 'invalid_bundle');
+ // Simulate new request by clearing static caches.
+ drupal_static_reset();
+ field_info_instances('invalid_entity', 'invalid_bundle');
+ $instances = field_info_instances();
+ $this->assertFalse(isset($instances['invalid_entity']), 'field_info_instances() does not contain entries for the invalid entity type that was queried before');
}
/**
@@ -1174,7 +1400,7 @@
// Check that all expected settings are in place.
$field_type = field_info_field_types($field_definition['type']);
- $this->assertIdentical($field['settings'], $field_type['settings'], t('All expected default field settings are present.'));
+ $this->assertIdentical($field['settings'], $field_type['settings'], 'All expected default field settings are present.');
}
/**
@@ -1216,18 +1442,18 @@
// Check that all expected instance settings are in place.
$field_type = field_info_field_types($field_definition['type']);
- $this->assertIdentical($instance['settings'], $field_type['instance_settings'] , t('All expected instance settings are present.'));
+ $this->assertIdentical($instance['settings'], $field_type['instance_settings'] , 'All expected instance settings are present.');
// Check that the default widget is used and expected settings are in place.
- $this->assertIdentical($instance['widget']['type'], $field_type['default_widget'], t('Unavailable widget replaced with default widget.'));
+ $this->assertIdentical($instance['widget']['type'], $field_type['default_widget'], 'Unavailable widget replaced with default widget.');
$widget_type = field_info_widget_types($instance['widget']['type']);
- $this->assertIdentical($instance['widget']['settings'], $widget_type['settings'] , t('All expected widget settings are present.'));
+ $this->assertIdentical($instance['widget']['settings'], $widget_type['settings'] , 'All expected widget settings are present.');
// Check that display settings are set for the 'default' mode.
$display = $instance['display']['default'];
- $this->assertIdentical($display['type'], $field_type['default_formatter'], t("Formatter is set for the 'default' view mode"));
+ $this->assertIdentical($display['type'], $field_type['default_formatter'], "Formatter is set for the 'default' view mode");
$formatter_type = field_info_formatter_types($display['type']);
- $this->assertIdentical($display['settings'], $formatter_type['settings'] , t("Formatter settings are set for the 'default' view mode"));
+ $this->assertIdentical($display['settings'], $formatter_type['settings'] , "Formatter settings are set for the 'default' view mode");
}
/**
@@ -1250,7 +1476,81 @@
// Disable coment module. This clears field_info cache.
module_disable(array('comment'));
- $this->assertNull(field_info_instance('comment', 'field', 'comment_node_article'), t('No instances are returned on disabled entity types.'));
+ $this->assertNull(field_info_instance('comment', 'field', 'comment_node_article'), 'No instances are returned on disabled entity types.');
+ }
+
+ /**
+ * Test field_info_field_map().
+ */
+ function testFieldMap() {
+ // We will overlook fields created by the 'standard' install profile.
+ $exclude = field_info_field_map();
+
+ // Create a new bundle for 'test_entity' entity type.
+ field_test_create_bundle('test_bundle_2');
+
+ // Create a couple fields.
+ $fields = array(
+ array(
+ 'field_name' => 'field_1',
+ 'type' => 'test_field',
+ ),
+ array(
+ 'field_name' => 'field_2',
+ 'type' => 'hidden_test_field',
+ ),
+ );
+ foreach ($fields as $field) {
+ field_create_field($field);
+ }
+
+ // Create a couple instances.
+ $instances = array(
+ array(
+ 'field_name' => 'field_1',
+ 'entity_type' => 'test_entity',
+ 'bundle' => 'test_bundle',
+ ),
+ array(
+ 'field_name' => 'field_1',
+ 'entity_type' => 'test_entity',
+ 'bundle' => 'test_bundle_2',
+ ),
+ array(
+ 'field_name' => 'field_2',
+ 'entity_type' => 'test_entity',
+ 'bundle' => 'test_bundle',
+ ),
+ array(
+ 'field_name' => 'field_2',
+ 'entity_type' => 'test_cacheable_entity',
+ 'bundle' => 'test_bundle',
+ ),
+ );
+ foreach ($instances as $instance) {
+ field_create_instance($instance);
+ }
+
+ $expected = array(
+ 'field_1' => array(
+ 'type' => 'test_field',
+ 'bundles' => array(
+ 'test_entity' => array('test_bundle', 'test_bundle_2'),
+ ),
+ ),
+ 'field_2' => array(
+ 'type' => 'hidden_test_field',
+ 'bundles' => array(
+ 'test_entity' => array('test_bundle'),
+ 'test_cacheable_entity' => array('test_bundle'),
+ ),
+ ),
+ );
+
+ // Check that the field map is correct.
+ $map = field_info_field_map();
+ $map = array_diff_key($map, $exclude);
+ $this->assertEqual($map, $expected);
}
/**
@@ -1263,20 +1563,45 @@
$info[$name]['instance_settings']['user_register_form'] = FALSE;
}
foreach ($info as $type => $data) {
- $this->assertIdentical(field_info_field_settings($type), $data['settings'], "field_info_field_settings returns {$type}'s field settings");
- $this->assertIdentical(field_info_instance_settings($type), $data['instance_settings'], "field_info_field_settings returns {$type}'s field instance settings");
+ $this->assertIdentical(field_info_field_settings($type), $data['settings'], format_string("field_info_field_settings returns %type's field settings", array('%type' => $type)));
+ $this->assertIdentical(field_info_instance_settings($type), $data['instance_settings'], format_string("field_info_field_settings returns %type's field instance settings", array('%type' => $type)));
}
$info = field_test_field_widget_info();
foreach ($info as $type => $data) {
- $this->assertIdentical(field_info_widget_settings($type), $data['settings'], "field_info_widget_settings returns {$type}'s widget settings");
+ $this->assertIdentical(field_info_widget_settings($type), $data['settings'], format_string("field_info_widget_settings returns %type's widget settings", array('%type' => $type)));
}
$info = field_test_field_formatter_info();
foreach ($info as $type => $data) {
- $this->assertIdentical(field_info_formatter_settings($type), $data['settings'], "field_info_formatter_settings returns {$type}'s formatter settings");
+ $this->assertIdentical(field_info_formatter_settings($type), $data['settings'], format_string("field_info_formatter_settings returns %type's formatter settings", array('%type' => $type)));
}
}
+
+ /**
+ * Tests that the field info cache can be built correctly.
+ */
+ function testFieldInfoCache() {
+ // Create a test field and ensure it's in the array returned by
+ // field_info_fields().
+ $field_name = drupal_strtolower($this->randomName());
+ $field = array(
+ 'field_name' => $field_name,
+ 'type' => 'test_field',
+ );
+ field_create_field($field);
+ $fields = field_info_fields();
+ $this->assertTrue(isset($fields[$field_name]), 'The test field is initially found in the array returned by field_info_fields().');
+
+ // Now rebuild the field info cache, and set a variable which will cause
+ // the cache to be cleared while it's being rebuilt; see
+ // field_test_entity_info(). Ensure the test field is still in the returned
+ // array.
+ field_info_cache_clear();
+ variable_set('field_test_clear_info_cache_in_hook_entity_info', TRUE);
+ $fields = field_info_fields();
+ $this->assertTrue(isset($fields[$field_name]), 'The test field is found in the array returned by field_info_fields() even if its cache is cleared while being rebuilt.');
+ }
}
class FieldFormTestCase extends FieldTestCase {
@@ -1600,7 +1925,7 @@
// Display creation form.
$this->drupalGet('test-entity/add/test-bundle');
- $this->assertFieldByName("{$this->field_name}[$langcode]", '', t('Widget is displayed.'));
+ $this->assertFieldByName("{$this->field_name}[$langcode]", '', 'Widget is displayed.');
// Create entity with three values.
$edit = array("{$this->field_name}[$langcode]" => '1, 2, 3');
@@ -1614,12 +1939,12 @@
// Display the form, check that the values are correctly filled in.
$this->drupalGet('test-entity/manage/' . $id . '/edit');
- $this->assertFieldByName("{$this->field_name}[$langcode]", '1, 2, 3', t('Widget is displayed.'));
+ $this->assertFieldByName("{$this->field_name}[$langcode]", '1, 2, 3', 'Widget is displayed.');
// Submit the form with more values than the field accepts.
$edit = array("{$this->field_name}[$langcode]" => '1, 2, 3, 4, 5');
$this->drupalPost(NULL, $edit, t('Save'));
- $this->assertRaw('this field cannot hold more than 4 values', t('Form validation failed.'));
+ $this->assertRaw('this field cannot hold more than 4 values', 'Form validation failed.');
// Check that the field values were not submitted.
$this->assertFieldValues($entity_init, $this->field_name, $langcode, array(1, 2, 3));
}
@@ -1667,7 +1992,7 @@
// Display creation form.
$this->drupalGet('test-entity/add/test-bundle');
- $this->assertNoFieldByName("{$field_name_no_access}[$langcode][0][value]", '', t('Widget is not displayed if field access is denied.'));
+ $this->assertNoFieldByName("{$field_name_no_access}[$langcode][0][value]", '', 'Widget is not displayed if field access is denied.');
// Create entity.
$edit = array("{$field_name}[$langcode][0][value]" => 1);
@@ -1677,8 +2002,8 @@
// Check that the default value was saved.
$entity = field_test_entity_test_load($id);
- $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, t('Default value was saved for the field with no edit access.'));
- $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 1, t('Entered value vas saved for the field with edit access.'));
+ $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, 'Default value was saved for the field with no edit access.');
+ $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 1, 'Entered value vas saved for the field with edit access.');
// Create a new revision.
$edit = array("{$field_name}[$langcode][0][value]" => 2, 'revision' => TRUE);
@@ -1686,13 +2011,13 @@
// Check that the new revision has the expected values.
$entity = field_test_entity_test_load($id);
- $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, t('New revision has the expected value for the field with no edit access.'));
- $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 2, t('New revision has the expected value for the field with edit access.'));
+ $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, 'New revision has the expected value for the field with no edit access.');
+ $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 2, 'New revision has the expected value for the field with edit access.');
// Check that the revision is also saved in the revisions table.
$entity = field_test_entity_test_load($id, $entity->ftvid);
- $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, t('New revision has the expected value for the field with no edit access.'));
- $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 2, t('New revision has the expected value for the field with edit access.'));
+ $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, 'New revision has the expected value for the field with no edit access.');
+ $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 2, 'New revision has the expected value for the field with edit access.');
}
/**
@@ -1724,10 +2049,10 @@
// Display the 'combined form'.
$this->drupalGet('test-entity/nested/1/2');
- $this->assertFieldByName('field_single[und][0][value]', 0, t('Entity 1: field_single value appears correctly is the form.'));
- $this->assertFieldByName('field_unlimited[und][0][value]', 1, t('Entity 1: field_unlimited value 0 appears correctly is the form.'));
- $this->assertFieldByName('entity_2[field_single][und][0][value]', 10, t('Entity 2: field_single value appears correctly is the form.'));
- $this->assertFieldByName('entity_2[field_unlimited][und][0][value]', 11, t('Entity 2: field_unlimited value 0 appears correctly is the form.'));
+ $this->assertFieldByName('field_single[und][0][value]', 0, 'Entity 1: field_single value appears correctly is the form.');
+ $this->assertFieldByName('field_unlimited[und][0][value]', 1, 'Entity 1: field_unlimited value 0 appears correctly is the form.');
+ $this->assertFieldByName('entity_2[field_single][und][0][value]', 10, 'Entity 2: field_single value appears correctly is the form.');
+ $this->assertFieldByName('entity_2[field_unlimited][und][0][value]', 11, 'Entity 2: field_unlimited value 0 appears correctly is the form.');
// Submit the form and check that the entities are updated accordingly.
$edit = array(
@@ -1753,16 +2078,16 @@
'field_unlimited[und][1][value]' => -1,
);
$this->drupalPost('test-entity/nested/1/2', $edit, t('Save'));
- $this->assertRaw(t('%label does not accept the value -1', array('%label' => 'Unlimited field')), t('Entity 1: the field validation error was reported.'));
+ $this->assertRaw(t('%label does not accept the value -1', array('%label' => 'Unlimited field')), 'Entity 1: the field validation error was reported.');
$error_field = $this->xpath('//input[@id=:id and contains(@class, "error")]', array(':id' => 'edit-field-unlimited-und-1-value'));
- $this->assertTrue($error_field, t('Entity 1: the error was flagged on the correct element.'));
+ $this->assertTrue($error_field, 'Entity 1: the error was flagged on the correct element.');
$edit = array(
'entity_2[field_unlimited][und][1][value]' => -1,
);
$this->drupalPost('test-entity/nested/1/2', $edit, t('Save'));
- $this->assertRaw(t('%label does not accept the value -1', array('%label' => 'Unlimited field')), t('Entity 2: the field validation error was reported.'));
+ $this->assertRaw(t('%label does not accept the value -1', array('%label' => 'Unlimited field')), 'Entity 2: the field validation error was reported.');
$error_field = $this->xpath('//input[@id=:id and contains(@class, "error")]', array(':id' => 'edit-entity-2-field-unlimited-und-1-value'));
- $this->assertTrue($error_field, t('Entity 2: the error was flagged on the correct element.'));
+ $this->assertTrue($error_field, 'Entity 2: the error was flagged on the correct element.');
// Test that reordering works on both entities.
$edit = array(
@@ -1782,10 +2107,10 @@
// 'Add more' button in the first entity:
$this->drupalGet('test-entity/nested/1/2');
$this->drupalPostAJAX(NULL, array(), 'field_unlimited_add_more');
- $this->assertFieldByName('field_unlimited[und][0][value]', 3, t('Entity 1: field_unlimited value 0 appears correctly is the form.'));
- $this->assertFieldByName('field_unlimited[und][1][value]', 2, t('Entity 1: field_unlimited value 1 appears correctly is the form.'));
- $this->assertFieldByName('field_unlimited[und][2][value]', '', t('Entity 1: field_unlimited value 2 appears correctly is the form.'));
- $this->assertFieldByName('field_unlimited[und][3][value]', '', t('Entity 1: an empty widget was added for field_unlimited value 3.'));
+ $this->assertFieldByName('field_unlimited[und][0][value]', 3, 'Entity 1: field_unlimited value 0 appears correctly is the form.');
+ $this->assertFieldByName('field_unlimited[und][1][value]', 2, 'Entity 1: field_unlimited value 1 appears correctly is the form.');
+ $this->assertFieldByName('field_unlimited[und][2][value]', '', 'Entity 1: field_unlimited value 2 appears correctly is the form.');
+ $this->assertFieldByName('field_unlimited[und][3][value]', '', 'Entity 1: an empty widget was added for field_unlimited value 3.');
// 'Add more' button in the first entity (changing field values):
$edit = array(
'entity_2[field_unlimited][und][0][value]' => 13,
@@ -1793,10 +2118,10 @@
'entity_2[field_unlimited][und][2][value]' => 15,
);
$this->drupalPostAJAX(NULL, $edit, 'entity_2_field_unlimited_add_more');
- $this->assertFieldByName('entity_2[field_unlimited][und][0][value]', 13, t('Entity 2: field_unlimited value 0 appears correctly is the form.'));
- $this->assertFieldByName('entity_2[field_unlimited][und][1][value]', 14, t('Entity 2: field_unlimited value 1 appears correctly is the form.'));
- $this->assertFieldByName('entity_2[field_unlimited][und][2][value]', 15, t('Entity 2: field_unlimited value 2 appears correctly is the form.'));
- $this->assertFieldByName('entity_2[field_unlimited][und][3][value]', '', t('Entity 2: an empty widget was added for field_unlimited value 3.'));
+ $this->assertFieldByName('entity_2[field_unlimited][und][0][value]', 13, 'Entity 2: field_unlimited value 0 appears correctly is the form.');
+ $this->assertFieldByName('entity_2[field_unlimited][und][1][value]', 14, 'Entity 2: field_unlimited value 1 appears correctly is the form.');
+ $this->assertFieldByName('entity_2[field_unlimited][und][2][value]', 15, 'Entity 2: field_unlimited value 2 appears correctly is the form.');
+ $this->assertFieldByName('entity_2[field_unlimited][und][3][value]', '', 'Entity 2: an empty widget was added for field_unlimited value 3.');
// Save the form and check values are saved correclty.
$this->drupalPost(NULL, array(), t('Save'));
field_cache_clear();
@@ -1867,9 +2192,9 @@
$this->drupalSetContent(drupal_render($output));
$settings = field_info_formatter_settings('field_test_default');
$setting = $settings['test_formatter_setting'];
- $this->assertText($this->label, t('Label was displayed.'));
+ $this->assertText($this->label, 'Label was displayed.');
foreach ($this->values as $delta => $value) {
- $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
+ $this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
// Check that explicit display settings are used.
@@ -1881,16 +2206,17 @@
'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, t('Label was not displayed.'));
- $this->assertText('field_test_field_attach_view_alter', t('Alter fired, display passed.'));
+ $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'];
}
- $this->assertText($setting . '|' . implode('|', $array), t('Values were displayed with expected setting.'));
+ $this->assertText($setting . '|' . implode('|', $array), 'Values were displayed with expected setting.');
// Check the prepare_view steps are invoked.
$display = array(
@@ -1904,10 +2230,10 @@
$view = drupal_render($output);
$this->drupalSetContent($view);
$setting = $display['settings']['test_formatter_setting_additional'];
- $this->assertNoText($this->label, t('Label was not displayed.'));
- $this->assertNoText('field_test_field_attach_view_alter', t('Alter not fired.'));
+ $this->assertNoText($this->label, 'Label was not displayed.');
+ $this->assertNoText('field_test_field_attach_view_alter', 'Alter not fired.');
foreach ($this->values as $delta => $value) {
- $this->assertText($setting . '|' . $value['value'] . '|' . ($value['value'] + 1), t('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
+ $this->assertText($setting . '|' . $value['value'] . '|' . ($value['value'] + 1), format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
// View mode: check that display settings specified in the instance are
@@ -1915,9 +2241,9 @@
$output = field_view_field('test_entity', $this->entity, $this->field_name, 'teaser');
$this->drupalSetContent(drupal_render($output));
$setting = $this->instance['display']['teaser']['settings']['test_formatter_setting'];
- $this->assertText($this->label, t('Label was displayed.'));
+ $this->assertText($this->label, 'Label was displayed.');
foreach ($this->values as $delta => $value) {
- $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
+ $this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
// Unknown view mode: check that display settings for 'default' view mode
@@ -1925,9 +2251,9 @@
$output = field_view_field('test_entity', $this->entity, $this->field_name, 'unknown_view_mode');
$this->drupalSetContent(drupal_render($output));
$setting = $this->instance['display']['default']['settings']['test_formatter_setting'];
- $this->assertText($this->label, t('Label was displayed.'));
+ $this->assertText($this->label, 'Label was displayed.');
foreach ($this->values as $delta => $value) {
- $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
+ $this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
}
@@ -1942,7 +2268,7 @@
$item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta];
$output = field_view_value('test_entity', $this->entity, $this->field_name, $item);
$this->drupalSetContent(drupal_render($output));
- $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
+ $this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
// Check that explicit display settings are used.
@@ -1958,7 +2284,7 @@
$item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta];
$output = field_view_value('test_entity', $this->entity, $this->field_name, $item, $display);
$this->drupalSetContent(drupal_render($output));
- $this->assertText($setting . '|0:' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
+ $this->assertText($setting . '|0:' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
// Check that prepare_view steps are invoked.
@@ -1974,7 +2300,7 @@
$item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta];
$output = field_view_value('test_entity', $this->entity, $this->field_name, $item, $display);
$this->drupalSetContent(drupal_render($output));
- $this->assertText($setting . '|' . $value['value'] . '|' . ($value['value'] + 1), t('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
+ $this->assertText($setting . '|' . $value['value'] . '|' . ($value['value'] + 1), format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
// View mode: check that display settings specified in the instance are
@@ -1984,7 +2310,7 @@
$item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta];
$output = field_view_value('test_entity', $this->entity, $this->field_name, $item, 'teaser');
$this->drupalSetContent(drupal_render($output));
- $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
+ $this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
// Unknown view mode: check that display settings for 'default' view mode
@@ -1994,7 +2320,7 @@
$item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta];
$output = field_view_value('test_entity', $this->entity, $this->field_name, $item, 'unknown_view_mode');
$this->drupalSetContent(drupal_render($output));
- $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
+ $this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
}
}
@@ -2037,18 +2363,18 @@
$record['data'] = unserialize($record['data']);
// Ensure that basic properties are preserved.
- $this->assertEqual($record['field_name'], $field_definition['field_name'], t('The field name is properly saved.'));
- $this->assertEqual($record['type'], $field_definition['type'], t('The field type is properly saved.'));
+ $this->assertEqual($record['field_name'], $field_definition['field_name'], 'The field name is properly saved.');
+ $this->assertEqual($record['type'], $field_definition['type'], 'The field type is properly saved.');
// Ensure that cardinality defaults to 1.
- $this->assertEqual($record['cardinality'], 1, t('Cardinality defaults to 1.'));
+ $this->assertEqual($record['cardinality'], 1, 'Cardinality defaults to 1.');
// Ensure that default settings are present.
$field_type = field_info_field_types($field_definition['type']);
- $this->assertIdentical($record['data']['settings'], $field_type['settings'], t('Default field settings have been written.'));
+ $this->assertIdentical($record['data']['settings'], $field_type['settings'], 'Default field settings have been written.');
// Ensure that default storage was set.
- $this->assertEqual($record['storage_type'], variable_get('field_storage_default'), t('The field type is properly saved.'));
+ $this->assertEqual($record['storage_type'], variable_get('field_storage_default'), 'The field type is properly saved.');
// Guarantee that the name is unique.
try {
@@ -2175,7 +2501,42 @@
// Read the field back.
$field = field_read_field($field_definition['field_name']);
- $this->assertTrue($field_definition < $field, t('The field was properly read.'));
+ $this->assertTrue($field_definition < $field, 'The field was properly read.');
+ }
+
+ /**
+ * Tests reading field definitions.
+ */
+ function testReadFields() {
+ $field_definition = array(
+ 'field_name' => 'field_1',
+ 'type' => 'test_field',
+ );
+ field_create_field($field_definition);
+
+ // Check that 'single column' criteria works.
+ $fields = field_read_fields(array('field_name' => $field_definition['field_name']));
+ $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.');
+
+ // Check that 'multi column' criteria works.
+ $fields = field_read_fields(array('field_name' => $field_definition['field_name'], 'type' => $field_definition['type']));
+ $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.');
+ $fields = field_read_fields(array('field_name' => $field_definition['field_name'], 'type' => 'foo'));
+ $this->assertTrue(empty($fields), 'No field was found.');
+
+ // Create an instance of the field.
+ $instance_definition = array(
+ 'field_name' => $field_definition['field_name'],
+ 'entity_type' => 'test_entity',
+ 'bundle' => 'test_bundle',
+ );
+ field_create_instance($instance_definition);
+
+ // Check that criteria spanning over the field_config_instance table work.
+ $fields = field_read_fields(array('entity_type' => $instance_definition['entity_type'], 'bundle' => $instance_definition['bundle']));
+ $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.');
+ $fields = field_read_fields(array('entity_type' => $instance_definition['entity_type'], 'field_name' => $instance_definition['field_name']));
+ $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.');
}
/**
@@ -2190,7 +2551,7 @@
field_create_field($field_definition);
$field = field_read_field($field_definition['field_name']);
$expected_indexes = array('value' => array('value'));
- $this->assertEqual($field['indexes'], $expected_indexes, t('Field type indexes saved by default'));
+ $this->assertEqual($field['indexes'], $expected_indexes, 'Field type indexes saved by default');
// Check that indexes specified by the field definition override the field
// type indexes.
@@ -2204,7 +2565,7 @@
field_create_field($field_definition);
$field = field_read_field($field_definition['field_name']);
$expected_indexes = array('value' => array());
- $this->assertEqual($field['indexes'], $expected_indexes, t('Field definition indexes override field type indexes'));
+ $this->assertEqual($field['indexes'], $expected_indexes, 'Field definition indexes override field type indexes');
// Check that indexes specified by the field definition add to the field
// type indexes.
@@ -2218,7 +2579,7 @@
field_create_field($field_definition);
$field = field_read_field($field_definition['field_name']);
$expected_indexes = array('value' => array('value'), 'value_2' => array('value'));
- $this->assertEqual($field['indexes'], $expected_indexes, t('Field definition indexes are merged with field type indexes'));
+ $this->assertEqual($field['indexes'], $expected_indexes, 'Field definition indexes are merged with field type indexes');
}
/**
@@ -2249,41 +2610,41 @@
// Test that the first field is not deleted, and then delete it.
$field = field_read_field($this->field['field_name'], array('include_deleted' => TRUE));
- $this->assertTrue(!empty($field) && empty($field['deleted']), t('A new field is not marked for deletion.'));
+ $this->assertTrue(!empty($field) && empty($field['deleted']), 'A new field is not marked for deletion.');
field_delete_field($this->field['field_name']);
// Make sure that the field is marked as deleted when it is specifically
// loaded.
$field = field_read_field($this->field['field_name'], array('include_deleted' => TRUE));
- $this->assertTrue(!empty($field['deleted']), t('A deleted field is marked for deletion.'));
+ $this->assertTrue(!empty($field['deleted']), 'A deleted field is marked for deletion.');
// Make sure that this field's instance is marked as deleted when it is
// specifically loaded.
$instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE));
- $this->assertTrue(!empty($instance['deleted']), t('An instance for a deleted field is marked for deletion.'));
+ $this->assertTrue(!empty($instance['deleted']), 'An instance for a deleted field is marked for deletion.');
// Try to load the field normally and make sure it does not show up.
$field = field_read_field($this->field['field_name']);
- $this->assertTrue(empty($field), t('A deleted field is not loaded by default.'));
+ $this->assertTrue(empty($field), 'A deleted field is not loaded by default.');
// Try to load the instance normally and make sure it does not show up.
$instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']);
- $this->assertTrue(empty($instance), t('An instance for a deleted field is not loaded by default.'));
+ $this->assertTrue(empty($instance), 'An instance for a deleted field is not loaded by default.');
// Make sure the other field (and its field instance) are not deleted.
$another_field = field_read_field($this->another_field['field_name']);
- $this->assertTrue(!empty($another_field) && empty($another_field['deleted']), t('A non-deleted field is not marked for deletion.'));
+ $this->assertTrue(!empty($another_field) && empty($another_field['deleted']), 'A non-deleted field is not marked for deletion.');
$another_instance = field_read_instance('test_entity', $this->another_instance_definition['field_name'], $this->another_instance_definition['bundle']);
- $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), t('An instance of a non-deleted field is not marked for deletion.'));
+ $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), 'An instance of a non-deleted field is not marked for deletion.');
// Try to create a new field the same name as a deleted field and
// write data into it.
field_create_field($this->field);
field_create_instance($this->instance_definition);
$field = field_read_field($this->field['field_name']);
- $this->assertTrue(!empty($field) && empty($field['deleted']), t('A new field with a previously used name is created.'));
+ $this->assertTrue(!empty($field) && empty($field['deleted']), 'A new field with a previously used name is created.');
$instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']);
- $this->assertTrue(!empty($instance) && empty($instance['deleted']), t('A new instance for a previously used field name is created.'));
+ $this->assertTrue(!empty($instance) && empty($instance['deleted']), 'A new instance for a previously used field name is created.');
// Save an entity with data for the field
$entity = field_test_create_stub_entity(0, 0, $instance['bundle']);
@@ -2436,18 +2797,18 @@
// Read the field.
$field = field_read_field($field_name);
- $this->assertTrue($field_definition <= $field, t('The field was properly read.'));
+ $this->assertTrue($field_definition <= $field, 'The field was properly read.');
module_disable($modules, FALSE);
$fields = field_read_fields(array('field_name' => $field_name), array('include_inactive' => TRUE));
- $this->assertTrue(isset($fields[$field_name]) && $field_definition < $field, t('The field is properly read when explicitly fetching inactive fields.'));
+ $this->assertTrue(isset($fields[$field_name]) && $field_definition < $field, 'The field is properly read when explicitly fetching inactive fields.');
// Re-enable modules one by one, and check that the field is still inactive
// while some modules remain disabled.
while ($modules) {
$field = field_read_field($field_name);
- $this->assertTrue(empty($field), t('%modules disabled. The field is marked inactive.', array('%modules' => implode(', ', $modules))));
+ $this->assertTrue(empty($field), format_string('%modules disabled. The field is marked inactive.', array('%modules' => implode(', ', $modules))));
$module = array_shift($modules);
module_enable(array($module), FALSE);
@@ -2456,7 +2817,7 @@
// Check that the field is active again after all modules have been
// enabled.
$field = field_read_field($field_name);
- $this->assertTrue($field_definition <= $field, t('The field was was marked active.'));
+ $this->assertTrue($field_definition <= $field, 'The field was was marked active.');
}
}
@@ -2508,17 +2869,17 @@
$formatter_type = field_info_formatter_types($field_type['default_formatter']);
// Check that default values are set.
- $this->assertIdentical($record['data']['required'], FALSE, t('Required defaults to false.'));
- $this->assertIdentical($record['data']['label'], $this->instance_definition['field_name'], t('Label defaults to field name.'));
- $this->assertIdentical($record['data']['description'], '', t('Description defaults to empty string.'));
- $this->assertIdentical($record['data']['widget']['type'], $field_type['default_widget'], t('Default widget has been written.'));
- $this->assertTrue(isset($record['data']['display']['default']), t('Display for "full" view_mode has been written.'));
- $this->assertIdentical($record['data']['display']['default']['type'], $field_type['default_formatter'], t('Default formatter for "full" view_mode has been written.'));
+ $this->assertIdentical($record['data']['required'], FALSE, 'Required defaults to false.');
+ $this->assertIdentical($record['data']['label'], $this->instance_definition['field_name'], 'Label defaults to field name.');
+ $this->assertIdentical($record['data']['description'], '', 'Description defaults to empty string.');
+ $this->assertIdentical($record['data']['widget']['type'], $field_type['default_widget'], 'Default widget has been written.');
+ $this->assertTrue(isset($record['data']['display']['default']), 'Display for "full" view_mode has been written.');
+ $this->assertIdentical($record['data']['display']['default']['type'], $field_type['default_formatter'], 'Default formatter for "full" view_mode has been written.');
// Check that default settings are set.
- $this->assertIdentical($record['data']['settings'], $field_type['instance_settings'] , t('Default instance settings have been written.'));
- $this->assertIdentical($record['data']['widget']['settings'], $widget_type['settings'] , t('Default widget settings have been written.'));
- $this->assertIdentical($record['data']['display']['default']['settings'], $formatter_type['settings'], t('Default formatter settings for "full" view_mode have been written.'));
+ $this->assertIdentical($record['data']['settings'], $field_type['instance_settings'] , 'Default instance settings have been written.');
+ $this->assertIdentical($record['data']['widget']['settings'], $widget_type['settings'] , 'Default widget settings have been written.');
+ $this->assertIdentical($record['data']['display']['default']['settings'], $formatter_type['settings'], 'Default formatter settings for "full" view_mode have been written.');
// Guarantee that the field/bundle combination is unique.
try {
@@ -2583,7 +2944,7 @@
// Read the instance back.
$instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']);
- $this->assertTrue($this->instance_definition < $instance, t('The field was properly read.'));
+ $this->assertTrue($this->instance_definition < $instance, 'The field was properly read.');
}
/**
@@ -2606,13 +2967,13 @@
field_update_instance($instance);
$instance_new = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']);
- $this->assertEqual($instance['required'], $instance_new['required'], t('"required" change is saved'));
- $this->assertEqual($instance['label'], $instance_new['label'], t('"label" change is saved'));
- $this->assertEqual($instance['description'], $instance_new['description'], t('"description" change is saved'));
- $this->assertEqual($instance['widget']['settings']['test_widget_setting'], $instance_new['widget']['settings']['test_widget_setting'], t('Widget setting change is saved'));
- $this->assertEqual($instance['widget']['weight'], $instance_new['widget']['weight'], t('Widget weight change is saved'));
- $this->assertEqual($instance['display']['default']['settings']['test_formatter_setting'], $instance_new['display']['default']['settings']['test_formatter_setting'], t('Formatter setting change is saved'));
- $this->assertEqual($instance['display']['default']['weight'], $instance_new['display']['default']['weight'], t('Widget weight change is saved'));
+ $this->assertEqual($instance['required'], $instance_new['required'], '"required" change is saved');
+ $this->assertEqual($instance['label'], $instance_new['label'], '"label" change is saved');
+ $this->assertEqual($instance['description'], $instance_new['description'], '"description" change is saved');
+ $this->assertEqual($instance['widget']['settings']['test_widget_setting'], $instance_new['widget']['settings']['test_widget_setting'], 'Widget setting change is saved');
+ $this->assertEqual($instance['widget']['weight'], $instance_new['widget']['weight'], 'Widget weight change is saved');
+ $this->assertEqual($instance['display']['default']['settings']['test_formatter_setting'], $instance_new['display']['default']['settings']['test_formatter_setting'], 'Formatter setting change is saved');
+ $this->assertEqual($instance['display']['default']['weight'], $instance_new['display']['default']['weight'], 'Widget weight change is saved');
// Check that changing widget and formatter types updates the default settings.
$instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']);
@@ -2621,13 +2982,13 @@
field_update_instance($instance);
$instance_new = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']);
- $this->assertEqual($instance['widget']['type'], $instance_new['widget']['type'] , t('Widget type change is saved.'));
+ $this->assertEqual($instance['widget']['type'], $instance_new['widget']['type'] , 'Widget type change is saved.');
$settings = field_info_widget_settings($instance_new['widget']['type']);
- $this->assertIdentical($settings, array_intersect_key($instance_new['widget']['settings'], $settings) , t('Widget type change updates default settings.'));
- $this->assertEqual($instance['display']['default']['type'], $instance_new['display']['default']['type'] , t('Formatter type change is saved.'));
+ $this->assertIdentical($settings, array_intersect_key($instance_new['widget']['settings'], $settings) , 'Widget type change updates default settings.');
+ $this->assertEqual($instance['display']['default']['type'], $instance_new['display']['default']['type'] , 'Formatter type change is saved.');
$info = field_info_formatter_types($instance_new['display']['default']['type']);
$settings = $info['settings'];
- $this->assertIdentical($settings, array_intersect_key($instance_new['display']['default']['settings'], $settings) , t('Changing formatter type updates default settings.'));
+ $this->assertIdentical($settings, array_intersect_key($instance_new['display']['default']['settings'], $settings) , 'Changing formatter type updates default settings.');
// Check that adding a new view mode is saved and gets default settings.
$instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']);
@@ -2635,11 +2996,11 @@
field_update_instance($instance);
$instance_new = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']);
- $this->assertTrue(isset($instance_new['display']['teaser']), t('Display for the new view_mode has been written.'));
- $this->assertIdentical($instance_new['display']['teaser']['type'], $field_type['default_formatter'], t('Default formatter for the new view_mode has been written.'));
+ $this->assertTrue(isset($instance_new['display']['teaser']), 'Display for the new view_mode has been written.');
+ $this->assertIdentical($instance_new['display']['teaser']['type'], $field_type['default_formatter'], 'Default formatter for the new view_mode has been written.');
$info = field_info_formatter_types($instance_new['display']['teaser']['type']);
$settings = $info['settings'];
- $this->assertIdentical($settings, $instance_new['display']['teaser']['settings'] , t('Default formatter settings for the new view_mode have been written.'));
+ $this->assertIdentical($settings, $instance_new['display']['teaser']['settings'] , 'Default formatter settings for the new view_mode have been written.');
// TODO: test failures.
}
@@ -2661,26 +3022,26 @@
// Test that the first instance is not deleted, and then delete it.
$instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE));
- $this->assertTrue(!empty($instance) && empty($instance['deleted']), t('A new field instance is not marked for deletion.'));
+ $this->assertTrue(!empty($instance) && empty($instance['deleted']), 'A new field instance is not marked for deletion.');
field_delete_instance($instance);
// Make sure the instance is marked as deleted when the instance is
// specifically loaded.
$instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE));
- $this->assertTrue(!empty($instance['deleted']), t('A deleted field instance is marked for deletion.'));
+ $this->assertTrue(!empty($instance['deleted']), 'A deleted field instance is marked for deletion.');
// Try to load the instance normally and make sure it does not show up.
$instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']);
- $this->assertTrue(empty($instance), t('A deleted field instance is not loaded by default.'));
+ $this->assertTrue(empty($instance), 'A deleted field instance is not loaded by default.');
// Make sure the other field instance is not deleted.
$another_instance = field_read_instance('test_entity', $this->another_instance_definition['field_name'], $this->another_instance_definition['bundle']);
- $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), t('A non-deleted field instance is not marked for deletion.'));
+ $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), 'A non-deleted field instance is not marked for deletion.');
// Make sure the field is deleted when its last instance is deleted.
field_delete_instance($another_instance);
$field = field_read_field($another_instance['field_name'], array('include_deleted' => TRUE));
- $this->assertTrue(!empty($field['deleted']), t('A deleted field is marked for deletion after all its instances have been marked for deletion.'));
+ $this->assertTrue(!empty($field['deleted']), 'A deleted field is marked for deletion after all its instances have been marked for deletion.');
}
}
@@ -2747,17 +3108,17 @@
$available_languages = field_available_languages($this->entity_type, $this->field);
foreach ($available_languages as $delta => $langcode) {
if ($langcode != 'xx' && $langcode != 'en') {
- $this->assertTrue(in_array($langcode, $enabled_languages), t('%language is an enabled language.', array('%language' => $langcode)));
+ $this->assertTrue(in_array($langcode, $enabled_languages), format_string('%language is an enabled language.', array('%language' => $langcode)));
}
}
- $this->assertTrue(in_array('xx', $available_languages), t('%language was made available.', array('%language' => 'xx')));
- $this->assertFalse(in_array('en', $available_languages), t('%language was made unavailable.', array('%language' => 'en')));
+ $this->assertTrue(in_array('xx', $available_languages), format_string('%language was made available.', array('%language' => 'xx')));
+ $this->assertFalse(in_array('en', $available_languages), format_string('%language was made unavailable.', array('%language' => 'en')));
// Test field_available_languages() behavior for untranslatable fields.
$this->field['translatable'] = FALSE;
field_update_field($this->field);
$available_languages = field_available_languages($this->entity_type, $this->field);
- $this->assertTrue(count($available_languages) == 1 && $available_languages[0] === LANGUAGE_NONE, t('For untranslatable fields only LANGUAGE_NONE is available.'));
+ $this->assertTrue(count($available_languages) == 1 && $available_languages[0] === LANGUAGE_NONE, 'For untranslatable fields only LANGUAGE_NONE is available.');
}
/**
@@ -2792,10 +3153,10 @@
$hash = hash('sha256', serialize(array($entity_type, $entity, $this->field_name, $langcode, $values[$langcode])));
// Check whether the parameters passed to _field_invoke() were correctly
// forwarded to the callback function.
- $this->assertEqual($hash, $result, t('The result for %language is correctly stored.', array('%language' => $langcode)));
+ $this->assertEqual($hash, $result, format_string('The result for %language is correctly stored.', array('%language' => $langcode)));
}
- $this->assertEqual(count($results), count($available_languages), t('No unavailable language has been processed.'));
+ $this->assertEqual(count($results), count($available_languages), 'No unavailable language has been processed.');
}
/**
@@ -2856,17 +3217,17 @@
$hash = hash('sha256', serialize(array($entity_type, $entities[$id], $this->field_name, $langcode, $values[$id][$langcode])));
// Check whether the parameters passed to _field_invoke_multiple()
// were correctly forwarded to the callback function.
- $this->assertEqual($hash, $result, t('The result for entity %id/%language is correctly stored.', array('%id' => $id, '%language' => $langcode)));
+ $this->assertEqual($hash, $result, format_string('The result for entity %id/%language is correctly stored.', array('%id' => $id, '%language' => $langcode)));
}
}
- $this->assertEqual(count($results), count($available_languages), t('No unavailable language has been processed for entity %id.', array('%id' => $id)));
+ $this->assertEqual(count($results), count($available_languages), format_string('No unavailable language has been processed for entity %id.', array('%id' => $id)));
}
$null = NULL;
$grouped_results = _field_invoke_multiple('test_op_multiple', $entity_type, $entities, $null, $null, $options);
foreach ($grouped_results as $id => $results) {
foreach ($results as $langcode => $result) {
- $this->assertTrue(isset($options['language'][$id]), t('The result language %language for entity %id was correctly suggested (display language: %display_language).', array('%id' => $id, '%language' => $langcode, '%display_language' => $display_language)));
+ $this->assertTrue(isset($options['language'][$id]), format_string('The result language %language for entity %id was correctly suggested (display language: %display_language).', array('%id' => $id, '%language' => $langcode, '%display_language' => $display_language)));
}
}
}
@@ -2878,7 +3239,7 @@
// Enable field translations for nodes.
field_test_entity_info_translatable('node', TRUE);
$entity_info = entity_get_info('node');
- $this->assertTrue(count($entity_info['translation']), t('Nodes are translatable.'));
+ $this->assertTrue(count($entity_info['translation']), 'Nodes are translatable.');
// Prepare the field translations.
field_test_entity_info_translatable('test_entity', TRUE);
@@ -2887,7 +3248,7 @@
$entity = field_test_create_stub_entity($eid, $evid, $this->instance['bundle']);
$field_translations = array();
$available_languages = field_available_languages($entity_type, $this->field);
- $this->assertTrue(count($available_languages) > 1, t('Field is translatable.'));
+ $this->assertTrue(count($available_languages) > 1, 'Field is translatable.');
foreach ($available_languages as $langcode) {
$field_translations[$langcode] = $this->_generateTestFieldValues($this->field['cardinality']);
}
@@ -2904,7 +3265,7 @@
foreach ($items as $delta => $item) {
$result = $result && $item['value'] == $entity->{$this->field_name}[$langcode][$delta]['value'];
}
- $this->assertTrue($result, t('%language translation correctly handled.', array('%language' => $langcode)));
+ $this->assertTrue($result, format_string('%language translation correctly handled.', array('%language' => $langcode)));
}
}
@@ -2960,7 +3321,7 @@
$display_language = field_language($entity_type, $entity, NULL, $requested_language);
foreach ($instances as $instance) {
$field_name = $instance['field_name'];
- $this->assertTrue($display_language[$field_name] == LANGUAGE_NONE, t('The display language for field %field_name is %language.', array('%field_name' => $field_name, '%language' => LANGUAGE_NONE)));
+ $this->assertTrue($display_language[$field_name] == LANGUAGE_NONE, format_string('The display language for field %field_name is %language.', array('%field_name' => $field_name, '%language' => LANGUAGE_NONE)));
}
// Test multiple-fields display languages for translatable entities.
@@ -2974,20 +3335,20 @@
// As the requested language was not assinged to any field, if the
// returned language is defined for the current field, core fallback rules
// were successfully applied.
- $this->assertTrue(isset($entity->{$field_name}[$langcode]) && $langcode != $requested_language, t('The display language for the field %field_name is %language.', array('%field_name' => $field_name, '%language' => $langcode)));
+ $this->assertTrue(isset($entity->{$field_name}[$langcode]) && $langcode != $requested_language, format_string('The display language for the field %field_name is %language.', array('%field_name' => $field_name, '%language' => $langcode)));
}
// Test single-field display language.
drupal_static_reset('field_language');
$langcode = field_language($entity_type, $entity, $this->field_name, $requested_language);
- $this->assertTrue(isset($entity->{$this->field_name}[$langcode]) && $langcode != $requested_language, t('The display language for the (single) field %field_name is %language.', array('%field_name' => $field_name, '%language' => $langcode)));
+ $this->assertTrue(isset($entity->{$this->field_name}[$langcode]) && $langcode != $requested_language, format_string('The display language for the (single) field %field_name is %language.', array('%field_name' => $field_name, '%language' => $langcode)));
// Test field_language() basic behavior without language fallback.
variable_set('field_test_language_fallback', FALSE);
$entity->{$this->field_name}[$requested_language] = mt_rand(1, 127);
drupal_static_reset('field_language');
$display_language = field_language($entity_type, $entity, $this->field_name, $requested_language);
- $this->assertEqual($display_language, $requested_language, t('Display language behave correctly when language fallback is disabled'));
+ $this->assertEqual($display_language, $requested_language, 'Display language behave correctly when language fallback is disabled');
}
/**
@@ -3031,7 +3392,7 @@
$entity = field_test_entity_test_load($eid, $evid);
foreach ($available_languages as $langcode => $value) {
$passed = isset($entity->{$field_name}[$langcode]) && $entity->{$field_name}[$langcode][0]['value'] == $value + 1;
- $this->assertTrue($passed, t('The @language translation for revision @revision was correctly stored', array('@language' => $langcode, '@revision' => $entity->ftvid)));
+ $this->assertTrue($passed, format_string('The @language translation for revision @revision was correctly stored', array('@language' => $langcode, '@revision' => $entity->ftvid)));
}
}
}
diff -Naur drupal-7.18/modules/field/tests/field_test.entity.inc drupal-7.66/modules/field/tests/field_test.entity.inc
--- drupal-7.18/modules/field/tests/field_test.entity.inc 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/tests/field_test.entity.inc 2019-04-17 22:20:46.000000000 +0200
@@ -9,6 +9,12 @@
* Implements hook_entity_info().
*/
function field_test_entity_info() {
+ // If requested, clear the field cache while this hook is being called. See
+ // testFieldInfoCache().
+ if (variable_get('field_test_clear_info_cache_in_hook_entity_info', FALSE)) {
+ field_info_cache_clear();
+ }
+
$bundles = variable_get('field_test_bundles', array('test_bundle' => array('label' => 'Test Bundle')));
$test_entity_modes = array(
'full' => array(
diff -Naur drupal-7.18/modules/field/tests/field_test.field.inc drupal-7.66/modules/field/tests/field_test.field.inc
--- drupal-7.18/modules/field/tests/field_test.field.inc 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/tests/field_test.field.inc 2019-04-17 22:20:46.000000000 +0200
@@ -28,7 +28,9 @@
'shape' => array(
'label' => t('Shape'),
'description' => t('Another dummy field type.'),
- 'settings' => array(),
+ 'settings' => array(
+ 'foreign_key_name' => 'shape',
+ ),
'instance_settings' => array(),
'default_widget' => 'test_field_widget',
'default_formatter' => 'field_test_default',
diff -Naur drupal-7.18/modules/field/tests/field_test.info drupal-7.66/modules/field/tests/field_test.info
--- drupal-7.18/modules/field/tests/field_test.info 2012-12-19 20:06:43.000000000 +0100
+++ drupal-7.66/modules/field/tests/field_test.info 2019-04-17 22:39:36.000000000 +0200
@@ -6,8 +6,7 @@
version = VERSION
hidden = TRUE
-; Information added by drupal.org packaging script on 2012-12-19
-version = "7.18"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1355944003"
-
+datestamp = "1555533576"
diff -Naur drupal-7.18/modules/field/tests/field_test.install drupal-7.66/modules/field/tests/field_test.install
--- drupal-7.18/modules/field/tests/field_test.install 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/tests/field_test.install 2019-04-17 22:20:46.000000000 +0200
@@ -60,7 +60,7 @@
'description' => 'The base table for test entities with a bundle key.',
'fields' => array(
'ftid' => array(
- 'description' => 'The primary indentifier for a test_entity_bundle_key.',
+ 'description' => 'The primary identifier for a test_entity_bundle_key.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
@@ -79,7 +79,7 @@
'description' => 'The base table for test entities with a bundle.',
'fields' => array(
'ftid' => array(
- 'description' => 'The primary indentifier for a test_entity_bundle.',
+ 'description' => 'The primary identifier for a test_entity_bundle.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
@@ -132,6 +132,18 @@
);
}
else {
+ $foreign_keys = array();
+ // The 'foreign keys' key is not always used in tests.
+ if (!empty($field['settings']['foreign_key_name'])) {
+ $foreign_keys['foreign keys'] = array(
+ // This is a dummy foreign key definition, references a table that
+ // doesn't exist, but that's not a problem.
+ $field['settings']['foreign_key_name'] => array(
+ 'table' => $field['settings']['foreign_key_name'],
+ 'columns' => array($field['settings']['foreign_key_name'] => 'id'),
+ ),
+ );
+ }
return array(
'columns' => array(
'shape' => array(
@@ -145,6 +157,6 @@
'not null' => FALSE,
),
),
- );
+ ) + $foreign_keys;
}
}
diff -Naur drupal-7.18/modules/field/tests/field_test.module drupal-7.66/modules/field/tests/field_test.module
--- drupal-7.18/modules/field/tests/field_test.module 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/tests/field_test.module 2019-04-17 22:20:46.000000000 +0200
@@ -204,10 +204,7 @@
}
/**
- * Entity label callback.
- *
- * @param $entity
- * The entity object.
+ * Implements callback_entity_info_label().
*
* @return
* The label of the entity prefixed with "label callback".
@@ -223,6 +220,10 @@
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']);
+ }
}
/**
@@ -270,3 +271,14 @@
// 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;
+}
diff -Naur drupal-7.18/modules/field/theme/field.tpl.php drupal-7.66/modules/field/theme/field.tpl.php
--- drupal-7.18/modules/field/theme/field.tpl.php 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/field/theme/field.tpl.php 2019-04-17 22:20:46.000000000 +0200
@@ -4,8 +4,10 @@
* @file field.tpl.php
* Default template implementation to display the value of a field.
*
- * This file is not used and is here as a starting point for customization only.
- * @see theme_field()
+ * This file is not used by Drupal core, which uses theme functions instead for
+ * performance reasons. The markup is the same, though, so if you want to use
+ * template files rather than functions to extend field theming, copy this to
+ * your custom theme. See theme_field() for a discussion of performance.
*
* Available variables:
* - $items: An array of field values. Use render() to output them.
@@ -45,7 +47,7 @@
*/
?>
' placeholder.
if ($mode) {
$content = $match[1];
- $hash = md5($content);
+ $hash = hash('sha256', $content);
$comments[$hash] = $content;
return "";
}
@@ -1582,21 +1669,30 @@
}
/**
- * Filter tips callback for URL filter.
+ * Implements callback_filter_tips().
+ *
+ * Provides help for the URL filter.
+ *
+ * @see filter_filter_info()
*/
function _filter_url_tips($filter, $format, $long = FALSE) {
return t('Web page addresses and e-mail addresses turn into links automatically.');
}
/**
- * Scan input and make sure that all HTML tags are properly closed and nested.
+ * Implements callback_filter_process().
+ *
+ * Scans the input and makes sure that HTML tags are properly closed.
*/
function _filter_htmlcorrector($text) {
return filter_dom_serialize(filter_dom_load($text));
}
/**
- * Convert line breaks into and
in an intelligent fashion.
+ * Implements callback_filter_process().
+ *
+ * Converts line breaks into
and
in an intelligent fashion.
+ *
* Based on: http://photomatt.net/scripts/autop
*/
function _filter_autop($text) {
@@ -1662,7 +1758,11 @@
}
/**
- * Filter tips callback for auto-paragraph filter.
+ * Implements callback_filter_tips().
+ *
+ * Provides help for the auto-paragraph filter.
+ *
+ * @see filter_filter_info()
*/
function _filter_autop_tips($filter, $format, $long = FALSE) {
if ($long) {
@@ -1674,6 +1774,8 @@
}
/**
+ * Implements callback_filter_process().
+ *
* Escapes all HTML tags, so they will be visible instead of being effective.
*/
function _filter_html_escape($text) {
@@ -1681,7 +1783,11 @@
}
/**
- * Filter tips callback for HTML escaping filter.
+ * Implements callback_filter_tips().
+ *
+ * Provides help for the HTML escaping filter.
+ *
+ * @see filter_filter_info()
*/
function _filter_html_escape_tips($filter, $format, $long = FALSE) {
return t('No HTML tags allowed.');
diff -Naur drupal-7.18/modules/filter/filter.pages.inc drupal-7.66/modules/filter/filter.pages.inc
--- drupal-7.18/modules/filter/filter.pages.inc 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/filter/filter.pages.inc 2019-04-17 22:20:46.000000000 +0200
@@ -2,17 +2,21 @@
/**
* @file
- * User page callbacks for the filter module.
+ * User page callbacks for the Filter module.
*/
-
/**
- * Menu callback; show a page with long filter tips.
+ * Page callback: Displays a page with long filter tips.
+ *
+ * @return string
+ * An HTML-formatted string.
+ *
+ * @see filter_menu()
+ * @see theme_filter_tips()
*/
-function filter_tips_long() {
- $format_id = arg(2);
- if ($format_id) {
- $output = theme('filter_tips', array('tips' => _filter_tips($format_id, TRUE), 'long' => TRUE));
+function filter_tips_long($format = NULL) {
+ if (!empty($format)) {
+ $output = theme('filter_tips', array('tips' => _filter_tips($format->format, TRUE), 'long' => TRUE));
}
else {
$output = theme('filter_tips', array('tips' => _filter_tips(-1, TRUE), 'long' => TRUE));
@@ -20,13 +24,12 @@
return $output;
}
-
/**
* Returns HTML for a set of filter tips.
*
* @param $variables
* An associative array containing:
- * - tips: An array containing descriptions and a CSS id in the form of
+ * - tips: An array containing descriptions and a CSS ID in the form of
* 'module-name/filter-id' (only used when $long is TRUE) for each
* filter in one or more text formats. Example:
* @code
@@ -64,7 +67,7 @@
foreach ($tips as $name => $tiplist) {
if ($multiple) {
$output .= '
';
- $output .= '' . $name . '
';
+ $output .= '' . check_plain($name) . '
';
}
if (count($tiplist) > 0) {
diff -Naur drupal-7.18/modules/filter/filter.test drupal-7.66/modules/filter/filter.test
--- drupal-7.18/modules/filter/filter.test 2012-12-19 19:52:59.000000000 +0100
+++ drupal-7.66/modules/filter/filter.test 2019-04-17 22:20:46.000000000 +0200
@@ -22,7 +22,7 @@
}
/**
- * Test CRUD operations for text formats and filters.
+ * Tests CRUD operations for text formats and filters.
*/
function testTextFormatCRUD() {
// Add a text format with minimum data only.
@@ -67,13 +67,22 @@
filter_format_disable($format);
$db_format = db_query("SELECT * FROM {filter_format} WHERE format = :format", array(':format' => $format->format))->fetchObject();
- $this->assertFalse($db_format->status, t('Database: Disabled text format is marked as disabled.'));
+ $this->assertFalse($db_format->status, 'Database: Disabled text format is marked as disabled.');
$formats = filter_formats();
- $this->assertTrue(!isset($formats[$format->format]), t('filter_formats: Disabled text format no longer exists.'));
+ $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 = '';
+ 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.');
}
/**
- * Verify that a text format is properly stored.
+ * Verifies that a text format is properly stored.
*/
function verifyTextFormat($format) {
$t_args = array('%format' => $format->name);
@@ -83,17 +92,17 @@
->condition('format', $format->format)
->execute()
->fetchObject();
- $this->assertEqual($db_format->format, $format->format, t('Database: Proper format id for text format %format.', $t_args));
- $this->assertEqual($db_format->name, $format->name, t('Database: Proper title for text format %format.', $t_args));
- $this->assertEqual($db_format->cache, $format->cache, t('Database: Proper cache indicator for text format %format.', $t_args));
- $this->assertEqual($db_format->weight, $format->weight, t('Database: Proper weight for text format %format.', $t_args));
+ $this->assertEqual($db_format->format, $format->format, format_string('Database: Proper format id for text format %format.', $t_args));
+ $this->assertEqual($db_format->name, $format->name, format_string('Database: Proper title for text format %format.', $t_args));
+ $this->assertEqual($db_format->cache, $format->cache, format_string('Database: Proper cache indicator for text format %format.', $t_args));
+ $this->assertEqual($db_format->weight, $format->weight, format_string('Database: Proper weight for text format %format.', $t_args));
// Verify filter_format_load().
$filter_format = filter_format_load($format->format);
- $this->assertEqual($filter_format->format, $format->format, t('filter_format_load: Proper format id for text format %format.', $t_args));
- $this->assertEqual($filter_format->name, $format->name, t('filter_format_load: Proper title for text format %format.', $t_args));
- $this->assertEqual($filter_format->cache, $format->cache, t('filter_format_load: Proper cache indicator for text format %format.', $t_args));
- $this->assertEqual($filter_format->weight, $format->weight, t('filter_format_load: Proper weight for text format %format.', $t_args));
+ $this->assertEqual($filter_format->format, $format->format, format_string('filter_format_load: Proper format id for text format %format.', $t_args));
+ $this->assertEqual($filter_format->name, $format->name, format_string('filter_format_load: Proper title for text format %format.', $t_args));
+ $this->assertEqual($filter_format->cache, $format->cache, format_string('filter_format_load: Proper cache indicator for text format %format.', $t_args));
+ $this->assertEqual($filter_format->weight, $format->weight, format_string('filter_format_load: Proper weight for text format %format.', $t_args));
// Verify the 'cache' text format property according to enabled filters.
$filter_info = filter_get_filters();
@@ -107,11 +116,11 @@
break;
}
}
- $this->assertEqual($filter_format->cache, $cacheable, t('Text format contains proper cache property.'));
+ $this->assertEqual($filter_format->cache, $cacheable, 'Text format contains proper cache property.');
}
/**
- * Verify that filters are properly stored for a text format.
+ * Verifies that filters are properly stored for a text format.
*/
function verifyFilters($format) {
// Verify filter database records.
@@ -121,20 +130,20 @@
$t_args = array('%format' => $format->name, '%filter' => $name);
// Verify that filter status is properly stored.
- $this->assertEqual($filter->status, $format_filters[$name]['status'], t('Database: Proper status for %filter in text format %format.', $t_args));
+ $this->assertEqual($filter->status, $format_filters[$name]['status'], format_string('Database: Proper status for %filter in text format %format.', $t_args));
// Verify that filter settings were properly stored.
- $this->assertEqual(unserialize($filter->settings), isset($format_filters[$name]['settings']) ? $format_filters[$name]['settings'] : array(), t('Database: Proper filter settings for %filter in text format %format.', $t_args));
+ $this->assertEqual(unserialize($filter->settings), isset($format_filters[$name]['settings']) ? $format_filters[$name]['settings'] : array(), format_string('Database: Proper filter settings for %filter in text format %format.', $t_args));
// Verify that each filter has a module name assigned.
- $this->assertTrue(!empty($filter->module), t('Database: Proper module name for %filter in text format %format.', $t_args));
+ $this->assertTrue(!empty($filter->module), format_string('Database: Proper module name for %filter in text format %format.', $t_args));
// Remove the filter from the copy of saved $format to check whether all
// filters have been processed later.
unset($format_filters[$name]);
}
// Verify that all filters have been processed.
- $this->assertTrue(empty($format_filters), t('Database contains values for all filters in the saved format.'));
+ $this->assertTrue(empty($format_filters), 'Database contains values for all filters in the saved format.');
// Verify filter_list_format().
$filters = filter_list_format($format->format);
@@ -143,23 +152,26 @@
$t_args = array('%format' => $format->name, '%filter' => $name);
// Verify that filter status is properly stored.
- $this->assertEqual($filter->status, $format_filters[$name]['status'], t('filter_list_format: Proper status for %filter in text format %format.', $t_args));
+ $this->assertEqual($filter->status, $format_filters[$name]['status'], format_string('filter_list_format: Proper status for %filter in text format %format.', $t_args));
// Verify that filter settings were properly stored.
- $this->assertEqual($filter->settings, isset($format_filters[$name]['settings']) ? $format_filters[$name]['settings'] : array(), t('filter_list_format: Proper filter settings for %filter in text format %format.', $t_args));
+ $this->assertEqual($filter->settings, isset($format_filters[$name]['settings']) ? $format_filters[$name]['settings'] : array(), format_string('filter_list_format: Proper filter settings for %filter in text format %format.', $t_args));
// Verify that each filter has a module name assigned.
- $this->assertTrue(!empty($filter->module), t('filter_list_format: Proper module name for %filter in text format %format.', $t_args));
+ $this->assertTrue(!empty($filter->module), format_string('filter_list_format: Proper module name for %filter in text format %format.', $t_args));
// Remove the filter from the copy of saved $format to check whether all
// filters have been processed later.
unset($format_filters[$name]);
}
// Verify that all filters have been processed.
- $this->assertTrue(empty($format_filters), t('filter_list_format: Loaded filters contain values for all filters in the saved format.'));
+ $this->assertTrue(empty($format_filters), 'filter_list_format: Loaded filters contain values for all filters in the saved format.');
}
}
+/**
+ * Tests the administrative functionality of the Filter module.
+ */
class FilterAdminTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
@@ -185,6 +197,9 @@
$this->drupalLogin($this->admin_user);
}
+ /**
+ * Tests the format administration functionality.
+ */
function testFormatAdmin() {
// Add text format.
$this->drupalGet('admin/config/content/formats');
@@ -199,14 +214,14 @@
// Verify default weight of the text format.
$this->drupalGet('admin/config/content/formats');
- $this->assertFieldByName("formats[$format_id][weight]", 0, t('Text format weight was saved.'));
+ $this->assertFieldByName("formats[$format_id][weight]", 0, 'Text format weight was saved.');
// Change the weight of the text format.
$edit = array(
"formats[$format_id][weight]" => 5,
);
$this->drupalPost('admin/config/content/formats', $edit, t('Save changes'));
- $this->assertFieldByName("formats[$format_id][weight]", 5, t('Text format weight was saved.'));
+ $this->assertFieldByName("formats[$format_id][weight]", 5, 'Text format weight was saved.');
// Edit text format.
$this->drupalGet('admin/config/content/formats');
@@ -216,7 +231,7 @@
// Verify that the custom weight of the text format has been retained.
$this->drupalGet('admin/config/content/formats');
- $this->assertFieldByName("formats[$format_id][weight]", 5, t('Text format weight was retained.'));
+ $this->assertFieldByName("formats[$format_id][weight]", 5, 'Text format weight was retained.');
// Disable text format.
$this->assertLinkByHref('admin/config/content/formats/' . $format_id . '/disable');
@@ -225,7 +240,7 @@
// Verify that disabled text format no longer exists.
$this->drupalGet('admin/config/content/formats/' . $format_id);
- $this->assertResponse(404, t('Disabled text format no longer exists.'));
+ $this->assertResponse(404, 'Disabled text format no longer exists.');
// Attempt to create a format of the same machine name as the disabled
// format but with a different human readable name.
@@ -249,7 +264,7 @@
}
/**
- * Test filter administration functionality.
+ * Tests filter administration functionality.
*/
function testFilterAdmin() {
// URL filter.
@@ -262,44 +277,44 @@
$plain = 'plain_text';
// Check that the fallback format exists and cannot be disabled.
- $this->assertTrue($plain == filter_fallback_format(), t('The fallback format is set to plain text.'));
+ $this->assertTrue($plain == filter_fallback_format(), 'The fallback format is set to plain text.');
$this->drupalGet('admin/config/content/formats');
- $this->assertNoRaw('admin/config/content/formats/' . $plain . '/disable', t('Disable link for the fallback format not found.'));
+ $this->assertNoRaw('admin/config/content/formats/' . $plain . '/disable', 'Disable link for the fallback format not found.');
$this->drupalGet('admin/config/content/formats/' . $plain . '/disable');
- $this->assertResponse(403, t('The fallback format cannot be disabled.'));
+ $this->assertResponse(403, 'The fallback format cannot be disabled.');
// Verify access permissions to Full HTML format.
- $this->assertTrue(filter_access(filter_format_load($full), $this->admin_user), t('Admin user may use Full HTML.'));
- $this->assertFalse(filter_access(filter_format_load($full), $this->web_user), t('Web user may not use Full HTML.'));
+ $this->assertTrue(filter_access(filter_format_load($full), $this->admin_user), 'Admin user may use Full HTML.');
+ $this->assertFalse(filter_access(filter_format_load($full), $this->web_user), 'Web user may not use Full HTML.');
// Add an additional tag.
$edit = array();
$edit['filters[filter_html][settings][allowed_html]'] = ' -
-
-
';
$this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration'));
- $this->assertFieldByName('filters[filter_html][settings][allowed_html]', $edit['filters[filter_html][settings][allowed_html]'], t('Allowed HTML tag added.'));
+ $this->assertFieldByName('filters[filter_html][settings][allowed_html]', $edit['filters[filter_html][settings][allowed_html]'], 'Allowed HTML tag added.');
$result = db_query('SELECT * FROM {cache_filter}')->fetchObject();
- $this->assertFalse($result, t('Cache cleared.'));
+ $this->assertFalse($result, 'Cache cleared.');
$elements = $this->xpath('//select[@name=:first]/following::select[@name=:second]', array(
':first' => 'filters[' . $first_filter . '][weight]',
':second' => 'filters[' . $second_filter . '][weight]',
));
- $this->assertTrue(!empty($elements), t('Order confirmed in admin interface.'));
+ $this->assertTrue(!empty($elements), 'Order confirmed in admin interface.');
// Reorder filters.
$edit = array();
$edit['filters[' . $second_filter . '][weight]'] = 1;
$edit['filters[' . $first_filter . '][weight]'] = 2;
$this->drupalPost(NULL, $edit, t('Save configuration'));
- $this->assertFieldByName('filters[' . $second_filter . '][weight]', 1, t('Order saved successfully.'));
- $this->assertFieldByName('filters[' . $first_filter . '][weight]', 2, t('Order saved successfully.'));
+ $this->assertFieldByName('filters[' . $second_filter . '][weight]', 1, 'Order saved successfully.');
+ $this->assertFieldByName('filters[' . $first_filter . '][weight]', 2, 'Order saved successfully.');
$elements = $this->xpath('//select[@name=:first]/following::select[@name=:second]', array(
':first' => 'filters[' . $second_filter . '][weight]',
':second' => 'filters[' . $first_filter . '][weight]',
));
- $this->assertTrue(!empty($elements), t('Reorder confirmed in admin interface.'));
+ $this->assertTrue(!empty($elements), 'Reorder confirmed in admin interface.');
$result = db_query('SELECT * FROM {filter} WHERE format = :format ORDER BY weight ASC', array(':format' => $filtered));
$filters = array();
@@ -308,7 +323,7 @@
$filters[] = $filter;
}
}
- $this->assertTrue(($filters[0]->name == $second_filter && $filters[1]->name == $first_filter), t('Order confirmed in database.'));
+ $this->assertTrue(($filters[0]->name == $second_filter && $filters[1]->name == $first_filter), 'Order confirmed in database.');
// Add format.
$edit = array();
@@ -318,19 +333,19 @@
$edit['filters[' . $second_filter . '][status]'] = TRUE;
$edit['filters[' . $first_filter . '][status]'] = TRUE;
$this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
- $this->assertRaw(t('Added text format %format.', array('%format' => $edit['name'])), t('New filter created.'));
+ $this->assertRaw(t('Added text format %format.', array('%format' => $edit['name'])), 'New filter created.');
drupal_static_reset('filter_formats');
$format = filter_format_load($edit['format']);
- $this->assertNotNull($format, t('Format found in database.'));
+ $this->assertNotNull($format, 'Format found in database.');
- $this->assertFieldByName('roles[' . DRUPAL_AUTHENTICATED_RID . ']', '', t('Role found.'));
- $this->assertFieldByName('filters[' . $second_filter . '][status]', '', t('Line break filter found.'));
- $this->assertFieldByName('filters[' . $first_filter . '][status]', '', t('Url filter found.'));
+ $this->assertFieldByName('roles[' . DRUPAL_AUTHENTICATED_RID . ']', '', 'Role found.');
+ $this->assertFieldByName('filters[' . $second_filter . '][status]', '', 'Line break filter found.');
+ $this->assertFieldByName('filters[' . $first_filter . '][status]', '', 'Url filter found.');
// Disable new filter.
$this->drupalPost('admin/config/content/formats/' . $format->format . '/disable', array(), t('Disable'));
- $this->assertRaw(t('Disabled text format %format.', array('%format' => $edit['name'])), t('Format successfully disabled.'));
+ $this->assertRaw(t('Disabled text format %format.', array('%format' => $edit['name'])), 'Format successfully disabled.');
// Allow authenticated users on full HTML.
$format = filter_format_load($full);
@@ -338,14 +353,14 @@
$edit['roles[' . DRUPAL_ANONYMOUS_RID . ']'] = 0;
$edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = 1;
$this->drupalPost('admin/config/content/formats/' . $full, $edit, t('Save configuration'));
- $this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), t('Full HTML format successfully updated.'));
+ $this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), 'Full HTML format successfully updated.');
// Switch user.
$this->drupalLogout();
$this->drupalLogin($this->web_user);
$this->drupalGet('node/add/page');
- $this->assertRaw('', t('Full HTML filter accessible.'));
+ $this->assertRaw('', 'Full HTML filter accessible.');
// Use filtered HTML and see if it removes tags that are not allowed.
$body = '' . $this->randomName() . '';
@@ -358,20 +373,20 @@
$edit["body[$langcode][0][value]"] = $text;
$edit["body[$langcode][0][format]"] = $filtered;
$this->drupalPost('node/add/page', $edit, t('Save'));
- $this->assertRaw(t('Basic page %title has been created.', array('%title' => $edit["title"])), t('Filtered node created.'));
+ $this->assertRaw(t('Basic page %title has been created.', array('%title' => $edit["title"])), 'Filtered node created.');
$node = $this->drupalGetNodeByTitle($edit["title"]);
- $this->assertTrue($node, t('Node found in database.'));
+ $this->assertTrue($node, 'Node found in database.');
$this->drupalGet('node/' . $node->nid);
- $this->assertRaw($body . $extra_text, t('Filter removed invalid tag.'));
+ $this->assertRaw($body . $extra_text, 'Filter removed invalid tag.');
// Use plain text and see if it escapes all tags, whether allowed or not.
$edit = array();
$edit["body[$langcode][0][format]"] = $plain;
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
$this->drupalGet('node/' . $node->nid);
- $this->assertText(check_plain($text), t('The "Plain text" text format escapes all HTML tags.'));
+ $this->assertText(check_plain($text), 'The "Plain text" text format escapes all HTML tags.');
// Switch user.
$this->drupalLogout();
@@ -382,22 +397,22 @@
$edit = array();
$edit['filters[filter_html][settings][allowed_html]'] = ' -
-
- ';
$this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration'));
- $this->assertFieldByName('filters[filter_html][settings][allowed_html]', $edit['filters[filter_html][settings][allowed_html]'], t('Changes reverted.'));
+ $this->assertFieldByName('filters[filter_html][settings][allowed_html]', $edit['filters[filter_html][settings][allowed_html]'], 'Changes reverted.');
// Full HTML.
$edit = array();
$edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = FALSE;
$this->drupalPost('admin/config/content/formats/' . $full, $edit, t('Save configuration'));
- $this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), t('Full HTML format successfully reverted.'));
- $this->assertFieldByName('roles[' . DRUPAL_AUTHENTICATED_RID . ']', $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'], t('Changes reverted.'));
+ $this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), 'Full HTML format successfully reverted.');
+ $this->assertFieldByName('roles[' . DRUPAL_AUTHENTICATED_RID . ']', $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'], 'Changes reverted.');
// Filter order.
$edit = array();
$edit['filters[' . $second_filter . '][weight]'] = 2;
$edit['filters[' . $first_filter . '][weight]'] = 1;
$this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration'));
- $this->assertFieldByName('filters[' . $second_filter . '][weight]', $edit['filters[' . $second_filter . '][weight]'], t('Changes reverted.'));
- $this->assertFieldByName('filters[' . $first_filter . '][weight]', $edit['filters[' . $first_filter . '][weight]'], t('Changes reverted.'));
+ $this->assertFieldByName('filters[' . $second_filter . '][weight]', $edit['filters[' . $second_filter . '][weight]'], 'Changes reverted.');
+ $this->assertFieldByName('filters[' . $first_filter . '][weight]', $edit['filters[' . $first_filter . '][weight]'], 'Changes reverted.');
}
/**
@@ -413,11 +428,43 @@
}
}
+/**
+ * Tests the filter format access functionality in the Filter module.
+ */
class FilterFormatAccessTestCase extends DrupalWebTestCase {
+ /**
+ * A user with administrative permissions.
+ *
+ * @var object
+ */
protected $admin_user;
+
+ /**
+ * A user with 'administer filters' permission.
+ *
+ * @var object
+ */
protected $filter_admin_user;
+
+ /**
+ * A user with permission to create and edit own content.
+ *
+ * @var object
+ */
protected $web_user;
+
+ /**
+ * An object representing an allowed text format.
+ *
+ * @var object
+ */
protected $allowed_format;
+
+ /**
+ * An object representing a disallowed text format.
+ *
+ * @var object
+ */
protected $disallowed_format;
public static function getInfo() {
@@ -471,23 +518,26 @@
));
}
+ /**
+ * Tests the Filter format access permissions functionality.
+ */
function testFormatPermissions() {
// Make sure that a regular user only has access to the text format they
// were granted access to, as well to the fallback format.
- $this->assertTrue(filter_access($this->allowed_format, $this->web_user), t('A regular user has access to a text format they were granted access to.'));
- $this->assertFalse(filter_access($this->disallowed_format, $this->web_user), t('A regular user does not have access to a text format they were not granted access to.'));
- $this->assertTrue(filter_access(filter_format_load(filter_fallback_format()), $this->web_user), t('A regular user has access to the fallback format.'));
+ $this->assertTrue(filter_access($this->allowed_format, $this->web_user), 'A regular user has access to a text format they were granted access to.');
+ $this->assertFalse(filter_access($this->disallowed_format, $this->web_user), 'A regular user does not have access to a text format they were not granted access to.');
+ $this->assertTrue(filter_access(filter_format_load(filter_fallback_format()), $this->web_user), 'A regular user has access to the fallback format.');
// Perform similar checks as above, but now against the entire list of
// available formats for this user.
- $this->assertTrue(in_array($this->allowed_format->format, array_keys(filter_formats($this->web_user))), t('The allowed format appears in the list of available formats for a regular user.'));
- $this->assertFalse(in_array($this->disallowed_format->format, array_keys(filter_formats($this->web_user))), t('The disallowed format does not appear in the list of available formats for a regular user.'));
- $this->assertTrue(in_array(filter_fallback_format(), array_keys(filter_formats($this->web_user))), t('The fallback format appears in the list of available formats for a regular user.'));
+ $this->assertTrue(in_array($this->allowed_format->format, array_keys(filter_formats($this->web_user))), 'The allowed format appears in the list of available formats for a regular user.');
+ $this->assertFalse(in_array($this->disallowed_format->format, array_keys(filter_formats($this->web_user))), 'The disallowed format does not appear in the list of available formats for a regular user.');
+ $this->assertTrue(in_array(filter_fallback_format(), array_keys(filter_formats($this->web_user))), 'The fallback format appears in the list of available formats for a regular user.');
// Make sure that a regular user only has permission to use the format
// they were granted access to.
- $this->assertTrue(user_access(filter_permission_name($this->allowed_format), $this->web_user), t('A regular user has permission to use the allowed text format.'));
- $this->assertFalse(user_access(filter_permission_name($this->disallowed_format), $this->web_user), t('A regular user does not have permission to use the disallowed text format.'));
+ $this->assertTrue(user_access(filter_permission_name($this->allowed_format), $this->web_user), 'A regular user has permission to use the allowed text format.');
+ $this->assertFalse(user_access(filter_permission_name($this->disallowed_format), $this->web_user), 'A regular user does not have permission to use the disallowed text format.');
// Make sure that the allowed format appears on the node form and that
// the disallowed format does not.
@@ -502,11 +552,35 @@
foreach ($elements as $element) {
$options[(string) $element['value']] = $element;
}
- $this->assertTrue(isset($options[$this->allowed_format->format]), t('The allowed text format appears as an option when adding a new node.'));
- $this->assertFalse(isset($options[$this->disallowed_format->format]), t('The disallowed text format does not appear as an option when adding a new node.'));
- $this->assertTrue(isset($options[filter_fallback_format()]), t('The fallback format appears as an option when adding a new node.'));
+ $this->assertTrue(isset($options[$this->allowed_format->format]), 'The allowed text format appears as an option when adding a new node.');
+ $this->assertFalse(isset($options[$this->disallowed_format->format]), 'The disallowed text format does not appear as an option when adding a new node.');
+ $this->assertTrue(isset($options[filter_fallback_format()]), 'The fallback format appears as an option when adding a new node.');
+
+ // Check regular user access to the filter tips pages.
+ $this->drupalGet('filter/tips/' . $this->allowed_format->format);
+ $this->assertResponse(200);
+ $this->drupalGet('filter/tips/' . $this->disallowed_format->format);
+ $this->assertResponse(403);
+ $this->drupalGet('filter/tips/' . filter_fallback_format());
+ $this->assertResponse(200);
+ $this->drupalGet('filter/tips/invalid-format');
+ $this->assertResponse(404);
+
+ // Check admin user access to the filter tips pages.
+ $this->drupalLogin($this->admin_user);
+ $this->drupalGet('filter/tips/' . $this->allowed_format->format);
+ $this->assertResponse(200);
+ $this->drupalGet('filter/tips/' . $this->disallowed_format->format);
+ $this->assertResponse(200);
+ $this->drupalGet('filter/tips/' . filter_fallback_format());
+ $this->assertResponse(200);
+ $this->drupalGet('filter/tips/invalid-format');
+ $this->assertResponse(404);
}
+ /**
+ * Tests if text format is available to a role.
+ */
function testFormatRoles() {
// Get the role ID assigned to the regular user; it must be the maximum.
$rid = max(array_keys($this->web_user->roles));
@@ -514,27 +588,27 @@
// Check that this role appears in the list of roles that have access to an
// allowed text format, but does not appear in the list of roles that have
// access to a disallowed text format.
- $this->assertTrue(in_array($rid, array_keys(filter_get_roles_by_format($this->allowed_format))), t('A role which has access to a text format appears in the list of roles that have access to that format.'));
- $this->assertFalse(in_array($rid, array_keys(filter_get_roles_by_format($this->disallowed_format))), t('A role which does not have access to a text format does not appear in the list of roles that have access to that format.'));
+ $this->assertTrue(in_array($rid, array_keys(filter_get_roles_by_format($this->allowed_format))), 'A role which has access to a text format appears in the list of roles that have access to that format.');
+ $this->assertFalse(in_array($rid, array_keys(filter_get_roles_by_format($this->disallowed_format))), 'A role which does not have access to a text format does not appear in the list of roles that have access to that format.');
// Check that the correct text format appears in the list of formats
// available to that role.
- $this->assertTrue(in_array($this->allowed_format->format, array_keys(filter_get_formats_by_role($rid))), t('A text format which a role has access to appears in the list of formats available to that role.'));
- $this->assertFalse(in_array($this->disallowed_format->format, array_keys(filter_get_formats_by_role($rid))), t('A text format which a role does not have access to does not appear in the list of formats available to that role.'));
+ $this->assertTrue(in_array($this->allowed_format->format, array_keys(filter_get_formats_by_role($rid))), 'A text format which a role has access to appears in the list of formats available to that role.');
+ $this->assertFalse(in_array($this->disallowed_format->format, array_keys(filter_get_formats_by_role($rid))), 'A text format which a role does not have access to does not appear in the list of formats available to that role.');
// Check that the fallback format is always allowed.
- $this->assertEqual(filter_get_roles_by_format(filter_format_load(filter_fallback_format())), user_roles(), t('All roles have access to the fallback format.'));
- $this->assertTrue(in_array(filter_fallback_format(), array_keys(filter_get_formats_by_role($rid))), t('The fallback format appears in the list of allowed formats for any role.'));
+ $this->assertEqual(filter_get_roles_by_format(filter_format_load(filter_fallback_format())), user_roles(), 'All roles have access to the fallback format.');
+ $this->assertTrue(in_array(filter_fallback_format(), array_keys(filter_get_formats_by_role($rid))), 'The fallback format appears in the list of allowed formats for any role.');
}
/**
- * Test editing a page using a disallowed text format.
+ * Tests editing a page using a disallowed text format.
*
- * Verifies that regular users and administrators are able to edit a page,
- * but not allowed to change the fields which use an inaccessible text
- * format. Also verifies that fields which use a text format that does not
- * exist can be edited by administrators only, but that the administrator is
- * forced to choose a new format before saving the page.
+ * Verifies that regular users and administrators are able to edit a page, but
+ * not allowed to change the fields which use an inaccessible text format.
+ * Also verifies that fields which use a text format that does not exist can
+ * be edited by administrators only, but that the administrator is forced to
+ * choose a new format before saving the page.
*/
function testFormatWidgetPermissions() {
$langcode = LANGUAGE_NONE;
@@ -557,19 +631,19 @@
$this->clickLink(t('Edit'));
// Verify that body field is read-only and contains replacement value.
- $this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), t('Text format access denied message found.'));
+ $this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), 'Text format access denied message found.');
// Verify that title can be changed, but preview displays original body.
$new_edit = array();
$new_edit['title'] = $this->randomName(8);
$this->drupalPost(NULL, $new_edit, t('Preview'));
- $this->assertText($edit[$body_value_key], t('Old body found in preview.'));
+ $this->assertText($edit[$body_value_key], 'Old body found in preview.');
// Save and verify that only the title was changed.
$this->drupalPost(NULL, $new_edit, t('Save'));
- $this->assertNoText($edit['title'], t('Old title not found.'));
- $this->assertText($new_edit['title'], t('New title found.'));
- $this->assertText($edit[$body_value_key], t('Old body found.'));
+ $this->assertNoText($edit['title'], 'Old title not found.');
+ $this->assertText($new_edit['title'], 'New title found.');
+ $this->assertText($edit[$body_value_key], 'Old body found.');
// Check that even an administrator with "administer filters" permission
// cannot edit the body field if they do not have specific permission to
@@ -578,7 +652,7 @@
// else.)
$this->drupalLogin($this->filter_admin_user);
$this->drupalGet('node/' . $node->nid . '/edit');
- $this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), t('Text format access denied message found.'));
+ $this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), 'Text format access denied message found.');
// Disable the text format used above.
filter_format_disable($this->disallowed_format);
@@ -589,14 +663,14 @@
// edit content that does not have an assigned format.
$this->drupalLogin($this->web_user);
$this->drupalGet('node/' . $node->nid . '/edit');
- $this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), t('Text format access denied message found.'));
+ $this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), 'Text format access denied message found.');
// Log back in as the filter administrator and verify that the body field
// can be edited.
$this->drupalLogin($this->filter_admin_user);
$this->drupalGet('node/' . $node->nid . '/edit');
- $this->assertNoFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", NULL, t('Text format access denied message not found.'));
- $this->assertFieldByXPath("//select[@name='$body_format_key']", NULL, t('Text format selector found.'));
+ $this->assertNoFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", NULL, 'Text format access denied message not found.');
+ $this->assertFieldByXPath("//select[@name='$body_format_key']", NULL, 'Text format selector found.');
// Verify that trying to save the node without selecting a new text format
// produces an error message, and does not result in the node being saved.
@@ -604,17 +678,17 @@
$new_title = $this->randomName(8);
$edit = array('title' => $new_title);
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
- $this->assertText(t('!name field is required.', array('!name' => t('Text format'))), t('Error message is displayed.'));
+ $this->assertText(t('!name field is required.', array('!name' => t('Text format'))), 'Error message is displayed.');
$this->drupalGet('node/' . $node->nid);
- $this->assertText($old_title, t('Old title found.'));
- $this->assertNoText($new_title, t('New title not found.'));
+ $this->assertText($old_title, 'Old title found.');
+ $this->assertNoText($new_title, 'New title not found.');
// Now select a new text format and make sure the node can be saved.
$edit[$body_format_key] = filter_fallback_format();
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
$this->assertUrl('node/' . $node->nid);
- $this->assertText($new_title, t('New title found.'));
- $this->assertNoText($old_title, t('Old title not found.'));
+ $this->assertText($new_title, 'New title found.');
+ $this->assertNoText($old_title, 'Old title not found.');
// Switch the text format to a new one, then disable that format and all
// other formats on the site (leaving only the fallback format).
@@ -638,19 +712,19 @@
$new_title = $this->randomName(8);
$edit = array('title' => $new_title);
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
- $this->assertText(t('!name field is required.', array('!name' => t('Text format'))), t('Error message is displayed.'));
+ $this->assertText(t('!name field is required.', array('!name' => t('Text format'))), 'Error message is displayed.');
$this->drupalGet('node/' . $node->nid);
- $this->assertText($old_title, t('Old title found.'));
- $this->assertNoText($new_title, t('New title not found.'));
+ $this->assertText($old_title, 'Old title found.');
+ $this->assertNoText($new_title, 'New title not found.');
$edit[$body_format_key] = filter_fallback_format();
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
$this->assertUrl('node/' . $node->nid);
- $this->assertText($new_title, t('New title found.'));
- $this->assertNoText($old_title, t('Old title not found.'));
+ $this->assertText($new_title, 'New title found.');
+ $this->assertNoText($old_title, 'Old title not found.');
}
/**
- * Rebuild text format and permission caches in the thread running the tests.
+ * Rebuilds text format and permission caches in the thread running the tests.
*/
protected function resetFilterCaches() {
filter_formats_reset();
@@ -658,6 +732,9 @@
}
}
+/**
+ * Tests the default filter functionality in the Filter module.
+ */
class FilterDefaultFormatTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
@@ -667,6 +744,9 @@
);
}
+ /**
+ * Tests if the default text format is accessible to users.
+ */
function testDefaultTextFormats() {
// Create two text formats, and two users. The first user has access to
// both formats, but the second user only has access to the second one.
@@ -697,8 +777,8 @@
// Check that each user's default format is the lowest weighted format that
// the user has access to.
- $this->assertEqual(filter_default_format($first_user), $first_format->format, t("The first user's default format is the lowest weighted format that the user has access to."));
- $this->assertEqual(filter_default_format($second_user), $second_format->format, t("The second user's default format is the lowest weighted format that the user has access to, and is different than the first user's."));
+ $this->assertEqual(filter_default_format($first_user), $first_format->format, "The first user's default format is the lowest weighted format that the user has access to.");
+ $this->assertEqual(filter_default_format($second_user), $second_format->format, "The second user's default format is the lowest weighted format that the user has access to, and is different than the first user's.");
// Reorder the two formats, and check that both users now have the same
// default.
@@ -706,11 +786,11 @@
$edit['formats[' . $second_format->format . '][weight]'] = $minimum_weight - 3;
$this->drupalPost('admin/config/content/formats', $edit, t('Save changes'));
$this->resetFilterCaches();
- $this->assertEqual(filter_default_format($first_user), filter_default_format($second_user), t('After the formats are reordered, both users have the same default format.'));
+ $this->assertEqual(filter_default_format($first_user), filter_default_format($second_user), 'After the formats are reordered, both users have the same default format.');
}
/**
- * Rebuild text format and permission caches in the thread running the tests.
+ * Rebuilds text format and permission caches in the thread running the tests.
*/
protected function resetFilterCaches() {
filter_formats_reset();
@@ -718,6 +798,9 @@
}
}
+/**
+ * Tests the behavior of check_markup() when it is called without text format.
+ */
class FilterNoFormatTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
@@ -727,6 +810,12 @@
);
}
+ /**
+ * Tests text without format.
+ *
+ * Tests if text with no format is filtered the same way as text in the
+ * fallback format.
+ */
function testCheckMarkupNoFormat() {
// Create some text. Include some HTML and line breaks, so we get a good
// test of the filtering that is applied to it.
@@ -734,7 +823,7 @@
// Make sure that when this text is run through check_markup() with no text
// format, it is filtered as though it is in the fallback format.
- $this->assertEqual(check_markup($text), check_markup($text, filter_fallback_format()), t('Text with no format is filtered the same as text in the fallback format.'));
+ $this->assertEqual(check_markup($text), check_markup($text, filter_fallback_format()), 'Text with no format is filtered the same as text in the fallback format.');
}
}
@@ -757,7 +846,10 @@
}
/**
- * Test that filtered content is emptied when an actively used filter module is disabled.
+ * Tests removal of filtered content when an active filter is disabled.
+ *
+ * Tests that filtered content is emptied when an actively used filter module
+ * is disabled.
*/
function testDisableFilterModule() {
// Create a new node.
@@ -765,7 +857,7 @@
$body_raw = $node->body[LANGUAGE_NONE][0]['value'];
$format_id = $node->body[LANGUAGE_NONE][0]['format'];
$this->drupalGet('node/' . $node->nid);
- $this->assertText($body_raw, t('Node body found.'));
+ $this->assertText($body_raw, 'Node body found.');
// Enable the filter_test_replace filter.
$edit = array(
@@ -775,15 +867,15 @@
// Verify that filter_test_replace filter replaced the content.
$this->drupalGet('node/' . $node->nid);
- $this->assertNoText($body_raw, t('Node body not found.'));
- $this->assertText('Filter: Testing filter', t('Testing filter output found.'));
+ $this->assertNoText($body_raw, 'Node body not found.');
+ $this->assertText('Filter: Testing filter', 'Testing filter output found.');
// Disable the text format entirely.
$this->drupalPost('admin/config/content/formats/' . $format_id . '/disable', array(), t('Disable'));
// Verify that the content is empty, because the text format does not exist.
$this->drupalGet('node/' . $node->nid);
- $this->assertNoText($body_raw, t('Node body not found.'));
+ $this->assertNoText($body_raw, 'Node body not found.');
}
}
@@ -800,7 +892,7 @@
}
/**
- * Test the line break filter.
+ * Tests the line break filter.
*/
function testLineBreakFilter() {
// Setup dummy filter object.
@@ -870,7 +962,7 @@
$limit = max(ini_get('pcre.backtrack_limit'), ini_get('pcre.recursion_limit'));
$source = $this->randomName($limit);
$result = _filter_autop($source);
- $success = $this->assertEqual($result, '
' . $source . "
\n", t('Line break filter can process very long strings.'));
+ $success = $this->assertEqual($result, '' . $source . "
\n", 'Line break filter can process very long strings.');
if (!$success) {
$this->verbose("\n" . $source . "\n
\n" . $result);
}
@@ -891,176 +983,180 @@
function testFilterXSS() {
// Tag stripping, different ways to work around removal of HTML tags.
$f = filter_xss('');
- $this->assertNoNormalized($f, 'script', t('HTML tag stripping -- simple script without special characters.'));
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping -- simple script without special characters.');
$f = filter_xss('');
- $this->assertNoNormalized($f, 'script', t('HTML tag stripping -- empty script with source.'));
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping -- empty script with source.');
$f = filter_xss('');
- $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- non whitespace character after tag name.'));
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- non whitespace character after tag name.');
$f = filter_xss('');
- $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no space between tag and attribute.'));
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- no space between tag and attribute.');
// Null between < and tag name works at least with IE6.
$f = filter_xss("<\0scr\0ipt>alert(0)");
- $this->assertNoNormalized($f, 'ipt', t('HTML tag stripping evasion -- breaking HTML with nulls.'));
+ $this->assertNoNormalized($f, 'ipt', 'HTML tag stripping evasion -- breaking HTML with nulls.');
$f = filter_xss("");
- $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- filter just removing "script".'));
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- filter just removing "script".');
$f = filter_xss('<');
- $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- double opening brackets.'));
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- double opening brackets.');
$f = filter_xss('', array('img'));
- $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- a malformed image tag.'));
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- a malformed image tag.');
$f = filter_xss('', array('blockquote'));
- $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- script in a blockqoute.'));
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- script in a blockqoute.');
$f = filter_xss("");
- $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- script within a comment.'));
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- script within a comment.');
// Dangerous attributes removal.
$f = filter_xss('', array('p'));
- $this->assertNoNormalized($f, 'onmouseover', t('HTML filter attributes removal -- events, no evasion.'));
+ $this->assertNoNormalized($f, 'onmouseover', 'HTML filter attributes removal -- events, no evasion.');
$f = filter_xss('
- ', array('li'));
- $this->assertNoNormalized($f, 'style', t('HTML filter attributes removal -- style, no evasion.'));
+ $this->assertNoNormalized($f, 'style', 'HTML filter attributes removal -- style, no evasion.');
$f = filter_xss('
', array('img'));
- $this->assertNoNormalized($f, 'onerror', t('HTML filter attributes removal evasion -- spaces before equals sign.'));
+ $this->assertNoNormalized($f, 'onerror', 'HTML filter attributes removal evasion -- spaces before equals sign.');
$f = filter_xss('
', array('img'));
- $this->assertNoNormalized($f, 'onabort', t('HTML filter attributes removal evasion -- non alphanumeric characters before equals sign.'));
+ $this->assertNoNormalized($f, 'onabort', 'HTML filter attributes removal evasion -- non alphanumeric characters before equals sign.');
$f = filter_xss('
', array('img'));
- $this->assertNoNormalized($f, 'onmediaerror', t('HTML filter attributes removal evasion -- varying case.'));
+ $this->assertNoNormalized($f, 'onmediaerror', 'HTML filter attributes removal evasion -- varying case.');
// Works at least with IE6.
$f = filter_xss("
", array('img'));
- $this->assertNoNormalized($f, 'focus', t('HTML filter attributes removal evasion -- breaking with nulls.'));
+ $this->assertNoNormalized($f, 'focus', 'HTML filter attributes removal evasion -- breaking with nulls.');
// Only whitelisted scheme names allowed in attributes.
$f = filter_xss('
', array('img'));
- $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- no evasion.'));
+ $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing -- no evasion.');
$f = filter_xss('
', array('img'));
- $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- no quotes.'));
+ $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- no quotes.');
// A bit like CVE-2006-0070.
$f = filter_xss('
', array('img'));
- $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- no alert ;)'));
+ $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- no alert ;)');
$f = filter_xss('
', array('img'));
- $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- grave accents.'));
+ $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- grave accents.');
$f = filter_xss('
', array('img'));
- $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- rare attribute.'));
+ $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing -- rare attribute.');
$f = filter_xss('