diff -Naur drupal-7.23/.editorconfig drupal-7.66/.editorconfig
--- drupal-7.23/.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.23/.htaccess drupal-7.66/.htaccess
--- drupal-7.23/.htaccess 2013-08-08 04:04:26.000000000 +0200
+++ 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
-----------------------
@@ -250,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.
@@ -299,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.
@@ -338,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
@@ -695,7 +1299,7 @@
requests.
Drupal 6.23-dev, xxxx-xx-xx (development release)
------------------------
+---------------------------
Drupal 6.22, 2011-05-25
-----------------------
@@ -705,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
@@ -732,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.
@@ -744,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
@@ -758,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
@@ -777,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.
@@ -1173,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
@@ -1286,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.23/COPYRIGHT.txt drupal-7.66/COPYRIGHT.txt
--- drupal-7.23/COPYRIGHT.txt 2013-08-08 04:04:26.000000000 +0200
+++ 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.23/INSTALL.mysql.txt drupal-7.66/INSTALL.mysql.txt
--- drupal-7.23/INSTALL.mysql.txt 2013-08-08 04:04:26.000000000 +0200
+++ 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.23/INSTALL.txt drupal-7.66/INSTALL.txt
--- drupal-7.23/INSTALL.txt 2013-08-08 04:04:26.000000000 +0200
+++ 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.23/MAINTAINERS.txt drupal-7.66/MAINTAINERS.txt
--- drupal-7.23/MAINTAINERS.txt 2013-08-08 04:04:26.000000000 +0200
+++ 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.23/README.txt drupal-7.66/README.txt
--- drupal-7.23/README.txt 2013-08-08 04:04:26.000000000 +0200
+++ 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.23/UPGRADE.txt drupal-7.66/UPGRADE.txt
--- drupal-7.23/UPGRADE.txt 2013-08-08 04:04:26.000000000 +0200
+++ 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.23/includes/ajax.inc drupal-7.66/includes/ajax.inc
--- drupal-7.23/includes/ajax.inc 2013-08-08 04:04:26.000000000 +0200
+++ 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
@@ -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.
@@ -1210,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.23/includes/batch.inc drupal-7.66/includes/batch.inc
--- drupal-7.23/includes/batch.inc 2013-08-08 04:04:26.000000000 +0200
+++ 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.23/includes/bootstrap.inc drupal-7.66/includes/bootstrap.inc
--- drupal-7.23/includes/bootstrap.inc 2013-08-08 04:04:26.000000000 +0200
+++ 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.23');
+define('VERSION', '7.66');
/**
* Core API compatibility.
@@ -244,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
@@ -278,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
@@ -520,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
@@ -683,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();
+ }
+ }
}
/**
@@ -699,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';
}
/**
@@ -715,7 +759,7 @@
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.
@@ -812,14 +856,21 @@
* @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 or NULL if the item is not found.
*/
-function drupal_get_filename($type, $name, $filename = NULL) {
+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') {
@@ -831,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');
}
}
@@ -1039,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.
@@ -1229,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()
*/
@@ -1258,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);
}
@@ -1278,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.
@@ -1328,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
@@ -1434,6 +1706,23 @@
* 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
@@ -1444,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.
@@ -1551,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
@@ -1641,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) {
@@ -1774,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();
}
@@ -1933,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
@@ -1945,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');
+ }
+
+ // openssl_random_pseudo_bytes() will find entropy in a system-dependent
+ // way.
+ if ($has_openssl) {
+ $bytes .= openssl_random_pseudo_bytes($missing_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')) {
+
+ // 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()
@@ -1984,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);
@@ -1997,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('+' => '-', '/' => '_', '=' => ''));
}
@@ -2108,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();
@@ -2127,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:
@@ -2140,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) {
@@ -2166,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) {
@@ -2312,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();
}
/**
@@ -2420,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');
+ }
}
/**
@@ -2437,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']);
+ }
+ }
}
/**
@@ -2459,7 +2824,7 @@
* @see drupal_bootstrap()
*/
function drupal_get_bootstrap_phase() {
- return drupal_bootstrap();
+ return drupal_bootstrap(NULL, FALSE);
}
/**
@@ -2573,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:
@@ -2715,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' => ''));
@@ -2870,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);
+ }
}
}
}
@@ -2888,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.
@@ -3044,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
@@ -3061,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;
}
@@ -3094,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];
}
@@ -3102,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.
@@ -3116,7 +3513,7 @@
$lookup_cache[$cache_key] = $file;
if ($file) {
- require_once DRUPAL_ROOT . '/' . $file;
+ include_once DRUPAL_ROOT . '/' . $file;
return TRUE;
}
else {
@@ -3253,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.
*
@@ -3279,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.
@@ -3328,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);
@@ -3401,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) {
@@ -3443,3 +3844,34 @@
// - 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.23/includes/cache.inc drupal-7.66/includes/cache.inc
--- drupal-7.23/includes/cache.inc 2013-08-08 04:04:26.000000000 +0200
+++ 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.
*
@@ -98,9 +99,11 @@
* @param $data
* The data to store in the cache. Complex data types will be automatically
* serialized before insertion. Strings will be stored as plain text and are
- * not serialized.
+ * not serialized. Some storage engines only allow objects up to a maximum of
+ * 1MB in size to be stored by default. When caching large arrays or similar,
+ * take care to ensure $data does not exceed this size.
* @param $bin
- * The cache bin to store the data in. Valid core values are:
+ * (optional) The cache bin to store the data in. Valid core values are:
* - cache: (default) Generic cache storage bin (used for theme registry,
* locale date, list of simpletest tests, etc.).
* - cache_block: Stores the content of various blocks.
@@ -119,7 +122,12 @@
* 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
@@ -254,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
diff -Naur drupal-7.23/includes/common.inc drupal-7.66/includes/common.inc
--- drupal-7.23/includes/common.inc 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/includes/common.inc 2019-04-17 22:20:46.000000000 +0200
@@ -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
- *
- * This is required, because url() does not support relative URLs containing a
- * query string or fragment in its $path argument. Instead, any query string
- * needs to be parsed into an associative query parameter array in
- * $options['query'] and the fragment into $options['fragment'].
+ * Note that, unlike the RFC, when passed an external URL, this function
+ * groups the scheme, authority, and path together into the path component.
*
- * @param $url
- * The URL string to parse, f.e. $_GET['destination'].
+ * @param string $url
+ * The internal path or external URL string to parse.
*
- * @return
- * An associative array containing the keys:
- * - 'path': The path of the URL. If the given $url is external, this includes
- * the scheme and host.
- * - 'query': An array of query parameters of $url, if existent.
- * - 'fragment': The fragment of $url, if existent.
+ * @return array
+ * An associative array containing:
+ * - path: The path component of $url. If $url is an external URL, this
+ * includes the scheme, authority, and path.
+ * - query: An array of query parameters from $url, if they exist.
+ * - fragment: The fragment component from $url, if it exists.
*
- * @see url()
* @see drupal_goto()
+ * @see l()
+ * @see url()
+ * @see http://tools.ietf.org/html/rfc3986
+ *
* @ingroup php_wrappers
*/
function drupal_parse_url($url) {
@@ -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']);
}
@@ -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,6 +787,8 @@
* 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
@@ -861,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':
@@ -872,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:
@@ -929,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
@@ -990,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();
@@ -1061,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
@@ -1075,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;
}
@@ -1083,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
@@ -1134,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') {
@@ -1426,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
@@ -1496,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 '';
}
@@ -1728,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 = '';
@@ -1743,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";
@@ -1950,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
@@ -2188,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.
@@ -2233,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.
@@ -2260,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, '/');
}
}
@@ -2310,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);
}
/**
@@ -2595,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?
@@ -2610,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', ''));
@@ -2639,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', ''));
@@ -2703,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();
}
@@ -2764,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
@@ -2785,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);
+ }
}
}
@@ -2966,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)) {
@@ -3001,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']) {
@@ -3448,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;
@@ -3673,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;
}
/**
@@ -3700,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.
@@ -3719,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*
@@ -3744,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;
}
@@ -3768,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);
}
/**
@@ -3804,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);
@@ -3833,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];
}
/**
@@ -3868,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
@@ -3913,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(' ' => '-', '_' => '-', '[' => '-', ']' => ''));
@@ -4097,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.23/modules/field/field.api.php drupal-7.66/modules/field/field.api.php
--- drupal-7.23/modules/field/field.api.php 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/field/field.api.php 2019-04-17 22:20:46.000000000 +0200
@@ -1,4 +1,8 @@
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.'),
+ );
+ }
+ }
+ }
+ }
}
/**
@@ -1510,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
@@ -1531,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.
@@ -1581,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.
@@ -1596,7 +1640,7 @@
}
/**
- * @} End of "defgroup field_attach".
+ * @} End of "addtogroup field_attach".
*/
/**
@@ -1853,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,
@@ -2257,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
@@ -2269,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();
@@ -2284,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
@@ -2350,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
@@ -2364,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') {
@@ -2393,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.
@@ -2404,10 +2468,6 @@
}
/**
- * @} End of "addtogroup field_storage".
- */
-
-/**
* @addtogroup field_crud
* @{
*/
@@ -2602,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);
@@ -2619,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')
@@ -2640,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);
@@ -2679,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.23/modules/field/field.attach.inc drupal-7.66/modules/field/field.attach.inc
--- drupal-7.23/modules/field/field.attach.inc 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/field/field.attach.inc 2019-04-17 22:20:46.000000000 +0200
@@ -318,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();
@@ -976,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
diff -Naur drupal-7.23/modules/field/field.crud.inc drupal-7.66/modules/field/field.crud.inc
--- drupal-7.23/modules/field/field.crud.inc 2013-08-08 04:04:26.000000000 +0200
+++ 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);
@@ -288,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);
@@ -430,7 +430,7 @@
->execute();
// Clear the cache.
- field_cache_clear(TRUE);
+ field_cache_clear();
module_invoke_all('field_delete_field', $field);
}
@@ -540,9 +540,9 @@
* // Fetch an instance info array.
* $instance_info = field_info_instance($entity_type, $field_name, $bundle_name);
* // Change a single property in the instance definition.
- * $instance_info['definition']['required'] = TRUE;
+ * $instance_info['required'] = TRUE;
* // Write the changed definition back.
- * field_update_instance($instance_info['definition']);
+ * field_update_instance($instance_info);
* @endcode
*
* @throws FieldException
diff -Naur drupal-7.23/modules/field/field.info drupal-7.66/modules/field/field.info
--- drupal-7.23/modules/field/field.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/field/field.info 2019-04-17 22:39:36.000000000 +0200
@@ -11,8 +11,7 @@
required = TRUE
stylesheets[all][] = theme/field.css
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/field/field.info.class.inc drupal-7.66/modules/field/field.info.class.inc
--- drupal-7.23/modules/field/field.info.class.inc 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/field/field.info.class.inc 2019-04-17 22:20:46.000000000 +0200
@@ -146,7 +146,10 @@
// Save in "static" and persistent caches.
$this->fieldMap = $map;
- cache_set('field_info:field_map', $map, 'cache_field');
+ if (lock_acquire('field_info:field_map')) {
+ cache_set('field_info:field_map', $map, 'cache_field');
+ lock_release('field_info:field_map');
+ }
return $map;
}
@@ -174,7 +177,10 @@
}
// Store in persistent cache.
- cache_set('field_info:fields', $this->fieldsById, 'cache_field');
+ if (lock_acquire('field_info:fields')) {
+ cache_set('field_info:fields', $this->fieldsById, 'cache_field');
+ lock_release('field_info:fields');
+ }
}
// Fill the name/ID map.
@@ -231,7 +237,10 @@
}
// Store in persistent cache.
- cache_set('field_info:instances', $this->bundleInstances, 'cache_field');
+ if (lock_acquire('field_info:instances')) {
+ cache_set('field_info:instances', $this->bundleInstances, 'cache_field');
+ lock_release('field_info:instances');
+ }
}
$this->loadedAllInstances = TRUE;
@@ -419,7 +428,11 @@
foreach ($instances as $instance) {
$cache['fields'][] = $this->fieldsById[$instance['field_id']];
}
- cache_set("field_info:bundle:$entity_type:$bundle", $cache, 'cache_field');
+
+ if (lock_acquire("field_info:bundle:$entity_type:$bundle")) {
+ cache_set("field_info:bundle:$entity_type:$bundle", $cache, 'cache_field');
+ lock_release("field_info:bundle:$entity_type:$bundle");
+ }
return $instances;
}
@@ -460,7 +473,10 @@
// Store in the 'static' and persistent caches.
$this->bundleExtraFields[$entity_type][$bundle] = $info;
- cache_set("field_info:bundle_extra:$entity_type:$bundle", $info, 'cache_field');
+ if (lock_acquire("field_info:bundle_extra:$entity_type:$bundle")) {
+ cache_set("field_info:bundle_extra:$entity_type:$bundle", $info, 'cache_field');
+ lock_release("field_info:bundle_extra:$entity_type:$bundle");
+ }
return $this->bundleExtraFields[$entity_type][$bundle];
}
@@ -596,10 +612,12 @@
// Fill in default values.
$display += array(
'label' => 'above',
- 'type' => $field_type_info['default_formatter'],
'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.
diff -Naur drupal-7.23/modules/field/field.info.inc drupal-7.66/modules/field/field.info.inc
--- drupal-7.23/modules/field/field.info.inc 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/field/field.info.inc 2019-04-17 22:20:46.000000000 +0200
@@ -223,7 +223,11 @@
}
drupal_alter('field_storage_info', $info['storage types']);
- cache_set("field_info_types:$langcode", $info, 'cache_field');
+ // Set the cache if we can acquire a lock.
+ if (lock_acquire("field_info_types:$langcode")) {
+ cache_set("field_info_types:$langcode", $info, 'cache_field');
+ lock_release("field_info_types:$langcode");
+ }
}
}
diff -Naur drupal-7.23/modules/field/field.install drupal-7.66/modules/field/field.install
--- drupal-7.23/modules/field/field.install 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/field/field.install 2019-04-17 22:20:46.000000000 +0200
@@ -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;
}
@@ -467,5 +468,26 @@
}
/**
+ * 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.23/modules/field/field.module drupal-7.66/modules/field/field.module
--- drupal-7.23/modules/field/field.module 2013-08-08 04:04:26.000000000 +0200
+++ 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.
@@ -905,6 +909,7 @@
'entity' => $entity,
'view_mode' => '_custom',
'display' => $display,
+ 'language' => $langcode,
);
drupal_alter('field_attach_view', $result, $context);
@@ -947,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'
diff -Naur drupal-7.23/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.23/modules/field/modules/field_sql_storage/field_sql_storage.info 2013-08-08 04:17:18.000000000 +0200
+++ 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 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/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.23/modules/field/modules/field_sql_storage/field_sql_storage.module 2013-08-08 04:04:26.000000000 +0200
+++ 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);
+ }
}
}
@@ -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);
@@ -422,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,
@@ -504,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.23/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.23/modules/field/modules/field_sql_storage/field_sql_storage.test 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/field/modules/field_sql_storage/field_sql_storage.test 2019-04-17 22:20:46.000000000 +0200
@@ -355,14 +355,14 @@
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"), 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"), format_string("Index on value_format created in %table", array('%table' => $table)));
@@ -438,4 +438,149 @@
$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.23/modules/field/modules/list/list.info drupal-7.66/modules/field/modules/list/list.info
--- drupal-7.23/modules/field/modules/list/list.info 2013-08-08 04:17:18.000000000 +0200
+++ 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 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/field/modules/list/list.install drupal-7.66/modules/field/modules/list/list.install
--- drupal-7.23/modules/field/modules/list/list.install 2013-08-08 04:04:26.000000000 +0200
+++ 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.23/modules/field/modules/list/tests/list.test drupal-7.66/modules/field/modules/list/tests/list.test
--- drupal-7.23/modules/field/modules/list/tests/list.test 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/field/modules/list/tests/list.test 2019-04-17 22:20:46.000000000 +0200
@@ -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.
diff -Naur drupal-7.23/modules/field/modules/list/tests/list_test.info drupal-7.66/modules/field/modules/list/tests/list_test.info
--- drupal-7.23/modules/field/modules/list/tests/list_test.info 2013-08-08 04:17:18.000000000 +0200
+++ 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 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/field/modules/number/number.info drupal-7.66/modules/field/modules/number/number.info
--- drupal-7.23/modules/field/modules/number/number.info 2013-08-08 04:17:18.000000000 +0200
+++ 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 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/field/modules/number/number.module drupal-7.66/modules/field/modules/number/number.module
--- drupal-7.23/modules/field/modules/number/number.module 2013-08-08 04:04:26.000000000 +0200
+++ 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.23/modules/field/modules/number/number.test drupal-7.66/modules/field/modules/number/number.test
--- drupal-7.23/modules/field/modules/number/number.test 2013-08-08 04:04:26.000000000 +0200
+++ 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);
}
@@ -69,7 +69,7 @@
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)), 'Entity was created');
- $this->assertRaw(round($value, 2), 'Value is displayed.');
+ $this->assertRaw($value, 'Value is displayed.');
// Try to create entries with more than one decimal separator; assert fail.
$wrong_entries = array(
@@ -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.23/modules/field/modules/options/options.info drupal-7.66/modules/field/modules/options/options.info
--- drupal-7.23/modules/field/modules/options/options.info 2013-08-08 04:17:18.000000000 +0200
+++ 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 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/field/modules/options/options.module drupal-7.66/modules/field/modules/options/options.module
--- drupal-7.23/modules/field/modules/options/options.module 2013-08-08 04:04:26.000000000 +0200
+++ 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.23/modules/field/modules/options/options.test drupal-7.66/modules/field/modules/options/options.test
--- drupal-7.23/modules/field/modules/options/options.test 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/field/modules/options/options.test 2019-04-17 22:20:46.000000000 +0200
@@ -23,8 +23,15 @@
'type' => '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);
}
@@ -233,6 +249,7 @@
$this->assertNoOptionSelected("edit-card-1-$langcode", 1);
$this->assertNoOptionSelected("edit-card-1-$langcode", 2);
$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');
@@ -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'));
@@ -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.
diff -Naur drupal-7.23/modules/field/modules/text/text.info drupal-7.66/modules/field/modules/text/text.info
--- drupal-7.23/modules/field/modules/text/text.info 2013-08-08 04:17:18.000000000 +0200
+++ 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 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/field/modules/text/text.js drupal-7.66/modules/field/modules/text/text.js
--- drupal-7.23/modules/field/modules/text/text.js 2013-08-08 04:04:26.000000000 +0200
+++ 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.23/modules/field/modules/text/text.module drupal-7.66/modules/field/modules/text/text.module
--- drupal-7.23/modules/field/modules/text/text.module 2013-08-08 04:04:26.000000000 +0200
+++ 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.23/modules/field/modules/text/text.test drupal-7.66/modules/field/modules/text/text.test
--- drupal-7.23/modules/field/modules/text/text.test 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/field/modules/text/text.test 2019-04-17 22:20:46.000000000 +0200
@@ -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'));
diff -Naur drupal-7.23/modules/field/tests/field.test drupal-7.66/modules/field/tests/field.test
--- drupal-7.23/modules/field/tests/field.test 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/field/tests/field.test 2019-04-17 22:20:46.000000000 +0200
@@ -485,6 +485,66 @@
}
/**
+ * Test field_has_data().
+ */
+ function testFieldHasData() {
+ $entity_type = 'test_entity';
+ $langcode = LANGUAGE_NONE;
+
+ $field_name = 'field_1';
+ $field = array('field_name' => $field_name, 'type' => 'test_field');
+ $field = field_create_field($field);
+
+ $this->assertFalse(field_has_data($field), "No data should be detected.");
+
+ $instance = array(
+ 'field_name' => $field_name,
+ 'entity_type' => 'test_entity',
+ 'bundle' => 'test_bundle'
+ );
+ $instance = field_create_instance($instance);
+ $table = _field_sql_storage_tablename($field);
+ $revision_table = _field_sql_storage_revision_tablename($field);
+
+ $columns = array('entity_type', 'entity_id', 'revision_id', 'delta', 'language', $field_name . '_value');
+
+ $eid = 0;
+
+ // Insert values into the field revision table.
+ $query = db_insert($revision_table)->fields($columns);
+ $query->values(array($entity_type, $eid, 0, 0, $langcode, 1));
+ $query->execute();
+
+ $this->assertTrue(field_has_data($field), "Revision data only should be detected.");
+
+ $field_name = 'field_2';
+ $field = array('field_name' => $field_name, 'type' => 'test_field');
+ $field = field_create_field($field);
+
+ $this->assertFalse(field_has_data($field), "No data should be detected.");
+
+ $instance = array(
+ 'field_name' => $field_name,
+ 'entity_type' => 'test_entity',
+ 'bundle' => 'test_bundle'
+ );
+ $instance = field_create_instance($instance);
+ $table = _field_sql_storage_tablename($field);
+ $revision_table = _field_sql_storage_revision_tablename($field);
+
+ $columns = array('entity_type', 'entity_id', 'revision_id', 'delta', 'language', $field_name . '_value');
+
+ $eid = 1;
+
+ // Insert values into the field table.
+ $query = db_insert($table)->fields($columns);
+ $query->values(array($entity_type, $eid, 0, 0, $langcode, 1));
+ $query->execute();
+
+ $this->assertTrue(field_has_data($field), "Values only in field table should be detected.");
+ }
+
+ /**
* Test field_attach_delete().
*/
function testFieldAttachDelete() {
@@ -2146,11 +2206,12 @@
'alter' => TRUE,
),
);
- $output = field_view_field('test_entity', $this->entity, $this->field_name, $display);
+ $output = field_view_field('test_entity', $this->entity, $this->field_name, $display, LANGUAGE_NONE);
$this->drupalSetContent(drupal_render($output));
$setting = $display['settings']['test_formatter_setting_multiple'];
$this->assertNoText($this->label, 'Label was not displayed.');
$this->assertText('field_test_field_attach_view_alter', 'Alter fired, display passed.');
+ $this->assertText('field language is ' . LANGUAGE_NONE, 'Language is placed onto the context.');
$array = array();
foreach ($this->values as $delta => $value) {
$array[] = $delta . ':' . $value['value'];
diff -Naur drupal-7.23/modules/field/tests/field_test.info drupal-7.66/modules/field/tests/field_test.info
--- drupal-7.23/modules/field/tests/field_test.info 2013-08-08 04:17:18.000000000 +0200
+++ 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 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/field/tests/field_test.install drupal-7.66/modules/field/tests/field_test.install
--- drupal-7.23/modules/field/tests/field_test.install 2013-08-08 04:04:26.000000000 +0200
+++ 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,
diff -Naur drupal-7.23/modules/field/tests/field_test.module drupal-7.66/modules/field/tests/field_test.module
--- drupal-7.23/modules/field/tests/field_test.module 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/field/tests/field_test.module 2019-04-17 22:20:46.000000000 +0200
@@ -220,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']);
+ }
}
/**
@@ -267,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.23/modules/field/theme/field.tpl.php drupal-7.66/modules/field/theme/field.tpl.php
--- drupal-7.23/modules/field/theme/field.tpl.php 2013-08-08 04:04:26.000000000 +0200
+++ 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 "";
}
diff -Naur drupal-7.23/modules/filter/filter.pages.inc drupal-7.66/modules/filter/filter.pages.inc
--- drupal-7.23/modules/filter/filter.pages.inc 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/filter/filter.pages.inc 2019-04-17 22:20:46.000000000 +0200
@@ -14,10 +14,9 @@
* @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));
@@ -68,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.23/modules/filter/filter.test drupal-7.66/modules/filter/filter.test
--- drupal-7.23/modules/filter/filter.test 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/filter/filter.test 2019-04-17 22:20:46.000000000 +0200
@@ -70,6 +70,15 @@
$this->assertFalse($db_format->status, 'Database: Disabled text format is marked as disabled.');
$formats = filter_formats();
$this->assertTrue(!isset($formats[$format->format]), 'filter_formats: Disabled text format no longer exists.');
+
+ // Add a new format to check for Xss in format name.
+ $format = new stdClass();
+ $format->format = 'xss_format';
+ $format->name = '';
+ 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.');
}
/**
@@ -546,6 +555,27 @@
$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);
}
/**
@@ -1090,8 +1120,12 @@
$f = filter_xss("", array('img'));
$this->assertNoNormalized($f, 'cript', 'HTML scheme clearing evasion -- embedded nulls.');
- $f = filter_xss('', array('img'));
- $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- spaces and metacharacters before scheme.');
+ // @todo This dataset currently fails under 5.4 because of
+ // https://www.drupal.org/node/1210798. Restore after it's fixed.
+ if (version_compare(PHP_VERSION, '5.4.0', '<')) {
+ $f = filter_xss('', array('img'));
+ $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- spaces and metacharacters before scheme.');
+ }
$f = filter_xss('', array('img'));
$this->assertNoNormalized($f, 'vbscript', 'HTML scheme clearing evasion -- another scheme.');
@@ -1139,7 +1173,7 @@
// Setup dummy filter object.
$filter = new stdClass();
$filter->settings = array(
- 'allowed_html' => ' -
-
- ',
+ 'allowed_html' => '
-
-
-
',
'filter_html_help' => 1,
'filter_html_nofollow' => 0,
);
@@ -1175,6 +1209,10 @@
$f = _filter_html('
', $filter);
$this->assertNoNormalized($f, 'onerror', 'HTML filter should remove empty on* attributes on default.');
+
+ // Custom tags are supported and should be allowed through.
+ $f = _filter_html(' ', $filter);
+ $this->assertNormalized($f, 'test-element', 'HTML filter should allow custom elements.');
}
/**
@@ -1260,6 +1298,7 @@
// Create a e-mail that is too long.
$long_email = str_repeat('a', 254) . '@example.com';
$too_long_email = str_repeat('b', 255) . '@example.com';
+ $email_with_plus_sign = 'one+two@example.com';
// Filter selection/pattern matching.
@@ -1273,12 +1312,13 @@
),
// MAILTO URLs.
'
-person@example.com or mailto:person2@example.com or ' . $long_email . ' but not ' . $too_long_email . '
+person@example.com or mailto:person2@example.com or ' . $email_with_plus_sign . ' or ' . $long_email . ' but not ' . $too_long_email . '
' => array(
'person@example.com' => TRUE,
'mailto:person2@example.com' => TRUE,
'' . $long_email . '' => TRUE,
'' . $too_long_email . '' => FALSE,
+ '' . $email_with_plus_sign . '' => TRUE,
),
// URI parts and special characters.
'
@@ -1970,3 +2010,26 @@
}
}
}
+
+/**
+ * Tests DOMDocument serialization.
+ */
+class FilterDOMSerializeTestCase extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Serialization',
+ 'description' => 'Test serialization of DOMDocument objects.',
+ 'group' => 'Filter',
+ );
+ }
+
+ /**
+ * Tests empty DOMDocument object.
+ */
+ function testFilterEmptyDOMSerialization() {
+ $document = new DOMDocument();
+ $result = filter_dom_serialize($document);
+ $this->assertEqual('', $result);
+ }
+}
diff -Naur drupal-7.23/modules/forum/forum.info drupal-7.66/modules/forum/forum.info
--- drupal-7.23/modules/forum/forum.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/forum/forum.info 2019-04-17 22:39:36.000000000 +0200
@@ -9,8 +9,7 @@
configure = admin/structure/forum
stylesheets[all][] = forum.css
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/forum/forum.module drupal-7.66/modules/forum/forum.module
--- drupal-7.23/modules/forum/forum.module 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/forum/forum.module 2019-04-17 22:20:46.000000000 +0200
@@ -263,10 +263,10 @@
* Implements hook_node_view().
*/
function forum_node_view($node, $view_mode) {
- $vid = variable_get('forum_nav_vocabulary', 0);
- $vocabulary = taxonomy_vocabulary_load($vid);
if (_forum_node_check_node_type($node)) {
if ($view_mode == 'full' && node_is_page($node)) {
+ $vid = variable_get('forum_nav_vocabulary', 0);
+ $vocabulary = taxonomy_vocabulary_load($vid);
// Breadcrumb navigation
$breadcrumb[] = l(t('Home'), NULL);
$breadcrumb[] = l($vocabulary->name, 'forum');
diff -Naur drupal-7.23/modules/help/help.api.php drupal-7.66/modules/help/help.api.php
--- drupal-7.23/modules/help/help.api.php 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/help/help.api.php 1970-01-01 01:00:00.000000000 +0100
@@ -1,63 +0,0 @@
-' . t('Blocks are boxes of content rendered into an area, or region, of a web page. The default theme Bartik, for example, implements the regions "Sidebar first", "Sidebar second", "Featured", "Content", "Header", "Footer", etc., and a block may appear in any one of these areas. The blocks administration page provides a drag-and-drop interface for assigning a block to a region, and for controlling the order of blocks within regions.', array('@blocks' => url('admin/structure/block'))) . '';
-
- // Help for another path in the block module
- case 'admin/structure/block':
- return '' . t('This page provides a drag-and-drop interface for assigning a block to a region, and for controlling the order of blocks within regions. Since not all themes implement the same regions, or display regions in the same way, blocks are positioned on a per-theme basis. Remember that your changes will not be saved until you click the Save blocks button at the bottom of the page.') . '
';
- }
-}
-
-/**
- * @} End of "addtogroup hooks".
- */
diff -Naur drupal-7.23/modules/help/help.info drupal-7.66/modules/help/help.info
--- drupal-7.23/modules/help/help.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/help/help.info 2019-04-17 22:39:36.000000000 +0200
@@ -5,8 +5,7 @@
core = 7.x
files[] = help.test
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/image/image.admin.inc drupal-7.66/modules/image/image.admin.inc
--- drupal-7.23/modules/image/image.admin.inc 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/image/image.admin.inc 2019-04-17 22:20:46.000000000 +0200
@@ -592,15 +592,15 @@
'#type' => 'radios',
'#title' => t('Anchor'),
'#options' => array(
- 'left-top' => t('Top') . ' ' . t('Left'),
- 'center-top' => t('Top') . ' ' . t('Center'),
- 'right-top' => t('Top') . ' ' . t('Right'),
- 'left-center' => t('Center') . ' ' . t('Left'),
+ 'left-top' => t('Top left'),
+ 'center-top' => t('Top center'),
+ 'right-top' => t('Top right'),
+ 'left-center' => t('Center left'),
'center-center' => t('Center'),
- 'right-center' => t('Center') . ' ' . t('Right'),
- 'left-bottom' => t('Bottom') . ' ' . t('Left'),
- 'center-bottom' => t('Bottom') . ' ' . t('Center'),
- 'right-bottom' => t('Bottom') . ' ' . t('Right'),
+ 'right-center' => t('Center right'),
+ 'left-bottom' => t('Bottom left'),
+ 'center-bottom' => t('Bottom center'),
+ 'right-bottom' => t('Bottom right'),
),
'#theme' => 'image_anchor',
'#default_value' => $data['anchor'],
@@ -736,7 +736,8 @@
if (!isset($form[$key]['#access']) || $form[$key]['#access']) {
$rows[] = array(
'data' => $row,
- 'class' => !empty($form[$key]['weight']['#access']) || $key == 'new' ? array('draggable') : array(),
+ // Use a strict (===) comparison since $key can be 0.
+ 'class' => !empty($form[$key]['weight']['#access']) || $key === 'new' ? array('draggable') : array(),
);
}
}
diff -Naur drupal-7.23/modules/image/image.field.inc drupal-7.66/modules/image/image.field.inc
--- drupal-7.23/modules/image/image.field.inc 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/image/image.field.inc 2019-04-17 22:20:46.000000000 +0200
@@ -351,7 +351,7 @@
if ($field['cardinality'] == 1) {
// If there's only one field, return it as delta 0.
if (empty($elements[0]['#default_value']['fid'])) {
- $elements[0]['#description'] = theme('file_upload_help', array('description' => $instance['description'], 'upload_validators' => $elements[0]['#upload_validators']));
+ $elements[0]['#description'] = theme('file_upload_help', array('description' => field_filter_xss($instance['description']), 'upload_validators' => $elements[0]['#upload_validators']));
}
}
else {
diff -Naur drupal-7.23/modules/image/image.info drupal-7.66/modules/image/image.info
--- drupal-7.23/modules/image/image.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/image/image.info 2019-04-17 22:39:36.000000000 +0200
@@ -7,8 +7,7 @@
files[] = image.test
configure = admin/config/media/image-styles
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/image/image.module drupal-7.66/modules/image/image.module
--- drupal-7.23/modules/image/image.module 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/image/image.module 2019-04-17 22:20:46.000000000 +0200
@@ -64,7 +64,7 @@
$effect = image_effect_definition_load($arg[7]);
return isset($effect['help']) ? ('' . $effect['help'] . '
') : NULL;
case 'admin/config/media/image-styles/edit/%/effects/%':
- $effect = ($arg[5] == 'add') ? image_effect_definition_load($arg[6]) : image_effect_load($arg[6], $arg[4]);
+ $effect = ($arg[5] == 'add') ? image_effect_definition_load($arg[6]) : image_effect_load($arg[7], $arg[5]);
return isset($effect['help']) ? ('' . $effect['help'] . '
') : NULL;
}
}
@@ -801,6 +801,8 @@
*
* @param $style
* The image style
+ * @param $scheme
+ * The file scheme, for example 'public' for public files.
*/
function image_style_deliver($style, $scheme) {
$args = func_get_args();
@@ -833,9 +835,9 @@
file_download($scheme, file_uri_target($derivative_uri));
}
else {
- $headers = module_invoke_all('file_download', $image_uri);
- if (in_array(-1, $headers) || empty($headers)) {
- return drupal_access_denied();
+ $headers = file_download_headers($image_uri);
+ if (empty($headers)) {
+ return MENU_ACCESS_DENIED;
}
if (count($headers)) {
foreach ($headers as $name => $value) {
@@ -845,6 +847,12 @@
}
}
+ // Confirm that the original source image exists before trying to process it.
+ if (!is_file($image_uri)) {
+ watchdog('image', 'Source image at %source_image_path not found while trying to generate derivative image at %derivative_path.', array('%source_image_path' => $image_uri, '%derivative_path' => $derivative_uri));
+ return MENU_NOT_FOUND;
+ }
+
// Don't start generating the image if the derivative already exists or if
// generation is in progress in another thread.
$lock_name = 'image_style_deliver:' . $style['name'] . ':' . drupal_hash_base64($image_uri);
@@ -854,6 +862,7 @@
// Tell client to retry again in 3 seconds. Currently no browsers are known
// to support Retry-After.
drupal_add_http_header('Status', '503 Service Unavailable');
+ drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
drupal_add_http_header('Retry-After', 3);
print t('Image generation in progress. Try again shortly.');
drupal_exit();
@@ -875,6 +884,7 @@
else {
watchdog('image', 'Unable to generate the derived image located at %path.', array('%path' => $derivative_uri));
drupal_add_http_header('Status', '500 Internal Server Error');
+ drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
print t('Error generating image.');
drupal_exit();
}
@@ -972,7 +982,9 @@
// Delete the style directory in each registered wrapper.
$wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE);
foreach ($wrappers as $wrapper => $wrapper_data) {
- file_unmanaged_delete_recursive($wrapper . '://styles/' . $style['name']);
+ if (file_exists($directory = $wrapper . '://styles/' . $style['name'])) {
+ file_unmanaged_delete_recursive($directory);
+ }
}
// Let other modules update as necessary on flush.
@@ -1010,10 +1022,22 @@
*/
function image_style_url($style_name, $path) {
$uri = image_style_path($style_name, $path);
+
+ // The passed-in $path variable can be either a relative path or a full URI.
+ $original_uri = file_uri_scheme($path) ? file_stream_wrapper_uri_normalize($path) : file_build_uri($path);
+
// The token query is added even if the 'image_allow_insecure_derivatives'
// variable is TRUE, so that the emitted links remain valid if it is changed
// back to the default FALSE.
- $token_query = array(IMAGE_DERIVATIVE_TOKEN => image_style_path_token($style_name, file_stream_wrapper_uri_normalize($path)));
+ // However, sites which need to prevent the token query from being emitted at
+ // all can additionally set the 'image_suppress_itok_output' variable to TRUE
+ // to achieve that (if both are set, the security token will neither be
+ // emitted in the image derivative URL nor checked for in
+ // image_style_deliver()).
+ $token_query = array();
+ if (!variable_get('image_suppress_itok_output', FALSE)) {
+ $token_query = array(IMAGE_DERIVATIVE_TOKEN => image_style_path_token($style_name, $original_uri));
+ }
// If not using clean URLs, the image derivative callback is only available
// with the query string. If the file does not exist, use url() to ensure
@@ -1025,8 +1049,12 @@
}
$file_url = file_create_url($uri);
- // Append the query string with the token.
- return $file_url . (strpos($file_url, '?') !== FALSE ? '&' : '?') . drupal_http_build_query($token_query);
+ // Append the query string with the token, if necessary.
+ if ($token_query) {
+ $file_url .= (strpos($file_url, '?') !== FALSE ? '&' : '?') . drupal_http_build_query($token_query);
+ }
+
+ return $file_url;
}
/**
diff -Naur drupal-7.23/modules/image/image.test drupal-7.66/modules/image/image.test
--- drupal-7.23/modules/image/image.test 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/image/image.test 2019-04-17 22:20:46.000000000 +0200
@@ -32,7 +32,7 @@
function setUp() {
parent::setUp('image');
- $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer content types', 'administer nodes', 'create article content', 'edit any article content', 'delete any article content', 'administer image styles'));
+ $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer content types', 'administer nodes', 'create article content', 'edit any article content', 'delete any article content', 'administer image styles', 'administer fields'));
$this->drupalLogin($this->admin_user);
}
@@ -78,6 +78,24 @@
}
/**
+ * Create a random style.
+ *
+ * @return array
+ * A list containing the details of the generated image style.
+ */
+ function createRandomStyle() {
+ $style_name = strtolower($this->randomName(10));
+ $style_label = $this->randomString();
+ image_style_save(array('name' => $style_name, 'label' => $style_label));
+ $style_path = 'admin/config/media/image-styles/edit/' . $style_name;
+ return array(
+ 'name' => $style_name,
+ 'label' => $style_label,
+ 'path' => $style_path,
+ );
+ }
+
+ /**
* Upload an image to a node.
*
* @param $image
@@ -174,6 +192,32 @@
}
/**
+ * Test that an invalid source image returns a 404.
+ */
+ function testImageStyleUrlForMissingSourceImage() {
+ $non_existent_uri = 'public://foo.png';
+ $generated_url = image_style_url($this->style_name, $non_existent_uri);
+ $this->drupalGet($generated_url);
+ $this->assertResponse(404, 'Accessing an image style URL with a source image that does not exist provides a 404 error response.');
+ }
+
+ /**
+ * Test that we do not pass an array to drupal_add_http_header.
+ */
+ function testImageContentTypeHeaders() {
+ $files = $this->drupalGetTestFiles('image');
+ $file = array_shift($files);
+ // Copy the test file to private folder.
+ $private_file = file_copy($file, 'private://', FILE_EXISTS_RENAME);
+ // Tell image_module_test module to return the headers we want to test.
+ variable_set('image_module_test_invalid_headers', $private_file->uri);
+ // Invoke image_style_deliver so it will try to set headers.
+ $generated_url = image_style_url($this->style_name, $private_file->uri);
+ $this->drupalGet($generated_url);
+ variable_del('image_module_test_invalid_headers');
+ }
+
+ /**
* Test image_style_url().
*/
function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE, $extra_slash = FALSE) {
@@ -216,10 +260,20 @@
}
// Add some extra chars to the token.
$this->drupalGet(str_replace(IMAGE_DERIVATIVE_TOKEN . '=', IMAGE_DERIVATIVE_TOKEN . '=Zo', $generate_url));
- $this->assertResponse(403, 'Image was inaccessible at the URL wih an invalid token.');
+ $this->assertResponse(403, 'Image was inaccessible at the URL with an invalid token.');
// Change the parameter name so the token is missing.
$this->drupalGet(str_replace(IMAGE_DERIVATIVE_TOKEN . '=', 'wrongparam=', $generate_url));
- $this->assertResponse(403, 'Image was inaccessible at the URL wih a missing token.');
+ $this->assertResponse(403, 'Image was inaccessible at the URL with a missing token.');
+
+ // Check that the generated URL is the same when we pass in a relative path
+ // rather than a URI. We need to temporarily switch the default scheme to
+ // match the desired scheme before testing this, then switch it back to the
+ // "temporary" scheme used throughout this test afterwards.
+ variable_set('file_default_scheme', $scheme);
+ $relative_path = file_uri_target($original_uri);
+ $generate_url_from_relative_path = image_style_url($this->style_name, $relative_path);
+ $this->assertEqual($generate_url, $generate_url_from_relative_path, 'Generated URL is the same regardless of whether it came from a relative path or a file URI.');
+ variable_set('file_default_scheme', 'temporary');
// Fetch the URL that generates the file.
$this->drupalGet($generate_url);
@@ -231,7 +285,7 @@
$this->assertEqual($this->drupalGetHeader('Content-Length'), $generated_image_info['file_size'], 'Expected Content-Length was reported.');
if ($scheme == 'private') {
$this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.');
- $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate, post-check=0, pre-check=0', 'Cache-Control header was set to prevent caching.');
+ $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate', 'Cache-Control header was set to prevent caching.');
$this->assertEqual($this->drupalGetHeader('X-Image-Owned-By'), 'image_module_test', 'Expected custom header has been added.');
// Make sure that a second request to the already existing derivate works
@@ -268,7 +322,7 @@
elseif ($clean_url) {
// Add some extra chars to the token.
$this->drupalGet(str_replace(IMAGE_DERIVATIVE_TOKEN . '=', IMAGE_DERIVATIVE_TOKEN . '=Zo', $generate_url));
- $this->assertResponse(200, 'Existing image was accessible at the URL wih an invalid token.');
+ $this->assertResponse(200, 'Existing image was accessible at the URL with an invalid token.');
}
// Allow insecure image derivatives to be created for the remainder of this
@@ -310,6 +364,15 @@
$this->drupalGet($nested_url);
$this->assertResponse(200, 'Image was accessible when a correct token was provided in the URL.');
+ // Suppress the security token in the URL, then get the URL of a file. Check
+ // that the security token is not present in the URL but that the image is
+ // still accessible.
+ variable_set('image_suppress_itok_output', TRUE);
+ $generate_url = image_style_url($this->style_name, $original_uri);
+ $this->assertIdentical(strpos($generate_url, IMAGE_DERIVATIVE_TOKEN . '='), FALSE, 'The security token does not appear in the image style URL.');
+ $this->drupalGet($generate_url);
+ $this->assertResponse(200, 'Image was accessible at the URL with a missing token.');
+
// Check that requesting a nonexistent image does not create any new
// directories in the file system.
$directory = $scheme . '://styles/' . $this->style_name . '/' . $scheme . '/' . $this->randomName();
@@ -441,6 +504,58 @@
}
/**
+ * Tests the administrative user interface.
+ */
+class ImageAdminUiTestCase extends ImageFieldTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Administrative user interface',
+ 'description' => 'Tests the forms used in the administrative user interface.',
+ 'group' => 'Image',
+ );
+ }
+
+ function setUp() {
+ parent::setUp(array('image'));
+ }
+
+ /**
+ * Test if the help text is available on the add effect form.
+ */
+ function testAddEffectHelpText() {
+ // Create a random image style.
+ $style = $this->createRandomStyle();
+
+ // Open the add effect form and check for the help text.
+ $this->drupalGet($style['path'] . '/add/image_crop');
+ $this->assertText(t('Cropping will remove portions of an image to make it the specified dimensions.'), 'The image style effect help text was displayed on the add effect page.');
+ }
+
+ /**
+ * Test if the help text is available on the edit effect form.
+ */
+ function testEditEffectHelpText() {
+ // Create a random image style.
+ $random_style = $this->createRandomStyle();
+
+ // Add the crop effect to the image style.
+ $edit = array();
+ $edit['data[width]'] = 20;
+ $edit['data[height]'] = 20;
+ $this->drupalPost($random_style['path'] . '/add/image_crop', $edit, t('Add effect'));
+
+ // Open the edit effect form and check for the help text.
+ drupal_static_reset('image_styles');
+ $style = image_style_load($random_style['name']);
+
+ foreach ($style['effects'] as $ieid => $effect) {
+ $this->drupalGet($random_style['path'] . '/effects/' . $ieid);
+ $this->assertText(t('Cropping will remove portions of an image to make it the specified dimensions.'), 'The image style effect help text was displayed on the edit effect page.');
+ }
+ }
+}
+
+/**
* Tests creation, deletion, and editing of image styles and effects.
*/
class ImageAdminStylesUnitTest extends ImageFieldTestCase {
diff -Naur drupal-7.23/modules/image/tests/image_module_test.info drupal-7.66/modules/image/tests/image_module_test.info
--- drupal-7.23/modules/image/tests/image_module_test.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/image/tests/image_module_test.info 2019-04-17 22:39:36.000000000 +0200
@@ -6,8 +6,7 @@
files[] = image_module_test.module
hidden = TRUE
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/image/tests/image_module_test.module drupal-7.66/modules/image/tests/image_module_test.module
--- drupal-7.23/modules/image/tests/image_module_test.module 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/image/tests/image_module_test.module 2019-04-17 22:20:46.000000000 +0200
@@ -9,6 +9,9 @@
if (variable_get('image_module_test_file_download', FALSE) == $uri) {
return array('X-Image-Owned-By' => 'image_module_test');
}
+ if (variable_get('image_module_test_invalid_headers', FALSE) == $uri) {
+ return array('Content-Type' => 'image/png');
+ }
}
/**
diff -Naur drupal-7.23/modules/locale/locale.admin.inc drupal-7.66/modules/locale/locale.admin.inc
--- drupal-7.23/modules/locale/locale.admin.inc 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/locale/locale.admin.inc 2019-04-17 22:20:46.000000000 +0200
@@ -1139,11 +1139,11 @@
'#value' => $source->location
);
- // Include default form controls with empty values for all languages.
- // This ensures that the languages are always in the same order in forms.
+ // Include both translated and not yet translated target languages in the
+ // list. The source language is English for built-in strings and the default
+ // language for other strings.
$languages = language_list();
$default = language_default();
- // We don't need the default language value, that value is in $source.
$omit = $source->textgroup == 'default' ? 'en' : $default->language;
unset($languages[($omit)]);
$form['translations'] = array('#tree' => TRUE);
@@ -1194,7 +1194,7 @@
$translation = db_query("SELECT translation FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $key))->fetchField();
if (!empty($value)) {
// Only update or insert if we have a value to use.
- if (!empty($translation)) {
+ if (is_string($translation)) {
db_update('locales_target')
->fields(array(
'translation' => $value,
@@ -1242,9 +1242,7 @@
if ($source = db_query('SELECT lid, source FROM {locales_source} WHERE lid = :lid', array(':lid' => $lid))->fetchObject()) {
return drupal_get_form('locale_translate_delete_form', $source);
}
- else {
- return drupal_not_found();
- }
+ return MENU_NOT_FOUND;
}
/**
diff -Naur drupal-7.23/modules/locale/locale.info drupal-7.66/modules/locale/locale.info
--- drupal-7.23/modules/locale/locale.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/locale/locale.info 2019-04-17 22:39:36.000000000 +0200
@@ -6,8 +6,7 @@
files[] = locale.test
configure = admin/config/regional/language
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/locale/locale.test drupal-7.66/modules/locale/locale.test
--- drupal-7.23/modules/locale/locale.test 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/locale/locale.test 2019-04-17 22:20:46.000000000 +0200
@@ -393,6 +393,16 @@
// The indicator should not be here.
$this->assertNoRaw($language_indicator, 'String is translated.');
+ // Verify that a translation set which has an empty target string can be
+ // updated without any database error.
+ db_update('locales_target')
+ ->fields(array('translation' => ''))
+ ->condition('language', $langcode, '=')
+ ->condition('lid', $lid, '=')
+ ->execute();
+ $this->drupalPost('admin/config/regional/translate/edit/' . $lid, $edit, t('Save translations'));
+ $this->assertText(t('The string has been saved.'), 'The string has been saved.');
+
// Try to edit a non-existent string and ensure we're redirected correctly.
// Assuming we don't have 999,999 strings already.
$random_lid = 999999;
@@ -809,7 +819,7 @@
* Additional options to pass to the translation import form.
*/
function importPoFile($contents, array $options = array()) {
- $name = tempnam('temporary://', "po_") . '.po';
+ $name = drupal_tempnam('temporary://', "po_") . '.po';
file_put_contents($name, $contents);
$options['files[file]'] = $name;
$this->drupalPost('admin/config/regional/translate/import', $options, t('Import'));
@@ -1103,7 +1113,7 @@
* Additional options to pass to the translation import form.
*/
function importPoFile($contents, array $options = array()) {
- $name = tempnam('temporary://', "po_") . '.po';
+ $name = drupal_tempnam('temporary://', "po_") . '.po';
file_put_contents($name, $contents);
$options['files[file]'] = $name;
$this->drupalPost('admin/config/regional/translate/import', $options, t('Import'));
@@ -1202,7 +1212,7 @@
* Helper function that returns a .po file with context.
*/
function getPoFileWithContext() {
- // Croatian (code hr) is one the the languages that have a different
+ // Croatian (code hr) is one of the languages that have a different
// form for the full name and the abbreviated name for the month May.
return <<< EOF
msgid ""
@@ -1330,7 +1340,7 @@
function testExportTranslation() {
// First import some known translations.
// This will also automatically enable the 'fr' language.
- $name = tempnam('temporary://', "po_") . '.po';
+ $name = drupal_tempnam('temporary://', "po_") . '.po';
file_put_contents($name, $this->getPoFile());
$this->drupalPost('admin/config/regional/translate/import', array(
'langcode' => 'fr',
@@ -1685,7 +1695,7 @@
);
$test_cases = array(
- // Equal qvalue for each language, choose the site prefered one.
+ // Equal qvalue for each language, choose the site preferred one.
'en,en-US,fr-CA,fr,es-MX' => 'en',
'en-US,en,fr-CA,fr,es-MX' => 'en',
'fr,en' => 'en',
@@ -2237,6 +2247,37 @@
$this->drupalLogout();
}
+
+ /**
+ * Verifies that nodes may be created with different languages.
+ */
+ function testNodeCreationWithLanguage() {
+ // Create an admin user and log them in.
+ $perms = array(
+ // Standard node permissions.
+ 'create page content',
+ 'administer content types',
+ 'administer nodes',
+ 'bypass node access',
+ // Locale.
+ 'administer languages',
+ );
+ $web_user = $this->drupalCreateUser($perms);
+ $this->drupalLogin($web_user);
+
+ // Create some test nodes using different langcodes.
+ foreach (array(LANGUAGE_NONE, 'en', 'fr') as $langcode) {
+ $node_args = array(
+ 'type' => 'page',
+ 'promote' => 1,
+ 'language' => $langcode,
+ );
+ $node = $this->drupalCreateNode($node_args);
+ $node_reloaded = node_load($node->nid, NULL, TRUE);
+ $this->assertEqual($node_reloaded->language, $langcode, format_string('The language code of the node was successfully set to @langcode.', array('@langcode' => $langcode)));
+ }
+ }
+
}
/**
@@ -2629,6 +2670,68 @@
$this->drupalGet("$prefix/$path");
$this->assertResponse(404, $message2);
}
+
+ /**
+ * Check URL rewriting when using a domain name and a non-standard port.
+ */
+ function testDomainNameNegotiationPort() {
+ $language_domain = 'example.fr';
+ $edit = array(
+ 'locale_language_negotiation_url_part' => 1,
+ );
+ $this->drupalPost('admin/config/regional/language/configure/url', $edit, t('Save configuration'));
+ $edit = array(
+ 'prefix' => '',
+ 'domain' => $language_domain
+ );
+ $this->drupalPost('admin/config/regional/language/edit/fr', $edit, t('Save language'));
+
+ // Enable domain configuration.
+ variable_set('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN);
+
+ // Reset static caching.
+ drupal_static_reset('language_list');
+ drupal_static_reset('language_url_outbound_alter');
+ drupal_static_reset('language_url_rewrite_url');
+
+ // In case index.php is part of the URLs, we need to adapt the asserted
+ // URLs as well.
+ $index_php = strpos(url('', array('absolute' => TRUE)), 'index.php') !== FALSE;
+
+ // Remember current HTTP_HOST.
+ $http_host = $_SERVER['HTTP_HOST'];
+
+ // Fake a different port.
+ $_SERVER['HTTP_HOST'] .= ':88';
+
+ // Create an absolute French link.
+ $languages = language_list();
+ $language = $languages['fr'];
+ $url = url('', array(
+ 'absolute' => TRUE,
+ 'language' => $language
+ ));
+
+ $expected = 'http://example.fr:88/';
+ $expected .= $index_php ? 'index.php/' : '';
+
+ $this->assertEqual($url, $expected, 'The right port is used.');
+
+ // If we set the port explicitly in url(), it should not be overriden.
+ $url = url('', array(
+ 'absolute' => TRUE,
+ 'language' => $language,
+ 'base_url' => $GLOBALS['base_url'] . ':90',
+ ));
+
+ $expected = 'http://example.fr:90/';
+ $expected .= $index_php ? 'index.php/' : '';
+
+ $this->assertEqual($url, $expected, 'A given port is not overriden.');
+
+ // Restore HTTP_HOST.
+ $_SERVER['HTTP_HOST'] = $http_host;
+ }
}
/**
@@ -3085,11 +3188,7 @@
foreach (language_types_info() as $type => $info) {
if (isset($info['fixed'])) {
$negotiation = variable_get("language_negotiation_$type", array());
- $equal = count($info['fixed']) == count($negotiation);
- while ($equal && list($id) = each($negotiation)) {
- list(, $info_id) = each($info['fixed']);
- $equal = $info_id == $id;
- }
+ $equal = array_keys($negotiation) === array_values($info['fixed']);
$this->assertTrue($equal, format_string('language negotiation for %type is properly set up', array('%type' => $type)));
}
}
@@ -3141,3 +3240,46 @@
$this->assertRaw('@import url("' . $base_url . '/modules/system/system.messages.css' . $query_string . '");' . "\n" . '@import url("' . $base_url . '/modules/system/system.messages-rtl.css' . $query_string . '");' . "\n", 'CSS: system.messages-rtl.css is added directly after system.messages.css.');
}
}
+
+/**
+ * Tests locale translation safe string handling.
+ */
+class LocaleStringIsSafeTest extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Test if a string is safe',
+ 'description' => 'Tests locale translation safe string handling.',
+ 'group' => 'Locale',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('locale');
+ }
+
+ /**
+ * Tests for locale_string_is_safe().
+ */
+ public function testLocaleStringIsSafe() {
+ // Check a translatable string without HTML.
+ $string = 'Hello world!';
+ $result = locale_string_is_safe($string);
+ $this->assertTrue($result);
+
+ // Check a translatable string which includes trustable HTML.
+ $string = 'Hello world!';
+ $result = locale_string_is_safe($string);
+ $this->assertTrue($result);
+
+ // Check an untranslatable string which includes untrustable HTML (according
+ // to the locale_string_is_safe() function definition).
+ $string = 'Hello !';
+ $result = locale_string_is_safe($string);
+ $this->assertFalse($result);
+
+ // Check a translatable string which includes a token in an href attribute.
+ $string = 'Hi user';
+ $result = locale_string_is_safe($string);
+ $this->assertTrue($result);
+ }
+}
diff -Naur drupal-7.23/modules/locale/tests/locale_test.info drupal-7.66/modules/locale/tests/locale_test.info
--- drupal-7.23/modules/locale/tests/locale_test.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/locale/tests/locale_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 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/menu/menu.admin.inc drupal-7.66/modules/menu/menu.admin.inc
--- drupal-7.23/modules/menu/menu.admin.inc 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/menu/menu.admin.inc 2019-04-17 22:20:46.000000000 +0200
@@ -281,6 +281,7 @@
$form['link_title'] = array(
'#type' => 'textfield',
'#title' => t('Menu link title'),
+ '#maxlength' => 255,
'#default_value' => $item['link_title'],
'#description' => t('The text to be used for this link in the menu.'),
'#required' => TRUE,
@@ -305,7 +306,7 @@
'#title' => t('Path'),
'#maxlength' => 255,
'#default_value' => $path,
- '#description' => t('The path for this menu link. This can be an internal Drupal path such as %add-node or an external URL such as %drupal. Enter %front to link to the front page.', array('%front' => '', '%add-node' => 'node/add', '%drupal' => 'http://drupal.org')),
+ '#description' => t('The path for this menu link. This can be an internal path such as %add-node or an external URL such as %example. Enter %front to link to the front page.', array('%front' => '', '%add-node' => 'node/add', '%example' => 'http://example.com')),
'#required' => TRUE,
);
$form['actions']['delete'] = array(
@@ -512,8 +513,7 @@
// System-defined menus may not be deleted.
$system_menus = menu_list_system_menus();
if (isset($system_menus[$menu['menu_name']])) {
- drupal_access_denied();
- return;
+ return MENU_ACCESS_DENIED;
}
return drupal_get_form('menu_delete_menu_confirm', $menu);
}
@@ -622,8 +622,7 @@
// Links defined via hook_menu may not be deleted. Updated items are an
// exception, as they can be broken.
if ($item['module'] == 'system' && !$item['updated']) {
- drupal_access_denied();
- return;
+ return MENU_ACCESS_DENIED;
}
return drupal_get_form('menu_item_delete_form', $item);
}
diff -Naur drupal-7.23/modules/menu/menu.info drupal-7.66/modules/menu/menu.info
--- drupal-7.23/modules/menu/menu.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/menu/menu.info 2019-04-17 22:39:36.000000000 +0200
@@ -6,8 +6,7 @@
files[] = menu.test
configure = admin/structure/menu
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/menu/menu.module drupal-7.66/modules/menu/menu.module
--- drupal-7.23/modules/menu/menu.module 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/menu/menu.module 2019-04-17 22:20:46.000000000 +0200
@@ -69,7 +69,7 @@
'title' => 'Parent menu items',
'page callback' => 'menu_parent_options_js',
'type' => MENU_CALLBACK,
- 'access arguments' => array(TRUE),
+ 'access arguments' => array('administer menu'),
);
$items['admin/structure/menu/list'] = array(
'title' => 'List menus',
@@ -674,6 +674,7 @@
$form['menu']['link']['link_title'] = array(
'#type' => 'textfield',
'#title' => t('Menu link title'),
+ '#maxlength' => 255,
'#default_value' => $link['link_title'],
);
diff -Naur drupal-7.23/modules/menu/menu.test drupal-7.66/modules/menu/menu.test
--- drupal-7.23/modules/menu/menu.test 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/menu/menu.test 2019-04-17 22:20:46.000000000 +0200
@@ -72,6 +72,17 @@
$saved_item = menu_link_load($item['mlid']);
$this->assertEqual($description, $saved_item['options']['attributes']['title'], 'Saving an existing link updates the description (title attribute)');
$this->resetMenuLink($item, $old_title);
+
+ // Test that the page title is correct when a local task appears in a
+ // top-level menu item. See https://www.drupal.org/node/1973262.
+ $item = $this->addMenuLink(0, 'user/register', 'user-menu');
+ $this->drupalGet('user/password');
+ $this->assertNoTitle('Home | Drupal');
+ $this->drupalLogout();
+ $this->drupalGet('user/register');
+ $this->assertTitle($item['link_title'] . ' | Drupal');
+ $this->drupalGet('user');
+ $this->assertNoTitle('Home | Drupal');
}
/**
@@ -514,6 +525,23 @@
}
/**
+ * Test administrative users other than user 1 can access the menu parents AJAX callback.
+ */
+ public function testMenuParentsJsAccess() {
+ $admin = $this->drupalCreateUser(array('administer menu'));
+ $this->drupalLogin($admin);
+ // Just check access to the callback overall, the POST data is irrelevant.
+ $this->drupalGetAJAX('admin/structure/menu/parents');
+ $this->assertResponse(200);
+
+ // Do standard user tests.
+ // Login the user.
+ $this->drupalLogin($this->std_user);
+ $this->drupalGetAJAX('admin/structure/menu/parents');
+ $this->assertResponse(403);
+ }
+
+ /**
* Get standard menu link.
*/
private function getStandardMenuLink() {
@@ -620,7 +648,12 @@
);
$this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
- // Create a node.
+ // Verify that the menu link title on the node add form has the correct
+ // maxlength.
+ $this->drupalGet('node/add/page');
+ $this->assertPattern('//', 'Menu link title field has correct maxlength in node add form.');
+
+ // Create a node with menu link disabled.
$node_title = $this->randomName();
$language = LANGUAGE_NONE;
$edit = array(
@@ -656,6 +689,10 @@
$this->drupalGet('node/' . $node->nid . '/edit');
$this->assertOptionSelected('edit-menu-weight', 17, 'Menu weight correct in edit form');
+ // Verify that the menu link title on the node edit form has the correct
+ // maxlength.
+ $this->assertPattern('//', 'Menu link title field has correct maxlength in node edit form.');
+
// Edit the node and remove the menu link.
$edit = array(
'menu[enabled]' => FALSE,
diff -Naur drupal-7.23/modules/node/content_types.inc drupal-7.66/modules/node/content_types.inc
--- drupal-7.23/modules/node/content_types.inc 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/node/content_types.inc 2019-04-17 22:20:46.000000000 +0200
@@ -11,7 +11,7 @@
function node_overview_types() {
$types = node_type_get_types();
$names = node_type_get_names();
- $field_ui = module_exists('field_ui');
+ $field_ui = module_exists('field_ui') && user_access('administer fields');
$header = array(t('Name'), array('data' => t('Operations'), 'colspan' => $field_ui ? '4' : '2'));
$rows = array();
@@ -255,11 +255,11 @@
*/
function node_type_form_validate($form, &$form_state) {
$type = new stdClass();
- $type->type = trim($form_state['values']['type']);
+ $type->type = $form_state['values']['type'];
$type->name = trim($form_state['values']['name']);
// Work out what the type was before the user submitted this form
- $old_type = trim($form_state['values']['old_type']);
+ $old_type = $form_state['values']['old_type'];
$types = node_type_get_names();
@@ -288,7 +288,7 @@
$type = node_type_set_defaults();
- $type->type = trim($form_state['values']['type']);
+ $type->type = $form_state['values']['type'];
$type->name = trim($form_state['values']['name']);
$type->orig_type = trim($form_state['values']['orig_type']);
$type->old_type = isset($form_state['values']['old_type']) ? $form_state['values']['old_type'] : $type->type;
diff -Naur drupal-7.23/modules/node/node.admin.inc drupal-7.66/modules/node/node.admin.inc
--- drupal-7.23/modules/node/node.admin.inc 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/node/node.admin.inc 2019-04-17 22:20:46.000000000 +0200
@@ -329,6 +329,8 @@
}
/**
+ * Implements callback_batch_operation().
+ *
* Executes a batch operation for node_mass_update().
*
* @param array $nodes
@@ -367,7 +369,9 @@
}
/**
- * Menu callback: Reports the status of batch operation for node_mass_update().
+ * Implements callback_batch_finished().
+ *
+ * Reports the status of batch operation for node_mass_update().
*
* @param bool $success
* A boolean indicating whether the batch mass update operation successfully
@@ -471,6 +475,7 @@
$header['operations'] = array('data' => t('Operations'));
$query = db_select('node', 'n')->extend('PagerDefault')->extend('TableSort');
+ $query->addTag('node_admin_filter');
node_build_filter_query($query);
if (!user_access('bypass node access')) {
@@ -503,14 +508,17 @@
$options = array();
foreach ($nodes as $node) {
$langcode = entity_language('node', $node);
- $l_options = $langcode != LANGUAGE_NONE && isset($languages[$langcode]) ? array('language' => $languages[$langcode]) : array();
+ $uri = entity_uri('node', $node);
+ if ($langcode != LANGUAGE_NONE && isset($languages[$langcode])) {
+ $uri['options']['language'] = $languages[$langcode];
+ }
$options[$node->nid] = array(
'title' => array(
'data' => array(
'#type' => 'link',
'#title' => $node->title,
- '#href' => 'node/' . $node->nid,
- '#options' => $l_options,
+ '#href' => $uri['path'],
+ '#options' => $uri['options'],
'#suffix' => ' ' . theme('mark', array('type' => node_mark($node->nid, $node->changed))),
),
),
@@ -695,6 +703,7 @@
function node_multiple_delete_confirm_submit($form, &$form_state) {
if ($form_state['values']['confirm']) {
node_delete_multiple(array_keys($form_state['values']['nodes']));
+ cache_clear_all();
$count = count($form_state['values']['nodes']);
watchdog('content', 'Deleted @count posts.', array('@count' => $count));
drupal_set_message(format_plural($count, 'Deleted 1 post.', 'Deleted @count posts.'));
diff -Naur drupal-7.23/modules/node/node.api.php drupal-7.66/modules/node/node.api.php
--- drupal-7.23/modules/node/node.api.php 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/node/node.api.php 2019-04-17 22:20:46.000000000 +0200
@@ -17,11 +17,14 @@
* During node operations (create, update, view, delete, etc.), there are
* several sets of hooks that get invoked to allow modules to modify the base
* node operation:
- * - Node-type-specific hooks: These hooks are only invoked on the primary
- * module, using the "base" return component of hook_node_info() as the
- * function prefix. For example, poll.module defines the base for the Poll
- * content type as "poll", so during creation of a poll node, hook_insert() is
- * only invoked by calling poll_insert().
+ * - Node-type-specific hooks: When defining a node type, hook_node_info()
+ * returns a 'base' component. Node-type-specific hooks are named
+ * base_hookname() instead of mymodule_hookname() (in a module called
+ * 'mymodule' for example). Only the node type's corresponding implementation
+ * is invoked. For example, poll_node_info() in poll.module defines the base
+ * for the 'poll' node type as 'poll'. So when a poll node is created,
+ * hook_insert() is invoked on poll_insert() only.
+ * Hooks that are node-type-specific are noted below.
* - All-module hooks: This set of hooks is invoked on all implementing modules,
* to allow other modules to modify what the primary node module is doing. For
* example, hook_node_insert() is invoked on all modules when creating a poll
@@ -195,7 +198,7 @@
if (user_access('access private content', $account)) {
$grants['example'] = array(1);
}
- $grants['example_owner'] = array($account->uid);
+ $grants['example_author'] = array($account->uid);
return $grants;
}
@@ -885,11 +888,10 @@
* name as the key. Each sub-array has up to 10 attributes. Possible
* attributes:
* - name: (required) The human-readable name of the node type.
- * - base: (required) The base string used to construct callbacks
- * corresponding to this node type (for example, if base is defined as
- * example_foo, then example_foo_insert will be called when inserting a node
- * of that type). This string is usually the name of the module, but not
- * always.
+ * - base: (required) The base name for implementations of node-type-specific
+ * hooks that respond to this node type. Base is usually the name of the
+ * module or 'node_content', but not always. See
+ * @link node_api_hooks Node API hooks @endlink for more information.
* - description: (required) A brief description of the node type.
* - help: (optional) Help information shown to the user when creating a node
* of this type.
@@ -948,7 +950,7 @@
* 'recent', or 'comments'. The values should be arrays themselves, with the
* following keys available:
* - title: (required) The human readable name of the ranking mechanism.
- * - join: (optional) The part of a query string to join to any additional
+ * - join: (optional) An array with information to join any additional
* necessary table. This is not necessary if the table required is already
* joined to by the base query, such as for the {node} table. Other tables
* should use the full table name as an alias to avoid naming collisions.
@@ -972,7 +974,12 @@
'title' => t('Average vote'),
// Note that we use i.sid, the search index's search item id, rather than
// n.nid.
- 'join' => 'LEFT JOIN {vote_node_data} vote_node_data ON vote_node_data.nid = i.sid',
+ 'join' => array(
+ 'type' => 'LEFT',
+ 'table' => 'vote_node_data',
+ 'alias' => 'vote_node_data',
+ 'on' => 'vote_node_data.nid = i.sid',
+ ),
// The highest possible score should be 1, and the lowest possible score,
// always 0, should be 0.
'score' => 'vote_node_data.average / CAST(%f AS DECIMAL)',
@@ -1030,12 +1037,23 @@
/**
* Respond to node deletion.
*
- * This hook is invoked only on the module that defines the node's content type
- * (use hook_node_delete() to respond to all node deletions).
- *
- * This hook is invoked from node_delete_multiple() after the node has been
- * removed from the node table in the database, before hook_node_delete() is
- * invoked, and before field_attach_delete() is called.
+ * This is a node-type-specific hook, which is invoked only for the node type
+ * being affected. See
+ * @link node_api_hooks Node API hooks @endlink for more information.
+ *
+ * Use hook_node_delete() to respond to node deletion of all node types.
+ *
+ * This hook is invoked from node_delete_multiple() before hook_node_delete()
+ * is invoked and before field_attach_delete() is called.
+ *
+ * Note that when this hook is invoked, the changes have not yet been written
+ * to the database, because a database transaction is still in progress. The
+ * transaction is not finalized until the delete operation is entirely
+ * completed and node_delete_multiple() goes out of scope. You should not rely
+ * on data in the database at this time as it is not updated yet. You should
+ * also note that any write/update database queries executed from this hook are
+ * also not committed immediately. Check node_delete_multiple() and
+ * db_transaction() for more info.
*
* @param $node
* The node that is being deleted.
@@ -1051,8 +1069,11 @@
/**
* Act on a node object about to be shown on the add/edit form.
*
- * This hook is invoked only on the module that defines the node's content type
- * (use hook_node_prepare() to act on all node preparations).
+ * This is a node-type-specific hook, which is invoked only for the node type
+ * being affected. See
+ * @link node_api_hooks Node API hooks @endlink for more information.
+ *
+ * Use hook_node_prepare() to respond to node preparation of all node types.
*
* This hook is invoked from node_object_prepare() before the general
* hook_node_prepare() is invoked.
@@ -1063,26 +1084,21 @@
* @ingroup node_api_hooks
*/
function hook_prepare($node) {
- if ($file = file_check_upload($field_name)) {
- $file = file_save_upload($field_name, _image_filename($file->filename, NULL, TRUE));
- if ($file) {
- if (!image_get_info($file->uri)) {
- form_set_error($field_name, t('Uploaded file is not a valid image'));
- return;
- }
- }
- else {
- return;
- }
- $node->images['_original'] = $file->uri;
- _image_build_derivatives($node, TRUE);
- $node->new_file = TRUE;
+ if (!isset($node->mymodule_value)) {
+ $node->mymodule_value = 'foo';
}
}
/**
* Display a node editing form.
*
+ * This is a node-type-specific hook, which is invoked only for the node type
+ * being affected. See
+ * @link node_api_hooks Node API hooks @endlink for more information.
+ *
+ * Use hook_form_BASE_FORM_ID_alter(), with base form ID 'node_form', to alter
+ * node forms for all node types.
+ *
* This hook, implemented by node modules, is called to retrieve the form
* that is displayed to create or edit a node. This form is displayed at path
* node/add/[node type] or node/[node ID]/edit.
@@ -1138,8 +1154,11 @@
/**
* Respond to creation of a new node.
*
- * This hook is invoked only on the module that defines the node's content type
- * (use hook_node_insert() to act on all node insertions).
+ * This is a node-type-specific hook, which is invoked only for the node type
+ * being affected. See
+ * @link node_api_hooks Node API hooks @endlink for more information.
+ *
+ * Use hook_node_insert() to respond to node insertion of all node types.
*
* This hook is invoked from node_save() after the node is inserted into the
* node table in the database, before field_attach_insert() is called, and
@@ -1162,8 +1181,11 @@
/**
* Act on nodes being loaded from the database.
*
- * This hook is invoked only on the module that defines the node's content type
- * (use hook_node_load() to respond to all node loads).
+ * This is a node-type-specific hook, which is invoked only for the node type
+ * being affected. See
+ * @link node_api_hooks Node API hooks @endlink for more information.
+ *
+ * Use hook_node_load() to respond to node load of all node types.
*
* This hook is invoked during node loading, which is handled by entity_load(),
* via classes NodeController and DrupalDefaultEntityController. After the node
@@ -1196,8 +1218,11 @@
/**
* Respond to updates to a node.
*
- * This hook is invoked only on the module that defines the node's content type
- * (use hook_node_update() to act on all node updates).
+ * This is a node-type-specific hook, which is invoked only for the node type
+ * being affected. See
+ * @link node_api_hooks Node API hooks @endlink for more information.
+ *
+ * Use hook_node_update() to respond to node update of all node types.
*
* This hook is invoked from node_save() after the node is updated in the
* node table in the database, before field_attach_update() is called, and
@@ -1218,8 +1243,11 @@
/**
* Perform node validation before a node is created or updated.
*
- * This hook is invoked only on the module that defines the node's content type
- * (use hook_node_validate() to act on all node validations).
+ * This is a node-type-specific hook, which is invoked only for the node type
+ * being affected. See
+ * @link node_api_hooks Node API hooks @endlink for more information.
+ *
+ * Use hook_node_validate() to respond to node validation of all node types.
*
* This hook is invoked from node_validate(), after a user has finished
* editing the node and is previewing or submitting it. It is invoked at the end
@@ -1252,8 +1280,11 @@
/**
* Display a node.
*
- * This hook is invoked only on the module that defines the node's content type
- * (use hook_node_view() to act on all node views).
+ * This is a node-type-specific hook, which is invoked only for the node type
+ * being affected. See
+ * @link node_api_hooks Node API hooks @endlink for more information.
+ *
+ * Use hook_node_view() to respond to node view of all node types.
*
* This hook is invoked during node viewing after the node is fully loaded, so
* that the node type module can define a custom method for display, or add to
@@ -1263,6 +1294,10 @@
* The node to be displayed, as returned by node_load().
* @param $view_mode
* View mode, e.g. 'full', 'teaser', ...
+ * @param $langcode
+ * (optional) A language code to use for rendering. Defaults to the global
+ * content language of the current request.
+ *
* @return
* The passed $node parameter should be modified as necessary and returned so
* it can be properly presented. Nodes are prepared for display by assembling
@@ -1276,7 +1311,7 @@
*
* @ingroup node_api_hooks
*/
-function hook_view($node, $view_mode) {
+function hook_view($node, $view_mode, $langcode = NULL) {
if ($view_mode == 'full' && node_is_page($node)) {
$breadcrumb = array();
$breadcrumb[] = l(t('Home'), NULL);
diff -Naur drupal-7.23/modules/node/node.info drupal-7.66/modules/node/node.info
--- drupal-7.23/modules/node/node.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/node/node.info 2019-04-17 22:39:36.000000000 +0200
@@ -9,8 +9,7 @@
configure = admin/structure/types
stylesheets[all][] = node.css
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/node/node.install drupal-7.66/modules/node/node.install
--- drupal-7.23/modules/node/node.install 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/node/node.install 2019-04-17 22:20:46.000000000 +0200
@@ -114,6 +114,7 @@
'uid' => array('uid'),
'tnid' => array('tnid'),
'translate' => array('translate'),
+ 'language' => array('language'),
),
'unique keys' => array(
'vid' => array('vid'),
@@ -409,6 +410,7 @@
'nid' => array(
'description' => 'The {node}.nid that was read.',
'type' => 'int',
+ 'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
@@ -926,5 +928,39 @@
}
/**
+ * Add an index on {node}.language.
+ */
+function node_update_7014() {
+ db_add_index('node', 'language', array('language'));
+}
+
+/**
+ * Enable node types that may have been erroneously disabled in Drupal 7.36.
+ */
+function node_update_7015() {
+ db_update('node_type')
+ ->fields(array('disabled' => 0))
+ ->condition('base', 'node_content')
+ ->execute();
+}
+
+/**
+ * Change {history}.nid to an unsigned int in order to match {node}.nid.
+ */
+function node_update_7016() {
+ db_drop_primary_key('history');
+ db_drop_index('history', 'nid');
+ db_change_field('history', 'nid', 'nid', array(
+ 'description' => 'The {node}.nid that was read.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ));
+ db_add_primary_key('history', array('uid', 'nid'));
+ db_add_index('history', 'nid', array('nid'));
+}
+
+/**
* @} End of "addtogroup updates-7.x-extra".
*/
diff -Naur drupal-7.23/modules/node/node.module drupal-7.66/modules/node/node.module
--- drupal-7.23/modules/node/node.module 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/node/node.module 2019-04-17 22:20:46.000000000 +0200
@@ -210,7 +210,7 @@
'custom settings' => FALSE,
),
'search_result' => array(
- 'label' => t('Search result'),
+ 'label' => t('Search result highlighting input'),
'custom settings' => FALSE,
),
);
@@ -506,7 +506,8 @@
* - custom: TRUE or FALSE indicating whether this type is defined by a module
* (FALSE) or by a user (TRUE) via Add Content Type.
* - modified: TRUE or FALSE indicating whether this type has been modified by
- * an administrator. Currently not used in any way.
+ * an administrator. When modifying an existing node type, set to TRUE, or
+ * the change will be ignored on node_types_rebuild().
* - locked: TRUE or FALSE indicating whether the administrator can change the
* machine name of this type.
* - disabled: TRUE or FALSE indicating whether this type has been disabled.
@@ -1179,10 +1180,8 @@
module_invoke_all('node_' . $op, $node);
module_invoke_all('entity_' . $op, $node, 'node');
- // Update the node access table for this node. There's no need to delete
- // existing records if the node is new.
- $delete = $op == 'update';
- node_access_acquire_grants($node, $delete);
+ // Update the node access table for this node.
+ node_access_acquire_grants($node);
// Clear internal properties.
unset($node->is_new);
@@ -1399,12 +1398,7 @@
$node->content = array();
// Allow modules to change the view mode.
- $context = array(
- 'entity_type' => 'node',
- 'entity' => $node,
- 'langcode' => $langcode,
- );
- drupal_alter('entity_view_mode', $view_mode, $context);
+ $view_mode = key(entity_view_mode_prepare('node', array($node->nid => $node), $view_mode, $langcode));
// The 'view' hook can be implemented to overwrite the default function
// to display nodes.
@@ -1587,9 +1581,7 @@
),
'access content overview' => array(
'title' => t('Access the content overview page'),
- 'description' => user_access('access content overview')
- ? t('Get an overview of all content.', array('@url' => url('admin/content')))
- : t('Get an overview of all content.'),
+ 'description' => t('Get an overview of all content.', array('@url' => url('admin/content'))),
),
'access content' => array(
'title' => t('View published content'),
@@ -1617,7 +1609,7 @@
}
/**
- * Gathers the rankings from the the hook_ranking() implementations.
+ * Gathers the rankings from the hook_ranking() implementations.
*
* @param $query
* A query object that has been extended with the Search DB Extender.
@@ -1685,7 +1677,7 @@
);
$form['content_ranking']['#theme'] = 'node_search_admin';
$form['content_ranking']['info'] = array(
- '#value' => '' . t('The following numbers control which properties the content search should favor when ordering the results. Higher numbers mean more influence, zero means the property is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . ''
+ '#markup' => '' . t('Influence is a numeric multiplier used in ordering search results. A higher number means the corresponding factor has more influence on search results; zero means the factor is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . '
'
);
// Note: reversed to reflect that higher number = higher ranking.
@@ -1872,7 +1864,7 @@
$output = drupal_render($form['info']);
- $header = array(t('Factor'), t('Weight'));
+ $header = array(t('Factor'), t('Influence'));
foreach (element_children($form['factors']) as $key) {
$row = array();
$row[] = $form['factors'][$key]['#title'];
@@ -2224,8 +2216,8 @@
/**
* Returns a list of all the existing revision numbers.
*
- * @param Drupal\node\Node $node
- * The node entity.
+ * @param $node
+ * The node object.
*
* @return
* An associative array keyed by node revision number.
@@ -2606,9 +2598,10 @@
$node->link = url("node/$node->nid", array('absolute' => TRUE));
$node->rss_namespaces = array();
+ $account = user_load($node->uid);
$node->rss_elements = array(
array('key' => 'pubDate', 'value' => gmdate('r', $node->created)),
- array('key' => 'dc:creator', 'value' => $node->name),
+ array('key' => 'dc:creator', 'value' => format_username($account)),
array('key' => 'guid', 'value' => $node->nid . ' at ' . $base_url, 'attributes' => array('isPermaLink' => 'false'))
);
@@ -2666,15 +2659,26 @@
* An array in the format expected by drupal_render().
*/
function node_view_multiple($nodes, $view_mode = 'teaser', $weight = 0, $langcode = NULL) {
- field_attach_prepare_view('node', $nodes, $view_mode, $langcode);
- entity_prepare_view('node', $nodes, $langcode);
$build = array();
+ $entities_by_view_mode = entity_view_mode_prepare('node', $nodes, $view_mode, $langcode);
+ foreach ($entities_by_view_mode as $entity_view_mode => $entities) {
+ field_attach_prepare_view('node', $entities, $entity_view_mode, $langcode);
+ entity_prepare_view('node', $entities, $langcode);
+
+ foreach ($entities as $entity) {
+ $build['nodes'][$entity->nid] = node_view($entity, $entity_view_mode, $langcode);
+ }
+ }
+
foreach ($nodes as $node) {
- $build['nodes'][$node->nid] = node_view($node, $view_mode, $langcode);
$build['nodes'][$node->nid]['#weight'] = $weight;
$weight++;
}
+ // Sort here, to preserve the input order of the entities that were passed to
+ // this function.
+ uasort($build['nodes'], 'element_sort');
$build['nodes']['#sorted'] = TRUE;
+
return $build;
}
@@ -2949,7 +2953,10 @@
* system. When adding a node listing to your module, be sure to use a dynamic
* query created by db_select() and add a tag of "node_access". This will allow
* modules dealing with node access to ensure only nodes to which the user has
- * access are retrieved, through the use of hook_query_TAG_alter().
+ * access are retrieved, through the use of hook_query_TAG_alter(). Tagging a
+ * query with "node_access" does not check the published/unpublished status of
+ * nodes, so the base query is responsible for ensuring that unpublished nodes
+ * are not displayed to inappropriate users.
*
* Note: Even a single module returning NODE_ACCESS_DENY from hook_node_access()
* will block access to the node. Therefore, implementers should take care to
@@ -3292,6 +3299,17 @@
/**
* Helper for node access functions.
*
+ * Queries tagged with 'node_access' that are not against the {node} table
+ * should add the base table as metadata. For example:
+ * @code
+ * $query
+ * ->addTag('node_access')
+ * ->addMetaData('base_table', 'taxonomy_index');
+ * @endcode
+ * If the query is not against the {node} table, an attempt is made to guess
+ * the table, but is not recommended to rely on this as it is deprecated and not
+ * allowed in Drupal 8. It is always safer to provide the table.
+ *
* @param $query
* The query to add conditions to.
* @param $type
@@ -3620,7 +3638,8 @@
// Try to allocate enough time to rebuild node grants
drupal_set_time_limit(240);
- $nids = db_query("SELECT nid FROM {node}")->fetchCol();
+ // Rebuild newest nodes first so that recent content becomes available quickly.
+ $nids = db_query("SELECT nid FROM {node} ORDER BY nid DESC")->fetchCol();
foreach ($nids as $nid) {
$node = node_load($nid, NULL, TRUE);
// To preserve database integrity, only acquire grants if the node
@@ -3653,6 +3672,8 @@
}
/**
+ * Implements callback_batch_operation().
+ *
* Performs batch operation for node_access_rebuild().
*
* This is a multistep operation: we go through all nodes by packs of 20. The
@@ -3667,7 +3688,7 @@
// Initiate multistep processing.
$context['sandbox']['progress'] = 0;
$context['sandbox']['current_node'] = 0;
- $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT nid) FROM {node}')->fetchField();
+ $context['sandbox']['max'] = db_query('SELECT COUNT(nid) FROM {node}')->fetchField();
}
// Process the next 20 nodes.
@@ -3691,6 +3712,8 @@
}
/**
+ * Implements callback_batch_finished().
+ *
* Performs post-processing for node_access_rebuild().
*
* @param bool $success
diff -Naur drupal-7.23/modules/node/node.pages.inc drupal-7.66/modules/node/node.pages.inc
--- drupal-7.23/modules/node/node.pages.inc 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/node/node.pages.inc 2019-04-17 22:20:46.000000000 +0200
@@ -371,35 +371,37 @@
* @see node_form_build_preview()
*/
function node_preview($node) {
- if (node_access('create', $node) || node_access('update', $node)) {
- _field_invoke_multiple('load', 'node', array($node->nid => $node));
+ // Clone the node before previewing it to prevent the node itself from being
+ // modified.
+ $cloned_node = clone $node;
+ if (node_access('create', $cloned_node) || node_access('update', $cloned_node)) {
+ _field_invoke_multiple('load', 'node', array($cloned_node->nid => $cloned_node));
// Load the user's name when needed.
- if (isset($node->name)) {
+ if (isset($cloned_node->name)) {
// The use of isset() is mandatory in the context of user IDs, because
// user ID 0 denotes the anonymous user.
- if ($user = user_load_by_name($node->name)) {
- $node->uid = $user->uid;
- $node->picture = $user->picture;
+ if ($user = user_load_by_name($cloned_node->name)) {
+ $cloned_node->uid = $user->uid;
+ $cloned_node->picture = $user->picture;
}
else {
- $node->uid = 0; // anonymous user
+ $cloned_node->uid = 0; // anonymous user
}
}
- elseif ($node->uid) {
- $user = user_load($node->uid);
- $node->name = $user->name;
- $node->picture = $user->picture;
+ elseif ($cloned_node->uid) {
+ $user = user_load($cloned_node->uid);
+ $cloned_node->name = $user->name;
+ $cloned_node->picture = $user->picture;
}
- $node->changed = REQUEST_TIME;
- $nodes = array($node->nid => $node);
- field_attach_prepare_view('node', $nodes, 'full');
+ $cloned_node->changed = REQUEST_TIME;
+ $nodes = array($cloned_node->nid => $cloned_node);
// Display a preview of the node.
if (!form_get_errors()) {
- $node->in_preview = TRUE;
- $output = theme('node_preview', array('node' => $node));
- unset($node->in_preview);
+ $cloned_node->in_preview = TRUE;
+ $output = theme('node_preview', array('node' => $cloned_node));
+ unset($cloned_node->in_preview);
}
drupal_set_title(t('Preview'), PASS_THROUGH);
@@ -542,6 +544,7 @@
if ($form_state['values']['confirm']) {
$node = node_load($form_state['values']['nid']);
node_delete($form_state['values']['nid']);
+ cache_clear_all();
watchdog('content', '@type: deleted %title.', array('@type' => $node->type, '%title' => $node->title));
drupal_set_message(t('@type %title has been deleted.', array('@type' => node_type_get_name($node), '%title' => $node->title)));
}
diff -Naur drupal-7.23/modules/node/node.test drupal-7.66/modules/node/node.test
--- drupal-7.23/modules/node/node.test 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/node/node.test 2019-04-17 22:20:46.000000000 +0200
@@ -457,10 +457,70 @@
}
function setUp() {
- parent::setUp();
+ parent::setUp(array('taxonomy', 'node'));
$web_user = $this->drupalCreateUser(array('edit own page content', 'create page content'));
$this->drupalLogin($web_user);
+
+ // Add a vocabulary so we can test different view modes.
+ $vocabulary = (object) array(
+ 'name' => $this->randomName(),
+ 'description' => $this->randomName(),
+ 'machine_name' => drupal_strtolower($this->randomName()),
+ 'help' => '',
+ 'nodes' => array('page' => 'page'),
+ );
+ taxonomy_vocabulary_save($vocabulary);
+
+ $this->vocabulary = $vocabulary;
+
+ // Add a term to the vocabulary.
+ $term = (object) array(
+ 'name' => $this->randomName(),
+ 'description' => $this->randomName(),
+ // Use the first available text format.
+ 'format' => db_query_range('SELECT format FROM {filter_format}', 0, 1)->fetchField(),
+ 'vid' => $this->vocabulary->vid,
+ 'vocabulary_machine_name' => $vocabulary->machine_name,
+ );
+ taxonomy_term_save($term);
+
+ $this->term = $term;
+
+ // Set up a field and instance.
+ $this->field_name = drupal_strtolower($this->randomName());
+ $this->field = array(
+ 'field_name' => $this->field_name,
+ 'type' => 'taxonomy_term_reference',
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => '0',
+ ),
+ ),
+ )
+ );
+
+ field_create_field($this->field);
+ $this->instance = array(
+ 'field_name' => $this->field_name,
+ 'entity_type' => 'node',
+ 'bundle' => 'page',
+ 'widget' => array(
+ 'type' => 'options_select',
+ ),
+ // Hide on full display but render on teaser.
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'hidden',
+ ),
+ 'teaser' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance);
}
/**
@@ -470,21 +530,26 @@
$langcode = LANGUAGE_NONE;
$title_key = "title";
$body_key = "body[$langcode][0][value]";
+ $term_key = "{$this->field_name}[$langcode]";
// Fill in node creation form and preview node.
$edit = array();
$edit[$title_key] = $this->randomName(8);
$edit[$body_key] = $this->randomName(16);
+ $edit[$term_key] = $this->term->tid;
$this->drupalPost('node/add/page', $edit, t('Preview'));
- // Check that the preview is displaying the title and body.
+ // Check that the preview is displaying the title, body, and term.
$this->assertTitle(t('Preview | Drupal'), 'Basic page title is preview.');
$this->assertText($edit[$title_key], 'Title displayed.');
$this->assertText($edit[$body_key], 'Body displayed.');
+ $this->assertText($this->term->name, 'Term displayed.');
- // Check that the title and body fields are displayed with the correct values.
+ // Check that the title, body, and term fields are displayed with the
+ // correct values.
$this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.');
$this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.');
+ $this->assertFieldByName($term_key, $edit[$term_key], 'Term field displayed.');
}
/**
@@ -494,6 +559,7 @@
$langcode = LANGUAGE_NONE;
$title_key = "title";
$body_key = "body[$langcode][0][value]";
+ $term_key = "{$this->field_name}[$langcode]";
// Force revision on "Basic page" content.
variable_set('node_options_page', array('status', 'revision'));
@@ -501,17 +567,21 @@
$edit = array();
$edit[$title_key] = $this->randomName(8);
$edit[$body_key] = $this->randomName(16);
+ $edit[$term_key] = $this->term->tid;
$edit['log'] = $this->randomName(32);
$this->drupalPost('node/add/page', $edit, t('Preview'));
- // Check that the preview is displaying the title and body.
+ // Check that the preview is displaying the title, body, and term.
$this->assertTitle(t('Preview | Drupal'), 'Basic page title is preview.');
$this->assertText($edit[$title_key], 'Title displayed.');
$this->assertText($edit[$body_key], 'Body displayed.');
+ $this->assertText($this->term->name, 'Term displayed.');
- // Check that the title and body fields are displayed with the correct values.
+ // Check that the title, body, and term fields are displayed with the
+ // correct values.
$this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.');
$this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.');
+ $this->assertFieldByName($term_key, $edit[$term_key], 'Term field displayed.');
// Check that the log field has the correct value.
$this->assertFieldByName('log', $edit['log'], 'Log field displayed.');
@@ -571,6 +641,8 @@
);
try {
+ // An exception is generated by node_test_exception_node_insert() if the
+ // title is 'testing_transaction_exception'.
node_save((object) $edit);
$this->fail(t('Expected exception has not been thrown.'));
}
@@ -1363,6 +1435,22 @@
$node = node_load($node->nid);
$this->assertEqual($node->title, 'updated_presave', 'Static cache has been cleared.');
}
+
+ /**
+ * Tests saving a node on node insert.
+ *
+ * This test ensures that a node has been fully saved when hook_node_insert()
+ * is invoked, so that the node can be saved again in a hook implementation
+ * without errors.
+ *
+ * @see node_test_node_insert()
+ */
+ function testNodeSaveOnInsert() {
+ // node_test_node_insert() triggers a save on insert if the title equals
+ // 'new'.
+ $node = $this->drupalCreateNode(array('title' => 'new'));
+ $this->assertEqual($node->title, 'Node ' . $node->nid, 'Node saved on node insert.');
+ }
}
/**
@@ -1430,7 +1518,7 @@
* Tests editing a node type using the UI.
*/
function testNodeTypeEditing() {
- $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types'));
+ $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types', 'administer fields'));
$this->drupalLogin($web_user);
$instance = field_info_instance('node', 'body', 'page');
@@ -2426,6 +2514,35 @@
$output = token_replace($input, array('node' => $node), array('language' => $language, 'sanitize' => FALSE));
$this->assertEqual($output, $expected, format_string('Unsanitized node token %token replaced.', array('%token' => $input)));
}
+
+ // Repeat for a node without a summary.
+ $settings['body'] = array(LANGUAGE_NONE => array(array('value' => $this->randomName(32), 'summary' => '')));
+ $node = $this->drupalCreateNode($settings);
+
+ // Load node (without summary) so that the body and summary fields are
+ // structured properly.
+ $node = node_load($node->nid);
+ $instance = field_info_instance('node', 'body', $node->type);
+
+ // Generate and test sanitized token - use full body as expected value.
+ $tests = array();
+ $tests['[node:summary]'] = _text_sanitize($instance, $langcode, $node->body[$langcode][0], 'value');
+
+ // Test to make sure that we generated something for each token.
+ $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated for node without a summary.');
+
+ foreach ($tests as $input => $expected) {
+ $output = token_replace($input, array('node' => $node), array('language' => $language));
+ $this->assertEqual($output, $expected, format_string('Sanitized node token %token replaced for node without a summary.', array('%token' => $input)));
+ }
+
+ // Generate and test unsanitized tokens.
+ $tests['[node:summary]'] = $node->body[$langcode][0]['value'];
+
+ foreach ($tests as $input => $expected) {
+ $output = token_replace($input, array('node' => $node), array('language' => $language, 'sanitize' => FALSE));
+ $this->assertEqual($output, $expected, format_string('Unsanitized node token %token replaced for node without a summary.', array('%token' => $input)));
+ }
}
}
@@ -2651,8 +2768,8 @@
node_access_rebuild();
// Create some users.
- $this->admin_user = $this->drupalCreateUser(array('access content', 'bypass node access'));
- $this->content_admin_user = $this->drupalCreateUser(array('access content', 'administer content types'));
+ $this->admin_user = $this->drupalCreateUser(array('access content', 'bypass node access', 'administer fields'));
+ $this->content_admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer fields'));
// Add a custom field to the page content type.
$this->field_name = drupal_strtolower($this->randomName() . '_field_name');
@@ -2735,8 +2852,8 @@
$edit = array();
$langcode = LANGUAGE_NONE;
$edit["title"] = $this->randomName(8);
- $edit["body[$langcode][0][value]"] = t('Data that should appear only in the body for the node.');
- $edit["body[$langcode][0][summary]"] = t('Extra data that should appear only in the teaser for the node.');
+ $edit["body[$langcode][0][value]"] = 'Data that should appear only in the body for the node.';
+ $edit["body[$langcode][0][summary]"] = 'Extra data that should appear only in the teaser for the node.';
$this->drupalPost('node/add/page', $edit, t('Save'));
$node = $this->drupalGetNodeByTitle($edit["title"]);
@@ -2754,4 +2871,151 @@
$build = node_view($node);
$this->assertEqual($build['#view_mode'], 'teaser', 'The view mode has correctly been set to teaser.');
}
+
+ /**
+ * Tests fields that were previously hidden when the view mode is changed.
+ */
+ function testNodeViewModeChangeHiddenField() {
+ // Hide the tags field on the default display
+ $instance = field_info_instance('node', 'field_tags', 'article');
+ $instance['display']['default']['type'] = 'hidden';
+ field_update_instance($instance);
+
+ $web_user = $this->drupalCreateUser(array('create article content', 'edit own article content'));
+ $this->drupalLogin($web_user);
+
+ // Create a node.
+ $edit = array();
+ $langcode = LANGUAGE_NONE;
+ $edit["title"] = $this->randomName(8);
+ $edit["body[$langcode][0][value]"] = 'Data that should appear only in the body for the node.';
+ $edit["body[$langcode][0][summary]"] = 'Extra data that should appear only in the teaser for the node.';
+ $edit["field_tags[$langcode]"] = 'Extra tag';
+ $this->drupalPost('node/add/article', $edit, t('Save'));
+
+ $node = $this->drupalGetNodeByTitle($edit["title"]);
+
+ // Set the flag to alter the view mode and view the node.
+ variable_set('node_test_change_view_mode', 'teaser');
+ $this->drupalGet('node/' . $node->nid);
+
+ // Check that teaser mode is viewed.
+ $this->assertText('Extra data that should appear only in the teaser for the node.', 'Teaser text present');
+ // Make sure body text is not present.
+ $this->assertNoText('Data that should appear only in the body for the node.', 'Body text not present');
+ // Make sure tags are present.
+ $this->assertText('Extra tag', 'Taxonomy term present');
+
+ // Test that the correct build mode has been set.
+ $build = node_view($node);
+ $this->assertEqual($build['#view_mode'], 'teaser', 'The view mode has correctly been set to teaser.');
+ }
+}
+
+/**
+ * Tests the cache invalidation of node operations.
+ */
+class NodePageCacheTest extends NodeWebTestCase {
+
+ /**
+ * An admin user with administrative permissions for nodes.
+ */
+ protected $admin_user;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node page cache test',
+ 'description' => 'Test cache invalidation of node operations.',
+ 'group' => 'Node',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ variable_set('cache', 1);
+ variable_set('page_cache_maximum_age', 300);
+
+ $this->admin_user = $this->drupalCreateUser(array(
+ 'bypass node access',
+ 'access content overview',
+ 'administer nodes',
+ ));
+ }
+
+ /**
+ * Tests deleting nodes clears page cache.
+ */
+ public function testNodeDelete() {
+ $node_path = 'node/' . $this->drupalCreateNode()->nid;
+
+ // Populate page cache.
+ $this->drupalGet($node_path);
+
+ // Login and delete the node.
+ $this->drupalLogin($this->admin_user);
+ $this->drupalPost($node_path . '/delete', array(), t('Delete'));
+
+ // Logout and check the node is not available.
+ $this->drupalLogout();
+ $this->drupalGet($node_path);
+ $this->assertResponse(404);
+
+ // Create two new nodes.
+ $nodes[0] = $this->drupalCreateNode();
+ $nodes[1] = $this->drupalCreateNode();
+ $node_path = 'node/' . $nodes[0]->nid;
+
+ // Populate page cache.
+ $this->drupalGet($node_path);
+
+ // Login and delete the nodes.
+ $this->drupalLogin($this->admin_user);
+ $this->drupalGet('admin/content');
+ $edit = array(
+ 'operation' => 'delete',
+ 'nodes[' . $nodes[0]->nid . ']' => TRUE,
+ 'nodes[' . $nodes[1]->nid . ']' => TRUE,
+ );
+ $this->drupalPost(NULL, $edit, t('Update'));
+ $this->drupalPost(NULL, array(), t('Delete'));
+
+ // Logout and check the node is not available.
+ $this->drupalLogout();
+ $this->drupalGet($node_path);
+ $this->assertResponse(404);
+ }
+}
+
+/**
+ * Tests that multi-byte UTF-8 characters are stored and retrieved correctly.
+ */
+class NodeMultiByteUtf8Test extends NodeWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Multi-byte UTF-8',
+ 'description' => 'Test that multi-byte UTF-8 characters are stored and retrieved correctly.',
+ 'group' => 'Node',
+ );
+ }
+
+ /**
+ * Tests that multi-byte UTF-8 characters are stored and retrieved correctly.
+ */
+ public function testMultiByteUtf8() {
+ $connection = Database::getConnection();
+ // On MySQL, this test will only run if 'charset' is set to 'utf8mb4' in
+ // settings.php.
+ if (!($connection->utf8mb4IsSupported() && $connection->utf8mb4IsActive())) {
+ return;
+ }
+ $title = '🐙';
+ $this->assertTrue(drupal_strlen($title, 'utf-8') < strlen($title), 'Title has multi-byte characters.');
+ $node = $this->drupalCreateNode(array('title' => $title));
+ $this->drupalGet('node/' . $node->nid);
+ $result = $this->xpath('//h1[@id="page-title"]');
+ $this->assertEqual(trim((string) $result[0]), $title, 'The passed title was returned.');
+ }
+
}
diff -Naur drupal-7.23/modules/node/node.tokens.inc drupal-7.66/modules/node/node.tokens.inc
--- drupal-7.23/modules/node/node.tokens.inc 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/node/node.tokens.inc 2019-04-17 22:20:46.000000000 +0200
@@ -136,10 +136,29 @@
case 'body':
case 'summary':
if ($items = field_get_items('node', $node, 'body', $language_code)) {
- $column = ($name == 'body') ? 'value' : 'summary';
$instance = field_info_instance('node', 'body', $node->type);
$field_langcode = field_language('node', $node, 'body', $language_code);
- $replacements[$original] = $sanitize ? _text_sanitize($instance, $field_langcode, $items[0], $column) : $items[0][$column];
+ // If the summary was requested and is not empty, use it.
+ if ($name == 'summary' && !empty($items[0]['summary'])) {
+ $output = $sanitize ? _text_sanitize($instance, $field_langcode, $items[0], 'summary') : $items[0]['summary'];
+ }
+ // Attempt to provide a suitable version of the 'body' field.
+ else {
+ $output = $sanitize ? _text_sanitize($instance, $field_langcode, $items[0], 'value') : $items[0]['value'];
+ // A summary was requested.
+ if ($name == 'summary') {
+ if (isset($instance['display']['teaser']['settings']['trim_length'])) {
+ $trim_length = $instance['display']['teaser']['settings']['trim_length'];
+ }
+ else {
+ // Use default value.
+ $trim_length = NULL;
+ }
+ // Generate an optionally trimmed summary of the body field.
+ $output = text_summary($output, $instance['settings']['text_processing'] ? $items[0]['format'] : NULL, $trim_length);
+ }
+ }
+ $replacements[$original] = $output;
}
break;
diff -Naur drupal-7.23/modules/node/tests/node_access_test.info drupal-7.66/modules/node/tests/node_access_test.info
--- drupal-7.23/modules/node/tests/node_access_test.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/node/tests/node_access_test.info 2019-04-17 22:39:36.000000000 +0200
@@ -5,8 +5,7 @@
core = 7.x
hidden = TRUE
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/node/tests/node_access_test.module drupal-7.66/modules/node/tests/node_access_test.module
--- drupal-7.23/modules/node/tests/node_access_test.module 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/node/tests/node_access_test.module 2019-04-17 22:20:46.000000000 +0200
@@ -211,7 +211,7 @@
}
/**
- * Implements hook_nodeapi_update().
+ * Implements hook_node_update().
*/
function node_access_test_node_update($node) {
_node_access_test_node_write($node);
diff -Naur drupal-7.23/modules/node/tests/node_test.info drupal-7.66/modules/node/tests/node_test.info
--- drupal-7.23/modules/node/tests/node_test.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/node/tests/node_test.info 2019-04-17 22:39:36.000000000 +0200
@@ -5,8 +5,7 @@
core = 7.x
hidden = TRUE
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/node/tests/node_test.module drupal-7.66/modules/node/tests/node_test.module
--- drupal-7.23/modules/node/tests/node_test.module 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/node/tests/node_test.module 2019-04-17 22:20:46.000000000 +0200
@@ -161,3 +161,21 @@
$view_mode = $change_view_mode;
}
}
+
+/**
+ * Implements hook_node_insert().
+ *
+ * This tests saving a node on node insert.
+ *
+ * @see NodeSaveTest::testNodeSaveOnInsert()
+ */
+function node_test_node_insert($node) {
+ // Set the node title to the node ID and save.
+ if ($node->title == 'new') {
+ $node->title = 'Node '. $node->nid;
+ // Remove the is_new flag, so that the node is updated and not inserted
+ // again.
+ unset($node->is_new);
+ node_save($node);
+ }
+}
diff -Naur drupal-7.23/modules/node/tests/node_test_exception.info drupal-7.66/modules/node/tests/node_test_exception.info
--- drupal-7.23/modules/node/tests/node_test_exception.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/node/tests/node_test_exception.info 2019-04-17 22:39:36.000000000 +0200
@@ -5,8 +5,7 @@
core = 7.x
hidden = TRUE
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/openid/openid.inc drupal-7.66/modules/openid/openid.inc
--- drupal-7.23/modules/openid/openid.inc 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/openid/openid.inc 2019-04-17 22:20:46.000000000 +0200
@@ -158,6 +158,11 @@
return array();
}
+ // Also stop parsing if there is an unreasonably large number of tags.
+ if ($dom->getElementsByTagName('*')->length > variable_get('openid_xrds_maximum_tag_count', 30000)) {
+ return array();
+ }
+
// Parse the DOM document for the information we need.
if ($xml = simplexml_import_dom($dom)) {
foreach ($xml->children(OPENID_NS_XRD)->XRD as $xrd) {
@@ -380,6 +385,9 @@
/**
* Return a nonce value - formatted per OpenID spec.
+ *
+ * NOTE: This nonce is not cryptographically secure and only suitable for use
+ * by the test framework.
*/
function _openid_nonce() {
// YYYY-MM-DDThh:mm:ssZ, plus some optional extra unique characters.
@@ -549,7 +557,7 @@
}
do {
- $bytes = "\x00" . _openid_get_bytes($nbytes);
+ $bytes = "\x00" . drupal_random_bytes($nbytes);
$n = _openid_dh_binary_to_long($bytes);
// Keep looping if this value is in the low duplicated range.
} while (_openid_math_cmp($n, $duplicate) < 0);
@@ -558,23 +566,7 @@
}
function _openid_get_bytes($num_bytes) {
- $f = &drupal_static(__FUNCTION__);
- $bytes = '';
- if (!isset($f)) {
- $f = @fopen(OPENID_RAND_SOURCE, "r");
- }
- if (!$f) {
- // pseudorandom used
- $bytes = '';
- for ($i = 0; $i < $num_bytes; $i += 4) {
- $bytes .= pack('L', mt_rand());
- }
- $bytes = substr($bytes, 0, $num_bytes);
- }
- else {
- $bytes = fread($f, $num_bytes);
- }
- return $bytes;
+ return drupal_random_bytes($num_bytes);
}
function _openid_response($str = NULL) {
diff -Naur drupal-7.23/modules/openid/openid.info drupal-7.66/modules/openid/openid.info
--- drupal-7.23/modules/openid/openid.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/openid/openid.info 2019-04-17 22:39:36.000000000 +0200
@@ -5,8 +5,7 @@
core = 7.x
files[] = openid.test
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/openid/openid.install drupal-7.66/modules/openid/openid.install
--- drupal-7.23/modules/openid/openid.install 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/openid/openid.install 2019-04-17 22:20:46.000000000 +0200
@@ -15,13 +15,14 @@
'idp_endpoint_uri' => array(
'type' => 'varchar',
'length' => 255,
- 'description' => 'URI of the OpenID Provider endpoint.',
+ 'not null' => TRUE,
+ 'description' => 'Primary Key: URI of the OpenID Provider endpoint.',
),
'assoc_handle' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
- 'description' => 'Primary Key: Used to refer to this association in subsequent messages.',
+ 'description' => 'Used to refer to this association in subsequent messages.',
),
'assoc_type' => array(
'type' => 'varchar',
@@ -51,7 +52,10 @@
'description' => 'The lifetime, in seconds, of this association.',
),
),
- 'primary key' => array('assoc_handle'),
+ 'primary key' => array('idp_endpoint_uri'),
+ 'unique keys' => array(
+ 'assoc_handle' => array('assoc_handle'),
+ ),
);
$schema['openid_nonce'] = array(
@@ -158,3 +162,69 @@
/**
* @} End of "addtogroup updates-6.x-to-7.x".
*/
+
+/**
+ * @addtogroup updates-7.x-extra
+ * @{
+ */
+
+/**
+ * Bind associations to their providers.
+ */
+function openid_update_7000() {
+ db_drop_table('openid_association');
+
+ $schema = array(
+ 'description' => 'Stores temporary shared key association information for OpenID authentication.',
+ 'fields' => array(
+ 'idp_endpoint_uri' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'description' => 'Primary Key: URI of the OpenID Provider endpoint.',
+ ),
+ 'assoc_handle' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'description' => 'Used to refer to this association in subsequent messages.',
+ ),
+ 'assoc_type' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'description' => 'The signature algorithm used: one of HMAC-SHA1 or HMAC-SHA256.',
+ ),
+ 'session_type' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'description' => 'Valid association session types: "no-encryption", "DH-SHA1", and "DH-SHA256".',
+ ),
+ 'mac_key' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'description' => 'The MAC key (shared secret) for this association.',
+ ),
+ 'created' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'UNIX timestamp for when the association was created.',
+ ),
+ 'expires_in' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'The lifetime, in seconds, of this association.',
+ ),
+ ),
+ 'primary key' => array('idp_endpoint_uri'),
+ 'unique keys' => array(
+ 'assoc_handle' => array('assoc_handle'),
+ ),
+ );
+ db_create_table('openid_association', $schema);
+}
+
+/**
+ * @} End of "addtogroup updates-7.x-extra".
+ */
diff -Naur drupal-7.23/modules/openid/openid.module drupal-7.66/modules/openid/openid.module
--- drupal-7.23/modules/openid/openid.module 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/openid/openid.module 2019-04-17 22:20:46.000000000 +0200
@@ -365,14 +365,20 @@
// to the OpenID Provider, we need to do discovery on the returned
// identififer to make sure that the provider is authorized to
// respond on behalf of this.
- if ($response_claimed_id != $claimed_id) {
+ if ($response_claimed_id != $claimed_id || $response_claimed_id != $response['openid.identity']) {
$discovery = openid_discovery($response['openid.claimed_id']);
+ $uris = array();
if ($discovery && !empty($discovery['services'])) {
- $uris = array();
foreach ($discovery['services'] as $discovered_service) {
- if (in_array('http://specs.openid.net/auth/2.0/server', $discovered_service['types']) || in_array('http://specs.openid.net/auth/2.0/signon', $discovered_service['types'])) {
- $uris[] = $discovered_service['uri'];
+ if (!in_array('http://specs.openid.net/auth/2.0/server', $discovered_service['types']) && !in_array('http://specs.openid.net/auth/2.0/signon', $discovered_service['types'])) {
+ continue;
}
+ // The OP-Local Identifier (if different than the Claimed
+ // Identifier) must be present in the XRDS document.
+ if ($response_claimed_id != $response['openid.identity'] && (!isset($discovered_service['identity']) || $discovered_service['identity'] != $response['openid.identity'])) {
+ continue;
+ }
+ $uris[] = $discovered_service['uri'];
}
}
if (!in_array($service['uri'], $uris)) {
@@ -839,7 +845,7 @@
// direct verification: ignore the openid.assoc_handle, even if present.
// See http://openid.net/specs/openid-authentication-2_0.html#rfc.section.11.4.1
if (!empty($response['openid.assoc_handle']) && empty($response['openid.invalidate_handle'])) {
- $association = db_query("SELECT * FROM {openid_association} WHERE assoc_handle = :assoc_handle", array(':assoc_handle' => $response['openid.assoc_handle']))->fetchObject();
+ $association = db_query("SELECT * FROM {openid_association} WHERE idp_endpoint_uri = :endpoint AND assoc_handle = :assoc_handle", array(':endpoint' => $service['uri'], ':assoc_handle' => $response['openid.assoc_handle']))->fetchObject();
}
if ($association && isset($association->session_type)) {
@@ -871,6 +877,7 @@
// database to avoid reusing it again on a subsequent authentication request.
// See http://openid.net/specs/openid-authentication-2_0.html#rfc.section.11.4.2.2
db_delete('openid_association')
+ ->condition('idp_endpoint_uri', $service['uri'])
->condition('assoc_handle', $response['invalidate_handle'])
->execute();
}
diff -Naur drupal-7.23/modules/openid/openid.test drupal-7.66/modules/openid/openid.test
--- drupal-7.23/modules/openid/openid.test 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/openid/openid.test 2019-04-17 22:20:46.000000000 +0200
@@ -94,7 +94,7 @@
$identity = url('openid-test/yadis/xrds/dummy-user', array('absolute' => TRUE, 'fragment' => $this->randomName()));
// Tell openid_test.module to respond with this identifier. If the fragment
// part is present in the identifier, it should be retained.
- variable_set('openid_test_response', array('openid.claimed_id' => $identity));
+ variable_set('openid_test_response', array('openid.claimed_id' => $identity, 'openid.identity' => openid_normalize($identity)));
$this->addIdentity(url('openid-test/yadis/xrds/server', array('absolute' => TRUE)), 2, 'http://specs.openid.net/auth/2.0/identifier_select', $identity);
variable_set('openid_test_response', array());
@@ -680,11 +680,11 @@
* Test _openid_dh_XXX_to_XXX() functions.
*/
function testConversion() {
- $this->assertEqual(_openid_dh_long_to_base64('12345678901234567890123456789012345678901234567890'), 'CHJ/Y2mq+DyhUCZ0evjH8ZbOPwrS', '_openid_dh_long_to_base64() returned expected result.');
- $this->assertEqual(_openid_dh_base64_to_long('BsH/g8Nrpn2dtBSdu/sr1y8hxwyx'), '09876543210987654321098765432109876543210987654321', '_openid_dh_base64_to_long() returned expected result.');
+ $this->assertIdentical(_openid_dh_long_to_base64('12345678901234567890123456789012345678901234567890'), 'CHJ/Y2mq+DyhUCZ0evjH8ZbOPwrS', '_openid_dh_long_to_base64() returned expected result.');
+ $this->assertIdentical(_openid_dh_base64_to_long('BsH/g8Nrpn2dtBSdu/sr1y8hxwyx'), '9876543210987654321098765432109876543210987654321', '_openid_dh_base64_to_long() returned expected result.');
- $this->assertEqual(_openid_dh_long_to_binary('12345678901234567890123456789012345678901234567890'), "\x08r\x7fci\xaa\xf8<\xa1P&tz\xf8\xc7\xf1\x96\xce?\x0a\xd2", '_openid_dh_long_to_binary() returned expected result.');
- $this->assertEqual(_openid_dh_binary_to_long("\x06\xc1\xff\x83\xc3k\xa6}\x9d\xb4\x14\x9d\xbb\xfb+\xd7/!\xc7\x0c\xb1"), '09876543210987654321098765432109876543210987654321', '_openid_dh_binary_to_long() returned expected result.');
+ $this->assertIdentical(_openid_dh_long_to_binary('12345678901234567890123456789012345678901234567890'), "\x08r\x7fci\xaa\xf8<\xa1P&tz\xf8\xc7\xf1\x96\xce?\x0a\xd2", '_openid_dh_long_to_binary() returned expected result.');
+ $this->assertIdentical(_openid_dh_binary_to_long("\x06\xc1\xff\x83\xc3k\xa6}\x9d\xb4\x14\x9d\xbb\xfb+\xd7/!\xc7\x0c\xb1"), '9876543210987654321098765432109876543210987654321', '_openid_dh_binary_to_long() returned expected result.');
}
/**
@@ -695,13 +695,6 @@
}
/**
- * Test _openid_get_bytes().
- */
- function testOpenidGetBytes() {
- $this->assertEqual(strlen(_openid_get_bytes(20)), 20, '_openid_get_bytes() returned expected result.');
- }
-
- /**
* Test _openid_signature().
*/
function testOpenidSignature() {
diff -Naur drupal-7.23/modules/openid/tests/openid_test.info drupal-7.66/modules/openid/tests/openid_test.info
--- drupal-7.23/modules/openid/tests/openid_test.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/openid/tests/openid_test.info 2019-04-17 22:39:36.000000000 +0200
@@ -6,8 +6,7 @@
dependencies[] = openid
hidden = TRUE
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/openid/tests/openid_test.install drupal-7.66/modules/openid/tests/openid_test.install
--- drupal-7.23/modules/openid/tests/openid_test.install 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/openid/tests/openid_test.install 2019-04-17 22:20:46.000000000 +0200
@@ -13,5 +13,5 @@
// Generate a MAC key (Message Authentication Code) used for signing messages.
// The variable is base64-encoded, because variables cannot contain non-UTF-8
// data.
- variable_set('openid_test_mac_key', base64_encode(_openid_get_bytes(20)));
+ variable_set('openid_test_mac_key', drupal_random_key(20));
}
diff -Naur drupal-7.23/modules/openid/tests/openid_test.module drupal-7.66/modules/openid/tests/openid_test.module
--- drupal-7.23/modules/openid/tests/openid_test.module 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/openid/tests/openid_test.module 2019-04-17 22:20:46.000000000 +0200
@@ -150,6 +150,7 @@
http://specs.openid.net/auth/2.0/server
' . url('openid-test/endpoint', array('absolute' => TRUE)) . '
+ ' . url('openid-test/yadis/xrds/server', array('absolute' => TRUE)) . '
';
}
elseif (arg(3) == 'delegate') {
diff -Naur drupal-7.23/modules/overlay/overlay-parent.js drupal-7.66/modules/overlay/overlay-parent.js
--- drupal-7.23/modules/overlay/overlay-parent.js 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/overlay/overlay-parent.js 2019-04-17 22:20:46.000000000 +0200
@@ -350,7 +350,7 @@
* TRUE if the URL represents an administrative link, FALSE otherwise.
*/
Drupal.overlay.isAdminLink = function (url) {
- if (Drupal.overlay.isExternalLink(url)) {
+ if (!Drupal.urlIsLocal(url)) {
return false;
}
@@ -378,6 +378,8 @@
/**
* Determine whether a link is external to the site.
*
+ * Deprecated. Use Drupal.urlIsLocal() instead.
+ *
* @param url
* The URL to be tested.
*
@@ -385,8 +387,28 @@
* TRUE if the URL is external to the site, FALSE otherwise.
*/
Drupal.overlay.isExternalLink = function (url) {
- var re = RegExp('^((f|ht)tps?:)?//(?!' + window.location.host + ')');
- return re.test(url);
+ return !Drupal.urlIsLocal(url);
+};
+
+/**
+ * Constructs an internal URL (relative to this site) from the provided path.
+ *
+ * For example, if the provided path is 'admin' and the site is installed at
+ * http://example.com/drupal, this function will return '/drupal/admin'.
+ *
+ * @param path
+ * The internal path, without any leading slash.
+ *
+ * @return
+ * The internal URL derived from the provided path, or null if a valid
+ * internal path cannot be constructed (for example, if an attempt to create
+ * an external link is detected).
+ */
+Drupal.overlay.getInternalUrl = function (path) {
+ var url = Drupal.settings.basePath + path;
+ if (Drupal.urlIsLocal(url)) {
+ return url;
+ }
};
/**
@@ -577,7 +599,7 @@
// If the link contains the overlay-restore class and the overlay-context
// state is set, also update the parent window's location.
var parentLocation = ($target.hasClass('overlay-restore') && typeof $.bbq.getState('overlay-context') == 'string')
- ? Drupal.settings.basePath + $.bbq.getState('overlay-context')
+ ? this.getInternalUrl($.bbq.getState('overlay-context'))
: null;
href = this.fragmentizeLink($target.get(0), parentLocation);
// Only override default behavior when left-clicking and user is not
@@ -657,11 +679,15 @@
}
// Get the overlay URL from the current URL fragment.
+ var internalUrl = null;
var state = $.bbq.getState('overlay');
if (state) {
+ internalUrl = this.getInternalUrl(state);
+ }
+ if (internalUrl) {
// Append render variable, so the server side can choose the right
// rendering and add child frame code to the page if needed.
- var url = $.param.querystring(Drupal.settings.basePath + state, { render: 'overlay' });
+ var url = $.param.querystring(internalUrl, { render: 'overlay' });
this.open(url);
this.resetActiveClass(this.getPath(Drupal.settings.basePath + state));
diff -Naur drupal-7.23/modules/overlay/overlay.info drupal-7.66/modules/overlay/overlay.info
--- drupal-7.23/modules/overlay/overlay.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/overlay/overlay.info 2019-04-17 22:39:36.000000000 +0200
@@ -4,8 +4,7 @@
version = VERSION
core = 7.x
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/overlay/overlay.module drupal-7.66/modules/overlay/overlay.module
--- drupal-7.23/modules/overlay/overlay.module 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/overlay/overlay.module 2019-04-17 22:20:46.000000000 +0200
@@ -79,6 +79,20 @@
}
/**
+ * Implements hook_form_alter().
+ */
+function overlay_form_alter(&$form, &$form_state) {
+ // Add a hidden element to prevent dropping out of the overlay when a form is
+ // submitted inside the overlay using a GET method.
+ if (isset($form['#method']) && $form['#method'] == 'get' && isset($_REQUEST['render']) && $_REQUEST['render'] == 'overlay' && !isset($form['render'])) {
+ $form['render'] = array(
+ '#type' => 'hidden',
+ '#value' => 'overlay',
+ );
+ }
+}
+
+/**
* Implements hook_form_FORM_ID_alter().
*/
function overlay_form_user_profile_form_alter(&$form, &$form_state) {
@@ -146,6 +160,10 @@
// If this page shouldn't be rendered inside the overlay, redirect to the
// parent.
elseif (!path_is_admin($current_path)) {
+ // Prevent open redirects by ensuring the current path is not an absolute URL.
+ if (url_is_external($current_path)) {
+ $current_path = '';
+ }
overlay_close_dialog($current_path, array('query' => drupal_get_query_parameters(NULL, array('q', 'render'))));
}
diff -Naur drupal-7.23/modules/path/path.info drupal-7.66/modules/path/path.info
--- drupal-7.23/modules/path/path.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/path/path.info 2019-04-17 22:39:36.000000000 +0200
@@ -6,8 +6,7 @@
files[] = path.test
configure = admin/config/search/path
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/path/path.module drupal-7.66/modules/path/path.module
--- drupal-7.23/modules/path/path.module 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/path/path.module 2019-04-17 22:20:46.000000000 +0200
@@ -185,7 +185,7 @@
* Implements hook_node_insert().
*/
function path_node_insert($node) {
- if (isset($node->path)) {
+ if (isset($node->path) && isset($node->path['alias'])) {
$path = $node->path;
$path['alias'] = trim($path['alias']);
// Only save a non-empty alias.
@@ -205,9 +205,9 @@
function path_node_update($node) {
if (isset($node->path)) {
$path = $node->path;
- $path['alias'] = trim($path['alias']);
+ $path['alias'] = isset($path['alias']) ? trim($path['alias']) : '';
// Delete old alias if user erased it.
- if (!empty($path['pid']) && empty($path['alias'])) {
+ if (!empty($path['pid']) && !$path['alias']) {
path_delete($path['pid']);
}
path_node_insert($node);
diff -Naur drupal-7.23/modules/path/path.test drupal-7.66/modules/path/path.test
--- drupal-7.23/modules/path/path.test 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/path/path.test 2019-04-17 22:20:46.000000000 +0200
@@ -21,7 +21,7 @@
parent::setUp('path');
// Create test user and login.
- $web_user = $this->drupalCreateUser(array('create page content', 'edit own page content', 'administer url aliases', 'create url aliases'));
+ $web_user = $this->drupalCreateUser(array('create page content', 'edit own page content', 'administer url aliases', 'create url aliases', 'access content overview'));
$this->drupalLogin($web_user);
}
@@ -160,6 +160,34 @@
$this->drupalGet($edit['path[alias]']);
$this->assertNoText($node1->title, 'Alias was successfully deleted.');
$this->assertResponse(404);
+
+ // Create third test node.
+ $node3 = $this->drupalCreateNode();
+
+ // Create an invalid alias with a leading slash and verify that the slash
+ // is removed when the link is generated. This ensures that URL aliases
+ // cannot be used to inject external URLs.
+ // @todo The user interface should either display an error message or
+ // automatically trim these invalid aliases, rather than allowing them to
+ // be silently created, at which point the functional aspects of this
+ // test will need to be moved elsewhere and switch to using a
+ // programmatically-created alias instead.
+ $alias = $this->randomName(8);
+ $edit = array('path[alias]' => '/' . $alias);
+ $this->drupalPost('node/' . $node3->nid . '/edit', $edit, t('Save'));
+ $this->drupalGet('admin/content');
+ // This checks the link href before clicking it, rather than using
+ // DrupalWebTestCase::assertUrl() after clicking it, because the test
+ // browser does not always preserve the correct number of slashes in the
+ // URL when it visits internal links; using DrupalWebTestCase::assertUrl()
+ // would actually make the test pass unconditionally on the testbot (or
+ // anywhere else where Drupal is installed in a subdirectory).
+ $link_xpath = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $node3->title));
+ $link_href = (string) $link_xpath[0]['href'];
+ $link_prefix = base_path() . (variable_get('clean_url', 0) ? '' : '?q=');
+ $this->assertEqual($link_href, $link_prefix . $alias);
+ $this->clickLink($node3->title);
+ $this->assertResponse(404);
}
/**
diff -Naur drupal-7.23/modules/php/php.info drupal-7.66/modules/php/php.info
--- drupal-7.23/modules/php/php.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/php/php.info 2019-04-17 22:39:36.000000000 +0200
@@ -5,8 +5,7 @@
core = 7.x
files[] = php.test
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/php/php.module drupal-7.66/modules/php/php.module
--- drupal-7.23/modules/php/php.module 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/php/php.module 2019-04-17 22:20:46.000000000 +0200
@@ -17,7 +17,7 @@
$output .= '' . t('Uses') . '
';
$output .= '';
$output .= '- ' . t('Enabling execution of PHP in text fields') . '
';
- $output .= '- ' . t('The PHP filter module allows users with the proper permissions to include custom PHP code that will get executed when pages of your site are processed. While this is a powerful and flexible feature if used by a trusted user with PHP experience, it is a significant and dangerous security risk in the hands of a malicious or inexperienced user. Even a trusted user may accidentally compromise the site by entering malformed or incorrect PHP code. Only the most trusted users should be granted permission to use the PHP filter, and all PHP code added through the PHP filter should be carefully examined before use. Example PHP snippets can be found on Drupal.org.', array('@php-snippets' => url('http://http://drupal.org/documentation/customization/php-snippets'))) . '
';
+ $output .= '- ' . t('The PHP filter module allows users with the proper permissions to include custom PHP code that will get executed when pages of your site are processed. While this is a powerful and flexible feature if used by a trusted user with PHP experience, it is a significant and dangerous security risk in the hands of a malicious or inexperienced user. Even a trusted user may accidentally compromise the site by entering malformed or incorrect PHP code. Only the most trusted users should be granted permission to use the PHP filter, and all PHP code added through the PHP filter should be carefully examined before use. Example PHP snippets can be found on Drupal.org.', array('@php-snippets' => url('http://drupal.org/documentation/customization/php-snippets'))) . '
';
$output .= '
';
return $output;
}
diff -Naur drupal-7.23/modules/poll/poll.info drupal-7.66/modules/poll/poll.info
--- drupal-7.23/modules/poll/poll.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/poll/poll.info 2019-04-17 22:39:36.000000000 +0200
@@ -6,8 +6,7 @@
files[] = poll.test
stylesheets[all][] = poll.css
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/poll/poll.module drupal-7.66/modules/poll/poll.module
--- drupal-7.23/modules/poll/poll.module 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/poll/poll.module 2019-04-17 22:20:46.000000000 +0200
@@ -191,7 +191,6 @@
'base' => 'poll',
'description' => t('A poll is a question with a set of possible responses. A poll, once created, automatically provides a simple running count of the number of votes received for each response.'),
'title_label' => t('Question'),
- 'has_body' => FALSE,
)
);
}
@@ -249,6 +248,7 @@
'#title' => check_plain($type->title_label),
'#required' => TRUE,
'#default_value' => $node->title,
+ '#maxlength' => 255,
'#weight' => -5,
);
@@ -631,9 +631,6 @@
* The node object to load.
*/
function poll_block_latest_poll_view($node) {
- global $user;
- $output = '';
-
// This is necessary for shared objects because PHP doesn't copy objects, but
// passes them by reference. So when the objects are cached it can result in
// the wrong output being displayed on subsequent calls. The cloning and
@@ -674,9 +671,6 @@
* Implements hook_view().
*/
function poll_view($node, $view_mode) {
- global $user;
- $output = '';
-
if (!empty($node->allowvotes) && empty($node->show_results)) {
$node->content['poll_view_voting'] = drupal_get_form('poll_view_voting', $node);
}
@@ -694,7 +688,7 @@
function poll_teaser($node) {
$teaser = NULL;
if (is_array($node->choice)) {
- foreach ($node->choice as $k => $choice) {
+ foreach ($node->choice as $choice) {
if ($choice['chtext'] != '') {
$teaser .= '* ' . check_plain($choice['chtext']) . "\n";
}
@@ -720,7 +714,6 @@
'#type' => 'radios',
'#title' => t('Choices'),
'#title_display' => 'invisible',
- '#default_value' => -1,
'#options' => $list,
);
}
@@ -748,7 +741,7 @@
* Validation function for processing votes
*/
function poll_view_voting_validate($form, &$form_state) {
- if ($form_state['values']['choice'] == -1) {
+ if (empty($form_state['values']['choice'])) {
form_set_error( 'choice', t('Your vote could not be recorded because you did not select any of the choices.'));
}
}
@@ -925,7 +918,6 @@
*
* @see poll-bar.tpl.php
* @see poll-bar--block.tpl.php
- * @see theme_poll_bar()
*/
function template_preprocess_poll_bar(&$variables) {
if ($variables['block']) {
diff -Naur drupal-7.23/modules/poll/poll.test drupal-7.66/modules/poll/poll.test
--- drupal-7.23/modules/poll/poll.test 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/poll/poll.test 2019-04-17 22:20:46.000000000 +0200
@@ -315,6 +315,11 @@
$this->drupalLogin($vote_user);
+ // Record a vote without selecting any choice.
+ $edit = array();
+ $this->drupalPost('node/' . $poll_nid, $edit, t('Vote'));
+ $this->assertText(t('Your vote could not be recorded because you did not select any of the choices.'), 'Found the empty poll submission error message.');
+
// Record a vote for the first choice.
$edit = array(
'choice' => '1',
diff -Naur drupal-7.23/modules/profile/profile.info drupal-7.66/modules/profile/profile.info
--- drupal-7.23/modules/profile/profile.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/profile/profile.info 2019-04-17 22:39:36.000000000 +0200
@@ -11,8 +11,7 @@
; See user_system_info_alter().
hidden = TRUE
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/profile/profile.module drupal-7.66/modules/profile/profile.module
--- drupal-7.23/modules/profile/profile.module 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/profile/profile.module 2019-04-17 22:20:46.000000000 +0200
@@ -571,6 +571,7 @@
// Supply filtered version of $fields that have values.
foreach ($variables['fields'] as $field) {
if ($field->value) {
+ $variables['profile'][$field->name] = new stdClass();
$variables['profile'][$field->name]->title = $field->title;
$variables['profile'][$field->name]->value = $field->value;
$variables['profile'][$field->name]->type = $field->type;
diff -Naur drupal-7.23/modules/profile/profile.pages.inc drupal-7.66/modules/profile/profile.pages.inc
--- drupal-7.23/modules/profile/profile.pages.inc 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/profile/profile.pages.inc 2019-04-17 22:20:46.000000000 +0200
@@ -17,17 +17,15 @@
if ($name && $field->fid) {
// Only allow browsing of fields that have a page title set.
if (empty($field->page)) {
- drupal_not_found();
- return;
+ return MENU_NOT_FOUND;
}
// Do not allow browsing of private and hidden fields by non-admins.
if (!user_access('administer users') && ($field->visibility == PROFILE_PRIVATE || $field->visibility == PROFILE_HIDDEN)) {
- drupal_access_denied();
- return;
+ return MENU_ACCESS_DENIED;
}
// Compile a list of fields to show.
- $fields = db_query('SELECT name, title, type, weight, page FROM {profile_field} WHERE fid <> :fid AND visibility = :visibility ORDER BY weight', array(
+ $fields = db_query('SELECT name, title, type, weight, page, visibility FROM {profile_field} WHERE fid <> :fid AND visibility = :visibility ORDER BY weight', array(
':fid' => $field->fid,
':visibility' => PROFILE_PUBLIC_LISTINGS,
))->fetchAll();
@@ -54,8 +52,7 @@
$query->condition('v.value', '%' . db_like($value) . '%', 'LIKE');
break;
default:
- drupal_not_found();
- return;
+ return MENU_NOT_FOUND;
}
$uids = $query
@@ -85,7 +82,7 @@
return $output;
}
elseif ($name && !$field->fid) {
- drupal_not_found();
+ return MENU_NOT_FOUND;
}
else {
// Compile a list of fields to show.
diff -Naur drupal-7.23/modules/profile/profile.test drupal-7.66/modules/profile/profile.test
--- drupal-7.23/modules/profile/profile.test 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/profile/profile.test 2019-04-17 22:20:46.000000000 +0200
@@ -42,25 +42,25 @@
$this->drupalPost('admin/config/people/profile/add/' . $type, $edit, t('Save field'));
$fid = db_query("SELECT fid FROM {profile_field} WHERE title = :title", array(':title' => $title))->fetchField();
- $this->assertTrue($fid, t('New Profile field has been entered in the database'));
+ $this->assertTrue($fid, 'New Profile field has been entered in the database');
// Check that the new field is appearing on the user edit form.
$this->drupalGet('user/' . $this->admin_user->uid . '/edit/' . $category);
// Checking field.
if ($type == 'date') {
- $this->assertField($form_name . '[month]', t('Found month selection field'));
- $this->assertField($form_name . '[day]', t('Found day selection field'));
- $this->assertField($form_name . '[year]', t('Found day selection field'));
+ $this->assertField($form_name . '[month]', 'Found month selection field');
+ $this->assertField($form_name . '[day]', 'Found day selection field');
+ $this->assertField($form_name . '[year]', 'Found day selection field');
}
else {
- $this->assertField($form_name , t('Found form named @name', array('@name' => $form_name)));
+ $this->assertField($form_name , format_string('Found form named @name', array('@name' => $form_name)));
}
// Checking name.
- $this->assertText($title, t('Checking title for field %title', array('%title' => $title)));
+ $this->assertText($title, format_string('Checking title for field %title', array('%title' => $title)));
// Checking explanation.
- $this->assertText($edit['explanation'], t('Checking explanation for field %title', array('%title' => $title)));
+ $this->assertText($edit['explanation'], format_string('Checking explanation for field %title', array('%title' => $title)));
return array(
'fid' => $fid,
@@ -96,18 +96,18 @@
// Checking field.
if ($type == 'date') {
- $this->assertField($form_name . '[month]', t('Found month selection field'));
- $this->assertField($form_name . '[day]', t('Found day selection field'));
- $this->assertField($form_name . '[year]', t('Found day selection field'));
+ $this->assertField($form_name . '[month]', 'Found month selection field');
+ $this->assertField($form_name . '[day]', 'Found day selection field');
+ $this->assertField($form_name . '[year]', 'Found day selection field');
}
else {
- $this->assertField($form_name , t('Found form named @name', array('@name' => $form_name)));
+ $this->assertField($form_name , format_string('Found form named @name', array('@name' => $form_name)));
}
// Checking name.
- $this->assertText($title, t('Checking title for field %title', array('%title' => $title)));
+ $this->assertText($title, format_string('Checking title for field %title', array('%title' => $title)));
// Checking explanation.
- $this->assertText($edit['explanation'], t('Checking explanation for field %title', array('%title' => $title)));
+ $this->assertText($edit['explanation'], format_string('Checking explanation for field %title', array('%title' => $title)));
return array(
'fid' => $fid,
@@ -141,11 +141,11 @@
// Check profile page.
$content = $this->drupalGet('user/' . $this->normal_user->uid);
- $this->assertText($field['title'], t('Found profile field with title %title', array('%title' => $field['title'])));
+ $this->assertText($field['title'], format_string('Found profile field with title %title', array('%title' => $field['title'])));
if ($field['type'] != 'checkbox') {
// $value must be cast to a string in order to be found by assertText.
- $this->assertText("$value", t('Found profile field with value %value', array('%value' => $value)));
+ $this->assertText("$value", format_string('Found profile field with value %value', array('%value' => $value)));
}
return $value;
@@ -160,7 +160,7 @@
function deleteProfileField($field) {
$this->drupalPost('admin/config/people/profile/delete/' . $field['fid'], array(), t('Delete'));
$this->drupalGet('admin/config/people/profile');
- $this->assertNoText($field['title'], t('Checking deleted field %title', array('%title' => $field['title'])));
+ $this->assertNoText($field['title'], format_string('Checking deleted field %title', array('%title' => $field['title'])));
}
}
@@ -270,9 +270,9 @@
// Check profile page.
$this->drupalGet('user/' . $this->normal_user->uid);
- $this->assertText($field['title'], t('Found profile field with title %title', array('%title' => $field['title'])));
+ $this->assertText($field['title'], format_string('Found profile field with title %title', array('%title' => $field['title'])));
- $this->assertText('01/09/1983', t('Found date profile field.'));
+ $this->assertText('01/09/1983', 'Found date profile field.');
$edit = array(
'name' => $field['form_name'],
@@ -305,10 +305,10 @@
$this->setProfileField($field2, $this->randomName(8));
$profile_edit = $this->drupalGet('user/' . $this->normal_user->uid . '/edit/' . $category);
- $this->assertTrue(strpos($profile_edit, $field1['title']) > strpos($profile_edit, $field2['title']), t('Profile field weights are respected on the user edit form.'));
+ $this->assertTrue(strpos($profile_edit, $field1['title']) > strpos($profile_edit, $field2['title']), 'Profile field weights are respected on the user edit form.');
$profile_page = $this->drupalGet('user/' . $this->normal_user->uid);
- $this->assertTrue(strpos($profile_page, $field1['title']) > strpos($profile_page, $field2['title']), t('Profile field weights are respected on the user profile page.'));
+ $this->assertTrue(strpos($profile_page, $field1['title']) > strpos($profile_page, $field2['title']), 'Profile field weights are respected on the user profile page.');
}
}
@@ -339,20 +339,30 @@
$this->setProfileField($field, $field['value']);
// Set some html for what we want to see in the page output later.
- $autocomplete_html = '';
- $field_html = '';
+ // Autocomplete always uses non-clean URLs.
+ $current_clean_url = isset($GLOBALS['conf']['clean_url']) ? $GLOBALS['conf']['clean_url'] : NULL;
+ $GLOBALS['conf']['clean_url'] = 0;
+ $autocomplete_url = url('profile/autocomplete/' . $field['fid'], array('absolute' => TRUE, 'script' => 'index.php'));
+ $GLOBALS['conf']['clean_url'] = $current_clean_url;
+ $autocomplete_id = drupal_html_id('edit-' . $field['form_name'] . '-autocomplete');
+ $autocomplete_html = '';
// Check that autocompletion html is found on the user's profile edit page.
$this->drupalGet('user/' . $this->admin_user->uid . '/edit/' . $category);
- $this->assertRaw($autocomplete_html, t('Autocomplete found.'));
- $this->assertRaw('misc/autocomplete.js', t('Autocomplete JavaScript found.'));
- $this->assertRaw('class="form-text form-autocomplete"', t('Autocomplete form element class found.'));
+ $this->assertRaw($autocomplete_html, 'Autocomplete found.');
+ $this->assertFieldByXPath(
+ '//input[@type="text" and @name="' . $field['form_name'] . '" and contains(@class, "form-autocomplete")]',
+ '',
+ 'Text input field found'
+ );
+ $this->assertRaw('misc/autocomplete.js', 'Autocomplete JavaScript found.');
+ $this->assertRaw('class="form-text form-autocomplete"', 'Autocomplete form element class found.');
// Check the autocompletion path using the first letter of our user's profile
// field value to make sure access is allowed and a valid result if found.
$this->drupalGet('profile/autocomplete/' . $field['fid'] . '/' . $field['value'][0]);
- $this->assertResponse(200, t('Autocomplete path allowed to user with permission.'));
- $this->assertRaw($field['value'], t('Autocomplete value found.'));
+ $this->assertResponse(200, 'Autocomplete path allowed to user with permission.');
+ $this->assertRaw($field['value'], 'Autocomplete value found.');
// Logout and login with a user without the 'access user profiles' permission.
$this->drupalLogout();
@@ -360,11 +370,11 @@
// Check that autocompletion html is not found on the user's profile edit page.
$this->drupalGet('user/' . $this->normal_user->uid . '/edit/' . $category);
- $this->assertNoRaw($autocomplete_html, t('Autocomplete not found.'));
+ $this->assertNoRaw($autocomplete_html, 'Autocomplete not found.');
// User should be denied access to the profile autocomplete path.
$this->drupalGet('profile/autocomplete/' . $field['fid'] . '/' . $field['value'][0]);
- $this->assertResponse(403, t('Autocomplete path denied to user without permission.'));
+ $this->assertResponse(403, 'Autocomplete path denied to user without permission.');
}
}
@@ -403,48 +413,48 @@
$edit = array();
$edit['blocks[profile_author-information][region]'] = 'footer';
$this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
- $this->assertText(t('The block settings have been updated.'), t('Block successfully move to footer region.'));
+ $this->assertText(t('The block settings have been updated.'), 'Block successfully move to footer region.');
// Enable field 1.
$this->drupalPost('admin/structure/block/manage/profile/author-information/configure', array(
'profile_block_author_fields[' . $this->field1['form_name'] . ']' => TRUE,
), t('Save block'));
- $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.'));
+ $this->assertText(t('The block configuration has been saved.'), 'Block configuration set.');
// Visit the node and confirm that the field is displayed.
$this->drupalGet('node/' . $this->node->nid);
- $this->assertRaw($this->value1, t('Field 1 is displayed'));
- $this->assertNoRaw($this->value2, t('Field 2 is not displayed'));
+ $this->assertRaw($this->value1, 'Field 1 is displayed');
+ $this->assertNoRaw($this->value2, 'Field 2 is not displayed');
// Enable only field 2.
$this->drupalPost('admin/structure/block/manage/profile/author-information/configure', array(
'profile_block_author_fields[' . $this->field1['form_name'] . ']' => FALSE,
'profile_block_author_fields[' . $this->field2['form_name'] . ']' => TRUE,
), t('Save block'));
- $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.'));
+ $this->assertText(t('The block configuration has been saved.'), 'Block configuration set.');
// Visit the node and confirm that the field is displayed.
$this->drupalGet('node/' . $this->node->nid);
- $this->assertNoRaw($this->value1, t('Field 1 is not displayed'));
- $this->assertRaw($this->value2, t('Field 2 is displayed'));
+ $this->assertNoRaw($this->value1, 'Field 1 is not displayed');
+ $this->assertRaw($this->value2, 'Field 2 is displayed');
// Enable both fields.
$this->drupalPost('admin/structure/block/manage/profile/author-information/configure', array(
'profile_block_author_fields[' . $this->field1['form_name'] . ']' => TRUE,
'profile_block_author_fields[' . $this->field2['form_name'] . ']' => TRUE,
), t('Save block'));
- $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.'));
+ $this->assertText(t('The block configuration has been saved.'), 'Block configuration set.');
// Visit the node and confirm that the field is displayed.
$this->drupalGet('node/' . $this->node->nid);
- $this->assertRaw($this->value1, t('Field 1 is displayed'));
- $this->assertRaw($this->value2, t('Field 2 is displayed'));
+ $this->assertRaw($this->value1, 'Field 1 is displayed');
+ $this->assertRaw($this->value2, 'Field 2 is displayed');
// Enable the link to the user profile.
$this->drupalPost('admin/structure/block/manage/profile/author-information/configure', array(
'profile_block_author_fields[user_profile]' => TRUE,
), t('Save block'));
- $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.'));
+ $this->assertText(t('The block configuration has been saved.'), 'Block configuration set.');
// Visit the node and confirm that the user profile link is displayed.
$this->drupalGet('node/' . $this->node->nid);
diff -Naur drupal-7.23/modules/rdf/rdf.info drupal-7.66/modules/rdf/rdf.info
--- drupal-7.23/modules/rdf/rdf.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/rdf/rdf.info 2019-04-17 22:39:36.000000000 +0200
@@ -5,8 +5,7 @@
core = 7.x
files[] = rdf.test
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/rdf/rdf.module drupal-7.66/modules/rdf/rdf.module
--- drupal-7.23/modules/rdf/rdf.module 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/rdf/rdf.module 2019-04-17 22:20:46.000000000 +0200
@@ -190,17 +190,33 @@
* An RDF mapping structure or an empty array if no record was found.
*/
function _rdf_mapping_load($type, $bundle) {
- $mapping = db_select('rdf_mapping')
- ->fields(NULL, array('mapping'))
+ $mappings = _rdf_mapping_load_multiple($type, array($bundle));
+ return $mappings ? reset($mappings) : array();
+}
+
+/**
+ * Helper function to retrieve a set of RDF mappings from the database.
+ *
+ * @param $type
+ * The entity type of the mappings.
+ * @param $bundles
+ * The bundles the mappings refer to.
+ *
+ * @return
+ * An array of RDF mapping structures, or an empty array.
+ */
+function _rdf_mapping_load_multiple($type, array $bundles) {
+ $mappings = db_select('rdf_mapping')
+ ->fields(NULL, array('bundle', 'mapping'))
->condition('type', $type)
- ->condition('bundle', $bundle)
+ ->condition('bundle', $bundles)
->execute()
- ->fetchField();
+ ->fetchAllKeyed();
- if (!$mapping) {
- return array();
+ foreach ($mappings as $bundle => $mapping) {
+ $mappings[$bundle] = unserialize($mapping);
}
- return unserialize($mapping);
+ return $mappings;
}
/**
@@ -368,10 +384,13 @@
function rdf_entity_info_alter(&$entity_info) {
// Loop through each entity type and its bundles.
foreach ($entity_info as $entity_type => $entity_type_info) {
- if (isset($entity_type_info['bundles'])) {
- foreach ($entity_type_info['bundles'] as $bundle => $bundle_info) {
- if ($mapping = _rdf_mapping_load($entity_type, $bundle)) {
- $entity_info[$entity_type]['bundles'][$bundle]['rdf_mapping'] = $mapping;
+ if (!empty($entity_type_info['bundles'])) {
+ $bundles = array_keys($entity_type_info['bundles']);
+ $mappings = _rdf_mapping_load_multiple($entity_type, $bundles);
+
+ foreach ($bundles as $bundle) {
+ if (isset($mappings[$bundle])) {
+ $entity_info[$entity_type]['bundles'][$bundle]['rdf_mapping'] = $mappings[$bundle];
}
else {
// If no mapping was found in the database, assign the default RDF
@@ -471,27 +490,17 @@
$variables['attributes_array']['about'] = empty($variables['node_url']) ? NULL: $variables['node_url'];
$variables['attributes_array']['typeof'] = empty($variables['node']->rdf_mapping['rdftype']) ? NULL : $variables['node']->rdf_mapping['rdftype'];
- // Adds RDFa markup to the title of the node. Because the RDFa markup is
- // added to the tag which might contain HTML code, we specify an empty
- // datatype to ensure the value of the title read by the RDFa parsers is a
- // literal.
- $variables['title_attributes_array']['property'] = empty($variables['node']->rdf_mapping['title']['predicates']) ? NULL : $variables['node']->rdf_mapping['title']['predicates'];
- $variables['title_attributes_array']['datatype'] = '';
-
- // In full node mode, the title is not displayed by node.tpl.php so it is
- // added in the tag of the HTML page.
- if ($variables['page']) {
- $element = array(
- '#tag' => 'meta',
- '#attributes' => array(
- 'content' => $variables['title'],
- 'about' => $variables['node_url'],
+ // Adds RDFa markup about the title of the node to the title_suffix.
+ if (!empty($variables['node']->rdf_mapping['title']['predicates'])) {
+ $variables['title_suffix']['rdf_meta_title'] = array(
+ '#theme' => 'rdf_metadata',
+ '#metadata' => array(
+ array(
+ 'property' => $variables['node']->rdf_mapping['title']['predicates'],
+ 'content' => $variables['node']->title,
+ ),
),
);
- if (!empty($variables['node']->rdf_mapping['title']['predicates'])) {
- $element['#attributes']['property'] = $variables['node']->rdf_mapping['title']['predicates'];
- }
- drupal_add_html_head($element, 'rdf_node_title');
}
// Adds RDFa markup for the date.
@@ -511,35 +520,20 @@
}
// Adds RDFa markup annotating the number of comments a node has.
- if (isset($variables['node']->comment_count) && !empty($variables['node']->rdf_mapping['comment_count']['predicates'])) {
- // Annotates the 'x comments' link in teaser view.
- if (isset($variables['content']['links']['comment']['#links']['comment-comments'])) {
- $comment_count_attributes['property'] = $variables['node']->rdf_mapping['comment_count']['predicates'];
- $comment_count_attributes['content'] = $variables['node']->comment_count;
- $comment_count_attributes['datatype'] = $variables['node']->rdf_mapping['comment_count']['datatype'];
- // According to RDFa parsing rule number 4, a new subject URI is created
- // from the href attribute if no rel/rev attribute is present. To get the
- // original node URL from the about attribute of the parent container we
- // set an empty rel attribute which triggers rule number 5. See
- // http://www.w3.org/TR/rdfa-syntax/#sec_5.5.
- $comment_count_attributes['rel'] = '';
- $variables['content']['links']['comment']['#links']['comment-comments']['attributes'] += $comment_count_attributes;
- }
- // In full node view, the number of comments is not displayed by
- // node.tpl.php so it is expressed in RDFa in the tag of the HTML
- // page.
- if ($variables['page'] && user_access('access comments')) {
- $element = array(
- '#tag' => 'meta',
- '#attributes' => array(
- 'about' => $variables['node_url'],
+ if (isset($variables['node']->comment_count) &&
+ !empty($variables['node']->rdf_mapping['comment_count']['predicates']) &&
+ user_access('access comments')) {
+ // Adds RDFa markup for the comment count near the node title as metadata.
+ $variables['title_suffix']['rdf_meta_comment_count'] = array(
+ '#theme' => 'rdf_metadata',
+ '#metadata' => array(
+ array(
'property' => $variables['node']->rdf_mapping['comment_count']['predicates'],
'content' => $variables['node']->comment_count,
'datatype' => $variables['node']->rdf_mapping['comment_count']['datatype'],
),
- );
- drupal_add_html_head($element, 'rdf_node_comment_count');
- }
+ ),
+ );
}
}
@@ -865,9 +859,9 @@
$output = '';
foreach ($variables['metadata'] as $attributes) {
// Add a class so that developers viewing the HTML source can see why there
- // are empty tags in the document. The class can also be used to set
- // a CSS display:none rule in a theme where empty spans affect display.
+ // are empty tags in the document.
$attributes['class'][] = 'rdf-meta';
+ $attributes['class'][] = 'element-hidden';
// The XHTML+RDFa doctype allows either or syntax to
// be used, but for maximum browser compatibility, W3C recommends the
// former when serving pages using the text/html media type, see
diff -Naur drupal-7.23/modules/rdf/rdf.test drupal-7.66/modules/rdf/rdf.test
--- drupal-7.23/modules/rdf/rdf.test 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/rdf/rdf.test 2019-04-17 22:20:46.000000000 +0200
@@ -301,7 +301,7 @@
// Ensure the default bundle mapping for node is used. These attributes come
// from the node default bundle definition.
- $blog_title = $this->xpath("//meta[@property='dc:title' and @content='$node->title']");
+ $blog_title = $this->xpath("//div[@about='$url']/span[@property='dc:title' and @content='$node->title']");
$blog_meta = $this->xpath("//div[(@about='$url') and (@typeof='sioct:Weblog')]//span[contains(@property, 'dc:date') and contains(@property, 'dc:created') and @datatype='xsd:dateTime' and @content='$isoDate']");
$this->assertTrue(!empty($blog_title), 'Property dc:title is present in meta tag.');
$this->assertTrue(!empty($blog_meta), 'RDF type is present on post. Properties dc:date and dc:created are present on post date.');
@@ -313,13 +313,18 @@
*/
function testAttributesInMarkup2() {
$type = $this->drupalCreateContentType(array('type' => 'test_bundle_hook_install'));
- $node = $this->drupalCreateNode(array('type' => 'test_bundle_hook_install'));
+ // Create node with single quotation mark title to ensure it does not get
+ // escaped more than once.
+ $node = $this->drupalCreateNode(array(
+ 'type' => 'test_bundle_hook_install',
+ 'title' => $this->randomName(8) . "'",
+ ));
$isoDate = date('c', $node->changed);
$url = url('node/' . $node->nid);
$this->drupalGet('node/' . $node->nid);
// Ensure the mapping defined in rdf_module.test is used.
- $test_bundle_title = $this->xpath("//meta[@property='dc:title' and @content='$node->title']");
+ $test_bundle_title = $this->xpath("//div[@about='$url']/span[@property='dc:title' and @content=\"$node->title\"]");
$test_bundle_meta = $this->xpath("//div[(@about='$url') and contains(@typeof, 'foo:mapping_install1') and contains(@typeof, 'bar:mapping_install2')]//span[contains(@property, 'dc:date') and contains(@property, 'dc:created') and @datatype='xsd:dateTime' and @content='$isoDate']");
$this->assertTrue(!empty($test_bundle_title), 'Property dc:title is present in meta tag.');
$this->assertTrue(!empty($test_bundle_meta), 'RDF type is present on post. Properties dc:date and dc:created are present on post date.');
@@ -338,7 +343,7 @@
// Ensure the default bundle mapping for node is used. These attributes come
// from the node default bundle definition.
- $random_bundle_title = $this->xpath("//meta[@property='dc:title' and @content='$node->title']");
+ $random_bundle_title = $this->xpath("//div[@about='$url']/span[@property='dc:title' and @content='$node->title']");
$random_bundle_meta = $this->xpath("//div[(@about='$url') and contains(@typeof, 'sioc:Item') and contains(@typeof, 'foaf:Document')]//span[contains(@property, 'dc:date') and contains(@property, 'dc:created') and @datatype='xsd:dateTime' and @content='$isoDate']");
$this->assertTrue(!empty($random_bundle_title), 'Property dc:title is present in meta tag.');
$this->assertTrue(!empty($random_bundle_meta), 'RDF type is present on post. Properties dc:date and dc:created are present on post date.');
@@ -436,7 +441,7 @@
$this->setCommentPreview(DRUPAL_OPTIONAL);
$this->setCommentForm(TRUE);
$this->setCommentSubject(TRUE);
- $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Comment paging changed.'));
+ $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, 'Comment paging changed.');
// Creates the nodes on which the test comments will be posted.
$this->drupalLogin($this->web_user);
@@ -456,15 +461,13 @@
// Tests number of comments in teaser view.
$this->drupalGet('node');
- $comment_count_teaser = $this->xpath('//div[contains(@typeof, "sioc:Item")]//li[contains(@class, "comment-comments")]/a[contains(@property, "sioc:num_replies") and contains(@content, "2") and @datatype="xsd:integer"]');
+ $node_url = url('node/' . $this->node1->nid);
+ $comment_count_teaser = $this->xpath('//div[@about=:node-url]/span[@property="sioc:num_replies" and @content="2" and @datatype="xsd:integer"]', array(':node-url' => $node_url));
$this->assertTrue(!empty($comment_count_teaser), 'RDFa markup for the number of comments found on teaser view.');
- $comment_count_link = $this->xpath('//div[@about=:url]//a[contains(@property, "sioc:num_replies") and @rel=""]', array(':url' => url("node/{$this->node1->nid}")));
- $this->assertTrue(!empty($comment_count_link), 'Empty rel attribute found in comment count link.');
// Tests number of comments in full node view.
$this->drupalGet('node/' . $this->node1->nid);
- $node_url = url('node/' . $this->node1->nid);
- $comment_count_teaser = $this->xpath('/html/head/meta[@about=:node-url and @property="sioc:num_replies" and @content="2" and @datatype="xsd:integer"]', array(':node-url' => $node_url));
+ $comment_count_teaser = $this->xpath('//div[@about=:node-url]/span[@property="sioc:num_replies" and @content="2" and @datatype="xsd:integer"]', array(':node-url' => $node_url));
$this->assertTrue(!empty($comment_count_teaser), 'RDFa markup for the number of comments found on full node view.');
}
diff -Naur drupal-7.23/modules/rdf/tests/rdf_test.info drupal-7.66/modules/rdf/tests/rdf_test.info
--- drupal-7.23/modules/rdf/tests/rdf_test.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/rdf/tests/rdf_test.info 2019-04-17 22:39:36.000000000 +0200
@@ -4,9 +4,9 @@
version = VERSION
core = 7.x
hidden = TRUE
+dependencies[] = blog
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/search/search-result.tpl.php drupal-7.66/modules/search/search-result.tpl.php
--- drupal-7.23/modules/search/search-result.tpl.php 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/search/search-result.tpl.php 2019-04-17 22:20:46.000000000 +0200
@@ -25,7 +25,7 @@
* the template.
*
* Default keys within $info_split:
- * - $info_split['type']: Node type (or item type string supplied by module).
+ * - $info_split['module']: The module that implemented the search query.
* - $info_split['user']: Author of the node linked to users profile. Depends
* on permission.
* - $info_split['date']: Last update of the node. Short formatted.
diff -Naur drupal-7.23/modules/search/search.admin.inc drupal-7.66/modules/search/search.admin.inc
--- drupal-7.23/modules/search/search.admin.inc 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/search/search.admin.inc 2019-04-17 22:20:46.000000000 +0200
@@ -125,6 +125,16 @@
'#options' => $module_options,
'#description' => t('Choose which search module is the default.')
);
+ $form['logging'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Logging')
+ );
+ $form['logging']['search_logging'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Log searches'),
+ '#default_value' => variable_get('search_logging', 1),
+ '#description' => t('If checked, all searches will be logged. Uncheck to skip logging. Logging may affect performance.'),
+ );
$form['#validate'][] = 'search_admin_settings_validate';
$form['#submit'][] = 'search_admin_settings_submit';
diff -Naur drupal-7.23/modules/search/search.api.php drupal-7.66/modules/search/search.api.php
--- drupal-7.23/modules/search/search.api.php 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/search/search.api.php 2019-04-17 22:20:46.000000000 +0200
@@ -30,8 +30,9 @@
*
* @return
* Array with optional keys:
- * - title: Title for the tab on the search page for this module. Defaults
- * to the module name if not given.
+ * - title: Title for the tab on the search page for this module. Title must
+ * be untranslated. Outside of this return array, pass the title through the
+ * t() function to register it as a translatable string.
* - path: Path component after 'search/' for searching with this module.
* Defaults to the module name if not given.
* - conditions_callback: An implementation of callback_search_conditions().
@@ -39,6 +40,9 @@
* @ingroup search
*/
function hook_search_info() {
+ // Make the title translatable.
+ t('Content');
+
return array(
'title' => 'Content',
'path' => 'node',
diff -Naur drupal-7.23/modules/search/search.extender.inc drupal-7.66/modules/search/search.extender.inc
--- drupal-7.23/modules/search/search.extender.inc 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/search/search.extender.inc 2019-04-17 22:20:46.000000000 +0200
@@ -105,6 +105,8 @@
* Stores score expressions.
*
* @var array
+ *
+ * @see addScore()
*/
protected $scores = array();
@@ -116,7 +118,7 @@
protected $scoresArguments = array();
/**
- * Total value of all the multipliers.
+ * Stores multipliers for score expressions.
*
* @var array
*/
@@ -147,6 +149,17 @@
$this->searchExpression = $expression;
$this->type = $module;
+ // Add a search_* tag. This needs to be added before any preExecute methods
+ // for decorated queries are called, as $this->prepared will be set to TRUE
+ // and tags added in the execute method will never get used. For example,
+ // if $query is extended by 'SearchQuery' then 'PagerDefault', the
+ // search-specific tag will be added too late (when preExecute() has
+ // already been called from the PagerDefault extender), and as a
+ // consequence will not be available to hook_query_alter() implementations,
+ // nor will the correct hook_query_TAG_alter() implementations get invoked.
+ // See node_search_execute().
+ $this->addTag('search_' . $module);
+
return $this;
}
@@ -391,21 +404,39 @@
/**
* Adds a custom score expression to the search query.
*
- * Each score expression can optionally use a multiplier, and multiple
- * expressions are combined.
+ * Score expressions are used to order search results. If no calls to
+ * addScore() have taken place, a default keyword relevance score will be
+ * used. However, if at least one call to addScore() has taken place, the
+ * keyword relevance score is not automatically added.
+ *
+ * Note that you must use this method to add ordering to your searches, and
+ * not call orderBy() directly, when using the SearchQuery extender. This is
+ * because of the two-pass system the SearchQuery class uses to normalize
+ * scores.
*
* @param $score
- * The score expression.
+ * The score expression, which should evaluate to a number between 0 and 1.
+ * The string 'i.relevance' in a score expression will be replaced by a
+ * measure of keyword relevance between 0 and 1.
* @param $arguments
- * Custom query arguments for that expression.
+ * Query arguments needed to provide values to the score expression.
* @param $multiply
- * If set, the score is multiplied with that value. Search query ensures
- * that the search scores are still normalized.
+ * If set, the score is multiplied with this value. However, all scores
+ * with multipliers are then divided by the total of all multipliers, so
+ * that overall, the normalization is maintained.
+ *
+ * @return object
+ * The updated query object.
*/
public function addScore($score, $arguments = array(), $multiply = FALSE) {
if ($multiply) {
$i = count($this->multiply);
+ // Modify the score expression so it is multiplied by the multiplier,
+ // with a divisor to renormalize.
$score = "CAST(:multiply_$i AS DECIMAL) * COALESCE(( " . $score . "), 0) / CAST(:total_$i AS DECIMAL)";
+ // Add an argument for the multiplier. The :total_$i argument is taken
+ // care of in the execute() method, which is when the total divisor is
+ // calculated.
$arguments[':multiply_' . $i] = $multiply;
$this->multiply[] = $multiply;
}
@@ -446,8 +477,9 @@
}
if (count($this->multiply)) {
- // Add the total multiplicator as many times as requested to maintain
- // normalization as far as possible.
+ // Re-normalize scores with multipliers by dividing by the total of all
+ // multipliers. The expressions were altered in addScore(), so here just
+ // add the arguments for the total.
$i = 0;
$sum = array_sum($this->multiply);
foreach ($this->multiply as $total) {
@@ -456,19 +488,25 @@
}
}
- // Replace i.relevance pseudo-field with the actual, normalized value.
- $this->scores = str_replace('i.relevance', '(' . (1.0 / $this->normalize) . ' * i.score * t.count)', $this->scores);
- // Convert scores to an expression.
+ // Replace the pseudo-expression 'i.relevance' with a measure of keyword
+ // relevance in all score expressions, using string replacement. Careful
+ // though! If you just print out a float, some locales use ',' as the
+ // decimal separator in PHP, while SQL always uses '.'. So, make sure to
+ // set the number format correctly.
+ $relevance = number_format((1.0 / $this->normalize), 10, '.', '');
+ $this->scores = str_replace('i.relevance', '(' . $relevance . ' * i.score * t.count)', $this->scores);
+
+ // Add all scores together to form a query field.
$this->addExpression('SUM(' . implode(' + ', $this->scores) . ')', 'calculated_score', $this->scoresArguments);
+ // If an order has not yet been set for this query, add a default order
+ // that sorts by the calculated sum of scores.
if (count($this->getOrderBy()) == 0) {
- // Add default order after adding the expression.
$this->orderBy('calculated_score', 'DESC');
}
- // Add tag and useful metadata.
+ // Add useful metadata.
$this
- ->addTag('search_' . $this->type)
->addMetaData('normalize', $this->normalize)
->fields('i', array('type', 'sid'));
diff -Naur drupal-7.23/modules/search/search.info drupal-7.66/modules/search/search.info
--- drupal-7.23/modules/search/search.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/search/search.info 2019-04-17 22:39:36.000000000 +0200
@@ -8,8 +8,7 @@
configure = admin/config/search/settings
stylesheets[all][] = search.css
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/search/search.install drupal-7.66/modules/search/search.install
--- drupal-7.23/modules/search/search.install 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/search/search.install 2019-04-17 22:20:46.000000000 +0200
@@ -12,6 +12,7 @@
variable_del('minimum_word_size');
variable_del('overlap_cjk');
variable_del('search_cron_limit');
+ variable_del('search_logging');
}
/**
diff -Naur drupal-7.23/modules/search/search.pages.inc drupal-7.66/modules/search/search.pages.inc
--- drupal-7.23/modules/search/search.pages.inc 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/search/search.pages.inc 2019-04-17 22:20:46.000000000 +0200
@@ -49,7 +49,7 @@
// which will get us back to this page callback. In other words, the search
// form submits with POST but redirects to GET. This way we can keep
// the search query URL clean as a whistle.
- if (empty($_POST['form_id']) || $_POST['form_id'] != 'search_form') {
+ if (empty($_POST['form_id']) || ($_POST['form_id'] != 'search_form' && $_POST['form_id'] != 'search_block_form')) {
$conditions = NULL;
if (isset($info['conditions_callback']) && function_exists($info['conditions_callback'])) {
// Build an optional array of more search conditions.
@@ -57,9 +57,10 @@
}
// Only search if there are keywords or non-empty conditions.
if ($keys || !empty($conditions)) {
- // Log the search keys.
- watchdog('search', 'Searched %type for %keys.', array('%keys' => $keys, '%type' => $info['title']), WATCHDOG_NOTICE, l(t('results'), 'search/' . $info['path'] . '/' . $keys));
-
+ if (variable_get('search_logging', TRUE)) {
+ // Log the search keys.
+ watchdog('search', 'Searched %type for %keys.', array('%keys' => $keys, '%type' => $info['title']), WATCHDOG_NOTICE, l(t('results'), 'search/' . $info['path'] . '/' . $keys));
+ }
// Collect the search results.
$results = search_data($keys, $info['module'], $conditions);
}
diff -Naur drupal-7.23/modules/search/search.test drupal-7.66/modules/search/search.test
--- drupal-7.23/modules/search/search.test 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/search/search.test 2019-04-17 22:20:46.000000000 +0200
@@ -11,6 +11,9 @@
define('SEARCH_TYPE_2', '_test2_');
define('SEARCH_TYPE_JPN', '_test3_');
+/**
+ * Indexes content and queries it.
+ */
class SearchMatchTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
@@ -307,6 +310,9 @@
}
}
+/**
+ * Indexes content and tests the advanced search form.
+ */
class SearchAdvancedSearchForm extends DrupalWebTestCase {
protected $node;
@@ -370,6 +376,9 @@
}
}
+/**
+ * Indexes content and tests ranking factors.
+ */
class SearchRankingTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
@@ -580,6 +589,9 @@
}
}
+/**
+ * Tests the rendering of the search block.
+ */
class SearchBlockTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
@@ -654,6 +666,24 @@
url('search/node/', array('absolute' => TRUE)),
'Redirected to correct url.'
);
+
+ // Test that after entering a too-short keyword in the form, you can then
+ // search again with a longer keyword. First test using the block form.
+ $terms = array('search_block_form' => 'a');
+ $this->drupalPost('node', $terms, t('Search'));
+ $this->assertText('You must include at least one positive keyword with 3 characters or more');
+ $terms = array('search_block_form' => 'foo');
+ $this->drupalPost(NULL, $terms, t('Search'));
+ $this->assertNoText('You must include at least one positive keyword with 3 characters or more');
+ $this->assertText('Your search yielded no results');
+
+ // Same test again, using the search page form for the second search this time.
+ $terms = array('search_block_form' => 'a');
+ $this->drupalPost('node', $terms, t('Search'));
+ $terms = array('keys' => 'foo');
+ $this->drupalPost(NULL, $terms, t('Search'));
+ $this->assertNoText('You must include at least one positive keyword with 3 characters or more');
+ $this->assertText('Your search yielded no results');
}
}
@@ -727,7 +757,7 @@
public static function getInfo() {
return array(
'name' => 'Comment Search tests',
- 'description' => 'Verify text formats and filters used elsewhere.',
+ 'description' => 'Test integration searching comments.',
'group' => 'Search',
);
}
@@ -1423,7 +1453,7 @@
parent::setUp('search', 'search_extra_type');
// Login as a user that can create and search content.
- $this->search_user = $this->drupalCreateUser(array('search content', 'administer search', 'administer nodes', 'bypass node access', 'access user profiles', 'administer users', 'administer blocks'));
+ $this->search_user = $this->drupalCreateUser(array('search content', 'administer search', 'administer nodes', 'bypass node access', 'access user profiles', 'administer users', 'administer blocks', 'access site reports'));
$this->drupalLogin($this->search_user);
// Add a single piece of content and index it.
@@ -1472,6 +1502,19 @@
);
$this->drupalPost('admin/config/search/settings', $edit, t('Save configuration'));
$this->assertNoText(t('The configuration options have been saved.'), 'Form does not save with an invalid word length.');
+
+ // Test logging setting. It should be on by default.
+ $text = $this->randomName(5);
+ $this->drupalPost('search/node', array('keys' => $text), t('Search'));
+ $this->drupalGet('admin/reports/dblog');
+ $this->assertLink('Searched Content for ' . $text . '.', 0, 'Search was logged');
+
+ // Turn off logging.
+ variable_set('search_logging', FALSE);
+ $text = $this->randomName(5);
+ $this->drupalPost('search/node', array('keys' => $text), t('Search'));
+ $this->drupalGet('admin/reports/dblog');
+ $this->assertNoLink('Searched Content for ' . $text . '.', 'Search was not logged');
}
/**
@@ -1567,7 +1610,7 @@
/**
* Tests the search_excerpt() function.
*/
-class SearchExcerptTestCase extends DrupalUnitTestCase {
+class SearchExcerptTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Search excerpt extraction',
@@ -1577,8 +1620,7 @@
}
function setUp() {
- drupal_load('module', 'search');
- parent::setUp();
+ parent::setUp('search');
}
/**
@@ -1603,7 +1645,7 @@
$this->assertEqual($result, 'The quick brown fox & jumps over the lazy dog ...', 'Found keyword is highlighted');
$longtext = str_repeat($text . ' ', 10);
- $result = preg_replace('| +|', ' ', search_excerpt('nothing', $text));
+ $result = preg_replace('| +|', ' ', search_excerpt('nothing', $longtext));
$this->assertTrue(strpos($result, $expected) === 0, 'When keyword is not found in long string, return value starts as expected');
$entities = str_repeat('készítése ', 20);
@@ -2018,10 +2060,11 @@
}
/**
- * Tests that search returns results with punctuation in the search phrase.
+ * Tests that search works with punctuation and HTML entities.
*/
function testPhraseSearchPunctuation() {
$node = $this->drupalCreateNode(array('body' => array(LANGUAGE_NONE => array(array('value' => "The bunny's ears were fuzzy.")))));
+ $node2 = $this->drupalCreateNode(array('body' => array(LANGUAGE_NONE => array(array('value' => 'Dignissim Aliquam & Quieligo meus natu quae quia te. Damnum© erat— neo pneum. Facilisi feugiat ibidem ratis.')))));
// Update the search index.
module_invoke_all('update_index');
@@ -2034,5 +2077,99 @@
$edit = array('keys' => '"bunny\'s"');
$this->drupalPost('search/node', $edit, t('Search'));
$this->assertText($node->title);
+
+ // Search for "&" and verify entities are not broken up in the output.
+ $edit = array('keys' => '&');
+ $this->drupalPost('search/node', $edit, t('Search'));
+ $this->assertNoRaw('&');
+ $this->assertText('You must include at least one positive keyword');
+
+ $edit = array('keys' => '&');
+ $this->drupalPost('search/node', $edit, t('Search'));
+ $this->assertNoRaw('&');
+ $this->assertText('You must include at least one positive keyword');
+ }
+}
+
+/**
+ * Tests node search with query tags.
+ */
+class SearchNodeTagTest extends DrupalWebTestCase {
+ public $test_user;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node search query tags',
+ 'description' => 'Tests Node search tags functionality.',
+ 'group' => 'Search',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('search', 'search_node_tags');
+ node_access_rebuild();
+
+ // Create a test user and log in.
+ $this->test_user = $this->drupalCreateUser(array('search content'));
+ $this->drupalLogin($this->test_user);
+ }
+
+ /**
+ * Tests that the correct tags are available and hooks invoked.
+ */
+ function testNodeSearchQueryTags() {
+ $this->drupalCreateNode(array('body' => array(LANGUAGE_NONE => array(array('value' => 'testing testing testing.')))));
+
+ // Update the search index.
+ module_invoke_all('update_index');
+ search_update_totals();
+
+ $edit = array('keys' => 'testing');
+ $this->drupalPost('search/node', $edit, t('Search'));
+
+ $this->assertTrue(variable_get('search_node_tags_test_query_tag', FALSE), 'hook_query_alter() was invoked and the query contained the "search_node" tag.');
+ $this->assertTrue(variable_get('search_node_tags_test_query_tag_hook', FALSE), 'hook_query_search_node_alter() was invoked.');
+ }
+}
+
+/**
+ * Tests searching with locale values set.
+ */
+class SearchSetLocaleTest extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Search with numeric locale set',
+ 'description' => 'Check that search works with numeric locale settings',
+ 'group' => 'Search',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('search');
+
+ // Create a simple node so something will be put in the index.
+ $info = array(
+ 'body' => array(LANGUAGE_NONE => array(array('value' => 'Tapir'))),
+ );
+ $this->drupalCreateNode($info);
+
+ // Run cron to index.
+ $this->cronRun();
+ }
+
+ /**
+ * Verify that search works with a numeric locale set.
+ */
+ public function testSearchWithNumericLocale() {
+ // French decimal point is comma.
+ setlocale(LC_NUMERIC, 'fr_FR');
+
+ // An exception will be thrown if a float in the wrong format occurs in the
+ // query to the database, so an assertion is not necessary here.
+ db_select('search_index', 'i')
+ ->extend('searchquery')
+ ->searchexpression('tapir', 'node')
+ ->execute();
}
}
diff -Naur drupal-7.23/modules/search/tests/search_embedded_form.info drupal-7.66/modules/search/tests/search_embedded_form.info
--- drupal-7.23/modules/search/tests/search_embedded_form.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/search/tests/search_embedded_form.info 2019-04-17 22:39:36.000000000 +0200
@@ -5,8 +5,7 @@
core = 7.x
hidden = TRUE
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/search/tests/search_extra_type.info drupal-7.66/modules/search/tests/search_extra_type.info
--- drupal-7.23/modules/search/tests/search_extra_type.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/search/tests/search_extra_type.info 2019-04-17 22:39:36.000000000 +0200
@@ -5,8 +5,7 @@
core = 7.x
hidden = TRUE
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/search/tests/search_node_tags.info drupal-7.66/modules/search/tests/search_node_tags.info
--- drupal-7.23/modules/search/tests/search_node_tags.info 1970-01-01 01:00:00.000000000 +0100
+++ drupal-7.66/modules/search/tests/search_node_tags.info 2019-04-17 22:39:36.000000000 +0200
@@ -0,0 +1,11 @@
+name = "Test search node tags"
+description = "Support module for Node search tags testing."
+package = Testing
+version = VERSION
+core = 7.x
+hidden = TRUE
+
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
+project = "drupal"
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/search/tests/search_node_tags.module drupal-7.66/modules/search/tests/search_node_tags.module
--- drupal-7.23/modules/search/tests/search_node_tags.module 1970-01-01 01:00:00.000000000 +0100
+++ drupal-7.66/modules/search/tests/search_node_tags.module 2019-04-17 22:20:46.000000000 +0200
@@ -0,0 +1,23 @@
+hasTag('search_node')) {
+ variable_set('search_node_tags_test_query_tag', TRUE);
+ }
+}
+
+/**
+ * Implements hook_query_TAG_alter().
+ */
+function search_node_tags_query_search_node_alter(QueryAlterableInterface $query) {
+ variable_set('search_node_tags_test_query_tag_hook', TRUE);
+}
diff -Naur drupal-7.23/modules/shortcut/shortcut.admin.inc drupal-7.66/modules/shortcut/shortcut.admin.inc
--- drupal-7.23/modules/shortcut/shortcut.admin.inc 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/shortcut/shortcut.admin.inc 2019-04-17 22:20:46.000000000 +0200
@@ -784,5 +784,5 @@
drupal_goto();
}
- return drupal_access_denied();
+ return MENU_ACCESS_DENIED;
}
diff -Naur drupal-7.23/modules/shortcut/shortcut.info drupal-7.66/modules/shortcut/shortcut.info
--- drupal-7.23/modules/shortcut/shortcut.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/shortcut/shortcut.info 2019-04-17 22:39:36.000000000 +0200
@@ -6,8 +6,7 @@
files[] = shortcut.test
configure = admin/config/user-interface/shortcut
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/simpletest/drupal_web_test_case.php drupal-7.66/modules/simpletest/drupal_web_test_case.php
--- drupal-7.23/modules/simpletest/drupal_web_test_case.php 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/simpletest/drupal_web_test_case.php 2019-04-17 22:20:46.000000000 +0200
@@ -40,6 +40,13 @@
protected $originalFileDirectory = NULL;
/**
+ * URL to the verbose output file directory.
+ *
+ * @var string
+ */
+ protected $verboseDirectoryUrl;
+
+ /**
* Time limit for the test.
*/
protected $timeLimit = 500;
@@ -143,15 +150,7 @@
);
// Store assertion for display after the test has completed.
- try {
- $connection = Database::getConnection('default', 'simpletest_original_default');
- }
- catch (DatabaseConnectionNotDefinedException $e) {
- // If the test was not set up, the simpletest_original_default
- // connection does not exist.
- $connection = Database::getConnection('default', 'default');
- }
- $connection
+ self::getDatabaseConnection()
->insert('simpletest')
->fields($assertion)
->execute();
@@ -167,6 +166,25 @@
}
/**
+ * Returns the database connection to the site running Simpletest.
+ *
+ * @return DatabaseConnection
+ * The database connection to use for inserting assertions.
+ */
+ public static function getDatabaseConnection() {
+ try {
+ $connection = Database::getConnection('default', 'simpletest_original_default');
+ }
+ catch (DatabaseConnectionNotDefinedException $e) {
+ // If the test was not set up, the simpletest_original_default
+ // connection does not exist.
+ $connection = Database::getConnection('default', 'default');
+ }
+
+ return $connection;
+ }
+
+ /**
* Store an assertion from outside the testing context.
*
* This is useful for inserting assertions that can only be recorded after
@@ -205,7 +223,8 @@
'file' => $caller['file'],
);
- return db_insert('simpletest')
+ return self::getDatabaseConnection()
+ ->insert('simpletest')
->fields($assertion)
->execute();
}
@@ -221,7 +240,8 @@
* @see DrupalTestCase::insertAssert()
*/
public static function deleteAssert($message_id) {
- return (bool) db_delete('simpletest')
+ return (bool) self::getDatabaseConnection()
+ ->delete('simpletest')
->condition('message_id', $message_id)
->execute();
}
@@ -435,10 +455,10 @@
}
/**
- * Logs verbose message in a text file.
+ * Logs a verbose message in a text file.
*
- * The a link to the vebose message will be placed in the test results via
- * as a passing assertion with the text '[verbose message]'.
+ * The link to the verbose message will be placed in the test results as a
+ * passing assertion with the text '[verbose message]'.
*
* @param $message
* The verbose message to be stored.
@@ -448,8 +468,11 @@
protected function verbose($message) {
if ($id = simpletest_verbose($message)) {
$class_safe = str_replace('\\', '_', get_class($this));
- $url = file_create_url($this->originalFileDirectory . '/simpletest/verbose/' . $class_safe . '-' . $id . '.html');
- $this->error(l(t('Verbose message'), $url, array('attributes' => array('target' => '_blank'))), 'User notice');
+ $url = $this->verboseDirectoryUrl . '/' . $class_safe . '-' . $id . '.html';
+ // Not using l() to avoid invoking the theme system, so that unit tests
+ // can use verbose() as well.
+ $link = '' . t('Verbose message') . '';
+ $this->error($link, 'User notice');
}
}
@@ -541,6 +564,15 @@
E_RECOVERABLE_ERROR => 'Recoverable error',
);
+ // PHP 5.3 adds new error logging constants. Add these conditionally for
+ // backwards compatibility with PHP 5.2.
+ if (defined('E_DEPRECATED')) {
+ $error_map += array(
+ E_DEPRECATED => 'Deprecated',
+ E_USER_DEPRECATED => 'User deprecated',
+ );
+ }
+
$backtrace = debug_backtrace();
$this->error($message, $error_map[$severity], _drupal_get_last_caller($backtrace));
}
@@ -697,10 +729,17 @@
* method.
*/
protected function setUp() {
- global $conf;
+ global $conf, $language;
// Store necessary current values before switching to the test environment.
$this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files');
+ $this->verboseDirectoryUrl = file_create_url($this->originalFileDirectory . '/simpletest/verbose');
+
+ // Set up English language.
+ $this->originalLanguage = $language;
+ $this->originalLanguageDefault = variable_get('language_default');
+ unset($conf['language_default']);
+ $language = language_default();
// Reset all statics so that test is performed with a clean environment.
drupal_static_reset();
@@ -730,6 +769,10 @@
// subsequently will fail as the database is not accessible.
$module_list = module_list();
if (isset($module_list['locale'])) {
+ // Transform the list into the format expected as input to module_list().
+ foreach ($module_list as &$module) {
+ $module = array('filename' => drupal_get_filename('module', $module));
+ }
$this->originalModuleList = $module_list;
unset($module_list['locale']);
module_list(TRUE, FALSE, FALSE, $module_list);
@@ -738,7 +781,7 @@
}
protected function tearDown() {
- global $conf;
+ global $conf, $language;
// Get back to the original connection.
Database::removeConnection('default');
@@ -749,6 +792,12 @@
if (isset($this->originalModuleList)) {
module_list(TRUE, FALSE, FALSE, $this->originalModuleList);
}
+
+ // Reset language.
+ $language = $this->originalLanguage;
+ if ($this->originalLanguageDefault) {
+ $GLOBALS['conf']['language_default'] = $this->originalLanguageDefault;
+ }
}
}
@@ -828,6 +877,13 @@
protected $cookieFile = NULL;
/**
+ * The cookies of the page currently loaded in the internal browser.
+ *
+ * @var array
+ */
+ protected $cookies = array();
+
+ /**
* Additional cURL options.
*
* DrupalWebTestCase itself never sets this but always obeys what is set.
@@ -916,7 +972,6 @@
protected function drupalCreateNode($settings = array()) {
// Populate defaults array.
$settings += array(
- 'body' => array(LANGUAGE_NONE => array(array())),
'title' => $this->randomName(8),
'comment' => 2,
'changed' => REQUEST_TIME,
@@ -931,6 +986,12 @@
'language' => LANGUAGE_NONE,
);
+ // Add the body after the language is defined so that it may be set
+ // properly.
+ $settings += array(
+ 'body' => array($settings['language'] => array(array())),
+ );
+
// Use the original node's created time for existing nodes.
if (isset($settings['created']) && !isset($settings['date'])) {
$settings['date'] = format_date($settings['created'], 'custom', 'Y-m-d H:i:s O');
@@ -989,9 +1050,7 @@
'description' => '',
'help' => '',
'title_label' => 'Title',
- 'body_label' => 'Body',
'has_title' => 1,
- 'has_body' => 1,
);
// Imposed values for a custom type.
$forced = array(
@@ -1041,7 +1100,7 @@
$lines = array(16, 256, 1024, 2048, 20480);
$count = 0;
foreach ($lines as $line) {
- simpletest_generate_file('text-' . $count++, 64, $line);
+ simpletest_generate_file('text-' . $count++, 64, $line, 'text');
}
// Copy other test files from simpletest.
@@ -1132,7 +1191,7 @@
}
/**
- * Internal helper function; Create a role with specified permissions.
+ * Creates a role with specified permissions.
*
* @param $permissions
* Array of permission names to assign to role.
@@ -1338,12 +1397,14 @@
* @see DrupalWebTestCase::tearDown()
*/
protected function prepareEnvironment() {
- global $user, $language, $conf;
+ global $user, $language, $language_url, $conf;
// Store necessary current values before switching to prefixed database.
$this->originalLanguage = $language;
+ $this->originalLanguageUrl = $language_url;
$this->originalLanguageDefault = variable_get('language_default');
$this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files');
+ $this->verboseDirectoryUrl = file_create_url($this->originalFileDirectory . '/simpletest/verbose');
$this->originalProfile = drupal_get_profile();
$this->originalCleanUrl = variable_get('clean_url', 0);
$this->originalUser = $user;
@@ -1351,7 +1412,7 @@
// Set to English to prevent exceptions from utf8_truncate() from t()
// during install if the current language is not 'en'.
// The following array/object conversion is copied from language_default().
- $language = (object) array('language' => 'en', 'name' => 'English', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => '', 'weight' => 0, 'javascript' => '');
+ $language_url = $language = (object) array('language' => 'en', 'name' => 'English', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => '', 'weight' => 0, 'javascript' => '');
// Save and clean the shutdown callbacks array because it is static cached
// and will be changed by the test run. Otherwise it will contain callbacks
@@ -1409,7 +1470,7 @@
* @see DrupalWebTestCase::prepareEnvironment()
*/
protected function setUp() {
- global $user, $language, $conf;
+ global $user, $language, $language_url, $conf;
// Create the database prefix for this test.
$this->prepareDatabasePrefix();
@@ -1506,7 +1567,7 @@
// Set up English language.
unset($conf['language_default']);
- $language = language_default();
+ $language_url = $language = language_default();
// Use the test mail class instead of the default mail handler class.
variable_set('mail_system', array('default-system' => 'TestingMailSystem'));
@@ -1600,7 +1661,7 @@
* and reset the database prefix.
*/
protected function tearDown() {
- global $user, $language;
+ global $user, $language, $language_url;
// In case a fatal error occurred that was not in the test process read the
// log to pick up any fatal errors.
@@ -1665,12 +1726,15 @@
// Reset language.
$language = $this->originalLanguage;
+ $language_url = $this->originalLanguageUrl;
if ($this->originalLanguageDefault) {
$GLOBALS['conf']['language_default'] = $this->originalLanguageDefault;
}
- // Close the CURL handler.
+ // Close the CURL handler and reset the cookies array so test classes
+ // containing multiple tests are not polluted.
$this->curlClose();
+ $this->cookies = array();
}
/**
@@ -1743,14 +1807,24 @@
protected function curlExec($curl_options, $redirect = FALSE) {
$this->curlInitialize();
- // cURL incorrectly handles URLs with a fragment by including the
- // fragment in the request to the server, causing some web servers
- // to reject the request citing "400 - Bad Request". To prevent
- // this, we strip the fragment from the request.
- // TODO: Remove this for Drupal 8, since fixed in curl 7.20.0.
- if (!empty($curl_options[CURLOPT_URL]) && strpos($curl_options[CURLOPT_URL], '#')) {
- $original_url = $curl_options[CURLOPT_URL];
- $curl_options[CURLOPT_URL] = strtok($curl_options[CURLOPT_URL], '#');
+ if (!empty($curl_options[CURLOPT_URL])) {
+ // Forward XDebug activation if present.
+ if (isset($_COOKIE['XDEBUG_SESSION'])) {
+ $options = drupal_parse_url($curl_options[CURLOPT_URL]);
+ $options += array('query' => array());
+ $options['query'] += array('XDEBUG_SESSION_START' => $_COOKIE['XDEBUG_SESSION']);
+ $curl_options[CURLOPT_URL] = url($options['path'], $options);
+ }
+
+ // cURL incorrectly handles URLs with a fragment by including the
+ // fragment in the request to the server, causing some web servers
+ // to reject the request citing "400 - Bad Request". To prevent
+ // this, we strip the fragment from the request.
+ // TODO: Remove this for Drupal 8, since fixed in curl 7.20.0.
+ if (strpos($curl_options[CURLOPT_URL], '#')) {
+ $original_url = $curl_options[CURLOPT_URL];
+ $curl_options[CURLOPT_URL] = strtok($curl_options[CURLOPT_URL], '#');
+ }
}
$url = empty($curl_options[CURLOPT_URL]) ? curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL) : $curl_options[CURLOPT_URL];
@@ -2042,7 +2116,14 @@
foreach ($upload as $key => $file) {
$file = drupal_realpath($file);
if ($file && is_file($file)) {
- $post[$key] = '@' . $file;
+ // Use the new CurlFile class for file uploads when using PHP
+ // 5.5 or higher.
+ if (class_exists('CurlFile')) {
+ $post[$key] = curl_file_create($file);
+ }
+ else {
+ $post[$key] = '@' . $file;
+ }
}
}
}
@@ -2178,6 +2259,7 @@
// Submit the POST request.
$return = drupal_json_decode($this->drupalPost(NULL, $edit, array('path' => $ajax_path, 'triggering_element' => $triggering_element), $options, $headers, $form_html_id, $extra_post));
+ $this->assertIdentical($this->drupalGetHeader('X-Drupal-Ajax-Token'), '1', 'Ajax response header found.');
// Change the page content by applying the returned commands.
if (!empty($ajax_settings) && !empty($return)) {
@@ -2214,8 +2296,13 @@
if ($wrapperNode) {
// ajax.js adds an enclosing DIV to work around a Safari bug.
$newDom = new DOMDocument();
+ // DOM can load HTML soup. But, HTML soup can throw warnings,
+ // suppress them.
$newDom->loadHTML('' . $command['data'] . '');
- $newNode = $dom->importNode($newDom->documentElement->firstChild->firstChild, TRUE);
+ // Suppress warnings thrown when duplicate HTML IDs are
+ // encountered. This probably means we are replacing an element
+ // with the same ID.
+ $newNode = @$dom->importNode($newDom->documentElement->firstChild->firstChild, TRUE);
$method = isset($command['method']) ? $command['method'] : $ajax_settings['method'];
// The "method" is a jQuery DOM manipulation function. Emulate
// each one using PHP's DOMNode API.
@@ -2249,6 +2336,13 @@
}
break;
+ case 'updateBuildId':
+ $buildId = $xpath->query('//input[@name="form_build_id" and @value="' . $command['old'] . '"]')->item(0);
+ if ($buildId) {
+ $buildId->setAttribute('value', $command['new']);
+ }
+ break;
+
// @todo Add suitable implementations for these commands in order to
// have full test coverage of what ajax.js can do.
case 'remove':
@@ -2261,12 +2355,22 @@
break;
case 'restripe':
break;
+ case 'add_css':
+ break;
}
}
$content = $dom->saveHTML();
}
$this->drupalSetContent($content);
$this->drupalSetSettings($drupal_settings);
+
+ $verbose = 'AJAX POST request to: ' . $path;
+ $verbose .= '
AJAX callback path: ' . $ajax_path;
+ $verbose .= '
Ending URL: ' . $this->getUrl();
+ $verbose .= '
' . $this->content;
+
+ $this->verbose($verbose);
+
return $return;
}
@@ -2520,6 +2624,11 @@
*
* @param $xpath
* The xpath string to use in the search.
+ * @param array $arguments
+ * An array of arguments with keys in the form ':name' matching the
+ * placeholders in the query. The values may be either strings or numeric
+ * values.
+ *
* @return
* The return value of the xpath search. For details on the xpath string
* format and return values see the SimpleXML documentation,
@@ -2589,8 +2698,6 @@
*
* @param $label
* Text between the anchor tags.
- * @param $index
- * Link position counting from zero.
* @param $message
* Message to display.
* @param $group
@@ -2649,28 +2756,26 @@
*
* Will click the first link found with this link text by default, or a later
* one if an index is given. Match is case sensitive with normalized space.
- * The label is translated label. There is an assert for successful click.
+ * The label is translated label.
+ *
+ * If the link is discovered and clicked, the test passes. Fail otherwise.
*
* @param $label
* Text between the anchor tags.
* @param $index
* Link position counting from zero.
* @return
- * Page on success, or FALSE on failure.
+ * Page contents on success, or FALSE on failure.
*/
protected function clickLink($label, $index = 0) {
$url_before = $this->getUrl();
$urls = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $label));
-
if (isset($urls[$index])) {
$url_target = $this->getAbsoluteUrl($urls[$index]['href']);
- }
-
- $this->assertTrue(isset($urls[$index]), t('Clicked link %label (@url_target) from @url_before', array('%label' => $label, '@url_target' => $url_target, '@url_before' => $url_before)), t('Browser'));
-
- if (isset($url_target)) {
+ $this->pass(t('Clicked link %label (@url_target) from @url_before', array('%label' => $label, '@url_target' => $url_target, '@url_before' => $url_before)), 'Browser');
return $this->drupalGet($url_target);
}
+ $this->fail(t('Link %label does not exist on @url_before', array('%label' => $label, '@url_before' => $url_before)), 'Browser');
return FALSE;
}
@@ -2695,7 +2800,7 @@
$path = substr($path, $length);
}
// Ensure that we have an absolute path.
- if ($path[0] !== '/') {
+ if (empty($path) || $path[0] !== '/') {
$path = '/' . $path;
}
// Finally, prepend the $base_url.
@@ -2907,7 +3012,7 @@
if (!$message) {
$message = t('Raw "@raw" found', array('@raw' => $raw));
}
- return $this->assert(strpos($this->drupalGetContent(), $raw) !== FALSE, $message, $group);
+ return $this->assert(strpos($this->drupalGetContent(), (string) $raw) !== FALSE, $message, $group);
}
/**
@@ -2927,7 +3032,7 @@
if (!$message) {
$message = t('Raw "@raw" not found', array('@raw' => $raw));
}
- return $this->assert(strpos($this->drupalGetContent(), $raw) === FALSE, $message, $group);
+ return $this->assert(strpos($this->drupalGetContent(), (string) $raw) === FALSE, $message, $group);
}
/**
@@ -3154,7 +3259,7 @@
* @param $callback
* The name of the theme function to invoke; e.g. 'links' for theme_links().
* @param $variables
- * An array of variables to pass to the theme function.
+ * (optional) An array of variables to pass to the theme function.
* @param $expected
* The expected themed output string.
* @param $message
@@ -3190,7 +3295,9 @@
* @param $xpath
* XPath used to find the field.
* @param $value
- * (optional) Value of the field to assert.
+ * (optional) Value of the field to assert. You may pass in NULL (default)
+ * to skip checking the actual value, while still checking that the field
+ * exists.
* @param $message
* (optional) Message to display.
* @param $group
@@ -3258,12 +3365,14 @@
}
/**
- * Asserts that a field does not exist in the current page by the given XPath.
+ * Asserts that a field doesn't exist or its value doesn't match, by XPath.
*
* @param $xpath
* XPath used to find the field.
* @param $value
- * (optional) Value of the field to assert.
+ * (optional) Value for the field, to assert that the field's value on the
+ * page doesn't match it. You may pass in NULL to skip checking the
+ * value, while still checking that the field doesn't exist.
* @param $message
* (optional) Message to display.
* @param $group
@@ -3296,7 +3405,9 @@
* @param $name
* Name of field to assert.
* @param $value
- * Value of the field to assert.
+ * (optional) Value of the field to assert. You may pass in NULL (default)
+ * to skip checking the actual value, while still checking that the field
+ * exists.
* @param $message
* Message to display.
* @param $group
@@ -3327,9 +3438,12 @@
* @param $name
* Name of field to assert.
* @param $value
- * Value of the field to assert.
+ * (optional) Value for the field, to assert that the field's value on the
+ * page doesn't match it. You may pass in NULL to skip checking the
+ * value, while still checking that the field doesn't exist. However, the
+ * default value ('') asserts that the field value is not an empty string.
* @param $message
- * Message to display.
+ * (optional) Message to display.
* @param $group
* The group this message belongs to.
* @return
@@ -3340,14 +3454,17 @@
}
/**
- * Asserts that a field exists in the current page with the given id and value.
+ * Asserts that a field exists in the current page with the given ID and value.
*
* @param $id
- * Id of field to assert.
+ * ID of field to assert.
* @param $value
- * Value of the field to assert.
+ * (optional) Value for the field to assert. You may pass in NULL to skip
+ * checking the value, while still checking that the field exists.
+ * However, the default value ('') asserts that the field value is an empty
+ * string.
* @param $message
- * Message to display.
+ * (optional) Message to display.
* @param $group
* The group this message belongs to.
* @return
@@ -3358,14 +3475,17 @@
}
/**
- * Asserts that a field does not exist with the given id and value.
+ * Asserts that a field does not exist with the given ID and value.
*
* @param $id
- * Id of field to assert.
+ * ID of field to assert.
* @param $value
- * Value of the field to assert.
+ * (optional) Value for the field, to assert that the field's value on the
+ * page doesn't match it. You may pass in NULL to skip checking the value,
+ * while still checking that the field doesn't exist. However, the default
+ * value ('') asserts that the field value is not an empty string.
* @param $message
- * Message to display.
+ * (optional) Message to display.
* @param $group
* The group this message belongs to.
* @return
@@ -3379,9 +3499,9 @@
* Asserts that a checkbox field in the current page is checked.
*
* @param $id
- * Id of field to assert.
+ * ID of field to assert.
* @param $message
- * Message to display.
+ * (optional) Message to display.
* @return
* TRUE on pass, FALSE on fail.
*/
@@ -3394,9 +3514,9 @@
* Asserts that a checkbox field in the current page is not checked.
*
* @param $id
- * Id of field to assert.
+ * ID of field to assert.
* @param $message
- * Message to display.
+ * (optional) Message to display.
* @return
* TRUE on pass, FALSE on fail.
*/
@@ -3409,11 +3529,11 @@
* Asserts that a select option in the current page is checked.
*
* @param $id
- * Id of select field to assert.
+ * ID of select field to assert.
* @param $option
* Option to assert.
* @param $message
- * Message to display.
+ * (optional) Message to display.
* @return
* TRUE on pass, FALSE on fail.
*
@@ -3428,11 +3548,11 @@
* Asserts that a select option in the current page is not checked.
*
* @param $id
- * Id of select field to assert.
+ * ID of select field to assert.
* @param $option
* Option to assert.
* @param $message
- * Message to display.
+ * (optional) Message to display.
* @return
* TRUE on pass, FALSE on fail.
*/
@@ -3442,12 +3562,12 @@
}
/**
- * Asserts that a field exists with the given name or id.
+ * Asserts that a field exists with the given name or ID.
*
* @param $field
- * Name or id of field to assert.
+ * Name or ID of field to assert.
* @param $message
- * Message to display.
+ * (optional) Message to display.
* @param $group
* The group this message belongs to.
* @return
@@ -3458,12 +3578,12 @@
}
/**
- * Asserts that a field does not exist with the given name or id.
+ * Asserts that a field does not exist with the given name or ID.
*
* @param $field
- * Name or id of field to assert.
+ * Name or ID of field to assert.
* @param $message
- * Message to display.
+ * (optional) Message to display.
* @param $group
* The group this message belongs to.
* @return
diff -Naur drupal-7.23/modules/simpletest/files/css_test_files/css_input_with_import.css drupal-7.66/modules/simpletest/files/css_test_files/css_input_with_import.css
--- drupal-7.23/modules/simpletest/files/css_test_files/css_input_with_import.css 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/simpletest/files/css_test_files/css_input_with_import.css 2019-04-17 22:20:46.000000000 +0200
@@ -1,5 +1,7 @@
+@import url("http://example.com/style.css");
+@import url("//example.com/style.css");
@import "import1.css";
@import "import2.css";
diff -Naur drupal-7.23/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css drupal-7.66/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css
--- drupal-7.23/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css 2019-04-17 22:20:46.000000000 +0200
@@ -1,4 +1,4 @@
-ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}
+@import url("http://example.com/style.css");@import url("//example.com/style.css");ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}
p,select{font:1em/160% Verdana,sans-serif;color:#494949;}
body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this
.is
diff -Naur drupal-7.23/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css drupal-7.66/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css
--- drupal-7.23/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css 2019-04-17 22:20:46.000000000 +0200
@@ -1,6 +1,33 @@
+@import url("http://example.com/style.css");
+@import url("//example.com/style.css");
+ul, select {
+ font: 1em/160% Verdana, sans-serif;
+ color: #494949;
+}
+.ui-icon{background-image: url(images/icon.png);}
+
+/* Test data URI images with different quote styles. */
+.data .double-quote {
+ /* http://stackoverflow.com/a/13139830/11023 */
+ background-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
+}
+
+.data .single-quote {
+ background-image: url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');
+}
+
+.data .no-quote {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);
+}
+
+
+p, select {
+ font: 1em/160% Verdana, sans-serif;
+ color: #494949;
+}
body {
diff -Naur drupal-7.23/modules/simpletest/files/css_test_files/css_subfolder/css_input_with_import.css drupal-7.66/modules/simpletest/files/css_test_files/css_subfolder/css_input_with_import.css
--- drupal-7.23/modules/simpletest/files/css_test_files/css_subfolder/css_input_with_import.css 1970-01-01 01:00:00.000000000 +0100
+++ drupal-7.66/modules/simpletest/files/css_test_files/css_subfolder/css_input_with_import.css 2019-04-17 22:20:46.000000000 +0200
@@ -0,0 +1,29 @@
+
+
+@import "../import1.css";
+@import "../import2.css";
+
+body {
+ margin: 0;
+ padding: 0;
+ background: #edf5fa;
+ font: 76%/170% Verdana, sans-serif;
+ color: #494949;
+}
+
+.this .is .a .test {
+ font: 1em/100% Verdana, sans-serif;
+ color: #494949;
+}
+.this
+.is
+.a
+.test {
+font: 1em/100% Verdana, sans-serif;
+color: #494949;
+}
+
+textarea, select {
+ font: 1em/160% Verdana, sans-serif;
+ color: #494949;
+}
diff -Naur drupal-7.23/modules/simpletest/files/css_test_files/css_subfolder/css_input_with_import.css.optimized.css drupal-7.66/modules/simpletest/files/css_test_files/css_subfolder/css_input_with_import.css.optimized.css
--- drupal-7.23/modules/simpletest/files/css_test_files/css_subfolder/css_input_with_import.css.optimized.css 1970-01-01 01:00:00.000000000 +0100
+++ drupal-7.66/modules/simpletest/files/css_test_files/css_subfolder/css_input_with_import.css.optimized.css 2019-04-17 22:20:46.000000000 +0200
@@ -0,0 +1,6 @@
+ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(../images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}
+p,select{font:1em/160% Verdana,sans-serif;color:#494949;}
+body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this
+.is
+.a
+.test{font:1em/100% Verdana,sans-serif;color:#494949;}textarea,select{font:1em/160% Verdana,sans-serif;color:#494949;}
diff -Naur drupal-7.23/modules/simpletest/files/css_test_files/css_subfolder/css_input_with_import.css.unoptimized.css drupal-7.66/modules/simpletest/files/css_test_files/css_subfolder/css_input_with_import.css.unoptimized.css
--- drupal-7.23/modules/simpletest/files/css_test_files/css_subfolder/css_input_with_import.css.unoptimized.css 1970-01-01 01:00:00.000000000 +0100
+++ drupal-7.66/modules/simpletest/files/css_test_files/css_subfolder/css_input_with_import.css.unoptimized.css 2019-04-17 22:20:46.000000000 +0200
@@ -0,0 +1,54 @@
+
+
+
+ul, select {
+ font: 1em/160% Verdana, sans-serif;
+ color: #494949;
+}
+.ui-icon{background-image: url(../images/icon.png);}
+
+/* Test data URI images with different quote styles. */
+.data .double-quote {
+ /* http://stackoverflow.com/a/13139830/11023 */
+ background-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
+}
+
+.data .single-quote {
+ background-image: url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');
+}
+
+.data .no-quote {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);
+}
+
+
+p, select {
+ font: 1em/160% Verdana, sans-serif;
+ color: #494949;
+}
+
+
+body {
+ margin: 0;
+ padding: 0;
+ background: #edf5fa;
+ font: 76%/170% Verdana, sans-serif;
+ color: #494949;
+}
+
+.this .is .a .test {
+ font: 1em/100% Verdana, sans-serif;
+ color: #494949;
+}
+.this
+.is
+.a
+.test {
+font: 1em/100% Verdana, sans-serif;
+color: #494949;
+}
+
+textarea, select {
+ font: 1em/160% Verdana, sans-serif;
+ color: #494949;
+}
diff -Naur drupal-7.23/modules/simpletest/files/css_test_files/import1.css drupal-7.66/modules/simpletest/files/css_test_files/import1.css
--- drupal-7.23/modules/simpletest/files/css_test_files/import1.css 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/simpletest/files/css_test_files/import1.css 2019-04-17 22:20:46.000000000 +0200
@@ -3,4 +3,18 @@
font: 1em/160% Verdana, sans-serif;
color: #494949;
}
-.ui-icon{background-image: url(images/icon.png);}
\ No newline at end of file
+.ui-icon{background-image: url(images/icon.png);}
+
+/* Test data URI images with different quote styles. */
+.data .double-quote {
+ /* http://stackoverflow.com/a/13139830/11023 */
+ background-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
+}
+
+.data .single-quote {
+ background-image: url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');
+}
+
+.data .no-quote {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);
+}
diff -Naur drupal-7.23/modules/simpletest/files/image-test-no-transparency.gif drupal-7.66/modules/simpletest/files/image-test-no-transparency.gif
--- drupal-7.23/modules/simpletest/files/image-test-no-transparency.gif 1970-01-01 01:00:00.000000000 +0100
+++ drupal-7.66/modules/simpletest/files/image-test-no-transparency.gif 2019-04-17 22:20:46.000000000 +0200
@@ -0,0 +1 @@
+GIF89a( , ( 8 *L(C.HCV"ČC1b(S\YŌI%B0Oh N2w^icΠk|x̤<:$@*V5 Q ]fulح_{U&[iϲ-nBsݚ=7l^,+wHx
8bƅ?6 ;
\ No newline at end of file
diff -Naur drupal-7.23/modules/simpletest/files/image-test-transparent-out-of-range.gif drupal-7.66/modules/simpletest/files/image-test-transparent-out-of-range.gif
--- drupal-7.23/modules/simpletest/files/image-test-transparent-out-of-range.gif 1970-01-01 01:00:00.000000000 +0100
+++ drupal-7.66/modules/simpletest/files/image-test-transparent-out-of-range.gif 2019-04-17 22:20:46.000000000 +0200
@@ -0,0 +1,2 @@
+GIF89a( ! , ( |80
lݵҍԇd
+gp,E
Tl
{ Q1|ĩ^ޮ`L0er^`7G{vnoyiV40zk*$ar ;
\ No newline at end of file
diff -Naur drupal-7.23/modules/simpletest/files/phar-1.phar drupal-7.66/modules/simpletest/files/phar-1.phar
--- drupal-7.23/modules/simpletest/files/phar-1.phar 1970-01-01 01:00:00.000000000 +0100
+++ drupal-7.66/modules/simpletest/files/phar-1.phar 2019-04-17 22:20:46.000000000 +0200
@@ -0,0 +1,301 @@
+ 2,
+'c' => 'text/plain',
+'cc' => 'text/plain',
+'cpp' => 'text/plain',
+'c++' => 'text/plain',
+'dtd' => 'text/plain',
+'h' => 'text/plain',
+'log' => 'text/plain',
+'rng' => 'text/plain',
+'txt' => 'text/plain',
+'xsd' => 'text/plain',
+'php' => 1,
+'inc' => 1,
+'avi' => 'video/avi',
+'bmp' => 'image/bmp',
+'css' => 'text/css',
+'gif' => 'image/gif',
+'htm' => 'text/html',
+'html' => 'text/html',
+'htmls' => 'text/html',
+'ico' => 'image/x-ico',
+'jpe' => 'image/jpeg',
+'jpg' => 'image/jpeg',
+'jpeg' => 'image/jpeg',
+'js' => 'application/x-javascript',
+'midi' => 'audio/midi',
+'mid' => 'audio/midi',
+'mod' => 'audio/mod',
+'mov' => 'movie/quicktime',
+'mp3' => 'audio/mp3',
+'mpg' => 'video/mpeg',
+'mpeg' => 'video/mpeg',
+'pdf' => 'application/pdf',
+'png' => 'image/png',
+'swf' => 'application/shockwave-flash',
+'tif' => 'image/tiff',
+'tiff' => 'image/tiff',
+'wav' => 'audio/wav',
+'xbm' => 'image/xbm',
+'xml' => 'text/xml',
+);
+
+header("Cache-Control: no-cache, must-revalidate");
+header("Pragma: no-cache");
+
+$basename = basename(__FILE__);
+if (!strpos($_SERVER['REQUEST_URI'], $basename)) {
+chdir(Extract_Phar::$temp);
+include $web;
+return;
+}
+$pt = substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], $basename) + strlen($basename));
+if (!$pt || $pt == '/') {
+$pt = $web;
+header('HTTP/1.1 301 Moved Permanently');
+header('Location: ' . $_SERVER['REQUEST_URI'] . '/' . $pt);
+exit;
+}
+$a = realpath(Extract_Phar::$temp . DIRECTORY_SEPARATOR . $pt);
+if (!$a || strlen(dirname($a)) < strlen(Extract_Phar::$temp)) {
+header('HTTP/1.0 404 Not Found');
+echo "\n \n File Not Found\n \n \n 404 - File Not Found
\n \n";
+exit;
+}
+$b = pathinfo($a);
+if (!isset($b['extension'])) {
+header('Content-Type: text/plain');
+header('Content-Length: ' . filesize($a));
+readfile($a);
+exit;
+}
+if (isset($mimes[$b['extension']])) {
+if ($mimes[$b['extension']] === 1) {
+include $a;
+exit;
+}
+if ($mimes[$b['extension']] === 2) {
+highlight_file($a);
+exit;
+}
+header('Content-Type: ' .$mimes[$b['extension']]);
+header('Content-Length: ' . filesize($a));
+readfile($a);
+exit;
+}
+}
+
+class Extract_Phar
+{
+static $temp;
+static $origdir;
+const GZ = 0x1000;
+const BZ2 = 0x2000;
+const MASK = 0x3000;
+const START = 'index.php';
+const LEN = 6643;
+
+static function go($return = false)
+{
+$fp = fopen(__FILE__, 'rb');
+fseek($fp, self::LEN);
+$L = unpack('V', $a = fread($fp, 4));
+$m = '';
+
+do {
+$read = 8192;
+if ($L[1] - strlen($m) < 8192) {
+$read = $L[1] - strlen($m);
+}
+$last = fread($fp, $read);
+$m .= $last;
+} while (strlen($last) && strlen($m) < $L[1]);
+
+if (strlen($m) < $L[1]) {
+die('ERROR: manifest length read was "' .
+strlen($m) .'" should be "' .
+$L[1] . '"');
+}
+
+$info = self::_unpack($m);
+$f = $info['c'];
+
+if ($f & self::GZ) {
+if (!function_exists('gzinflate')) {
+die('Error: zlib extension is not enabled -' .
+' gzinflate() function needed for zlib-compressed .phars');
+}
+}
+
+if ($f & self::BZ2) {
+if (!function_exists('bzdecompress')) {
+die('Error: bzip2 extension is not enabled -' .
+' bzdecompress() function needed for bz2-compressed .phars');
+}
+}
+
+$temp = self::tmpdir();
+
+if (!$temp || !is_writable($temp)) {
+$sessionpath = session_save_path();
+if (strpos ($sessionpath, ";") !== false)
+$sessionpath = substr ($sessionpath, strpos ($sessionpath, ";")+1);
+if (!file_exists($sessionpath) || !is_dir($sessionpath)) {
+die('Could not locate temporary directory to extract phar');
+}
+$temp = $sessionpath;
+}
+
+$temp .= '/pharextract/'.basename(__FILE__, '.phar');
+self::$temp = $temp;
+self::$origdir = getcwd();
+@mkdir($temp, 0777, true);
+$temp = realpath($temp);
+
+if (!file_exists($temp . DIRECTORY_SEPARATOR . md5_file(__FILE__))) {
+self::_removeTmpFiles($temp, getcwd());
+@mkdir($temp, 0777, true);
+@file_put_contents($temp . '/' . md5_file(__FILE__), '');
+
+foreach ($info['m'] as $path => $file) {
+$a = !file_exists(dirname($temp . '/' . $path));
+@mkdir(dirname($temp . '/' . $path), 0777, true);
+clearstatcache();
+
+if ($path[strlen($path) - 1] == '/') {
+@mkdir($temp . '/' . $path, 0777);
+} else {
+file_put_contents($temp . '/' . $path, self::extractFile($path, $file, $fp));
+@chmod($temp . '/' . $path, 0666);
+}
+}
+}
+
+chdir($temp);
+
+if (!$return) {
+include self::START;
+}
+}
+
+static function tmpdir()
+{
+if (strpos(PHP_OS, 'WIN') !== false) {
+if ($var = getenv('TMP') ? getenv('TMP') : getenv('TEMP')) {
+return $var;
+}
+if (is_dir('/temp') || mkdir('/temp')) {
+return realpath('/temp');
+}
+return false;
+}
+if ($var = getenv('TMPDIR')) {
+return $var;
+}
+return realpath('/tmp');
+}
+
+static function _unpack($m)
+{
+$info = unpack('V', substr($m, 0, 4));
+ $l = unpack('V', substr($m, 10, 4));
+$m = substr($m, 14 + $l[1]);
+$s = unpack('V', substr($m, 0, 4));
+$o = 0;
+$start = 4 + $s[1];
+$ret['c'] = 0;
+
+for ($i = 0; $i < $info[1]; $i++) {
+ $len = unpack('V', substr($m, $start, 4));
+$start += 4;
+ $savepath = substr($m, $start, $len[1]);
+$start += $len[1];
+ $ret['m'][$savepath] = array_values(unpack('Va/Vb/Vc/Vd/Ve/Vf', substr($m, $start, 24)));
+$ret['m'][$savepath][3] = sprintf('%u', $ret['m'][$savepath][3]
+& 0xffffffff);
+$ret['m'][$savepath][7] = $o;
+$o += $ret['m'][$savepath][2];
+$start += 24 + $ret['m'][$savepath][5];
+$ret['c'] |= $ret['m'][$savepath][4] & self::MASK;
+}
+return $ret;
+}
+
+static function extractFile($path, $entry, $fp)
+{
+$data = '';
+$c = $entry[2];
+
+while ($c) {
+if ($c < 8192) {
+$data .= @fread($fp, $c);
+$c = 0;
+} else {
+$c -= 8192;
+$data .= @fread($fp, 8192);
+}
+}
+
+if ($entry[4] & self::GZ) {
+$data = gzinflate($data);
+} elseif ($entry[4] & self::BZ2) {
+$data = bzdecompress($data);
+}
+
+if (strlen($data) != $entry[0]) {
+die("Invalid internal .phar file (size error " . strlen($data) . " != " .
+$stat[7] . ")");
+}
+
+if ($entry[3] != sprintf("%u", crc32($data) & 0xffffffff)) {
+die("Invalid internal .phar file (checksum error)");
+}
+
+return $data;
+}
+
+static function _removeTmpFiles($temp, $origdir)
+{
+chdir($temp);
+
+foreach (glob('*') as $f) {
+if (file_exists($f)) {
+is_dir($f) ? @rmdir($f) : @unlink($f);
+if (file_exists($f) && is_dir($f)) {
+self::_removeTmpFiles($f, getcwd());
+}
+}
+}
+
+@rmdir($temp);
+clearstatcache();
+chdir($origdir);
+}
+}
+
+Extract_Phar::go();
+__HALT_COMPILER(); ?>7 index.php 8![ u $elapsed)));
@@ -328,25 +331,32 @@
// Also discover PSR-0 test classes, if the PHP version allows it.
if (version_compare(PHP_VERSION, '5.3') > 0) {
- // Select all PSR-0 classes in the Tests namespace of all modules.
+ // Select all PSR-0 and PSR-4 classes in the Tests namespace of all
+ // modules.
$system_list = db_query("SELECT name, filename FROM {system}")->fetchAllKeyed();
foreach ($system_list as $name => $filename) {
- // Build directory in which the test files would reside.
- $tests_dir = DRUPAL_ROOT . '/' . dirname($filename) . '/lib/Drupal/' . $name . '/Tests';
- // Scan it for test files if it exists.
- if (is_dir($tests_dir)) {
- $files = file_scan_directory($tests_dir, '/.*\.php/');
- if (!empty($files)) {
- $basedir = DRUPAL_ROOT . '/' . dirname($filename) . '/lib/';
- foreach ($files as $file) {
- // Convert the file name into the namespaced class name.
- $replacements = array(
- '/' => '\\',
- $basedir => '',
- '.php' => '',
- );
- $classes[] = strtr($file->uri, $replacements);
+ $module_dir = DRUPAL_ROOT . '/' . dirname($filename);
+ // Search both the 'lib/Drupal/mymodule' directory (for PSR-0 classes)
+ // and the 'src' directory (for PSR-4 classes).
+ foreach(array('lib/Drupal/' . $name, 'src') as $subdir) {
+ // Build directory in which the test files would reside.
+ $tests_dir = $module_dir . '/' . $subdir . '/Tests';
+ // Scan it for test files if it exists.
+ if (is_dir($tests_dir)) {
+ $files = file_scan_directory($tests_dir, '/.*\.php/');
+ if (!empty($files)) {
+ foreach ($files as $file) {
+ // Convert the file name into the namespaced class name.
+ $replacements = array(
+ '/' => '\\',
+ $module_dir . '/' => '',
+ 'lib/' => '',
+ 'src/' => 'Drupal\\' . $name . '\\',
+ '.php' => '',
+ );
+ $classes[] = strtr($file->uri, $replacements);
+ }
}
}
}
@@ -364,7 +374,10 @@
// If this test class requires a non-existing module, skip it.
if (!empty($info['dependencies'])) {
foreach ($info['dependencies'] as $module) {
- if (!drupal_get_filename('module', $module)) {
+ // Pass FALSE as fourth argument so no error gets created for
+ // the missing file.
+ $found_module = drupal_get_filename('module', $module, NULL, FALSE);
+ if (!$found_module) {
continue 2;
}
}
@@ -406,17 +419,20 @@
// Only register PSR-0 class loading if we are on PHP 5.3 or higher.
if (version_compare(PHP_VERSION, '5.3') > 0) {
- spl_autoload_register('_simpletest_autoload_psr0');
+ spl_autoload_register('_simpletest_autoload_psr4_psr0');
}
}
/**
- * Autoload callback to find PSR-0 test classes.
+ * Autoload callback to find PSR-4 and PSR-0 test classes.
+ *
+ * Looks in the 'src/Tests' and in the 'lib/Drupal/mymodule/Tests' directory of
+ * modules for the class.
*
* This will only work on classes where the namespace is of the pattern
* "Drupal\$extension\Tests\.."
*/
-function _simpletest_autoload_psr0($class) {
+function _simpletest_autoload_psr4_psr0($class) {
// Static cache for extension paths.
// This cache is lazily filled as soon as it is needed.
@@ -446,14 +462,26 @@
$namespace = substr($class, 0, $nspos);
$classname = substr($class, $nspos + 1);
- // Build the filepath where we expect the class to be defined.
- $path = dirname($extensions[$extension]) . '/lib/' .
- str_replace('\\', '/', $namespace) . '/' .
+ // Try the PSR-4 location first, and the PSR-0 location as a fallback.
+ // Build the PSR-4 filepath where we expect the class to be defined.
+ $psr4_path = dirname($extensions[$extension]) . '/src/' .
+ str_replace('\\', '/', substr($namespace, strlen('Drupal\\' . $extension . '\\'))) . '/' .
str_replace('_', '/', $classname) . '.php';
// Include the file, if it does exist.
- if (file_exists($path)) {
- include $path;
+ if (file_exists($psr4_path)) {
+ include $psr4_path;
+ }
+ else {
+ // Build the PSR-0 filepath where we expect the class to be defined.
+ $psr0_path = dirname($extensions[$extension]) . '/lib/' .
+ str_replace('\\', '/', $namespace) . '/' .
+ str_replace('_', '/', $classname) . '.php';
+
+ // Include the file, if it does exist.
+ if (file_exists($psr0_path)) {
+ include $psr0_path;
+ }
}
}
}
@@ -487,25 +515,25 @@
* Generate test file.
*/
function simpletest_generate_file($filename, $width, $lines, $type = 'binary-text') {
- $size = $width * $lines - $lines;
-
- // Generate random text
$text = '';
- for ($i = 0; $i < $size; $i++) {
- switch ($type) {
- case 'text':
- $text .= chr(rand(32, 126));
- break;
- case 'binary':
- $text .= chr(rand(0, 31));
- break;
- case 'binary-text':
- default:
- $text .= rand(0, 1);
- break;
+ for ($i = 0; $i < $lines; $i++) {
+ // Generate $width - 1 characters to leave space for the "\n" character.
+ for ($j = 0; $j < $width - 1; $j++) {
+ switch ($type) {
+ case 'text':
+ $text .= chr(rand(32, 126));
+ break;
+ case 'binary':
+ $text .= chr(rand(0, 31));
+ break;
+ case 'binary-text':
+ default:
+ $text .= rand(0, 1);
+ break;
+ }
}
+ $text .= "\n";
}
- $text = wordwrap($text, $width - 1, "\n", TRUE) . "\n"; // Add \n for symmetrical file.
// Create filename.
file_put_contents('public://' . $filename . '.txt', $text);
diff -Naur drupal-7.23/modules/simpletest/simpletest.test drupal-7.66/modules/simpletest/simpletest.test
--- drupal-7.23/modules/simpletest/simpletest.test 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/simpletest/simpletest.test 2019-04-17 22:20:46.000000000 +0200
@@ -322,6 +322,14 @@
* Test internal testing framework browser.
*/
class SimpleTestBrowserTestCase extends DrupalWebTestCase {
+
+ /**
+ * A flag indicating whether a cookie has been set in a test.
+ *
+ * @var bool
+ */
+ protected static $cookieSet = FALSE;
+
public static function getInfo() {
return array(
'name' => 'SimpleTest browser',
@@ -380,6 +388,46 @@
$urls = $this->xpath('//a[text()=:text]', array(':text' => 'A second "even more weird" link, in memory of George O\'Malley'));
$this->assertEqual($urls[0]['href'], 'link2', 'Match with mixed single and double quotes.');
}
+
+ /**
+ * Tests that cookies set during a request are available for testing.
+ */
+ public function testCookies() {
+ // Check that the $this->cookies property is populated when a user logs in.
+ $user = $this->drupalCreateUser();
+ $edit = array('name' => $user->name, 'pass' => $user->pass_raw);
+ $this->drupalPost('', $edit, t('Log in'));
+ $this->assertEqual(count($this->cookies), 1, 'A cookie is set when the user logs in.');
+
+ // Check that the name and value of the cookie match the request data.
+ $cookie_header = $this->drupalGetHeader('set-cookie', TRUE);
+
+ // The name and value are located at the start of the string, separated by
+ // an equals sign and ending in a semicolon.
+ preg_match('/^([^=]+)=([^;]+)/', $cookie_header, $matches);
+ $name = $matches[1];
+ $value = $matches[2];
+
+ $this->assertTrue(array_key_exists($name, $this->cookies), 'The cookie name is correct.');
+ $this->assertEqual($value, $this->cookies[$name]['value'], 'The cookie value is correct.');
+
+ // Set a flag indicating that a cookie has been set in this test.
+ // @see SimpleTestBrowserTestCase::testCookieDoesNotBleed().
+ self::$cookieSet = TRUE;
+ }
+
+ /**
+ * Tests that the cookies from a previous test do not bleed into a new test.
+ *
+ * @see SimpleTestBrowserTestCase::testCookies().
+ */
+ public function testCookieDoesNotBleed() {
+ // In order for this test to be effective it should always run after the
+ // testCookies() test.
+ $this->assertTrue(self::$cookieSet, 'Tests have been executed in the expected order.');
+ $this->assertEqual(count($this->cookies), 0, 'No cookies are present at the start of a new test.');
+ }
+
}
class SimpleTestMailCaptureTestCase extends DrupalWebTestCase {
@@ -703,7 +751,9 @@
$classes_all = simpletest_test_get_all();
foreach (array(
'Drupal\\simpletest\\Tests\\PSR0WebTest',
+ 'Drupal\\simpletest\\Tests\\PSR4WebTest',
'Drupal\\psr_0_test\\Tests\\ExampleTest',
+ 'Drupal\\psr_4_test\\Tests\\ExampleTest',
) as $class) {
$this->assert(!empty($classes_all['SimpleTest'][$class]), t('Class @class must be discovered by simpletest_test_get_all().', array('@class' => $class)));
}
@@ -726,15 +776,20 @@
// Don't expect PSR-0 tests to be discovered on older PHP versions.
return;
}
- // This one is provided by simpletest itself via PSR-0.
+ // These are provided by simpletest itself via PSR-0 and PSR-4.
$this->assertText('PSR0 web test');
+ $this->assertText('PSR4 web test');
$this->assertText('PSR0 example test: PSR-0 in disabled modules.');
+ $this->assertText('PSR4 example test: PSR-4 in disabled modules.');
$this->assertText('PSR0 example test: PSR-0 in nested subfolders.');
+ $this->assertText('PSR4 example test: PSR-4 in nested subfolders.');
// Test each test individually.
foreach (array(
'Drupal\\psr_0_test\\Tests\\ExampleTest',
'Drupal\\psr_0_test\\Tests\\Nested\\NestedExampleTest',
+ 'Drupal\\psr_4_test\\Tests\\ExampleTest',
+ 'Drupal\\psr_4_test\\Tests\\Nested\\NestedExampleTest',
) as $class) {
$this->drupalGet('admin/config/development/testing');
$edit = array($class => TRUE);
diff -Naur drupal-7.23/modules/simpletest/src/Tests/PSR4WebTest.php drupal-7.66/modules/simpletest/src/Tests/PSR4WebTest.php
--- drupal-7.23/modules/simpletest/src/Tests/PSR4WebTest.php 1970-01-01 01:00:00.000000000 +0100
+++ drupal-7.66/modules/simpletest/src/Tests/PSR4WebTest.php 2019-04-17 22:20:46.000000000 +0200
@@ -0,0 +1,18 @@
+ 'PSR4 web test',
+ 'description' => 'We want to assert that this PSR-4 test case is being discovered.',
+ 'group' => 'SimpleTest',
+ );
+ }
+
+ function testArithmetics() {
+ $this->assert(1 + 1 == 2, '1 + 1 == 2');
+ }
+}
diff -Naur drupal-7.23/modules/simpletest/tests/actions_loop_test.info drupal-7.66/modules/simpletest/tests/actions_loop_test.info
--- drupal-7.23/modules/simpletest/tests/actions_loop_test.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/simpletest/tests/actions_loop_test.info 2019-04-17 22:39:36.000000000 +0200
@@ -5,8 +5,7 @@
core = 7.x
hidden = TRUE
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/simpletest/tests/ajax.test drupal-7.66/modules/simpletest/tests/ajax.test
--- drupal-7.23/modules/simpletest/tests/ajax.test 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/simpletest/tests/ajax.test 2019-04-17 22:20:46.000000000 +0200
@@ -293,7 +293,7 @@
$this->assertCommand($commands, $expected, "'changed' AJAX command (with asterisk) issued with correct selector");
// Tests the 'css' command.
- $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("Set the the '#box' div to be blue.")));
+ $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("Set the '#box' div to be blue.")));
$expected = array(
'command' => 'css',
'selector' => '#css_div',
@@ -368,6 +368,14 @@
'settings' => array('ajax_forms_test' => array('foo' => 42)),
);
$this->assertCommand($commands, $expected, "'settings' AJAX command issued with correct data");
+
+ // Tests the 'add_css' command.
+ $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'add_css' command")));
+ $expected = array(
+ 'command' => 'add_css',
+ 'data' => 'my/file.css',
+ );
+ $this->assertCommand($commands, $expected, "'add_css' AJAX command issued with correct data");
}
}
@@ -498,6 +506,85 @@
}
/**
+ * Test Ajax forms when page caching for anonymous users is turned on.
+ */
+class AJAXFormPageCacheTestCase extends AJAXTestCase {
+ protected $profile = 'testing';
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'AJAX forms on cached pages',
+ 'description' => 'Tests that AJAX forms work properly for anonymous users on cached pages.',
+ 'group' => 'AJAX',
+ );
+ }
+
+ public function setUp() {
+ parent::setUp();
+
+ variable_set('cache', TRUE);
+ }
+
+ /**
+ * Return the build id of the current form.
+ */
+ protected function getFormBuildId() {
+ $build_id_fields = $this->xpath('//input[@name="form_build_id"]');
+ $this->assertEqual(count($build_id_fields), 1, 'One form build id field on the page');
+ return (string) $build_id_fields[0]['value'];
+ }
+
+ /**
+ * Create a simple form, then POST to system/ajax to change to it.
+ */
+ public function testSimpleAJAXFormValue() {
+ $this->drupalGet('ajax_forms_test_get_form');
+ $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.');
+ $build_id_initial = $this->getFormBuildId();
+
+ $edit = array('select' => 'green');
+ $commands = $this->drupalPostAJAX(NULL, $edit, 'select');
+ $build_id_first_ajax = $this->getFormBuildId();
+ $this->assertNotEqual($build_id_initial, $build_id_first_ajax, 'Build id is changed in the simpletest-DOM on first AJAX submission');
+ $expected = array(
+ 'command' => 'updateBuildId',
+ 'old' => $build_id_initial,
+ 'new' => $build_id_first_ajax,
+ );
+ $this->assertCommand($commands, $expected, 'Build id change command issued on first AJAX submission');
+
+ $edit = array('select' => 'red');
+ $commands = $this->drupalPostAJAX(NULL, $edit, 'select');
+ $build_id_second_ajax = $this->getFormBuildId();
+ $this->assertEqual($build_id_first_ajax, $build_id_second_ajax, 'Build id remains the same on subsequent AJAX submissions');
+
+ // Repeat the test sequence but this time with a page loaded from the cache.
+ $this->drupalGet('ajax_forms_test_get_form');
+ $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
+ $build_id_from_cache_initial = $this->getFormBuildId();
+ $this->assertEqual($build_id_initial, $build_id_from_cache_initial, 'Build id is the same as on the first request');
+
+ $edit = array('select' => 'green');
+ $commands = $this->drupalPostAJAX(NULL, $edit, 'select');
+ $build_id_from_cache_first_ajax = $this->getFormBuildId();
+ $this->assertNotEqual($build_id_from_cache_initial, $build_id_from_cache_first_ajax, 'Build id is changed in the simpletest-DOM on first AJAX submission');
+ $this->assertNotEqual($build_id_first_ajax, $build_id_from_cache_first_ajax, 'Build id from first user is not reused');
+ $expected = array(
+ 'command' => 'updateBuildId',
+ 'old' => $build_id_from_cache_initial,
+ 'new' => $build_id_from_cache_first_ajax,
+ );
+ $this->assertCommand($commands, $expected, 'Build id change command issued on first AJAX submission');
+
+ $edit = array('select' => 'red');
+ $commands = $this->drupalPostAJAX(NULL, $edit, 'select');
+ $build_id_from_cache_second_ajax = $this->getFormBuildId();
+ $this->assertEqual($build_id_from_cache_first_ajax, $build_id_from_cache_second_ajax, 'Build id remains the same on subsequent AJAX submissions');
+ }
+}
+
+
+/**
* Miscellaneous Ajax tests using ajax_test module.
*/
class AJAXElementValidation extends AJAXTestCase {
diff -Naur drupal-7.23/modules/simpletest/tests/ajax_forms_test.info drupal-7.66/modules/simpletest/tests/ajax_forms_test.info
--- drupal-7.23/modules/simpletest/tests/ajax_forms_test.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/simpletest/tests/ajax_forms_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 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/simpletest/tests/ajax_forms_test.module drupal-7.66/modules/simpletest/tests/ajax_forms_test.module
--- drupal-7.23/modules/simpletest/tests/ajax_forms_test.module 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/simpletest/tests/ajax_forms_test.module 2019-04-17 22:20:46.000000000 +0200
@@ -157,7 +157,7 @@
// Shows the Ajax 'css' command.
$form['css_command_example'] = array(
- '#value' => t("Set the the '#box' div to be blue."),
+ '#value' => t("Set the '#box' div to be blue."),
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_forms_test_advanced_commands_css_callback',
@@ -254,6 +254,15 @@
),
);
+ // Shows the Ajax 'add_css' command.
+ $form['add_css_command_example'] = array(
+ '#type' => 'submit',
+ '#value' => t("AJAX 'add_css' command"),
+ '#ajax' => array(
+ 'callback' => 'ajax_forms_test_advanced_commands_add_css_callback',
+ ),
+ );
+
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
@@ -407,6 +416,15 @@
}
/**
+ * Ajax callback for 'add_css'.
+ */
+function ajax_forms_test_advanced_commands_add_css_callback($form, $form_state) {
+ $commands = array();
+ $commands[] = ajax_command_add_css('my/file.css');
+ return array('#type' => 'ajax', '#commands' => $commands);
+}
+
+/**
* This form and its related submit and callback functions demonstrate
* not validating another form element when a single Ajax element is triggered.
*
diff -Naur drupal-7.23/modules/simpletest/tests/ajax_test.info drupal-7.66/modules/simpletest/tests/ajax_test.info
--- drupal-7.23/modules/simpletest/tests/ajax_test.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/simpletest/tests/ajax_test.info 2019-04-17 22:39:36.000000000 +0200
@@ -5,8 +5,7 @@
core = 7.x
hidden = TRUE
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/simpletest/tests/batch_test.callbacks.inc drupal-7.66/modules/simpletest/tests/batch_test.callbacks.inc
--- drupal-7.23/modules/simpletest/tests/batch_test.callbacks.inc 2013-08-08 04:04:26.000000000 +0200
+++ drupal-7.66/modules/simpletest/tests/batch_test.callbacks.inc 2019-04-17 22:20:46.000000000 +0200
@@ -7,6 +7,8 @@
*/
/**
+ * Implements callback_batch_operation().
+ *
* Simple batch operation.
*/
function _batch_test_callback_1($id, $sleep, &$context) {
@@ -20,6 +22,8 @@
}
/**
+ * Implements callback_batch_operation().
+ *
* Multistep batch operation.
*/
function _batch_test_callback_2($start, $total, $sleep, &$context) {
@@ -53,6 +57,8 @@
}
/**
+ * Implements callback_batch_operation().
+ *
* Simple batch operation.
*/
function _batch_test_callback_5($id, $sleep, &$context) {
@@ -68,6 +74,8 @@
}
/**
+ * Implements callback_batch_operation().
+ *
* Batch operation setting up its own batch.
*/
function _batch_test_nested_batch_callback() {
@@ -76,6 +84,8 @@
}
/**
+ * Implements callback_batch_finished().
+ *
* Common 'finished' callbacks for batches 1 to 4.
*/
function _batch_test_finished_helper($batch_id, $success, $results, $operations) {
@@ -99,6 +109,8 @@
}
/**
+ * Implements callback_batch_finished().
+ *
* 'finished' callback for batch 0.
*/
function _batch_test_finished_0($success, $results, $operations) {
@@ -106,6 +118,8 @@
}
/**
+ * Implements callback_batch_finished().
+ *
* 'finished' callback for batch 1.
*/
function _batch_test_finished_1($success, $results, $operations) {
@@ -113,6 +127,8 @@
}
/**
+ * Implements callback_batch_finished().
+ *
* 'finished' callback for batch 2.
*/
function _batch_test_finished_2($success, $results, $operations) {
@@ -120,6 +136,8 @@
}
/**
+ * Implements callback_batch_finished().
+ *
* 'finished' callback for batch 3.
*/
function _batch_test_finished_3($success, $results, $operations) {
@@ -127,6 +145,8 @@
}
/**
+ * Implements callback_batch_finished().
+ *
* 'finished' callback for batch 4.
*/
function _batch_test_finished_4($success, $results, $operations) {
@@ -134,6 +154,8 @@
}
/**
+ * Implements callback_batch_finished().
+ *
* 'finished' callback for batch 5.
*/
function _batch_test_finished_5($success, $results, $operations) {
diff -Naur drupal-7.23/modules/simpletest/tests/batch_test.info drupal-7.66/modules/simpletest/tests/batch_test.info
--- drupal-7.23/modules/simpletest/tests/batch_test.info 2013-08-08 04:17:18.000000000 +0200
+++ drupal-7.66/modules/simpletest/tests/batch_test.info 2019-04-17 22:39:36.000000000 +0200
@@ -5,8 +5,7 @@
core = 7.x
hidden = TRUE
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.23"
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
project = "drupal"
-datestamp = "1375928238"
-
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/simpletest/tests/boot.test drupal-7.66/modules/simpletest/tests/boot.test
--- drupal-7.23/modules/simpletest/tests/boot.test 1970-01-01 01:00:00.000000000 +0100
+++ drupal-7.66/modules/simpletest/tests/boot.test 2019-04-17 22:20:46.000000000 +0200
@@ -0,0 +1,38 @@
+ 'Early bootstrap test',
+ 'description' => 'Confirm that calling module_implements() during early bootstrap does not pollute the module_implements() cache.',
+ 'group' => 'System',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('boot_test_1', 'boot_test_2');
+ }
+
+ /**
+ * Test hook_boot() on both regular and "early exit" pages.
+ */
+ public function testHookBoot() {
+ $paths = array('', 'early_exit');
+ foreach ($paths as $path) {
+ // Empty the module_implements() caches.
+ module_implements(NULL, FALSE, TRUE);
+ // Do a request to the front page, which will call module_implements()
+ // during hook_boot().
+ $this->drupalGet($path);
+ // Reset the static cache so we get implementation data from the persistent
+ // cache.
+ drupal_static_reset();
+ // Make sure we get a full list of all modules implementing hook_help().
+ $modules = module_implements('help');
+ $this->assertTrue(in_array('boot_test_2', $modules));
+ }
+ }
+}
diff -Naur drupal-7.23/modules/simpletest/tests/boot_test_1.info drupal-7.66/modules/simpletest/tests/boot_test_1.info
--- drupal-7.23/modules/simpletest/tests/boot_test_1.info 1970-01-01 01:00:00.000000000 +0100
+++ drupal-7.66/modules/simpletest/tests/boot_test_1.info 2019-04-17 22:39:36.000000000 +0200
@@ -0,0 +1,11 @@
+name = Early bootstrap tests
+description = A support module for hook_boot testing.
+core = 7.x
+package = Testing
+version = VERSION
+hidden = TRUE
+
+; Information added by Drupal.org packaging script on 2019-04-17
+version = "7.66"
+project = "drupal"
+datestamp = "1555533576"
diff -Naur drupal-7.23/modules/simpletest/tests/boot_test_1.module drupal-7.66/modules/simpletest/tests/boot_test_1.module
--- drupal-7.23/modules/simpletest/tests/boot_test_1.module 1970-01-01 01:00:00.000000000 +0100
+++ drupal-7.66/modules/simpletest/tests/boot_test_1.module 2019-04-17 22:20:46.000000000 +0200
@@ -0,0 +1,21 @@
+proxy_ip;
+ $_SERVER['HTTP_X_FORWARDED_FOR'] = $this->proxy_ip;
+ drupal_static_reset('ip_address');
+ $this->assertTrue(
+ ip_address() == $this->proxy_ip,
+ 'Visiting from trusted proxy got proxy IP address.'
+ );
+
// Multi-tier architecture with comma separated values in header.
$_SERVER['REMOTE_ADDR'] = $this->proxy_ip;
$_SERVER['HTTP_X_FORWARDED_FOR'] = implode(', ', array($this->untrusted_ip, $this->forwarded_ip, $this->proxy2_ip));
@@ -93,6 +102,11 @@
$this->assertFalse(drupal_valid_http_host('security\\.drupal.org:80'), 'HTTP_HOST with \\ is invalid');
$this->assertFalse(drupal_valid_http_host('security<.drupal.org:80'), 'HTTP_HOST with < is invalid');
$this->assertFalse(drupal_valid_http_host('security..drupal.org:80'), 'HTTP_HOST with .. is invalid');
+ // Verifies that host names are shorter than 1000 characters.
+ $this->assertFalse(drupal_valid_http_host(str_repeat('x', 1001)), 'HTTP_HOST with more than 1000 characters is invalid.');
+ $this->assertFalse(drupal_valid_http_host(str_repeat('.', 101)), 'HTTP_HOST with more than 100 subdomains is invalid.');
+ $this->assertFalse(drupal_valid_http_host(str_repeat(':', 101)), 'HTTP_HOST with more than 100 portseparators is invalid.');
+
// IPv6 loopback address
$this->assertTrue(drupal_valid_http_host('[::1]:80'), 'HTTP_HOST containing IPv6 loopback is valid');
}
@@ -139,7 +153,7 @@
$this->assertResponse(200, 'Conditional request without If-None-Match returned 200 OK.');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
- $this->drupalGet('', array(), array('If-Modified-Since: ' . gmdate(DATE_RFC1123, strtotime($last_modified) + 1), 'If-None-Match: ' . $etag));
+ $this->drupalGet('', array(), array('If-Modified-Since: ' . gmdate(DATE_RFC7231, strtotime($last_modified) + 1), 'If-None-Match: ' . $etag));
$this->assertResponse(200, 'Conditional request with new a If-Modified-Since date newer than Last-Modified returned 200 OK.');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
@@ -147,7 +161,9 @@
$this->drupalLogin($user);
$this->drupalGet('', array(), array('If-Modified-Since: ' . $last_modified, 'If-None-Match: ' . $etag));
$this->assertResponse(200, 'Conditional request returned 200 OK for authenticated user.');
- $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Absense of Page was not cached.');
+ $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Absence of Page was not cached.');
+ $this->assertFalse($this->drupalGetHeader('ETag'), 'ETag HTTP headers are not present for logged in users.');
+ $this->assertFalse($this->drupalGetHeader('Last-Modified'), 'Last-Modified HTTP headers are not present for logged in users.');
}
/**
@@ -184,7 +200,7 @@
$this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar')));
$this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Caching was bypassed.');
$this->assertTrue(strpos($this->drupalGetHeader('Vary'), 'Cookie') === FALSE, 'Vary: Cookie header was not sent.');
- $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate, post-check=0, pre-check=0', 'Cache-Control header was sent.');
+ $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate', 'Cache-Control header was sent.');
$this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.');
$this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.');
@@ -219,6 +235,18 @@
$this->assertFalse($this->drupalGetHeader('Content-Encoding'), 'A Content-Encoding header was not sent.');
$this->assertTitle(t('Welcome to @site-name | @site-name', array('@site-name' => variable_get('site_name', 'Drupal'))), 'Site title matches.');
$this->assertRaw('', 'Page was not compressed.');
+
+ // Disable compression mode.
+ variable_set('page_compression', FALSE);
+
+ // Verify if cached page is still available for a client with compression support.
+ $this->drupalGet('', array(), array('Accept-Encoding: gzip,deflate'));
+ $this->drupalSetContent(gzinflate(substr($this->drupalGetContent(), 10, -8)));
+ $this->assertRaw('', 'Page was delivered after compression mode is changed (compression support enabled).');
+
+ // Verify if cached page is still available for a client without compression support.
+ $this->drupalGet('');
+ $this->assertRaw('