diff -Naur drupal-7.14/.editorconfig drupal-7.66/.editorconfig --- drupal-7.14/.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.14/.htaccess drupal-7.66/.htaccess --- drupal-7.14/.htaccess 2012-05-03 00:10:42.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. - - Order allow,deny + + + Require all denied + + + Order allow,deny + # Don't show directory listings for URLs which map to a directory. @@ -20,7 +25,7 @@ DirectoryIndex index.php index.html index.htm # Override PHP settings that cannot be changed at runtime. See -# sites/default/default.settings.php and drupal_initialize_variables() in +# sites/default/default.settings.php and drupal_environment_initialize() in # includes/bootstrap.inc for settings that can be changed at runtime. # PHP 5, Apache 1 and 2. @@ -56,6 +61,17 @@ RewriteEngine on + # Set "protossl" to "s" if we were accessed via https://. This is used later + # if you enable "www." stripping or enforcement, in order to ensure that + # you don't bounce between http and https. + RewriteRule ^ - [E=protossl] + RewriteCond %{HTTPS} on + RewriteRule ^ - [E=protossl:s] + + # Make sure Authorization HTTP header is available to PHP + # even when running as CGI or FastCGI. + RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + # Block access to "hidden" directories whose names begin with a period. This # includes directories used by version control systems such as Subversion or # Git to store control files. Files whose names begin with a period, as well @@ -69,7 +85,7 @@ # If you do not have mod_rewrite installed, you should remove these # directories from your webroot or otherwise protect them from being # downloaded. - RewriteRule "(^|/)\." - [F] + RewriteRule "/\.|^\.(?!well-known/)" - [F] # If your site can be accessed both with and without the 'www.' prefix, you # can use one of the following settings to redirect users to your preferred @@ -78,14 +94,15 @@ # To redirect all users to access the site WITH the 'www.' prefix, # (http://example.com/... will be redirected to http://www.example.com/...) # uncomment the following: + # RewriteCond %{HTTP_HOST} . # RewriteCond %{HTTP_HOST} !^www\. [NC] - # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301] + # RewriteRule ^ http%{ENV:protossl}://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301] # # To redirect all users to access the site WITHOUT the 'www.' prefix, # (http://www.example.com/... will be redirected to http://example.com/...) # uncomment the following: # RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] - # RewriteRule ^ http://%1%{REQUEST_URI} [L,R=301] + # RewriteRule ^ http%{ENV:protossl}://%1%{REQUEST_URI} [L,R=301] # Modify the RewriteBase if you are using Drupal in a subdirectory or in a # VirtualDocumentRoot and the rewrite rules are not working properly. @@ -129,3 +146,9 @@ + +# Add headers to all responses. + + # Disable content sniffing, since it's an attack vector. + Header always set X-Content-Type-Options nosniff + diff -Naur drupal-7.14/CHANGELOG.txt drupal-7.66/CHANGELOG.txt --- drupal-7.14/CHANGELOG.txt 2012-05-03 00:10:42.000000000 +0200 +++ drupal-7.66/CHANGELOG.txt 2019-04-17 22:20:46.000000000 +0200 @@ -1,6 +1,861 @@ +Drupal 7.xx, xxxx-xx-xx (development version) +----------------------- -Drupal 7.14 2012-05-02 ----------------------- +Drupal 7.66, 2019-04-17 +----------------------- +- Fixed security issues: + - SA-CORE-2019-006 + +Drupal 7.65, 2019-03-20 +----------------------- +- Fixed security issues: + - SA-CORE-2019-004 + +Drupal 7.64, 2019-02-06 +----------------------- +- [regression] Unset the 'host' header in drupal_http_request() during redirect +- Fixed: 7.x does not have Phar protection and Phar tests are failing on Drupal 7 +- Fixed: Notice: Undefined index: display_field in file_field_widget_value() (line 582 of /module/file/file.field.inc) +- Performance improvement: Registry rebuild should not parse the same file twice in the same request +- Fixed _registry_update() to clear caches after transaction is committed + +Drupal 7.63, 2019-01-16 +----------------------- +- Fixed a fatal error for some Drush users introduced by SA-CORE-2019-002. + +Drupal 7.62, 2019-01-15 +----------------------- +- Fixed security issues: + - SA-CORE-2019-001 + - SA-CORE-2019-002 + +Drupal 7.61, 2018-11-07 +----------------------- +- File upload validation functions and hook_file_validate() implementations are + now always passed the correct file URI. +- The default form cache expiration of 6 hours is now configurable (API + addition: https://www.drupal.org/node/2857751). +- Allowed callers of drupal_http_request() to optionally specify an explicit + Host header. +- Allowed the + character to appear in usernames. +- PHP 7.2: Fixed Archive_Tar incompatibility. +- PHP 7.2: Removed deprecated function each(). +- PHP 7.2: Avoid count() calls on uncountable variables. +- PHP 7.2: Removed deprecated create_function() call. +- PHP 7.2: Make sure variables are arrays in theme_links(). +- Fixed theme-settings.php not being loaded on cached forms +- Fixed problem with IE11 & Chrome(PointerEvents enabled) & some Firefox scroll to the top of the page after dragging the bottom item with jquery 1.5 <-> 1.11 + +Drupal 7.60, 2018-10-18 +------------------------ +- Fixed security issues. See SA-CORE-2018-006. + +Drupal 7.59, 2018-04-25 +----------------------- +- Fixed security issues (remote code execution). See SA-CORE-2018-004. + +Drupal 7.58, 2018-03-28 +----------------------- +- Fixed security issues (remote code execution). See SA-CORE-2018-002. + +Drupal 7.57, 2018-02-21 +----------------------- +- Fixed security issues (multiple vulnerabilities). See SA-CORE-2018-001. + +Drupal 7.56, 2017-06-21 +----------------------- +- Fixed security issues (access bypass). See SA-CORE-2017-003. + +Drupal 7.55, 2017-06-07 +----------------------- +- Fixed incompatibility with PHP versions 7.0.19 and 7.1.5 due to duplicate + DATE_RFC7231 definition. +- Made Drupal core pass all automated tests on PHP 7.1. +- Allowed services such as Let's Encrypt to work with Drupal on Apache, by + making Drupal's .htaccess file allow access to the .well-known directory + defined by RFC 5785. +- Made new Drupal sites work correctly on Apache 2.4 when the mod_access_compat + Apache module is disabled. +- Fixed Drupal's URL-generating functions to always encode '[' and ']' so that + the URLs will pass HTML5 validation. +- Various additional bug fixes. +- Various API documentation improvements. +- Additional automated test coverage. + +Drupal 7.54, 2017-02-01 +----------------------- +- Modules are now able to define theme engines (API addition: + https://www.drupal.org/node/2826480). +- Logging of searches can now be disabled (new option in the administrative + interface). +- Added menu tree render structure to (pre-)process hooks for theme_menu_tree() + (API addition: https://www.drupal.org/node/2827134). +- Added new function for determining whether an HTTPS request is being served + (API addition: https://www.drupal.org/node/2824590). +- Fixed incorrect default value for short and medium date formats on the date + type configuration page. +- File validation error message is now removed after subsequent upload of valid + file. +- Numerous bug fixes. +- Numerous API documentation improvements. +- Additional performance improvements. +- Additional automated test coverage. + +Drupal 7.53, 2016-12-07 +----------------------- +- Fixed drag and drop support on newer Chrome/IE 11+ versions after 7.51 update + when jQuery is updated to 1.7-1.11.0. + +Drupal 7.52, 2016-11-16 +----------------------- +- Fixed security issues (multiple vulnerabilities). See SA-CORE-2016-005. + +Drupal 7.51, 2016-10-05 +----------------------- +- The Update module now also checks for updates to a disabled theme that is + used as an admin theme. +- Exceptions thrown in dblog_watchdog() are now caught and ignored. +- Clarified the warning that appears when modules are missing or have moved. +- Log messages are now XSS filtered on display. +- Draggable tables now work on touch screen devices. +- Added a setting for allowing double underscores in CSS identifiers + (https://www.drupal.org/node/2810369). +- If a user navigates away from a page while an Ajax request is running they + will no longer get an error message saying "An Ajax HTTP request terminated + abnormally". +- The system_region_list() API function now takes an optional third parameter + which allows region name translations to be skipped when they are not needed + (API addition: https://www.drupal.org/node/2810365). +- Numerous performance improvements. +- Numerous bug fixes. +- Numerous API documentation improvements. +- Additional automated test coverage. + +Drupal 7.50, 2016-07-07 +----------------------- +- Added a new "administer fields" permission for trusted users, which is + required in addition to other permissions to use the field UI + (https://www.drupal.org/node/2483307). +- Added clickjacking protection to Drupal core by setting the X-Frame-Options + header to SAMEORIGIN by default (https://www.drupal.org/node/2735873). +- Added support for full UTF-8 (emojis, Asian symbols, mathematical symbols) on + MySQL and other database drivers when the site and database are configured to + allow it (https://www.drupal.org/node/2761183). +- Improved performance by avoiding a re-scan of directories when a file is + missing; instead, trigger a PHP warning (minor API change: + https://www.drupal.org/node/2581445). +- Made it possible to use any PHP callable in Ajax form callbacks, form API + form-building functions, and form API wrapper callbacks (API addition: + https://www.drupal.org/node/2761169). +- Fixed that following a password reset link while logged in leaves users unable + to change their password (minor user interface change: + https://www.drupal.org/node/2759023). +- Implemented various fixes for automated test failures on PHP 5.4+ and PHP 7. + Drupal core automated tests now pass in these environments. +- Improved support for PHP 7 by fixing various problems. +- Fixed various bugs with PHP 5.5+ imagerotate(), including when incorrect + color indices are passed in. +- Fixed a regression introduced in Drupal 7.43 that allowed files uploaded by + anonymous users to be lost after form validation errors, and that also caused + regressions with certain contributed modules. +- Fixed a regression introduced in Drupal 7.36 which caused the default value + of hidden textarea fields to be ignored. +- Fixed robots.txt to allow search engines to access CSS, JavaScript and image + files. +- Changed wording on the Update Manager settings page to clarify that the + option to check for disabled module updates also applies to uninstalled + modules (administrative-facing translatable string change). +- Changed the help text when editing menu links and configuring URL redirect + actions so that it does not reference "Drupal" or the drupal.org website + (administrative-facing translatable string change). +- Fixed the locale safety check that is used to ensure that translations are + safe to allow for tokens in the href/src attributes of translated strings. +- Fixed that URL generation only works on port 80 when using domain based + language negotation. +- Made method="get" forms work inside the administrative overlay. The fix adds + a new hidden field to these forms when they appear inside the overlay (minor + data structure change). +- Increased maxlength of menu link title input fields in the node form and + menu link form from 128 to 255 characters. +- Removed meaningless post-check=0 and pre-check=0 cache control headers from + Drupal HTTP responses. +- Added a .editorconfig file to auto-configure editors that support it. +- Added --directory option to run-tests.sh for easier test discovery of all + tests within a project. +- Made run-tests.sh exit with a failure code when there are test fails or + problems running the script. +- Fixed that cookies from previous tests are still present when a new test + starts in DrupalWebTestCase. +- Improved performance of queries on the {authmap} database table. +- Fixed handling of missing files and functions inside the registry. +- Fixed Ajax handling for tableselect form elements that use checkboxes. +- Fixed a bug which caused ip_address() to return nothing when the client IP + address and proxy IP address are the same. +- Added a new option to format_xml_elements() to allow for already encoded + values. +- Changed the {history} table's node ID field to be an unsigned integer, to + match the same field in the {node} table and to prevent errors with very + large node IDs. +- Added an explicit page callback to the "admin/people/create" menu item in the + User module (minor data structure change). Previously this automatically + inherited the page callback from the parent "admin/people" menu item, which + broke contributed modules that override the "admin/people" page. +- Numerous small bug fixes. +- Numerous API documentation improvements. +- Additional automated test coverage. + +Drupal 7.44, 2016-06-15 +----------------------- +- Fixed security issues (privilege escalation). See SA-CORE-2016-002. + +Drupal 7.43, 2016-02-24 +----------------------- +- Fixed security issues (multiple vulnerabilities). See SA-CORE-2016-001. + +Drupal 7.42, 2016-02-03 +----------------------- +- Stopped invoking hook_flush_caches() on every cron run, since some modules + use that hook for expensive operations that are only needed on cache clears. +- Changed the default .htaccess and web.config to block Composer-related files. +- Added static caching to module_load_include() to improve performance. +- Fixed double-encoding bugs in select field widgets provided by the Options + module. The fix deprecates the 'strip_tags' property on option widgets and + replaces it with a new 'strip_tags_and_unescape' property (minor data + structure change). +- Improved MySQL 5.7 support by changing the MySQL database driver to stop + using the ANSI SQL mode alias, which has different meanings for different + MySQL versions. +- Fixed a regression introduced in Drupal 7.39 which prevented autocomplete + functionality from working on servers that are not configured to + automatically recognize index.php. +- Updated the Archive_Tar PEAR package to the latest 1.4.0 release, to fix bugs + with tar file handling on various operating systems. +- Fixed fatal errors on node preview when a field is displayed in the node + teaser but hidden in the full node view. The fix removes a + field_attach_prepare_view() call from the node_preview() function since it is + redundant with one in the node preview theme layer. +- Improved the description of the "Trimmed" format option on text fields + (translatable string change, and minor UI and data structure change). +- Numerous small bug fixes. +- Numerous API documentation improvements. +- Additional automated test coverage. + +Drupal 7.41, 2015-10-21 +----------------------- +- Fixed security issues (open redirect). See SA-CORE-2015-004. + +Drupal 7.40, 2015-10-14 +----------------------- +- Made Drupal's code for parsing .info files run much faster and use much less + memory. +- Prevented drupal_http_request() from returning an error when it receives a + 201 through 206 HTTP status code. +- Added support for autoloading traits via the registry on sites running PHP + 5.4 or higher. +- Allowed the user-picture.tpl.php theme template to have HTML classes besides + the default "user-picture" class printed in it (markup change). +- Fixed the URL text filter to convert e-mail addresses with plus signs into + mailto: links. +- Added alternate text to file icons displayed by the File module, to improve + accessibility (string change, and minor API addition to theme_file_icon()). +- Changed one-time login link failure messages to be displayed as errors or + warnings as appropriate, rather than as regular status messages (minor UI + change and data structure change). +- Changed the default settings.php configuration to exclude private files from + the "404_fast_paths" behavior. +- Changed the page that displays filter tips for a particular text format, for + example filter/tips/full_html, to return "page not found" or "access denied" + if the format does not exist or the user does not have access to it. This + change adds a new menu item to the Filter module's hook_menu() entry (minor + data structure change). +- Added a new hook, hook_block_cid_parts_alter(), to allow modules to alter the + cache keys used for caching a particular block. +- Made drupal_set_message() display and return messages when "0" is passed in + as the message to set. +- Fixed non-functional "Files displayed by default" setting on file fields. +- The "worker callback" provided in hook_cron_queue_info() and the "finished" + callback specified during batch processing can now be any PHP callable + instead of just functions. +- Prevented drupal_set_time_limit() from decreasing the time limit in the case + where the PHP maximum execution time is already unlimited. +- Changed the default thousand marker for numeric fields from a space ("1 000") + to nothing ("1000") (minor UI change: https://www.drupal.org/node/1388376). +- Prevented malformed theme .info files (without a "name" key) from causing + exceptions during menu rebuilds. If an .info file without a "name" key is + found in a module or theme directory, Drupal will now use the module or + theme's machine name as the display name instead. +- Made the format column in the {date_format_locale} database table + case-sensitive, to match the equivalent column in the {date_formats} table. +- Fixed a bug in the Statistics module that caused JavaScript files attached to + a node while it is being viewed to be omitted from the page. +- Added an optional 'project:' prefix that can be added to dependencies in a + module's .info file to indicate which project the dependency resides in (API + addition: https://www.drupal.org/node/2299747). +- Fixed various bugs that occurred after hooks were invoked early in the Drupal + bootstrap and that caused module_implements() and drupal_alter() to cache an + incomplete set of hook implementations for later use. +- Set the X-Content-Type-Options header to "nosniff" when possible, to prevent + certain web browsers from picking an unsafe MIME type. +- Prevented the database API from executing multiple queries at once on MySQL, + if the site's PHP version is new enough to do so. This is a secondary defense + against SQL injection (API change: https://www.drupal.org/node/2463973). +- Fixed a bug in the Drupal 6 to Drupal 7 upgrade path which caused the upgrade + to fail when there were multiple file records pointing to the same file. +- Numerous small bug fixes. +- Numerous API documentation improvements. +- Additional automated test coverage. + +Drupal 7.39, 2015-08-19 +----------------------- +- Fixed security issues (multiple vulnerabilities). See SA-CORE-2015-003. + +Drupal 7.38, 2015-06-17 +----------------------- +- Fixed security issues (multiple vulnerabilities). See SA-CORE-2015-002. + +Drupal 7.37, 2015-05-07 +----------------------- +- Fixed a regression in Drupal 7.36 which caused certain kinds of content types + to become disabled if they were defined by a no-longer-enabled module. +- Removed a confusing description regarding automatic time zone detection from + the user account form (minor UI and data structure change). +- Allowed custom HTML tags with a dash in the name to pass through filter_xss() + when specified in the list of allowed tags. +- Allowed hook_field_schema() implementations to specify indexes for fields + based on a fixed-length column prefix (rather than the entire column), as was + already allowed in hook_schema() implementations. +- Fixed PDO exceptions on PostgreSQL when accessing invalid entity URLs. +- Added a sites/all/libraries folder to the codebase, with instructions for + using it. +- Added a description to the "Administer text formats and filters" permission + on the Permissions page (string change). +- Numerous small bug fixes. +- Numerous API documentation improvements. +- Additional automated test coverage. + +Drupal 7.36, 2015-04-01 +----------------------- +- Added a 'file_public_schema' variable which allows modules that define + publicly-accessible streams in hook_stream_wrappers() to bypass file download + access checks when processing managed file upload fields. +- Fixed a bug that caused database query tags not to be added to search-related + database queries under many circumstances, and which prevented the + corresponding hook_query_TAG_alter() implementations from being called. +- Fixed the "for" attribute on managed file upload field labels to improve + accessibility (minor markup change). +- Added a 'javascript_always_use_jquery' variable which can be set to FALSE by + sites that may not need jQuery loaded on all pages, and a 'requires_jquery' + option to drupal_add_js() which modules can set to FALSE when adding + JavaScript files that have no dependency on jQuery (API addition: + https://www.drupal.org/node/2462717). +- Fixed incorrect foreign keys in the User module's role_permission and + users_roles database tables. +- Changed permission descriptions throughout Drupal core to consistently link + to relevant administrative pages, regardless of whether the user viewing the + Permissions page can view the page being linked to (minor UI change). +- Fixed the drupal_add_region_content() function so that it actually adds + content to the page. +- Added an 'image_suppress_itok_output' variable to allow sites already using + the existing 'image_allow_insecure_derivatives' variable to also prevent + security tokens from appearing in image derivative URLs. +- Fixed double-escaping of theme names in the Block module administrative + interface (minor string change). +- Added basic support for Xdebug when running automated tests. +- Fixed a bug which caused previewing a node to remove elements from the node + being edited. With this fix, calling node_preview() will no longer modify the + passed-in node object (minor API change). +- Added a user_has_role() function to check whether a user has a particular + role (API addition: https://www.drupal.org/node/2462411). +- Fixed installation failures when an opcode cache is enabled. +- Fixed a bug in the Drupal 6 to Drupal 7 upgrade path which caused private + files to be inaccessible. +- Fixed a bug in the Drupal 6 to Drupal 7 upgrade path which caused user + pictures to be lost. +- Fixed missing language code in hook_field_attach_view_alter() when it is + invoked from field_view_field(). +- Stopped sending ETag and Last-Modified headers for uncached page requests, + since they break caching for certain Varnish and Nginx configurations. +- Changed the Simpletest module to allow PSR-4 test classes to be used in + Drupal 7. +- Fixed a fatal error that occurred when using the Comment module's "Unpublish + comment containing keyword(s)" action. +- Changed the "lang" attribute on language links to "xml:lang" so it validates + as XHTML (minor markup change). +- Prevented the form API from allowing arrays to be submitted for various form + elements, such as textfields, textareas, and password fields (API change: + https://www.drupal.org/node/2462723). +- Fixed a bug in the Contact module which caused the global user object to have + the incorrect name and e-mail address during the remainder of the page + request after the contact form is submitted. +- Numerous small bug fixes. +- Numerous API documentation improvements. +- Additional automated test coverage. + +Drupal 7.35, 2015-03-18 +----------------------- +- Fixed security issues (multiple vulnerabilities). See SA-CORE-2015-001. + +Drupal 7.34, 2014-11-19 +----------------------- +- Fixed security issues (multiple vulnerabilities). See SA-CORE-2014-006. + +Drupal 7.33, 2014-11-07 +----------------------- +- Began storing the file modification time of each module and theme in the + {system} database table so that contributed modules can use it to identify + recently changed modules and themes (minor data structure change to the + return value of system_get_info() and other related functions). +- Added a "Did you mean?" feature to the run-tests.sh script for running + automated tests from the command line, to help developers who are attempting + to run a particular test class or group. +- Changed the date format used in various HTTP headers output by Drupal core + from RFC 1123 format to RFC 7231 format. +- Added a "block_cache_bypass_node_grants" variable to allow sites which have + node access modules enabled to use the block cache if desired (API addition). +- Made image derivative generation HTTP requests return a 404 error (rather + than a 500 error) when the source image does not exist. +- Fixed a bug which caused user pictures to be removed from the user object + after saving, and resulted in data loss if the user account was subsequently + re-saved. +- Fixed a bug in which field_has_data() did not return TRUE for fields that + only had data in older entity revisions, leading to loss of the field's data + when the field configuration was edited. +- Fixed a bug which caused the Ajax progress throbber to appear misaligned in + many situatons (minor styling change). +- Prevented the Bartik theme from lower-casing the "Permalink" link on + comments, for improved multilingual support (minor UI change). +- Added a "preferred_menu_links" tag to the database query that is used by + menu_link_get_preferred() to find the preferred menu link for a given path, + to make it easier to alter. +- Increased the maximum allowed length of block titles to 255 characters + (database schema change to the {block} table). +- Removed the Field module's field_modules_uninstalled() function, since it did + not do anything when it was invoked. +- Added a "theme_hook_original" variable to templates and theme functions and + an optional sitewide theme debug mode, to provide contextual information in + the page's HTML to theme developers. The theme debug mode is based on the one + used with Twig in Drupal 8 and can be accessed by setting the "theme_debug" + variable to TRUE (API addition). +- Added an entity_view_mode_prepare() API function to allow entity-defining + modules to properly invoke hook_entity_view_mode_alter(), and used it + throughout Drupal core to fix bugs with the invocation of that hook (API + change: https://www.drupal.org/node/2369141). +- Security improvement: Made the database API's orderBy() method sanitize the + sort direction ("ASC" or "DESC") for queries built with db_select(), so that + calling code does not have to. +- Changed the RDF module to consistently output RDF metadata for nodes and + comments near where the node is rendered in the HTML (minor markup and data + structure change). +- Added an HTML class to RDFa metatags throughout Drupal to prevent them from + accidentally affecting the site appearance (minor markup change). +- Fixed a bug in the Unicode requirements check which prevented installing + Drupal on PHP 5.6. +- Fixed a bug which caused drupal_get_bootstrap_phase() to abort the bootstrap + when called early in the page request. +- Renamed the "Search result" view mode to "Search result highlighting input" + to better reflect how it is used (UI change). +- Improved database queries generated by EntityFieldQuery in the case where + delta or language condition groups are used, to reduce the number of INNER + JOINs (this is a minor data structure change affecting code which implements + hook_query_alter() on these queries). +- Removed special-case behavior for file uploads which allowed user #1 to + bypass maximum file size and user quota limits. +- Numerous small bug fixes. +- Numerous API documentation improvements. +- Additional automated test coverage. + +Drupal 7.32, 2014-10-15 +----------------------- +- Fixed security issues (SQL injection). See SA-CORE-2014-005. + +Drupal 7.31, 2014-08-06 +----------------------- +- Fixed security issues (denial of service). See SA-CORE-2014-004. + +Drupal 7.30, 2014-07-24 +----------------------- +- Fixed a regression introduced in Drupal 7.29 that caused files or images + attached to taxonomy terms to be deleted when the taxonomy term was edited + and resaved (and other related bugs with contributed and custom modules). +- Added a warning on the permissions page to recommend restricting access to + the "View site reports" permission to trusted administrators. See + DRUPAL-PSA-2014-002. +- Numerous API documentation improvements. +- Additional automated test coverage. + +Drupal 7.29, 2014-07-16 +----------------------- +- Fixed security issues (multiple vulnerabilities). See SA-CORE-2014-003. + +Drupal 7.28, 2014-05-08 +----------------------- +- Fixed a regression introduced in Drupal 7.27 that caused JavaScript to break + on older browsers (such as Internet Explorer 8 and earlier) when Ajax was + used. +- Increased the timeout used by the Update Manager module when it fetches data + from drupal.org (from 5 seconds to 30 seconds), to work around a problem + which causes incomplete information about security updates to be presented to + site administrators. This fix may lead to a performance slowdown on the + Update Manager administration pages, when installing Drupal distributions, + and (for sites that use the automated cron feature) on occasional page loads + by site visitors. +- Fixed the behavior of the token system's "[node:summary]" token when the body + field does not have a manual summary. +- Changed the behavior of db_query_temporary() so that it works on SELECT + queries even when they have leading comments/whitespace. A side effect of + this fix is that db_query_temporary() will now fail with an error if it is + ever used on non-SELECT queries. +- Added a "node_admin_filter" tag to the database query used to build the list + of nodes on the content administration page, to make it easier to alter. +- Made the cron queue system log any exceptions that are thrown while an item + in the queue is being processed, rather than stopping the entire PHP request. +- Improved screen reader support by adding an aria-live HTML attribute to file + upload fields when there is an error uploading the file (minor markup + change). +- Made the pager on the Tracker module listing pages show the same number of + items as other pagers throughout Drupal core (minor UI change). +- Fixed a bug which caused caches not to be properly cleared when a file entity + was saved or deleted. +- Added several missing countries to the default list returned by + country_get_list() (string change). +- Replaced the term "weight" with "influence" in the content ranking settings + for search, and added help text for administrators (string change). +- Fixed untranslatable text strings in the administrative interface for the + "Crop" effect provided by the Image module (minor string change). +- Fixed a bug in the Taxonomy module update function introduced in Drupal 7.26 + that caused memory and CPU problems on sites with very large numbers of + unpublished nodes. +- Numerous small bug fixes. +- Numerous API documentation improvements. +- Additional automated test coverage. + +Drupal 7.27, 2014-04-16 +----------------------- +- Fixed security issues (information disclosure). See SA-CORE-2014-002. + +Drupal 7.26, 2014-01-15 +----------------------- +- Fixed security issues (multiple vulnerabilities). See SA-CORE-2014-001. + +Drupal 7.25, 2014-01-02 +----------------------- +- Fixed a bug in node_save() which prevented the saved node from being updated + in hook_node_insert() and other similar hooks. +- Added a meta tag to install.php to prevent it from being indexed by search + engines even when Drupal is installed in a subfolder (minor markup change). +- Fixed a bug in the database API that caused frequent deadlock errors when + running merge queries on some servers. +- Performance improvement: Prevented block rehashing from writing blocks to the + database on every cache clear and cron run when the blocks have not changed. + This fix results in an extra 'saved' key which is added and set to TRUE for + each block returned by _block_rehash() that actually is saved to the database + (data structure change). +- Added an optional 'skip on cron' parameter to hook_cron_queue_info() to allow + queues to avoid being automatically processed on cron runs (API addition). +- Fixed a bug which caused hook_block_view_MODULE_DELTA_alter() to never be + invoked if the block delta had a hyphen in it. To implement the hook when the + block delta has a hyphen, modules should now replace hyphens with underscores + when constructing the function name for the hook implementation. +- Fixed a bug which caused cached pages to sometimes be sent to the browser + with incorrect compression. The fix adds a new 'page_compressed' key to the + $cache->data array returned by drupal_page_get_cache() (minor data structure + change). +- Fixed broken tests on PHP 5.5. +- Made the File and Image modules more robust when saving entities that have + deleted files attached. The code in file_field_presave() will now remove the + record of the deleted file from the entity before saving (minor data + structure change). +- Standardized menu callback functions throughout Drupal core to return + MENU_NOT_FOUND and MENU_ACCESS_DENIED rather than printing their own "page + not found" or "access denied" pages (minor API change in the return value of + these functions under some circumstances). +- Fixed a bug in which caches were not properly cleared when a node was deleted + via the administrative interface. +- Changed the Bartik theme to render content contained in
,  and
+  similar tags in a larger font size, so it is easier to read.
+- Fixed a bug in the Search module that caused exceptions to be thrown during
+  searches if the server was not configured to represent decimal points as a
+  period.
+- Fixed a regression in the Image module that made image_style_url() not work
+  when a relative path (rather than a complete file URI) was passed to it.
+- Added an optional feature to the Statistics module to allow node views to be
+  tracked by Ajax requests rather than during the server-side generation of the
+  page. This allows the node counter to work on sites that use external page
+  caches (string change and new administrative option:
+  https://drupal.org/node/2164069).
+- Added a link to the drupal.org documentation page for cron to the Cron
+  settings page (string change).
+- Added a 'drupal_anonymous_user_object' variable to allow the anonymous user
+  object returned by drupal_anonymous_user() to be overridden with a classed
+  object (API addition).
+- Changed the database API to allow inserts based on a SELECT * query to work
+  correctly.
+- Changed the database schema of the {file_managed} table to allow Drupal to
+  manage files larger than 4 GB.
+- Changed the File module's hook_field_load() implementation to prevent file
+  entity properties which have the same name as file or image field properties
+  from overwriting the field properties (minor API change).
+- Numerous small bug fixes.
+- Numerous API documentation improvements.
+- Additional automated test coverage.
+
+Drupal 7.24, 2013-11-20
+-----------------------
+- Fixed security issues (multiple vulnerabilities), see SA-CORE-2013-003.
+
+Drupal 7.23, 2013-08-07
+-----------------------
+- Fixed a fatal error on PostgreSQL databases when updating the Taxonomy module
+  from Drupal 6 to Drupal 7.
+- Fixed the default ordering of CSS files for sites using right-to-left
+  languages, to consistently place the right-to-left override file immediately
+  after the CSS it is overriding (API change: https://drupal.org/node/2058463).
+- Added a drupal_check_memory_limit() API function to allow the memory limit to
+  be checked consistently (API addition).
+- Changed the default web.config file for IIS servers to allow favicon.ico
+  files which are present in the filesystem to be accessed.
+- Fixed inconsistent support for the 'tel' protocol in Drupal's URL filtering
+  functions.
+- Performance improvement: Allowed all hooks to be included in the
+  module_implements() cache, even those that are only invoked on HTTP POST
+  requests.
+- Made the database system replace truncate queries with delete queries when
+  inside a transaction, to fix issues with PostgreSQL and other databases.
+- Fixed a bug which caused nested contextual links to display improperly.
+- Fixed a bug which prevented cached image derivatives from being flushed for
+  private files and other non-default file schemes.
+- Fixed drupal_render() to always return an empty string when there is no
+  output, rather than sometimes returning NULL (minor API change).
+- Added protection to cache_clear_all() to ensure that non-cache tables cannot
+  be truncated (API addition: a new isValidBin() method has been added to the
+  default database cache implementation).
+- Changed the default .htaccess file to support HTTP authorization in CGI
+  environments.
+- Changed the password reset form to pre-fill the username when requested via a
+  URL query parameter, and used this in the error message that appears after a
+  failed login attempt (minor data structure and behavior change).
+- Fixed broken support for foreign keys in the field API.
+- Fixed "No active batch" error when a user cancels their own account.
+- Added a description to the "access content overview" permission on the
+  permissions page (string change).
+- Added a drupal_array_diff_assoc_recursive() function to allow associative
+  arrays to be compared recursively (API addition).
+- Added human-readable labels to image styles, in addition to the existing
+  machine-readable name (API change: https://drupal.org/node/2058503).
+- Moved the drupal_get_hash_salt() function to bootstrap.inc and used it in
+  additional places in the code, for added security in the case where there is
+  no hash salt in settings.php.
+- Fixed a regression in Drupal 7.22 that caused internal server errors for
+  sites running on very old Apache 1.x web servers.
+- Numerous small bug fixes.
+- Numerous API documentation improvements.
+- Additional automated test coverage.
+
+Drupal 7.22, 2013-04-03
+-----------------------
+- Allowed the drupal_http_request() function to be overridden so that
+  additional HTTP request capabilities can be added by contributed modules.
+- Changed the Simpletest module to allow PSR-0 test classes to be used in
+  Drupal 7.
+- Removed an unnecessary "Content-Disposition" header from private file
+  downloads; it prevented many private files from being viewed inline in a web
+  browser.
+- Changed various field API functions to allow them to optionally act on a
+  single field within an entity (API addition: http://drupal.org/node/1825844).
+- Fixed a bug which prevented Drupal's file transfer functionality from working
+  on some PHP 5.4 systems.
+- Fixed incorrect log message when theme() is called for a theme hook that does
+  not exist (minor string change).
+- Fixed Drupal's token-replacement system to allow spaces in the token value.
+- Changed the default behavior after a user creates a node they do not have
+  access to view. The user will now be redirected to the front page rather than
+  an access denied page.
+- Fixed a bug which prevented empty HTTP headers (such as "0") from being set.
+  (Minor behavior change: Callers of drupal_add_http_header() must now set
+  FALSE explicitly to prevent a header from being sent at all; this was already
+  indicated in the function's documentation.)
+- Fixed OpenID errors when more than one module implements hook_openid(). The
+  behavior is now changed so that if more than one module tries to set the same
+  parameter, the last module's change takes effect.
+- Fixed a serious documentation bug: The $name variable in the
+  taxonomy-term.tpl.php theme template was incorrectly documented as being
+  sanitized when in fact it is not.
+- Fixed a bug which prevented Drupal 6 to Drupal 7 upgrades on sites which had
+  duplicate permission names in the User module's database tables.
+- Added an empty "datatype" attribute to taxonomy term and username links to
+  make the RDFa markup upward compatible with RDFa 1.1 (minor markup addition).
+- Fixed a bug which caused the denial-of-service protection added in Drupal
+  7.20 to break certain valid image URLs that had an extra slash in them.
+- Fixed a bug with update queries in the SQLite database driver that prevented
+  Drupal from being installed with SQLite on PHP 5.4.
+- Fixed enforced dependencies errors updating to recent versions of Drupal 7 on
+  certain non-MySQL databases.
+- Refactored the Field module's caching behavior to obtain large improvements
+  in memory usage for sites with many fields and instances (API addition:
+  http://drupal.org/node/1915646).
+- Fixed entity argument not being passed to implementations of
+  hook_file_download_access_alter(). The fix adds an additional context
+  parameter that can be passed when calling drupal_alter() for any hook (API
+  change: http://drupal.org/node/1882722).
+- Fixed broken support for translatable comment fields (API change:
+  http://drupal.org/node/1874724).
+- Added an assertThemeOutput() method to Simpletest to allow tests to check
+  that themed output matches an expected HTML string (API addition).
+- Added a link to "Install another module" after a module has been successfully
+  downloaded via the Update Manager (UI change).
+- Added an optional "exclusive" flag to installation profile .info files which
+  allows Drupal distributions to force a profile to be selected during
+  installation (API addition: http://drupal.org/node/1961012).
+- Fixed a bug which caused the database API to not properly close database
+  connections.
+- Added a link to the URL for running cron from outside the site to the Cron
+  settings page (UI change).
+- Fixed a bug which prevented image styles from being reverted on PHP 5.4.
+- Made the default .htaccess rules protocol sensitive to improve security for
+  sites which use HTTPS and redirect between "www" and non-"www" versions of
+  the page.
+- Numerous small bug fixes.
+- Numerous API documentation improvements.
+- Additional automated test coverage.
+
+Drupal 7.21, 2013-03-06
+-----------------------
+- Allowed sites using the 'image_allow_insecure_derivatives' variable to still
+  have partial protection from the security issues fixed in Drupal 7.20.
+
+Drupal 7.20, 2013-02-20
+-----------------------
+- Fixed security issues (denial of service). See SA-CORE-2013-002.
+
+Drupal 7.19, 2013-01-16
+-----------------------
+- Fixed security issues (multiple vulnerabilities). See SA-CORE-2013-001.
+
+Drupal 7.18, 2012-12-19
+-----------------------
+- Fixed security issues (multiple vulnerabilities). See SA-CORE-2012-004.
+
+Drupal 7.17, 2012-11-07
+-----------------------
+- Changed the default value of the '404_fast_html' variable to have a DOCTYPE
+  declaration.
+- Made it possible to use associative arrays for the 'items' variable in
+  theme_item_list().
+- Fixed a bug which prevented required form elements without a title from being
+  given an "error" class when the form fails validation.
+- Prevented duplicate HTML IDs from appearing when two forms are displayed on
+  the same page and one of them is submitted with invalid data (minor markup
+  change).
+- Fixed a bug which prevented Drupal 6 to Drupal 7 upgrades on sites which had
+  stale data in the Upload module's database tables.
+- Fixed a bug in the States API which prevented certain types of form elements
+  from being disabled when requested.
+- Allowed aggregator feed items with author names longer than 255 characters to
+  have a truncated version saved to the database (rather than causing a fatal
+  error).
+- Allowed aggregator feed items to have URLs longer than 255 characters
+  (schema change which results in several columns in the Aggregator module's
+  database tables changing from VARCHAR to TEXT fields).
+- Added hook_taxonomy_term_view() and standardized the process for rendering
+  taxonomy terms to invoke hook_entity_view() and otherwise make it consistent
+  with other entities (API change: http://drupal.org/node/1808870).
+- Added hook_entity_view_mode_alter() to allow modules to change entity view
+  modes on display (API addition: http://drupal.org/node/1833086).
+- Fixed a bug which made database queries running a "LIKE" query on blob fields
+  fail on PostgreSQL databases. This caused errors during the Drupal 6 to
+  Drupal 7 upgrade.
+- Changed the hook_menu() entry for Drupal's rss.xml page to prevent extra path
+  components from being accidentally passed to the page callback function (data
+  structure change).
+- Removed a non-standard "name" attribute from Drupal's default Content-Type
+  header for file downloads.
+- Fixed the theme settings form to properly clean up submitted values in
+  $form_state['values'] when the form is submitted (data structure change).
+- Fixed an inconsistency by removing the colon from the end of the label on
+  multi-valued form fields (minor string change).
+- Added support for 'weight' in hook_field_widget_info() to allow modules to
+  control the order in which widgets are displayed in the Field UI.
+- Updated various tables in the OpenID and Book modules to use the default
+  "empty table" text pattern (string change).
+- Added proxy server support to drupal_http_request().
+- Added "lang" attributes to language links, to better support screen readers.
+- Fixed double occurrence of a "ul" HTML tag on secondary local tasks in the
+  Seven theme (markup change).
+- Fixed bugs which caused taxonomy vocabulary and shortcut set titles to be
+  double-escaped. The fix replaces the taxonomy vocabulary overview page and
+  "Edit shortcuts" menu items' title callback entries in hook_menu() with new
+  functions that do not escape HTML characters (data structure change).
+- Modified the Update manager module to allow drupal.org to collect usage
+  statistics for individual modules and themes, rather than only for entire
+  projects.
+- Modified the node listing database query on Drupal's default front page to
+  add table aliases for better query altering (this is a data structure change
+  affecting code which implements hook_query_alter() on this query).
+- Improved the translatability of the "Field type(s) in use" message on the
+  modules page (admin-facing string change).
+- Fixed a regression which caused a "call to undefined function
+  drupal_find_base_themes()" fatal error under rare circumstances.
+- Numerous API documentation improvements.
+- Additional automated test coverage.
+
+Drupal 7.16, 2012-10-17
+-----------------------
+- Fixed security issues (Arbitrary PHP code execution and information
+  disclosure). See SA-CORE-2012-003.
+
+Drupal 7.15, 2012-08-01
+-----------------------
+- Introduced a 'user_password_reset_timeout' variable to allow the 24-hour
+  expiration for user password reset links to be adjusted (API addition).
+- Fixed database errors due to ambiguous column names that occurred when
+  EntityFieldQuery was used in certain situations.
+- Changed the drupal_array_get_nested_value() function to return a reference
+  (API addition).
+- Changed the System module's hook_block_info() implementation to assign the
+  "Main page content" and "System help" blocks to appropriate regions by
+  default and prevent error messages on the block administration page (data
+  structure change).
+- Fixed regression: Non-node entities couldn't be accessed with
+  EntityFieldQuery.
+- Fixed regression: Optional radio buttons with an empty, non-NULL default
+  value led to an illegal choice error when none were selected.
+- Reorganized the testing framework to split setUp() into specific sub-methods
+  and fix several regressions in the process.
+- Fixed bug which made it impossible to search for strings that have not been
+  translated into a particular language.
+- Renamed the "Field" column on the Manage Fields screen to "Field type", since
+  the former was confusing and inaccurate (UI change).
+- Performance improvement: Removed needless call to system_rebuild_module_data()
+  in field_sync_field_status(), greatly speeding up bulk module enable/disable.
+- Fixed bug which prevented notifications from being sent when core, module, and
+  theme updates are available.
+- Fixed bug which prevented sub-themes from inheriting the default values of
+  theme settings defined by the base theme.
+- Fixed bug which prevented the jQuery UI Datepicker from being localized.
+- Made Ajax alert dialogs respect error reporting settings.
+- Fixed bug which prevented image styles from being deleted on PHP 5.4.
+- Fixed bug: Language detection by domain only worked on port 80.
+- Fixed regression: The first plural index on a page was not calculated
+  correctly.
+- Introduced generic entity language support. Entities may now declare their
+  language property in hook_entity_info(), and modules working with entities
+  may access the language using entity_language() (API change:
+  http://drupal.org/node/1626346).
+- Added EntityFieldQuery support for taxonomy bundles.
+- Fixed issue where field form structure was incomplete if field_access()
+  returned FALSE. Instead of being incomplete, the form structure now has
+  #access set to FALSE and field form validation is skipped (data structure
+  change: http://drupal.org/node/1663020).
+- Fixed data loss issue due to field_has_data() returning inconsistent results.
+  The fix adds an optional DANGEROUS_ACCESS_CHECK_OPT_OUT tag to entity field
+  queries which field storage engines can respond to (API addition:
+  http://drupal.org/node/1597378).
+- Fixed notice: Undefined index: default_image in image_field_prepare_view()
+- Numerous API documentation improvements.
+- Additional automated test coverage.
+
+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.
@@ -48,13 +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.
@@ -88,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
@@ -260,8 +1114,8 @@
       order can now be customized using the Views module.
     * Removed the 'related terms' feature from taxonomy module since this can
       now be achieved with Field API.
-    * Added additional features to the default install profile, and implemented
-      a "slimmed down" install profile designed for developers.
+    * Added additional features to the default installation profile, and
+      implemented a "slimmed down" profile designed for developers.
     * Added a built-in, automated cron run feature, which is triggered by site
       visitors.
     * Added an administrator role which is assigned all permissions for
@@ -445,7 +1299,7 @@
       requests.
 
 Drupal 6.23-dev, xxxx-xx-xx (development release)
------------------------
+---------------------------
 
 Drupal 6.22, 2011-05-25
 -----------------------
@@ -455,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
@@ -482,24 +1336,24 @@
 - 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.
 - Better support for updated jQuery versions.
 - Reduced resource usage of update.module.
-- Fixed several issues relating to support of install profiles and
+- Fixed several issues relating to support of installation profiles and
   distributions.
 - Added a locking framework to avoid data corruption on long operations.
 - 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
@@ -508,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
@@ -527,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.
@@ -606,7 +1460,7 @@
    * Expands the severity levels from 3 (Error, Warning, Notice) to the 8
      levels defined in RFC 3164.
    * The watchdog module is now called dblog, and is optional, but enabled by
-     default in the default install profile.
+     default in the default installation profile.
    * Extended the database log module so log messages can be filtered.
    * Added syslog module: useful for monitoring large Drupal installations.
 - Added optional e-mail notifications when users are approved, blocked, or
@@ -661,7 +1515,7 @@
     * Themed the installer with the Garland theme.
     * Added form to provide initial site information during installation.
     * Added ability to provide extra installation steps programmatically.
-    * Made it possible to import interface translations at install time.
+    * Made it possible to import interface translations during installation.
 - Added the HTML corrector filter:
     * Fixes faulty and chopped off HTML in postings.
     * Tags are now automatically closed at the end of the teaser.
@@ -840,7 +1694,7 @@
 - Added web-based installer which can:
     * Check installation and run-time requirements
     * Automatically generate the database configuration file
-    * Install pre-made 'install profiles' or distributions
+    * Install pre-made installation profiles or distributions
     * Import the database structure with automatic table prefixing
     * Be localized
 - Added new default Garland theme
@@ -900,7 +1754,7 @@
 - Removed the archive module.
 - Upgrade system:
     * Created space for update branches.
-- Forms API:
+- Form API:
     * Made it possible to programmatically submit forms.
     * Improved api for multistep forms.
 - Theme system:
@@ -923,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
@@ -1036,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.14/COPYRIGHT.txt drupal-7.66/COPYRIGHT.txt
--- drupal-7.14/COPYRIGHT.txt	2012-05-03 00:10:42.000000000 +0200
+++ drupal-7.66/COPYRIGHT.txt	2019-04-17 22:20:46.000000000 +0200
@@ -1,5 +1,4 @@
-
-All Drupal code is Copyright 2001 - 2010 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
@@ -21,5 +20,25 @@
 according to the terms of the GNU General Public License or a compatible
 license, including:
 
-  jQuery - Copyright (c) 2008 - 2009 John Resig
+Javascript
+
+  Farbtastic - Copyright (c) 2010 Matt Farina
+
+  jQuery - Copyright (c) 2010 John Resig
+
+  jQuery BBQ - Copyright (c) 2010 "Cowboy" Ben Alman
+
+  jQuery Cookie - Copyright (c) 2006 Klaus Hartl
+
+  jQuery Form - Copyright (c) 2010 Mike Alsup
+
+  jQuery Once - Copyright (c) 2009 Konstantin K�fer
+
+  jQuery UI - Copyright (c) 2010 by the original authors
+    (http://jqueryui.com/about)
+
+  Sizzle.js - Copyright (c) 2010 The Dojo Foundation (http://sizzlejs.com/)
+
+PHP
 
+  ArchiveTar - Copyright (c) 1997 - 2008 Vincent Blavet
diff -Naur drupal-7.14/INSTALL.mysql.txt drupal-7.66/INSTALL.mysql.txt
--- drupal-7.14/INSTALL.mysql.txt	2012-05-03 00:10:42.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.14/INSTALL.txt drupal-7.66/INSTALL.txt
--- drupal-7.14/INSTALL.txt	2012-05-03 00:10:42.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"
@@ -89,8 +91,8 @@
    - Download a translation file for the correct Drupal version and language
      from the translation server: http://localize.drupal.org/translate/downloads
 
-   - Place the file into your installation profile's translations
-     directory. For instance, if you are using the Standard install profile,
+   - Place the file into your installation profile's translations directory.
+     For instance, if you are using the Standard installation profile,
      move the .po file into the directory:
 
        profiles/standard/translations/
diff -Naur drupal-7.14/MAINTAINERS.txt drupal-7.66/MAINTAINERS.txt
--- drupal-7.14/MAINTAINERS.txt	2012-05-03 00:10:42.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,143 +10,154 @@
 The Drupal Core branch maintainers oversee the development of Drupal as a whole.
 The branch maintainers for Drupal 7 are:
 
-- Dries Buytaert 'dries' 
-- Angela Byron 'webchick' 
+- 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' 
-- Randy Fay 'rfay' 
-- Earl Miles 'merlinofchaos' 
+- 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' 
-- Damien Tournoud 'DamZ' 
-- Moshe Weitzman 'moshe weitzman' 
+- 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' 
+- Yves Chedemois 'yched' https://www.drupal.org/u/yched
 
 Cache system
-- Damien Tournoud 'DamZ' 
-- Nathaniel Catchpole 'catch' 
+- 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' 
-- Derek Wright 'dww' 
+- Derek Wright 'dww' https://www.drupal.org/u/dww
 
 Database system
-- Larry Garfield 'Crell' 
+- ?
 
   - MySQL driver
-    - Larry Garfield 'Crell' 
-    - David Strauss 'David Strauss' 
+    - David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss
 
   - PostgreSQL driver
-    - Damien Tournoud 'DamZ' 
-    - Josh Waihi 'fiasco' 
+    - 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' 
-    - Károly Négyesi 'chx' 
+    - Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud
 
 Database update system
-- Károly Négyesi 'chx' 
-- Ashok Modi 'btmash' 
+- Ashok Modi 'BTMash' https://www.drupal.org/u/btmash
 
 Entity system
-- Wolfgang Ziegler 'fago' 
-- Nathaniel Catchpole 'catch' 
-- Franz Heinzmann 'Frando' 
+- 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' 
-- Aaron Winborn 'aaron' 
+- 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' 
-- Alex Bronstein 'effulgentsia' 
-- Wolfgang Ziegler 'fago' 
-- Daniel F. Kudwien 'sun' 
-- Franz Heinzmann 'Frando' 
+- 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' 
-- Nathan Haug 'quicksketch' 
+- Andrew Morton 'drewish' https://www.drupal.org/u/drewish
+- Nathan Haug 'quicksketch' https://www.drupal.org/u/quicksketch
 
 Install system
-- David Rothstein 'David_Rothstein' 
+- David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein
 
 JavaScript
-- Théodore Biadala 'nod_' 
+- 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' 
-- Daniel F. Kudwien 'sun' 
+- Francesco Placella 'plach' https://www.drupal.org/u/plach
+- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
 
 Lock system
-- Damien Tournoud 'DamZ' 
+- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud
 
 Mail system
 - ?
 
 Markup
-- Jacine Luisi 'Jacine' 
-- Daniel F. Kudwien 'sun' 
+- Jacine Luisi 'Jacine' https://www.drupal.org/u/jacine
+- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
 
 Menu system
-- Peter Wolanin 'pwolanin' 
-- Károly Négyesi 'chx' 
+- Peter Wolanin 'pwolanin' https://www.drupal.org/u/pwolanin
 
 Path system
-- Dave Reid 'davereid' 
-- Nathaniel Catchpole 'catch' 
+- 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' 
-- Alex Bronstein 'effulgentsia' 
-- Franz Heinzmann 'Frando' 
+- 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' 
-- Alex Bronstein 'effulgentsia' 
-- Joon Park 'dvessel' 
-- John Albin Wilkins 'JohnAlbin' 
+- 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' 
+- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid
 
 XML-RPC system
-- Frederic G. Marand 'fgm' 
+- Frederic G. Marand 'fgm' https://www.drupal.org/u/fgm
 
 
 Topic coordinators
 ------------------
 
 Accessibility
-- Everett Zufelt 'Everett Zufelt' 
-- Brandon Bowersox-Johnson 'bowersox' 
+- 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' 
-
-Security
-- Heine Deelstra 'Heine' 
+- Jennifer Hodgdon 'jhodgdon' https://www.drupal.org/u/jhodgdon
 
 Translations
-- Gerhard Killesreiter 'killes' 
+- Gerhard Killesreiter 'killes' https://www.drupal.org/u/gerhard-killesreiter
 
 User experience and usability
-- Roy Scholten 'yoroy' 
-- Bojhan Somers 'Bojhan' 
+- Roy Scholten 'yoroy' https://www.drupal.org/u/yoroy
+- Bojhan Somers 'Bojhan' https://www.drupal.org/u/bojhan
+
+Node Access
+- 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
@@ -155,145 +167,141 @@
 - ?
 
 Block module
-- John Albin Wilkins 'JohnAlbin' 
+- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin
 
 Blog module
 - ?
 
 Book module
-- Peter Wolanin 'pwolanin' 
+- Peter Wolanin 'pwolanin' https://www.drupal.org/u/pwolanin
 
 Color module
 - ?
 
 Comment module
-- Nathaniel Catchpole 'catch' 
+- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch
 
 Contact module
-- Dave Reid 'davereid' 
+- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid
 
 Contextual module
-- Daniel F. Kudwien 'sun' 
+- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
 
 Dashboard module
 - ?
 
 Database logging module
-- Khalid Baheyeldin 'kbahey' 
+- Khalid Baheyeldin 'kbahey' https://www.drupal.org/u/kbahey
 
 Field module
-- Yves Chedemois 'yched' 
-- Barry Jaspan 'bjaspan' 
+- Yves Chedemois 'yched' https://www.drupal.org/u/yched
+- Barry Jaspan 'bjaspan' https://www.drupal.org/u/bjaspan
 
 Field UI module
-- Yves Chedemois 'yched' 
+- Yves Chedemois 'yched' https://www.drupal.org/u/yched
 
 File module
-- Aaron Winborn 'aaron' 
+- Aaron Winborn 'aaron' https://www.drupal.org/u/aaron
 
 Filter module
-- Daniel F. Kudwien 'sun' 
+- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
 
 Forum module
-- Lee Rowlands 'larowlan' 
+- Lee Rowlands 'larowlan' https://www.drupal.org/u/larowlan
 
 Help module
 - ?
 
 Image module
-- Nathan Haug 'quicksketch' 
+- Nathan Haug 'quicksketch' https://www.drupal.org/u/quicksketch
 
 Locale module
-- Gábor Hojtsy 'Gábor Hojtsy' 
+- Gábor Hojtsy 'Gábor Hojtsy' https://www.drupal.org/u/gábor-hojtsy
 
 Menu module
 - ?
 
 Node module
-- Moshe Weitzman 'moshe weitzman' 
-- David Strauss 'David Strauss' 
+- 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' 
-- Heine Deelstra 'Heine' 
-- Christian Schmidt 'c960657' 
-- Damien Tournoud 'DamZ' 
+- 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' 
+- Katherine Senzee 'ksenzee' https://www.drupal.org/u/ksenzee
 
 Path module
-- Dave Reid 'davereid' 
+- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid
 
 PHP module
 - ?
 
 Poll module
-- Andrei Mateescu 'amateescu' 
+- Andrei Mateescu 'amateescu' https://www.drupal.org/u/amateescu
 
 Profile module
 - ?
 
 RDF module
-- Stéphane Corlosquet 'scor' 
+- Stéphane Corlosquet 'scor' https://www.drupal.org/u/scor
 
 Search module
-- Doug Green 'douggreen' 
+- Doug Green 'douggreen' https://www.drupal.org/u/douggreen
 
 Shortcut module
-- David Rothstein 'David_Rothstein' 
-- Kristof De Jaeger 'swentel' 
+- David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein
 
 Simpletest module
-- Jimmy Berry 'boombatower' 
-- Károly Négyesi 'chx' 
+- Jimmy Berry 'boombatower' https://www.drupal.org/u/boombatower
 
 Statistics module
-- Tim Millwood 'timmillwood' 
+- Tim Millwood 'timmillwood' https://www.drupal.org/u/timmillwood
 
 Syslog module
-- Khalid Baheyeldin 'kbahey' 
+- Khalid Baheyeldin 'kbahey' https://www.drupal.org/u/kbahey
 
 System module
 - ?
 
 Taxonomy module
-- Jess Myrbo 'xjm' 
-- Nathaniel Catchpole 'catch' 
-- Benjamin Doherty 'bangpound' 
+- 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' 
+- David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss
 
 Translation module
-- Francesco Placella 'plach' 
+- Francesco Placella 'plach' https://www.drupal.org/u/plach
 
 Trigger module
 - ?
 
 Update module
-- Derek Wright 'dww' 
+- Derek Wright 'dww' https://www.drupal.org/u/dww
 
 User module
-- Moshe Weitzman 'moshe weitzman' 
-- David Strauss 'David Strauss' 
+- 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' 
-- Jeff Burns 'Jeff Burnz' 
+- 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' 
+- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin
 
 Seven theme
-- Jeff Burns 'Jeff Burnz' 
+- Jeff Burns 'Jeff Burnz' https://www.drupal.org/u/jeff-burnz
 
 Stark theme
-- John Albin Wilkins 'JohnAlbin' 
+- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin
diff -Naur drupal-7.14/README.txt drupal-7.66/README.txt
--- drupal-7.14/README.txt	2012-05-03 00:10:42.000000000 +0200
+++ drupal-7.66/README.txt	2019-04-17 22:20:46.000000000 +0200
@@ -4,6 +4,7 @@
 
  * About Drupal
  * Configuration and features
+ * Installation profiles
  * Appearance
  * Developing for Drupal
 
@@ -43,6 +44,40 @@
    http://drupal.org/project/modules
  * See also: "Developing for Drupal" for writing your own modules, below.
 
+INSTALLATION PROFILES
+---------------------
+
+Installation profiles define additional steps (such as enabling modules,
+defining content types, etc.) that run after the base installation provided
+by core when Drupal is first installed. There are two basic installation
+profiles provided with Drupal core.
+
+Installation profiles from the Drupal community modify the installation process
+to provide a website for a specific use case, such as a CMS for media
+publishers, a web-based project tracking tool, or a full-fledged CRM for
+non-profit organizations raising money and accepting donations. They can be
+distributed as bare installation profiles or as "distributions". Distributions
+include Drupal core, the installation profile, and all other required
+extensions, such as contributed and custom modules, themes, and third-party
+libraries. Bare installation profiles require you to download Drupal Core and
+the required extensions separately; place the downloaded profile in the
+/profiles directory before you start the installation process. Note that the
+contents of this directory may be overwritten during updates of Drupal core;
+it is advised to keep code backups or use a version control system.
+
+Additionally, modules and themes may be placed inside subdirectories in a
+specific installation profile such as profiles/your_site_profile/modules and
+profiles/your_site_profile/themes respectively to restrict their usage to only
+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
+
 APPEARANCE
 ----------
 
diff -Naur drupal-7.14/UPGRADE.txt drupal-7.66/UPGRADE.txt
--- drupal-7.14/UPGRADE.txt	2012-05-03 00:10:42.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.
@@ -141,7 +151,7 @@
    download Drupal 6.x and follow the instructions in its UPGRADE.txt. This
    document only applies for upgrades from 6.x to 7.x.
 
-3. In addition to updating to the latest available version of Drupal 7.x core,
+3. In addition to updating to the latest available version of Drupal 6.x core,
    you must also upgrade all of your contributed modules for Drupal to their
    latest Drupal 6.x versions.
 
diff -Naur drupal-7.14/authorize.php drupal-7.66/authorize.php
--- drupal-7.14/authorize.php	2012-05-03 00:10:42.000000000 +0200
+++ drupal-7.66/authorize.php	2019-04-17 22:20:46.000000000 +0200
@@ -4,16 +4,16 @@
  * @file
  * Administrative script for running authorized file operations.
  *
- * Using this script, the site owner (the user actually owning the files on
- * the webserver) can authorize certain file-related operations to proceed
- * with elevated privileges, for example to deploy and upgrade modules or
- * themes. Users should not visit this page directly, but instead use an
- * administrative user interface which knows how to redirect the user to this
- * script as part of a multistep process. This script actually performs the
- * selected operations without loading all of Drupal, to be able to more
- * gracefully recover from errors. Access to the script is controlled by a
- * global killswitch in settings.php ('allow_authorize_operations') and via
- * the 'administer software updates' permission.
+ * Using this script, the site owner (the user actually owning the files on the
+ * webserver) can authorize certain file-related operations to proceed with
+ * elevated privileges, for example to deploy and upgrade modules or themes.
+ * Users should not visit this page directly, but instead use an administrative
+ * user interface which knows how to redirect the user to this script as part of
+ * a multistep process. This script actually performs the selected operations
+ * without loading all of Drupal, to be able to more gracefully recover from
+ * errors. Access to the script is controlled by a global killswitch in
+ * settings.php ('allow_authorize_operations') and via the 'administer software
+ * updates' permission.
  *
  * There are helper functions for setting up an operation to run via this
  * system in modules/system/system.module. For more information, see:
@@ -21,16 +21,17 @@
  */
 
 /**
- * Root directory of Drupal installation.
+ * Defines the root directory of the Drupal installation.
  */
 define('DRUPAL_ROOT', getcwd());
 
 /**
- * Global flag to identify update.php and authorize.php runs, and so
- * avoid various unwanted operations, such as hook_init() and
- * hook_exit() invokes, css/js preprocessing and translation, and
- * solve some theming issues. This flag is checked on several places
- * in Drupal code (not just authorize.php).
+ * Global flag to identify update.php and authorize.php runs.
+ *
+ * Identifies update.php and authorize.php runs, avoiding unwanted operations
+ * such as hook_init() and hook_exit() invokes, css/js preprocessing and
+ * translation, and solves some theming issues. The flag is checked in other
+ * places in Drupal code (not just authorize.php).
  */
 define('MAINTENANCE_MODE', 'update');
 
@@ -51,7 +52,7 @@
  * have access to the 'administer software updates' permission.
  *
  * @return
- *   TRUE if the current user can run authorize.php, otherwise FALSE.
+ *   TRUE if the current user can run authorize.php, and FALSE if not.
  */
 function authorize_access_allowed() {
   return variable_get('allow_authorize_operations', TRUE) && user_access('administer software updates');
@@ -60,7 +61,6 @@
 // *** Real work of the script begins here. ***
 
 require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
-require_once DRUPAL_ROOT . '/includes/session.inc';
 require_once DRUPAL_ROOT . '/includes/common.inc';
 require_once DRUPAL_ROOT . '/includes/file.inc';
 require_once DRUPAL_ROOT . '/includes/module.inc';
diff -Naur drupal-7.14/includes/ajax.inc drupal-7.66/includes/ajax.inc
--- drupal-7.14/includes/ajax.inc	2012-05-03 00:10:42.000000000 +0200
+++ drupal-7.66/includes/ajax.inc	2019-04-17 22:20:46.000000000 +0200
@@ -168,7 +168,7 @@
  *   displayed while awaiting a response from the callback, and add an optional
  *   message. Possible keys: 'type', 'message', 'url', 'interval'.
  *   More information is available in the
- *   @link http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html/7 Form API Reference @endlink
+ *   @link forms_api_reference.html Form API Reference @endlink
  *
  * In addition to using Form API for doing in-form modification, Ajax may be
  * enabled by adding classes to buttons and links. By adding the 'use-ajax'
@@ -211,7 +211,7 @@
  *
  * When returning an Ajax command array, it is often useful to have
  * status messages rendered along with other tasks in the command array.
- * In that case the the Ajax commands array may be constructed like this:
+ * In that case the Ajax commands array may be constructed like this:
  * @code
  *   $commands = array();
  *   $commands[] = ajax_command_replace(NULL, $output);
@@ -230,6 +230,10 @@
  *   functions.
  */
 function ajax_render($commands = array()) {
+  // Although ajax_deliver() does this, some contributed and custom modules
+  // render Ajax responses without using that delivery callback.
+  ajax_set_verification_header();
+
   // Ajax responses aren't rendered with html.tpl.php, so we have to call
   // drupal_get_css() and drupal_get_js() here, in order to have new files added
   // during this request to be loaded by the page. We only want to send back
@@ -251,8 +255,8 @@
       //   reliably diffed with array_diff_key(), since the number can change
       //   due to factors unrelated to the inline content, so for now, we strip
       //   the inline items from Ajax responses, and can add support for them
-      //   when drupal_add_css() and drupal_add_js() are changed to using md5()
-      //   or some other hash of the inline content.
+      //   when drupal_add_css() and drupal_add_js() are changed to use a hash
+      //   of the inline content as the array key.
       foreach ($items[$type] as $key => $item) {
         if (is_numeric($key)) {
           unset($items[$type][$key]);
@@ -276,7 +280,7 @@
 
   $extra_commands = array();
   if (!empty($styles)) {
-    $extra_commands[] = ajax_command_prepend('head', $styles);
+    $extra_commands[] = ajax_command_add_css($styles);
   }
   if (!empty($scripts_header)) {
     $extra_commands[] = ajax_command_prepend('head', $scripts_header);
@@ -292,7 +296,7 @@
   $scripts = drupal_add_js();
   if (!empty($scripts['settings'])) {
     $settings = $scripts['settings'];
-    array_unshift($commands, ajax_command_settings(call_user_func_array('array_merge_recursive', $settings['data']), TRUE));
+    array_unshift($commands, ajax_command_settings(drupal_array_merge_deep_array($settings['data']), TRUE));
   }
 
   // Allow modules to alter any Ajax response.
@@ -308,10 +312,11 @@
  * pulls the form info from $_POST.
  *
  * @return
- *   An array containing the $form and $form_state. Use the list() function
- *   to break these apart:
+ *   An array containing the $form, $form_state, $form_id, $form_build_id and an
+ *   initial list of Ajax $commands. Use the list() function to break these
+ *   apart:
  *   @code
- *     list($form, $form_state, $form_id, $form_build_id) = ajax_get_form();
+ *     list($form, $form_state, $form_id, $form_build_id, $commands) = ajax_get_form();
  *   @endcode
  */
 function ajax_get_form() {
@@ -331,6 +336,17 @@
     drupal_exit();
   }
 
+  // When a page level cache is enabled, the form-build id might have been
+  // replaced from within form_get_cache. If this is the case, it is also
+  // necessary to update it in the browser by issuing an appropriate Ajax
+  // command.
+  $commands = array();
+  if (isset($form['#build_id_old']) && $form['#build_id_old'] != $form['#build_id']) {
+    // If the form build ID has changed, issue an Ajax command to update it.
+    $commands[] = ajax_command_update_build_id($form);
+    $form_build_id = $form['#build_id'];
+  }
+
   // Since some of the submit handlers are run, redirects need to be disabled.
   $form_state['no_redirect'] = TRUE;
 
@@ -345,7 +361,7 @@
   $form_state['input'] = $_POST;
   $form_id = $form['#form_id'];
 
-  return array($form, $form_state, $form_id, $form_build_id);
+  return array($form, $form_state, $form_id, $form_build_id, $commands);
 }
 
 /**
@@ -366,7 +382,7 @@
  * @see system_menu()
  */
 function ajax_form_callback() {
-  list($form, $form_state) = ajax_get_form();
+  list($form, $form_state, $form_id, $form_build_id, $commands) = ajax_get_form();
   drupal_process_form($form['#form_id'], $form, $form_state);
 
   // We need to return the part of the form (or some other content) that needs
@@ -378,8 +394,20 @@
   if (!empty($form_state['triggering_element'])) {
     $callback = $form_state['triggering_element']['#ajax']['callback'];
   }
-  if (!empty($callback) && function_exists($callback)) {
-    return $callback($form, $form_state);
+  if (!empty($callback) && is_callable($callback)) {
+    $result = $callback($form, $form_state);
+
+    if (!(is_array($result) && isset($result['#type']) && $result['#type'] == 'ajax')) {
+      // Turn the response into a #type=ajax array if it isn't one already.
+      $result = array(
+        '#type' => 'ajax',
+        '#commands' => ajax_prepare_response($result),
+      );
+    }
+
+    $result['#commands'] = array_merge($commands, $result['#commands']);
+
+    return $result;
   }
 }
 
@@ -463,6 +491,9 @@
     }
   }
 
+  // Let ajax.js know that this response is safe to process.
+  ajax_set_verification_header();
+
   // Print the response.
   $commands = ajax_prepare_response($page_callback_result);
   $json = ajax_render($commands);
@@ -553,6 +584,29 @@
 }
 
 /**
+ * Sets a response header for ajax.js to trust the response body.
+ *
+ * It is not safe to invoke Ajax commands within user-uploaded files, so this
+ * header protects against those being invoked.
+ *
+ * @see Drupal.ajax.options.success()
+ */
+function ajax_set_verification_header() {
+  $added = &drupal_static(__FUNCTION__);
+
+  // User-uploaded files cannot set any response headers, so a custom header is
+  // used to indicate to ajax.js that this response is safe. Note that most
+  // Ajax requests bound using the Form API will be protected by having the URL
+  // flagged as trusted in Drupal.settings, so this header is used only for
+  // things like custom markup that gets Ajax behaviors attached.
+  if (empty($added)) {
+    drupal_add_http_header('X-Drupal-Ajax-Token', '1');
+    // Avoid sending the header twice.
+    $added = TRUE;
+  }
+}
+
+/**
  * Performs end-of-Ajax-request tasks.
  *
  * This function is the equivalent of drupal_page_footer(), but for Ajax
@@ -740,7 +794,12 @@
 
     $element['#attached']['js'][] = array(
       'type' => 'setting',
-      'data' => array('ajax' => array($element['#id'] => $settings)),
+      'data' => array(
+        'ajax' => array($element['#id'] => $settings),
+        'urlIsAjaxTrusted' => array(
+          $settings['url'] => TRUE,
+        ),
+      ),
     );
 
     // Indicate that Ajax processing was successful.
@@ -836,7 +895,8 @@
  * @return
  *   An array suitable for use with the ajax_render() function.
  *
- * See @link http://docs.jquery.com/Manipulation/replaceWith#content jQuery replaceWith command @endlink
+ * See
+ * @link http://docs.jquery.com/Manipulation/replaceWith#content jQuery replaceWith command @endlink
  */
 function ajax_command_replace($selector, $html, $settings = NULL) {
   return array(
@@ -1209,3 +1269,49 @@
     'selector' => $selector,
   );
 }
+
+/**
+ * Creates a Drupal Ajax 'update_build_id' command.
+ *
+ * This command updates the value of a hidden form_build_id input element on a
+ * form. It requires the form passed in to have keys for both the old build ID
+ * in #build_id_old and the new build ID in #build_id.
+ *
+ * The primary use case for this Ajax command is to serve a new build ID to a
+ * form served from the cache to an anonymous user, preventing one anonymous
+ * user from accessing the form state of another anonymous users on Ajax enabled
+ * forms.
+ *
+ * @param $form
+ *   The form array representing the form whose build ID should be updated.
+ */
+function ajax_command_update_build_id($form) {
+  return array(
+    'command' => 'updateBuildId',
+    'old' => $form['#build_id_old'],
+    'new' => $form['#build_id'],
+  );
+}
+
+/**
+ * Creates a Drupal Ajax 'add_css' command.
+ *
+ * This method will add css via ajax in a cross-browser compatible way.
+ *
+ * This command is implemented by Drupal.ajax.prototype.commands.add_css()
+ * defined in misc/ajax.js.
+ *
+ * @param $styles
+ *   A string that contains the styles to be added.
+ *
+ * @return
+ *   An array suitable for use with the ajax_render() function.
+ *
+ * @see misc/ajax.js
+ */
+function ajax_command_add_css($styles) {
+  return array(
+    'command' => 'add_css',
+    'data' => $styles,
+  );
+}
diff -Naur drupal-7.14/includes/authorize.inc drupal-7.66/includes/authorize.inc
--- drupal-7.14/includes/authorize.inc	2012-05-03 00:10:42.000000000 +0200
+++ drupal-7.66/includes/authorize.inc	2019-04-17 22:20:46.000000000 +0200
@@ -18,7 +18,7 @@
   global $base_url, $is_https;
   $form = array();
 
-  // If possible, we want to post this form securely via https.
+  // If possible, we want to post this form securely via HTTPS.
   $form['#https'] = TRUE;
 
   // CSS we depend on lives in modules/system/maintenance.css, which is loaded
diff -Naur drupal-7.14/includes/batch.inc drupal-7.66/includes/batch.inc
--- drupal-7.14/includes/batch.inc	2012-05-03 00:10:42.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.14/includes/bootstrap.inc drupal-7.66/includes/bootstrap.inc
--- drupal-7.14/includes/bootstrap.inc	2012-05-03 00:10:42.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.14');
+define('VERSION', '7.66');
 
 /**
  * Core API compatibility.
@@ -26,6 +26,21 @@
 define('DRUPAL_MINIMUM_PHP_MEMORY_LIMIT', '32M');
 
 /**
+ * Error reporting level: display no errors.
+ */
+define('ERROR_REPORTING_HIDE', 0);
+
+/**
+ * Error reporting level: display errors and warnings.
+ */
+define('ERROR_REPORTING_DISPLAY_SOME', 1);
+
+/**
+ * Error reporting level: display all messages.
+ */
+define('ERROR_REPORTING_DISPLAY_ALL', 2);
+
+/**
  * Indicates that the item should never be removed unless explicitly selected.
  *
  * The item may be removed using cache_clear_all() with a cache ID.
@@ -203,12 +218,16 @@
 define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
 
 /**
- * Flag for drupal_set_title(); text is not sanitized, so run check_plain().
+ * Flag used to indicate that text is not sanitized, so run check_plain().
+ *
+ * @see drupal_set_title()
  */
 define('CHECK_PLAIN', 0);
 
 /**
- * Flag for drupal_set_title(); text has already been sanitized.
+ * Flag used to indicate that text has already been sanitized.
+ *
+ * @see drupal_set_title()
  */
 define('PASS_THROUGH', -1);
 
@@ -225,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
@@ -259,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
@@ -365,11 +398,11 @@
    * without necessarily writing back to the persistent cache at the end.
    *
    * @param $offset
-   *   The array offset that was request.
+   *   The array offset that was requested.
    * @param $persist
    *   Optional boolean to specify whether the offset should be persisted or
    *   not, defaults to TRUE. When called with $persist = FALSE the offset will
-   *   be unflagged so that it will not written at the end of the request.
+   *   be unflagged so that it will not be written at the end of the request.
    */
   protected function persist($offset, $persist = TRUE) {
     $this->keysToPersist[$offset] = $persist;
@@ -498,56 +531,11 @@
 }
 
 /**
- * Finds the appropriate configuration directory.
+ * Returns the appropriate configuration directory.
  *
- * Finds a matching configuration directory by stripping the website's
- * hostname from left to right and pathname from right to left. The first
- * configuration file found will be used and the remaining ones will be ignored.
- * If no configuration file is found, return a default value '$confdir/default'.
- *
- * With a site located at http://www.example.com:8080/mysite/test/, the file,
- * settings.php, is searched for in the following directories:
- *
- * - $confdir/8080.www.example.com.mysite.test
- * - $confdir/www.example.com.mysite.test
- * - $confdir/example.com.mysite.test
- * - $confdir/com.mysite.test
- *
- * - $confdir/8080.www.example.com.mysite
- * - $confdir/www.example.com.mysite
- * - $confdir/example.com.mysite
- * - $confdir/com.mysite
- *
- * - $confdir/8080.www.example.com
- * - $confdir/www.example.com
- * - $confdir/example.com
- * - $confdir/com
- *
- * - $confdir/default
- *
- * If a file named sites.php is present in the $confdir, it will be loaded
- * prior to scanning for directories. It should define an associative array
- * named $sites, which maps domains to directories. It should be in the form
- * of:
- * @code
- * $sites = array(
- *   'The url to alias' => 'A directory within the sites directory'
- * );
- * @endcode
- * For example:
- * @code
- * $sites = array(
- *   'devexample.com' => 'example.com',
- *   'localhost.example' => 'example.com',
- * );
- * @endcode
- * The above array will cause Drupal to look for a directory named
- * "example.com" in the sites directory whenever a request comes from
- * "example.com", "devexample.com", or "localhost/example". That is useful
- * on development servers, where the domain name may not be the same as the
- * domain of the live server. Since Drupal stores file paths into the database
- * (files, system table, etc.) this will ensure the paths are correct while
- * accessed on development servers.
+ * Returns the configuration path based on the site's hostname, port, and
+ * 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
@@ -560,6 +548,8 @@
  *
  * @return
  *   The path of the matching directory.
+ *
+ * @see default.settings.php
  */
 function conf_path($require_settings = TRUE, $reset = FALSE) {
   $conf = &drupal_static(__FUNCTION__, '');
@@ -706,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();
+    }
+  }
 }
 
 /**
@@ -722,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';
 }
 
 /**
@@ -731,19 +752,18 @@
 function drupal_settings_initialize() {
   global $base_url, $base_path, $base_root;
 
-  // Export the following settings.php variables to the global namespace
+  // Export these settings.php variables to the global namespace.
   global $databases, $cookie_domain, $conf, $installed_profile, $update_free_access, $db_url, $db_prefix, $drupal_hash_salt, $is_https, $base_secure_url, $base_insecure_url;
   $conf = array();
 
   if (file_exists(DRUPAL_ROOT . '/' . conf_path() . '/settings.php')) {
     include_once DRUPAL_ROOT . '/' . conf_path() . '/settings.php';
   }
-  $is_https = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on';
+  $is_https = drupal_is_https();
 
   if (isset($base_url)) {
     // Parse fixed base URL from settings.php.
     $parts = parse_url($base_url);
-    $http_protocol = $parts['scheme'];
     if (!isset($parts['path'])) {
       $parts['path'] = '';
     }
@@ -752,7 +772,7 @@
     $base_root = substr($base_url, 0, strlen($base_url) - strlen($parts['path']));
   }
   else {
-    // Create base URL
+    // Create base URL.
     $http_protocol = $is_https ? 'https' : 'http';
     $base_root = $http_protocol . '://' . $_SERVER['HTTP_HOST'];
 
@@ -778,7 +798,7 @@
   }
   else {
     // Otherwise use $base_url as session name, without the protocol
-    // to use the same session identifiers across http and https.
+    // to use the same session identifiers across HTTP and HTTPS.
     list( , $session_name) = explode('://', $base_url, 2);
     // HTTP_HOST can be modified by a visitor, but we already sanitized it
     // in drupal_settings_initialize().
@@ -819,7 +839,7 @@
  *
  * This function plays a key role in allowing Drupal's resources (modules
  * and themes) to be located in different places depending on a site's
- * configuration. For example, a module 'foo' may legally be be located
+ * configuration. For example, a module 'foo' may legally be located
  * in any of these three places:
  *
  * modules/foo/foo.module
@@ -830,20 +850,27 @@
  * the above, depending on where the module is located.
  *
  * @param $type
- *   The type of the item (i.e. theme, theme_engine, module, profile).
+ *   The type of the item (theme, theme_engine, module, profile).
  * @param $name
  *   The name of the item for which the filename is requested.
  * @param $filename
  *   The filename of the item if it is to be set explicitly rather
  *   than by consulting the database.
+ * @param bool $trigger_error
+ *   Whether to trigger an error when a file is missing or has unexpectedly
+ *   moved. This defaults to TRUE, but can be set to FALSE by calling code that
+ *   merely wants to check whether an item exists in the filesystem.
  *
  * @return
- *   The filename of the requested item.
- */
-function drupal_get_filename($type, $name, $filename = NULL) {
+ *   The filename of the requested item or NULL if the item is not found.
+ */
+function drupal_get_filename($type, $name, $filename = NULL, $trigger_error = TRUE) {
+  // The $files static variable will hold the locations of all requested files.
+  // We can be sure that any file listed in this static variable actually
+  // exists as all additions have gone through a file_exists() check.
   // The location of files will not change during the request, so do not use
   // drupal_static().
-  static $files = array(), $dirs = array();
+  static $files = array();
 
   // Profiles are a special case: they have a fixed location and naming.
   if ($type == 'profile') {
@@ -855,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');
   }
 }
 
@@ -1063,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.
@@ -1213,10 +1472,11 @@
  * Headers are set in drupal_add_http_header(). Default headers are not set
  * if they have been replaced or unset using drupal_add_http_header().
  *
- * @param $default_headers
- *   An array of headers as name/value pairs.
- * @param $single
- *   If TRUE and headers have already be sent, send only the specified header.
+ * @param array $default_headers
+ *   (optional) An array of headers as name/value pairs.
+ * @param bool $only_default
+ *   (optional) If TRUE and headers have already been sent, send only the
+ *   specified headers.
  */
 function drupal_send_headers($default_headers = array(), $only_default = FALSE) {
   $headers_sent = &drupal_static(__FUNCTION__, FALSE);
@@ -1239,7 +1499,7 @@
       header($_SERVER['SERVER_PROTOCOL'] . ' ' . $value);
     }
     // Skip headers that have been unset.
-    elseif ($value) {
+    elseif ($value !== FALSE) {
       header($header_names[$name_lower] . ': ' . $value);
     }
   }
@@ -1252,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()
  */
@@ -1281,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);
 }
@@ -1301,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.
@@ -1351,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
@@ -1432,6 +1681,7 @@
  * more information, including recommendations on how to break up or not
  * break up strings for translation.
  *
+ * @section sec_translating_vars Translating Variables
  * You should never use t() to translate variables, such as calling
  * @code t($text); @endcode, unless the text that the variable holds has been
  * passed through t() elsewhere (e.g., $text is one of several translated
@@ -1447,13 +1697,32 @@
  * Basically, you can put variables like @name into your string, and t() will
  * substitute their sanitized values at translation time. (See the
  * Localization API pages referenced above and the documentation of
- * format_string() for details.) Translators can then rearrange the string as
- * necessary for the language (e.g., in Spanish, it might be "blog de @name").
+ * format_string() for details about how to define variables in your string.)
+ * Translators can then rearrange the string as necessary for the language
+ * (e.g., in Spanish, it might be "blog de @name").
  *
+ * @section sec_alt_funcs_install Use During Installation Phase
  * During the Drupal installation phase, some resources used by t() wil not be
  * available to code that needs localization. See st() and get_t() for
  * alternatives.
  *
+ * @section sec_context String context
+ * Matching source strings are normally only translated once, and the same
+ * translation is used everywhere that has a matching string. However, in some
+ * cases, a certain English source string needs to have multiple translations.
+ * One example of this is the string "May", which could be used as either a
+ * full month name or a 3-letter abbreviated month. In other languages where
+ * the month name for May has more than 3 letters, you would need to provide
+ * two different translations (one for the full name and one abbreviated), and
+ * the correct form would need to be chosen, depending on how "May" is being
+ * used. To facilitate this, the "May" string should be provided with two
+ * different contexts in the $options parameter when calling t(). For example:
+ * @code
+ * t('May', array(), array('context' => 'Long month name')
+ * t('May', array(), array('context' => 'Abbreviated month name')
+ * @endcode
+ * See https://localize.drupal.org/node/2109 for more information.
+ *
  * @param $string
  *   A string containing the English string to translate.
  * @param $args
@@ -1464,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.
@@ -1511,21 +1781,34 @@
 }
 
 /**
- * Replaces placeholders with sanitized values in a string.
+ * Formats a string for HTML display by replacing variable placeholders.
+ *
+ * This function replaces variable placeholders in a string with the requested
+ * values and escapes the values so they can be safely displayed as HTML. It
+ * should be used on any unknown text that is intended to be printed to an HTML
+ * page (especially text that may have come from untrusted users, since in that
+ * case it prevents cross-site scripting and other security problems).
+ *
+ * In most cases, you should use t() rather than calling this function
+ * directly, since it will translate the text (on non-English-only sites) in
+ * addition to formatting it.
  *
  * @param $string
  *   A string containing placeholders.
  * @param $args
  *   An associative array of replacements to make. Occurrences in $string of
- *   any key in $args are replaced with the corresponding value, after
- *   sanitization. The sanitization function depends on the first character of
- *   the key:
- *   - !variable: Inserted as is. Use this for text that has already been
- *     sanitized.
- *   - @variable: Escaped to HTML using check_plain(). Use this for anything
- *     displayed on a page on the site.
- *   - %variable: Escaped as a placeholder for user-submitted content using
- *     drupal_placeholder(), which shows up as emphasized text.
+ *   any key in $args are replaced with the corresponding value, after optional
+ *   sanitization and formatting. The type of sanitization and formatting
+ *   depends on the first character of the key:
+ *   - @variable: Escaped to HTML using check_plain(). Use this as the default
+ *     choice for anything displayed on a page on the site.
+ *   - %variable: Escaped to HTML and formatted using drupal_placeholder(),
+ *     which makes it display as emphasized text.
+ *   - !variable: Inserted as is, with no sanitization or formatting. Only use
+ *     this for text that has already been prepared for HTML display (for
+ *     example, user-supplied text that has already been run through
+ *     check_plain() previously, or is expected to contain some limited HTML
+ *     tags and has already been run through filter_xss() previously).
  *
  * @see t()
  * @ingroup sanitization
@@ -1558,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
@@ -1648,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) {
 
@@ -1732,7 +2016,8 @@
       'request_uri' => $base_root . request_uri(),
       'referer'     => isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '',
       'ip'          => ip_address(),
-      'timestamp'   => REQUEST_TIME,
+      // Request time isn't accurate for long processes, use time() instead.
+      'timestamp'   => time(),
     );
 
     // Call the logging hooks to log/process the message
@@ -1747,25 +2032,40 @@
 }
 
 /**
- * Sets a message which reflects the status of the performed operation.
+ * Sets a message to display to the user.
  *
- * If the function is called with no arguments, this function returns all set
- * messages without clearing them.
+ * Messages are stored in a session variable and displayed in page.tpl.php via
+ * the $messages theme variable.
  *
- * @param $message
- *   The message to be displayed to the user. For consistency with other
- *   messages, it should begin with a capital letter and end with a period.
- * @param $type
- *   The type of the message. One of the following values are possible:
+ * Example usage:
+ * @code
+ * drupal_set_message(t('An error occurred and processing did not complete.'), 'error');
+ * @endcode
+ *
+ * @param string $message
+ *   (optional) The translated message to be displayed to the user. For
+ *   consistency with other messages, it should begin with a capital letter and
+ *   end with a period.
+ * @param string $type
+ *   (optional) The message's type. Defaults to 'status'. These values are
+ *   supported:
  *   - 'status'
  *   - 'warning'
  *   - 'error'
- * @param $repeat
- *   If this is FALSE and the message is already set, then the message won't
- *   be repeated.
+ * @param bool $repeat
+ *   (optional) If this is FALSE and the message is already set, then the
+ *   message won't be repeated. Defaults to TRUE.
+ *
+ * @return array|null
+ *   A multidimensional array with keys corresponding to the set message types.
+ *   The indexed array values of each contain the set messages for that type.
+ *   Or, if there are no messages set, the function returns NULL.
+ *
+ * @see drupal_get_messages()
+ * @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();
     }
@@ -1783,18 +2083,29 @@
 }
 
 /**
- * Returns all messages that have been set.
+ * Returns all messages that have been set with drupal_set_message().
  *
- * @param $type
- *   (optional) Only return messages of this type.
- * @param $clear_queue
- *   (optional) Set to FALSE if you do not want to clear the messages queue
+ * @param string $type
+ *   (optional) Limit the messages returned by type. Defaults to NULL, meaning
+ *   all types. These values are supported:
+ *   - NULL
+ *   - 'status'
+ *   - 'warning'
+ *   - 'error'
+ * @param bool $clear_queue
+ *   (optional) If this is TRUE, the queue will be cleared of messages of the
+ *   type specified in the $type parameter. Otherwise the queue will be left
+ *   intact. Defaults to TRUE.
+ *
+ * @return array
+ *   A multidimensional array with keys corresponding to the set message types.
+ *   The indexed array values of each contain the set messages for that type.
+ *   The messages returned are limited to the type specified in the $type
+ *   parameter. If there are no messages of the specified type, an empty array
+ *   is returned.
  *
- * @return
- *   An associative array, the key is the message type, the value an array
- *   of messages. If the $type parameter is passed, you get only that type,
- *   or an empty array if there are no such messages. If $type is not passed,
- *   all message types are returned, or an empty array if none exist.
+ * @see drupal_set_message()
+ * @see theme_status_messages()
  */
 function drupal_get_messages($type = NULL, $clear_queue = TRUE) {
   if ($messages = drupal_set_message()) {
@@ -1913,6 +2224,33 @@
 }
 
 /**
+ * Returns a URL-safe, base64 encoded string of highly randomized bytes (over the full 8-bit range).
+ *
+ * @param $byte_count
+ *   The number of random bytes to fetch and base64 encode.
+ *
+ * @return string
+ *   The base64 encoded result will have a length of up to 4 * $byte_count.
+ */
+function drupal_random_key($byte_count = 32) {
+  return drupal_base64_encode(drupal_random_bytes($byte_count));
+}
+
+/**
+ * Returns a URL-safe, base64 encoded version of the supplied string.
+ *
+ * @param $string
+ *   The string to convert to base64.
+ *
+ * @return string
+ */
+function drupal_base64_encode($string) {
+  $data = base64_encode($string);
+  // Modify the output so it's safe to use in URLs.
+  return strtr($data, array('+' => '-', '/' => '_', '=' => ''));
+}
+
+/**
  * Returns a string of highly randomized bytes (over the full 8-bit range).
  *
  * This function is better than simply calling mt_rand() or any other built-in
@@ -1925,38 +2263,34 @@
  */
 function drupal_random_bytes($count)  {
   // $random_state does not use drupal_static as it stores random bytes.
-  static $random_state, $bytes, $php_compatible;
-  // Initialize on the first call. The contents of $_SERVER includes a mix of
-  // user-specific and system information that varies a little with each page.
-  if (!isset($random_state)) {
-    $random_state = print_r($_SERVER, TRUE);
-    if (function_exists('getmypid')) {
-      // Further initialize with the somewhat random PHP process ID.
-      $random_state .= getmypid();
-    }
-    $bytes = '';
-  }
-  if (strlen($bytes) < $count) {
+  static $random_state, $bytes, $has_openssl;
+
+  $missing_bytes = $count - strlen($bytes);
+
+  if ($missing_bytes > 0) {
     // PHP versions prior 5.3.4 experienced openssl_random_pseudo_bytes()
     // locking on Windows and rendered it unusable.
-    if (!isset($php_compatible)) {
-      $php_compatible = version_compare(PHP_VERSION, '5.3.4', '>=');
+    if (!isset($has_openssl)) {
+      $has_openssl = version_compare(PHP_VERSION, '5.3.4', '>=') && function_exists('openssl_random_pseudo_bytes');
     }
-    // /dev/urandom is available on many *nix systems and is considered the
-    // best commonly available pseudo-random source.
-    if ($fh = @fopen('/dev/urandom', 'rb')) {
+
+    // openssl_random_pseudo_bytes() will find entropy in a system-dependent
+    // way.
+    if ($has_openssl) {
+      $bytes .= openssl_random_pseudo_bytes($missing_bytes);
+    }
+
+    // Else, read directly from /dev/urandom, which is available on many *nix
+    // systems and is considered cryptographically secure.
+    elseif ($fh = @fopen('/dev/urandom', 'rb')) {
       // PHP only performs buffered reads, so in reality it will always read
       // at least 4096 bytes. Thus, it costs nothing extra to read and store
       // that much so as to speed any additional invocations.
-      $bytes .= fread($fh, max(4096, $count));
+      $bytes .= fread($fh, max(4096, $missing_bytes));
       fclose($fh);
     }
-    // openssl_random_pseudo_bytes() will find entropy in a system-dependent
-    // way.
-    elseif ($php_compatible && function_exists('openssl_random_pseudo_bytes')) {
-      $bytes .= openssl_random_pseudo_bytes($count - strlen($bytes));
-    }
-    // If /dev/urandom is not available or returns no bytes, this loop will
+
+    // If we couldn't get enough entropy, this simple hash-based PRNG will
     // generate a good set of pseudo-random bytes on any system.
     // Note that it may be important that our $random_state is passed
     // through hash() prior to being rolled into $output, that the two hash()
@@ -1964,9 +2298,23 @@
     // the microtime() - is prepended rather than appended. This is to avoid
     // directly leaking $random_state via the $output stream, which could
     // allow for trivial prediction of further "random" numbers.
-    while (strlen($bytes) < $count) {
-      $random_state = hash('sha256', microtime() . mt_rand() . $random_state);
-      $bytes .= hash('sha256', mt_rand() . $random_state, TRUE);
+    if (strlen($bytes) < $count) {
+      // Initialize on the first call. The contents of $_SERVER includes a mix of
+      // user-specific and system information that varies a little with each page.
+      if (!isset($random_state)) {
+        $random_state = print_r($_SERVER, TRUE);
+        if (function_exists('getmypid')) {
+          // Further initialize with the somewhat random PHP process ID.
+          $random_state .= getmypid();
+        }
+        $bytes = '';
+      }
+
+      do {
+        $random_state = hash('sha256', microtime() . mt_rand() . $random_state);
+        $bytes .= hash('sha256', mt_rand() . $random_state, TRUE);
+      }
+      while (strlen($bytes) < $count);
     }
   }
   $output = substr($bytes, 0, $count);
@@ -1977,17 +2325,21 @@
 /**
  * Calculates a base-64 encoded, URL-safe sha-256 hmac.
  *
- * @param $data
+ * @param string $data
  *   String to be validated with the hmac.
- * @param $key
+ * @param string $key
  *   A secret string key.
  *
- * @return
+ * @return string
  *   A base-64 encoded sha-256 hmac, with + replaced with -, / with _ and
  *   any = padding characters removed.
  */
 function drupal_hmac_base64($data, $key) {
-  $hmac = base64_encode(hash_hmac('sha256', $data, $key, TRUE));
+  // Casting $data and $key to strings here is necessary to avoid empty string
+  // results of the hash function if they are not scalar values. As this
+  // function is used in security-critical contexts like token validation it is
+  // important that it never returns an empty string.
+  $hmac = base64_encode(hash_hmac('sha256', (string) $data, (string) $key, TRUE));
   // Modify the hmac so it's safe to use in URLs.
   return strtr($hmac, array('+' => '-', '/' => '_', '=' => ''));
 }
@@ -2088,7 +2440,7 @@
  * @return Object - the user object.
  */
 function drupal_anonymous_user() {
-  $user = new stdClass();
+  $user = variable_get('drupal_anonymous_user_object', new stdClass);
   $user->uid = 0;
   $user->hostname = ip_address();
   $user->roles = array();
@@ -2100,19 +2452,31 @@
 /**
  * Ensures Drupal is bootstrapped to the specified phase.
  *
- * The bootstrap phase is an integer constant identifying a phase of Drupal
- * to load. Each phase adds to the previous one, so invoking a later phase
- * automatically runs the earlier phases as well. To access the Drupal
- * database from a script without loading anything else, include bootstrap.inc
- * and call drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE).
- *
- * @param $phase
- *   A constant. Allowed values are the DRUPAL_BOOTSTRAP_* constants.
- * @param $new_phase
+ * In order to bootstrap Drupal from another PHP script, you can use this code:
+ * @code
+ *   define('DRUPAL_ROOT', '/path/to/drupal');
+ *   require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
+ *   drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
+ * @endcode
+ *
+ * @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:
+ *   - DRUPAL_BOOTSTRAP_CONFIGURATION: Initializes configuration.
+ *   - DRUPAL_BOOTSTRAP_PAGE_CACHE: Tries to serve a cached page.
+ *   - DRUPAL_BOOTSTRAP_DATABASE: Initializes the database layer.
+ *   - DRUPAL_BOOTSTRAP_VARIABLES: Initializes the variable system.
+ *   - DRUPAL_BOOTSTRAP_SESSION: Initializes session handling.
+ *   - DRUPAL_BOOTSTRAP_PAGE_HEADER: Sets up the page header.
+ *   - DRUPAL_BOOTSTRAP_LANGUAGE: Finds out the language of the page.
+ *   - DRUPAL_BOOTSTRAP_FULL: Fully loads Drupal. Validates and fixes input
+ *     data.
+ * @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) {
@@ -2134,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) {
@@ -2207,6 +2572,19 @@
 }
 
 /**
+ * Gets a salt useful for hardening against SQL injection.
+ *
+ * @return
+ *   A salt based on information in settings.php, not in the database.
+ */
+function drupal_get_hash_salt() {
+  global $drupal_hash_salt, $databases;
+  // If the $drupal_hash_salt variable is empty, a hash of the serialized
+  // database credentials is used as a fallback salt.
+  return empty($drupal_hash_salt) ? hash('sha256', serialize($databases)) : $drupal_hash_salt;
+}
+
+/**
  * Provides custom PHP error handling.
  *
  * @param $error_level
@@ -2267,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();
 }
 
 /**
@@ -2375,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');
+  }
 }
 
 /**
@@ -2392,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']);
+    }
+  }
 }
 
 /**
@@ -2414,7 +2824,7 @@
  * @see drupal_bootstrap()
  */
 function drupal_get_bootstrap_phase() {
-  return drupal_bootstrap();
+  return drupal_bootstrap(NULL, FALSE);
 }
 
 /**
@@ -2426,7 +2836,6 @@
  *   HMAC and timestamp.
  */
 function drupal_valid_test_ua() {
-  global $drupal_hash_salt;
   // No reason to reset this.
   static $test_prefix;
 
@@ -2440,7 +2849,7 @@
     // We use the salt from settings.php to make the HMAC key, since
     // the database is not yet initialized and we can't access any Drupal variables.
     // The file properties add more entropy not easily accessible to others.
-    $key = $drupal_hash_salt . filectime(__FILE__) . fileinode(__FILE__);
+    $key = drupal_get_hash_salt() . filectime(__FILE__) . fileinode(__FILE__);
     $time_diff = REQUEST_TIME - $time;
     // Since we are making a local request a 5 second time window is allowed,
     // and the HMAC must match.
@@ -2458,14 +2867,13 @@
  * Generates a user agent string with a HMAC and timestamp for simpletest.
  */
 function drupal_generate_test_ua($prefix) {
-  global $drupal_hash_salt;
   static $key;
 
   if (!isset($key)) {
     // We use the salt from settings.php to make the HMAC key, since
     // the database is not yet initialized and we can't access any Drupal variables.
     // The file properties add more entropy not easily accessible to others.
-    $key = $drupal_hash_salt . filectime(__FILE__) . fileinode(__FILE__);
+    $key = drupal_get_hash_salt() . filectime(__FILE__) . fileinode(__FILE__);
   }
   // Generate a moderately secure HMAC based on the database credentials.
   $salt = uniqid('', TRUE);
@@ -2506,7 +2914,7 @@
     $fast_paths = variable_get('404_fast_paths', FALSE);
     if ($fast_paths && preg_match($fast_paths, $_GET['q'])) {
       drupal_add_http_header('Status', '404 Not Found');
-      $fast_404_html = variable_get('404_fast_html', '404 Not Found

Not Found

The requested URL "@path" was not found on this server.

'); + $fast_404_html = variable_get('404_fast_html', '404 Not Found

Not Found

The requested URL "@path" was not found on this server.

'); // Replace @path in the variable with the page path. print strtr($fast_404_html, array('@path' => check_plain(request_uri()))); exit; @@ -2530,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: @@ -2599,6 +3007,9 @@ /** * Returns TRUE if there is more than one language enabled. + * + * @return + * TRUE if more than one language is enabled. */ function drupal_multilingual() { // The "language_count" variable stores the number of enabled languages to @@ -2609,6 +3020,10 @@ /** * Returns an array of the available language types. + * + * @return + * An array of all language types where the keys of each are the language type + * name and its value is its configurability (TRUE/FALSE). */ function language_types() { return array_keys(variable_get('language_types', drupal_language_types())); @@ -2665,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' => '')); @@ -2699,7 +3118,7 @@ return $path; } - if (isset($_GET['q'])) { + if (isset($_GET['q']) && is_string($_GET['q'])) { // This is a request with a ?q=foo/bar query string. $_GET['q'] is // overwritten in drupal_path_initialize(), but request_path() is called // very early in the bootstrap process, so the original value is saved in @@ -2820,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); + } } } } @@ -2830,7 +3256,7 @@ } /** - * @ingroup schemaapi + * @addtogroup schemaapi * @{ */ @@ -2838,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. @@ -2952,12 +3380,12 @@ } /** - * @} End of "ingroup schemaapi". + * @} End of "addtogroup schemaapi". */ /** - * @ingroup registry + * @addtogroup registry * @{ */ @@ -2994,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 @@ -3011,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; } @@ -3044,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]; } @@ -3052,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. @@ -3066,7 +3513,7 @@ $lookup_cache[$cache_key] = $file; if ($file) { - require_once DRUPAL_ROOT . '/' . $file; + include_once DRUPAL_ROOT . '/' . $file; return TRUE; } else { @@ -3119,7 +3566,7 @@ } /** - * @} End of "ingroup registry". + * @} End of "addtogroup registry". */ /** @@ -3203,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. * @@ -3229,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. @@ -3278,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); @@ -3351,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) { @@ -3364,3 +3815,63 @@ } } } + +/** + * Compares the memory required for an operation to the available memory. + * + * @param $required + * The memory required for the operation, expressed as a number of bytes with + * optional SI or IEC binary unit prefix (e.g. 2, 3K, 5MB, 10G, 6GiB, 8bytes, + * 9mbytes). + * @param $memory_limit + * (optional) The memory limit for the operation, expressed as a number of + * bytes with optional SI or IEC binary unit prefix (e.g. 2, 3K, 5MB, 10G, + * 6GiB, 8bytes, 9mbytes). If no value is passed, the current PHP + * memory_limit will be used. Defaults to NULL. + * + * @return + * TRUE if there is sufficient memory to allow the operation, or FALSE + * otherwise. + */ +function drupal_check_memory_limit($required, $memory_limit = NULL) { + if (!isset($memory_limit)) { + $memory_limit = ini_get('memory_limit'); + } + + // There is sufficient memory if: + // - No memory limit is set. + // - The memory limit is set to unlimited (-1). + // - The memory limit is greater than the memory required for the operation. + return ((!$memory_limit) || ($memory_limit == -1) || (parse_size($memory_limit) >= parse_size($required))); +} + +/** + * Invalidates a PHP file from any active opcode caches. + * + * If the opcode cache does not support the invalidation of individual files, + * the entire cache will be flushed. + * + * @param string $filepath + * The absolute path of the PHP file to invalidate. + */ +function drupal_clear_opcode_cache($filepath) { + if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50300) { + // Below PHP 5.3, clearstatcache does not accept any function parameters. + clearstatcache(); + } + else { + clearstatcache(TRUE, $filepath); + } + + // Zend OPcache. + if (function_exists('opcache_invalidate')) { + opcache_invalidate($filepath, TRUE); + } + // APC. + if (function_exists('apc_delete_file')) { + // apc_delete_file() throws a PHP warning in case the specified file was + // not compiled yet. + // @see http://php.net/apc-delete-file + @apc_delete_file($filepath); + } +} diff -Naur drupal-7.14/includes/cache.inc drupal-7.66/includes/cache.inc --- drupal-7.14/includes/cache.inc 2012-05-03 00:10:42.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. * @@ -80,43 +81,15 @@ * same name. Other implementations might want to store several bins in data * structures that get flushed together. While it is not a problem for most * cache bins if the entries in them are flushed before their expire time, some - * might break functionality or are extremely expensive to recalculate. These - * will be marked with a (*). The other bins expired automatically by core. - * Contributed modules can add additional bins and get them expired - * automatically by implementing hook_flush_caches(). - * - * - cache: Generic cache storage bin (used for variables, theme registry, - * locale date, list of simpletest tests etc). - * - * - cache_block: Stores the content of various blocks. - * - * - cache field: Stores the field data belonging to a given object. - * - * - cache_filter: Stores filtered pieces of content. - * - * - cache_form(*): Stores multistep forms. Flushing this bin means that some - * forms displayed to users lose their state and the data already submitted - * to them. - * - * - cache_menu: Stores the structure of visible navigation menus per page. - * - * - cache_page: Stores generated pages for anonymous users. It is flushed - * very often, whenever a page changes, at least for every ode and comment - * submission. This is the only bin affected by the page cache setting on - * the administrator panel. - * - * - cache path: Stores the system paths that have an alias. - * - * - cache update(*): Stores available releases. The update server (for - * example, drupal.org) needs to produce the relevant XML for every project - * installed on the current site. As this is different for (almost) every - * site, it's very expensive to recalculate for the update server. + * might break functionality or are extremely expensive to recalculate. The + * other bins are expired automatically by core. Contributed modules can add + * additional bins and get them expired automatically by implementing + * hook_flush_caches(). * * The reasons for having several bins are as follows: - * - * - smaller bins mean smaller database tables and allow for faster selects and - * inserts - * - we try to put fast changing cache items and rather static ones into + * - Smaller bins mean smaller database tables and allow for faster selects and + * inserts. + * - We try to put fast changing cache items and rather static ones into * different bins. The effect is that only the fast changing bins will need a * lot of writes to disk. The more static bins will also be better cacheable * with MySQL's query cache. @@ -125,15 +98,36 @@ * The cache ID of the data to store. * @param $data * The data to store in the cache. Complex data types will be automatically - * serialized before insertion. - * Strings will be stored as plain text and not serialized. + * serialized before insertion. Strings will be stored as plain text and are + * not serialized. Some storage engines only allow objects up to a maximum of + * 1MB in size to be stored by default. When caching large arrays or similar, + * take care to ensure $data does not exceed this size. * @param $bin - * The cache bin to store the data in. Valid core values are 'cache_block', - * 'cache_bootstrap', 'cache_field', 'cache_filter', 'cache_form', - * 'cache_menu', 'cache_page', 'cache_update' or 'cache' for the default - * cache. + * (optional) The cache bin to store the data in. Valid core values are: + * - cache: (default) Generic cache storage bin (used for theme registry, + * locale date, list of simpletest tests, etc.). + * - cache_block: Stores the content of various blocks. + * - cache_bootstrap: Stores the class registry, the system list of modules, + * the list of which modules implement which hooks, and the Drupal variable + * list. + * - cache_field: Stores the field data belonging to a given object. + * - cache_filter: Stores filtered pieces of content. + * - cache_form: Stores multistep forms. Flushing this bin means that some + * forms displayed to users lose their state and the data already submitted + * to them. This bin should not be flushed before its expired time. + * - cache_menu: Stores the structure of visible navigation menus per page. + * - cache_page: Stores generated pages for anonymous users. It is flushed + * very often, whenever a page changes, at least for every node and comment + * submission. This is the only bin affected by the page cache setting on + * the administrator panel. + * - cache_path: Stores the system paths that have an alias. * @param $expire - * One of the following values: + * (optional) Controls the maximum lifetime of this cache entry. Note that + * caches might be subject to clearing at any time, so this setting does not + * guarantee a minimum lifetime. With this in mind, the cache should not be + * used for data that must be kept during a cache clear, like sessions. + * + * Use one of the following values: * - CACHE_PERMANENT: Indicates that the item should never be removed unless * explicitly told to using cache_clear_all() with a cache ID. * - CACHE_TEMPORARY: Indicates that the item should be removed at the next @@ -141,6 +135,7 @@ * - A Unix timestamp: Indicates that the item should be kept at least until * the given time, after which it behaves like CACHE_TEMPORARY. * + * @see _update_cache_set() * @see cache_get() */ function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) { @@ -150,18 +145,20 @@ /** * Expires data from the cache. * - * If called without arguments, expirable entries will be cleared from the - * cache_page and cache_block bins. + * If called with the arguments $cid and $bin set to NULL or omitted, then + * expirable entries will be cleared from the cache_page and cache_block bins, + * and the $wildcard argument is ignored. * * @param $cid - * If set, the cache ID to delete. Otherwise, all cache entries that can - * expire are deleted. + * If set, the cache ID or an array of cache IDs. Otherwise, all cache entries + * that can expire are deleted. The $wildcard argument will be ignored if set + * to NULL. * @param $bin * If set, the cache bin to delete from. Mandatory argument if $cid is set. * @param $wildcard - * If TRUE, cache IDs starting with $cid are deleted in addition to the - * exact cache ID specified by $cid. If $wildcard is TRUE and $cid is '*', - * the entire cache bin is emptied. + * If TRUE, the $cid argument must contain a string value and cache IDs + * starting with $cid are deleted in addition to the exact cache ID specified + * by $cid. If $wildcard is TRUE and $cid is '*', the entire cache is emptied. */ function cache_clear_all($cid = NULL, $bin = NULL, $wildcard = FALSE) { if (!isset($cid) && !isset($bin)) { @@ -230,13 +227,6 @@ * @see DrupalDatabaseCache */ interface DrupalCacheInterface { - /** - * Constructs a new cache interface. - * - * @param $bin - * The cache bin for which the object is created. - */ - function __construct($bin); /** * Returns data from the persistent cache. @@ -272,10 +262,17 @@ * The cache ID of the data to store. * @param $data * The data to store in the cache. Complex data types will be automatically - * serialized before insertion. - * Strings will be stored as plain text and not serialized. + * serialized before insertion. Strings will be stored as plain text and not + * serialized. Some storage engines only allow objects up to a maximum of + * 1MB in size to be stored by default. When caching large arrays or + * similar, take care to ensure $data does not exceed this size. * @param $expire - * One of the following values: + * (optional) Controls the maximum lifetime of this cache entry. Note that + * caches might be subject to clearing at any time, so this setting does not + * guarantee a minimum lifetime. With this in mind, the cache should not be + * used for data that must be kept during a cache clear, like sessions. + * + * Use one of the following values: * - CACHE_PERMANENT: Indicates that the item should never be removed unless * explicitly told to using cache_clear_all() with a cache ID. * - CACHE_TEMPORARY: Indicates that the item should be removed at the next @@ -293,12 +290,14 @@ * cache_page and cache_block bins. * * @param $cid - * If set, the cache ID to delete. Otherwise, all cache entries that can - * expire are deleted. + * If set, the cache ID or an array of cache IDs. Otherwise, all cache + * entries that can expire are deleted. The $wildcard argument will be + * ignored if set to NULL. * @param $wildcard - * If set to TRUE, the $cid is treated as a substring - * to match rather than a complete ID. The match is a right hand - * match. If '*' is given as $cid, the bin $bin will be emptied. + * If TRUE, the $cid argument must contain a string value and cache IDs + * starting with $cid are deleted in addition to the exact cache ID + * specified by $cid. If $wildcard is TRUE and $cid is '*', the entire + * cache is emptied. */ function clear($cid = NULL, $wildcard = FALSE); @@ -324,7 +323,10 @@ protected $bin; /** - * Constructs a new DrupalDatabaseCache object. + * Constructs a DrupalDatabaseCache object. + * + * @param $bin + * The cache bin for which the object is created. */ function __construct($bin) { $this->bin = $bin; @@ -518,7 +520,16 @@ else { if ($wildcard) { if ($cid == '*') { - db_truncate($this->bin)->execute(); + // Check if $this->bin is a cache table before truncating. Other + // cache_clear_all() operations throw a PDO error in this situation, + // so we don't need to verify them first. This ensures that non-cache + // tables cannot be truncated accidentally. + if ($this->isValidBin()) { + db_truncate($this->bin)->execute(); + } + else { + throw new Exception(t('Invalid or missing cache bin specified: %bin', array('%bin' => $this->bin))); + } } else { db_delete($this->bin) @@ -555,4 +566,25 @@ ->fetchField(); return empty($result); } + + /** + * Checks if $this->bin represents a valid cache table. + * + * This check is required to ensure that non-cache tables are not truncated + * accidentally when calling cache_clear_all(). + * + * @return boolean + */ + function isValidBin() { + if ($this->bin == 'cache' || substr($this->bin, 0, 6) == 'cache_') { + // Skip schema check for bins with standard table names. + return TRUE; + } + // These fields are required for any cache table. + $fields = array('cid', 'data', 'expire', 'created', 'serialized'); + // Load the table schema. + $schema = drupal_get_schema($this->bin); + // Confirm that all fields are present. + return isset($schema['fields']) && !array_diff($fields, array_keys($schema['fields'])); + } } diff -Naur drupal-7.14/includes/common.inc drupal-7.66/includes/common.inc --- drupal-7.14/includes/common.inc 2012-05-03 00:10:42.000000000 +0200 +++ drupal-7.66/includes/common.inc 2019-04-17 22:20:46.000000000 +0200 @@ -92,14 +92,20 @@ define('HTTP_REQUEST_TIMEOUT', -1); /** - * Constants defining cache granularity for blocks and renderable arrays. + * @defgroup block_caching Block Caching + * @{ + * Constants that define each block's caching state. * - * Modules specify the caching patterns for their blocks using binary - * combinations of these constants in their hook_block_info(): - * $block[delta]['cache'] = DRUPAL_CACHE_PER_ROLE | DRUPAL_CACHE_PER_PAGE; - * DRUPAL_CACHE_PER_ROLE is used as a default when no caching pattern is - * specified. Use DRUPAL_CACHE_CUSTOM to disable standard block cache and - * implement + * Modules specify how their blocks can be cached in their hook_block_info() + * implementations. Caching can be turned off (DRUPAL_NO_CACHE), managed by the + * module declaring the block (DRUPAL_CACHE_CUSTOM), or managed by the core + * Block module. If the Block module is managing the cache, you can specify that + * the block is the same for every page and user (DRUPAL_CACHE_GLOBAL), or that + * it can change depending on the page (DRUPAL_CACHE_PER_PAGE) or by user + * (DRUPAL_CACHE_PER_ROLE or DRUPAL_CACHE_PER_USER). Page and user settings can + * be combined with a bitwise-binary or operator; for example, + * DRUPAL_CACHE_PER_ROLE | DRUPAL_CACHE_PER_PAGE means that the block can change + * depending on the user role or page it is on. * * The block cache is cleared in cache_clear_all(), and uses the same clearing * policy than page cache (node, comment, user, taxonomy added or updated...). @@ -123,9 +129,8 @@ /** * The block is handling its own caching in its hook_block_view(). * - * From the perspective of the block cache system, this is equivalent to - * DRUPAL_NO_CACHE. Useful when time based expiration is needed or a site uses - * a node access which invalidates standard block cache. + * This setting is useful when time based expiration is needed or a site uses a + * node access which invalidates standard block cache. */ define('DRUPAL_CACHE_CUSTOM', -2); @@ -156,6 +161,10 @@ define('DRUPAL_CACHE_GLOBAL', 0x0008); /** + * @} End of "defgroup block_caching". + */ + +/** * Adds content to a specified region. * * @param $region @@ -199,7 +208,7 @@ } /** - * Gets the name of the currently active install profile. + * Gets the name of the currently active installation profile. * * When this function is called during Drupal's initial installation process, * the name of the profile that's about to be installed is stored in the global @@ -208,7 +217,7 @@ * variable_get() to determine what one is active. * * @return $profile - * The name of the install profile. + * The name of the installation profile. */ function drupal_get_profile() { global $install_state; @@ -272,7 +281,7 @@ /** * Adds output to the HEAD tag of the HTML page. * - * This function can be called as long the headers aren't sent. Pass no + * This function can be called as long as the headers aren't sent. Pass no * arguments (or NULL for both) to retrieve the currently stored elements. * * @param $data @@ -443,13 +452,13 @@ * The query string to split. * * @return - * An array of url decoded couples $param_name => $value. + * An array of URL decoded couples $param_name => $value. */ function drupal_get_query_array($query) { $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]) : ''; } } @@ -478,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)) { @@ -505,6 +514,12 @@ * previous request, that destination is returned. As such, a destination can * persist across multiple pages. * + * @return + * An associative array containing the key: + * - destination: The path provided via the destination query string or, if + * not available, the current path. + * + * @see current_path() * @see drupal_goto() */ function drupal_get_destination() { @@ -529,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. - * - * 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 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. * - * 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) { @@ -601,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']); } @@ -626,7 +637,7 @@ } /** - * Sends the user to a different Drupal page. + * Sends the user to a different page. * * This issues an on-site HTTP redirect. The function makes sure the redirected * URL is formatted correctly. @@ -647,20 +658,23 @@ * callback. * * @param $path - * A Drupal path or a full URL. + * (optional) A Drupal path or a full URL, which will be passed to url() to + * compute the redirect for the URL. * @param $options - * An associative array of additional URL options to pass to url(). + * (optional) An associative array of additional URL options to pass to url(). * @param $http_response_code - * Valid values for an actual "goto" as per RFC 2616 section 10.3 are: - * - 301 Moved Permanently (the recommended value for most redirects) - * - 302 Found (default in Drupal and PHP, sometimes used for spamming search - * engines) - * - 303 See Other - * - 304 Not Modified - * - 305 Use Proxy - * - 307 Temporary Redirect (alternative to "503 Site Down for Maintenance") - * Note: Other values are defined by RFC 2616, but are rarely used and poorly - * supported. + * (optional) The HTTP status code to use for the redirection, defaults to + * 302. The valid values for 3xx redirection status codes are defined in + * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3 RFC 2616 @endlink + * and the + * @link http://tools.ietf.org/html/draft-reschke-http-status-308-07 draft for the new HTTP status codes: @endlink + * - 301: Moved Permanently (the recommended value for most redirects). + * - 302: Found (default in Drupal and PHP, sometimes used for spamming search + * engines). + * - 303: See Other. + * - 304: Not Modified. + * - 305: Use Proxy. + * - 307: Temporary Redirect. * * @see drupal_get_destination() * @see url() @@ -675,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. @@ -740,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 @@ -765,8 +787,17 @@ * HTTP header names are case-insensitive (RFC 2616, section 4.2), so for * easy access the array keys are returned in lower case. * - data: A string containing the response body that was received. + * + * @see http_build_query() */ function drupal_http_request($url, array $options = array()) { + // Allow an alternate HTTP client library to replace Drupal's default + // implementation. + $override_function = variable_get('drupal_http_request_function', FALSE); + if (!empty($override_function) && function_exists($override_function)) { + return $override_function($url, $options); + } + $result = new stdClass(); // Parse the URL and make sure we can handle the schema. @@ -795,10 +826,53 @@ 'timeout' => 30.0, 'context' => NULL, ); + + // Merge the default headers. + $options['headers'] += array( + 'User-Agent' => 'Drupal (+http://drupal.org/)', + ); + // stream_socket_client() requires timeout to be a float. $options['timeout'] = (float) $options['timeout']; + // Use a proxy if one is defined and the host is not on the excluded list. + $proxy_server = variable_get('proxy_server', ''); + if ($proxy_server && _drupal_http_use_proxy($uri['host'])) { + // Set the scheme so we open a socket to the proxy server. + $uri['scheme'] = 'proxy'; + // Set the path to be the full URL. + $uri['path'] = $url; + // Since the URL is passed as the path, we won't use the parsed query. + unset($uri['query']); + + // Add in username and password to Proxy-Authorization header if needed. + if ($proxy_username = variable_get('proxy_username', '')) { + $proxy_password = variable_get('proxy_password', ''); + $options['headers']['Proxy-Authorization'] = 'Basic ' . base64_encode($proxy_username . (!empty($proxy_password) ? ":" . $proxy_password : '')); + } + // Some proxies reject requests with any User-Agent headers, while others + // require a specific one. + $proxy_user_agent = variable_get('proxy_user_agent', ''); + // The default value matches neither condition. + if ($proxy_user_agent === NULL) { + unset($options['headers']['User-Agent']); + } + elseif ($proxy_user_agent) { + $options['headers']['User-Agent'] = $proxy_user_agent; + } + } + switch ($uri['scheme']) { + case 'proxy': + // 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. + if (!isset($options['headers']['Host'])) { + $options['headers']['Host'] = $uri['host']; + $options['headers']['Host'] .= isset($uri['port']) && $uri['port'] != 80 ? ':' . $uri['port'] : ''; + } + break; + case 'http': case 'feed': $port = isset($uri['port']) ? $uri['port'] : 80; @@ -806,14 +880,20 @@ // 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: $result->error = 'invalid schema ' . $uri['scheme']; $result->code = -1003; @@ -850,11 +930,6 @@ $path .= '?' . $uri['query']; } - // Merge the default headers. - $options['headers'] += array( - 'User-Agent' => 'Drupal (+http://drupal.org/)', - ); - // Only add Content-Length if we actually have any content or if it is a POST // or PUT request. Some non-standard servers get confused by Content-Length in // at least HEAD/GET requests, and Squid always requires Content-Length in @@ -866,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 @@ -927,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(); @@ -998,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 @@ -1012,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; } @@ -1020,11 +1107,54 @@ } 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 + * TRUE if a proxy should be used for this host. + */ +function _drupal_http_use_proxy($host) { + $proxy_exceptions = variable_get('proxy_exceptions', array('localhost', '127.0.0.1')); + return !in_array(strtolower($host), $proxy_exceptions, TRUE); +} + /** * @} End of "HTTP handling". */ @@ -1059,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') { @@ -1099,7 +1229,8 @@ /** * Verifies the syntax of the given e-mail address. * - * Empty e-mail addresses are allowed. See RFC 2822 for details. + * This uses the + * @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink * * @param $mail * A string containing an e-mail address. @@ -1350,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 @@ -1420,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 ''; } @@ -1652,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 = ''; @@ -1667,7 +1803,7 @@ } if (isset($value['value']) && $value['value'] != '') { - $output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : check_plain($value['value'])) . '\n"; + $output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : (!empty($value['encoded']) ? $value['value'] : check_plain($value['value']))) . '\n"; } else { $output .= " />\n"; @@ -1698,7 +1834,7 @@ * $output = format_plural($update_count, * 'Changed the content type of 1 post from %old-type to %new-type.', * 'Changed the content type of @count posts from %old-type to %new-type.', - * array('%old-type' => $info->old_type, '%new-type' => $info->new_type))); + * array('%old-type' => $info->old_type, '%new-type' => $info->new_type)); * @endcode * * @param $count @@ -1874,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 @@ -2010,6 +2146,9 @@ /** * Format a username. * + * This is also the label callback implementation of + * callback_entity_info_label() for user_entity_info(). + * * By default, the passed-in object's 'name' property is used if it exists, or * else, the site-defined value for the 'anonymous' variable. However, a module * may override this by implementing hook_username_alter(&$name, $account). @@ -2041,8 +2180,9 @@ * alternative than url(). * * @param $path - * The internal path or external URL being linked to, such as "node/34" or - * "http://example.com/foo". A few notes: + * (optional) The internal path or external URL being linked to, such as + * "node/34" or "http://example.com/foo". The default value is equivalent to + * passing in ''. A few notes: * - If you provide a full URL, it will be considered an external URL. * - If you provide only the path (e.g. "node/34"), it will be * considered an internal link. In this case, it should be a system URL, @@ -2058,7 +2198,8 @@ * include them in $path, or use $options['query'] to let this function * URL encode them. * @param $options - * An associative array of additional options, with the following elements: + * (optional) An associative array of additional options, with the following + * elements: * - 'query': An array of query key/value-pairs (without any URL-encoding) to * append to the URL. * - 'fragment': A fragment identifier (named anchor) to append to the URL. @@ -2074,7 +2215,7 @@ * for the URL. If $options['language'] is omitted, the global $language_url * will be used. * - 'https': Whether this URL should point to a secure location. If not - * defined, the current scheme is used, so the user stays on http or https + * defined, the current scheme is used, so the user stays on HTTP or HTTPS * respectively. TRUE enforces HTTPS and FALSE enforces HTTP, but HTTPS can * only be enforced when the variable 'https' is set to TRUE. * - 'base_url': Only used internally, to modify the base URL when a language @@ -2107,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. @@ -2152,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. @@ -2179,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, '/'); } } @@ -2229,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); } /** @@ -2304,21 +2461,30 @@ /** * Formats an internal or external URL link as an HTML anchor tag. * - * This function correctly handles aliased paths, and adds an 'active' class + * This function correctly handles aliased paths and adds an 'active' class * attribute to links that point to the current page (for theming), so all * internal links output by modules should be generated by this function if * possible. * - * @param $text - * The link text for the anchor tag. - * @param $path + * However, for links enclosed in translatable text you should use t() and + * embed the HTML anchor tag directly in the translated string. For example: + * @code + * t('Visit the settings page', array('@url' => url('admin'))); + * @endcode + * This keeps the context of the link title ('settings' in the example) for + * translators. + * + * @param string $text + * The translated link text for the anchor tag. + * @param string $path * The internal path or external URL being linked to, such as "node/34" or * "http://example.com/foo". After the url() function is called to construct * the URL from $path and $options, the resulting URL is passed through * check_plain() before it is inserted into the HTML anchor tag, to ensure * well-formed HTML. See url() for more information and notes. * @param array $options - * An associative array of additional options, with the following elements: + * An associative array of additional options. Defaults to an empty array. It + * may contain the following elements. * - 'attributes': An associative array of HTML attributes to apply to the * anchor tag. If element 'class' is included, it must be an array; 'title' * must be a string; other elements are more flexible, as they just need @@ -2334,8 +2500,10 @@ * well as the path must match). This element is also used by url(). * - Additional $options elements used by the url() function. * - * @return + * @return string * An HTML string containing a link to the given path. + * + * @see url() */ function l($text, $path, array $options = array()) { global $language_url; @@ -2503,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? @@ -2518,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', '')); @@ -2547,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', '')); @@ -2611,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(); } @@ -2672,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 @@ -2693,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); + } } } @@ -2706,7 +2894,7 @@ * The name of the item for which the path is requested. * * @return - * The path to the requested item. + * The path to the requested item or an empty string if the item is not found. */ function drupal_get_path($type, $name) { return dirname(drupal_get_filename($type, $name)); @@ -2874,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)) { @@ -2909,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']) { @@ -3356,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; @@ -3581,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; } /** @@ -3608,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. @@ -3627,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* @@ -3652,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; } @@ -3676,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); } /** @@ -3712,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); @@ -3741,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]; } /** @@ -3776,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 @@ -3796,7 +4028,16 @@ // requested id. $_POST['ajax_html_ids'] contains the ids as they were // returned by this function, potentially with the appended counter, so // we parse that to reconstruct the $seen_ids array. - foreach ($_POST['ajax_html_ids'] as $seen_id) { + if (isset($_POST['ajax_html_ids'][0]) && strpos($_POST['ajax_html_ids'][0], ',') === FALSE) { + $ajax_html_ids = $_POST['ajax_html_ids']; + } + else { + // jquery.form.js may send the server a comma-separated string as the + // first element of an array (see http://drupal.org/node/1575060), so + // we need to convert it to an array in that case. + $ajax_html_ids = explode(',', $_POST['ajax_html_ids'][0]); + } + foreach ($ajax_html_ids as $seen_id) { // We rely on '--' being used solely for separating a base id from the // counter, which this function ensures when returning an id. $parts = explode('--', $seen_id, 2); @@ -3812,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(' ' => '-', '_' => '-', '[' => '-', ']' => '')); @@ -3922,7 +4166,8 @@ * actually needed. * * @param $data - * (optional) If given, the value depends on the $options parameter: + * (optional) If given, the value depends on the $options parameter, or + * $options['type'] if $options is passed as an associative array: * - 'file': Path to the file relative to base_path(). * - 'inline': The JavaScript code that should be placed in the given scope. * - 'external': The absolute path to an external JavaScript file that is not @@ -3995,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.14/modules/field/field.api.php drupal-7.66/modules/field/field.api.php --- drupal-7.14/modules/field/field.api.php 2012-05-03 00:10:42.000000000 +0200 +++ drupal-7.66/modules/field/field.api.php 2019-04-17 22:20:46.000000000 +0200 @@ -1,4 +1,8 @@ array( 'label' => t('Text field'), 'field types' => array('text'), @@ -765,6 +789,8 @@ 'multiple values' => FIELD_BEHAVIOR_DEFAULT, 'default value' => FIELD_BEHAVIOR_DEFAULT, ), + // As an advanced widget, force it to sink to the bottom of the choices. + 'weight' => 2, ), ); } @@ -869,7 +895,7 @@ '#type' => $instance['widget']['type'], '#default_value' => isset($items[$delta]) ? $items[$delta] : '', ); - return $element; + return array('value' => $element); } /** @@ -935,6 +961,38 @@ } /** + * Alters the widget properties of a field instance before it gets displayed. + * + * Note that instead of hook_field_widget_properties_alter(), which is called + * for all fields on all entity types, + * hook_field_widget_properties_ENTITY_TYPE_alter() may be used to alter widget + * properties for fields on a specific entity type only. + * + * This hook is called once per field per added or edit entity. If the result + * of the hook involves reading from the database, it is highly recommended to + * statically cache the information. + * + * @param $widget + * The instance's widget properties. + * @param $context + * An associative array containing: + * - entity_type: The entity type; e.g., 'node' or 'user'. + * - entity: The entity object. + * - field: The field that the widget belongs to. + * - instance: The instance of the field. + * + * @see hook_field_widget_properties_ENTITY_TYPE_alter() + */ +function hook_field_widget_properties_alter(&$widget, $context) { + // Change a widget's type according to the time of day. + $field = $context['field']; + if ($context['entity_type'] == 'node' && $field['field_name'] == 'field_foo') { + $time = date('H'); + $widget['type'] = $time < 12 ? 'widget_am' : 'widget_pm'; + } +} + +/** * Flag a field-level validation error. * * @param $element @@ -954,12 +1012,12 @@ * An associative array containing the current state of the form. */ function hook_field_widget_error($element, $error, $form, &$form_state) { - form_error($element['value'], $error['message']); + form_error($element, $error['message']); } /** - * @} End of "defgroup field_widget" + * @} End of "defgroup field_widget". */ @@ -1040,8 +1098,8 @@ * Perform alterations on Field API formatter types. * * @param $info - * Array of informations on formatter types exposed by - * hook_field_field_formatter_info() implementations. + * An array of information on formatter types exposed by + * hook_field_formatter_info() implementations. */ function hook_field_formatter_info_alter(&$info) { // Add a setting to a formatter type. @@ -1198,11 +1256,11 @@ } /** - * @} End of "defgroup field_formatter" + * @} End of "defgroup field_formatter". */ /** - * @ingroup field_attach + * @addtogroup field_attach * @{ */ @@ -1264,9 +1322,33 @@ * This hook is invoked after the field module has performed the operation. * * See field_attach_validate() for details and arguments. + * + * @param $entity_type + * The type of $entity; e.g., 'node' or 'user'. + * @param $entity + * The entity with fields to validate. + * @param array $errors + * The array of errors (keyed by field name, language code, and delta) that + * have already been reported for the entity. The function should add its + * errors to this array. Each error is an associative array with the following + * keys and values: + * - error: An error code (should be a string prefixed with the module name). + * - message: The human readable message to be displayed. */ function hook_field_attach_validate($entity_type, $entity, &$errors) { - // @todo Needs function body. + // Make sure any images in article nodes have an alt text. + if ($entity_type == 'node' && $entity->type == 'article' && !empty($entity->field_image)) { + foreach ($entity->field_image as $langcode => $items) { + foreach ($items as $delta => $item) { + if (!empty($item['fid']) && empty($item['alt'])) { + $errors['field_image'][$langcode][$delta][] = array( + 'error' => 'field_example_invalid', + 'message' => t('All images in articles need to have an alternative text set.'), + ); + } + } + } + } } /** @@ -1468,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 @@ -1489,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. @@ -1539,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. @@ -1554,11 +1640,11 @@ } /** - * @} End of "defgroup field_attach" + * @} End of "addtogroup field_attach". */ /** - * @ingroup field_storage + * @addtogroup field_storage * @{ */ @@ -1699,11 +1785,14 @@ * loaded. */ function hook_field_storage_load($entity_type, $entities, $age, $fields, $options) { - $field_info = field_info_field_by_ids(); $load_current = $age == FIELD_LOAD_CURRENT; foreach ($fields as $field_id => $ids) { - $field = $field_info[$field_id]; + // By the time this hook runs, the relevant field definitions have been + // populated and cached in FieldInfo, so calling field_info_field_by_id() + // on each field individually is more efficient than loading all fields in + // memory upfront with field_info_field_by_ids(). + $field = field_info_field_by_id($field_id); $field_name = $field['field_name']; $table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field); @@ -1808,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, @@ -2212,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 @@ -2224,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(); @@ -2239,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 @@ -2305,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 @@ -2319,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') { @@ -2327,38 +2434,6 @@ } /** - * Alters the widget properties of a field instance before it gets displayed. - * - * Note that instead of hook_field_widget_properties_alter(), which is called - * for all fields on all entity types, - * hook_field_widget_properties_ENTITY_TYPE_alter() may be used to alter widget - * properties for fields on a specific entity type only. - * - * This hook is called once per field per added or edit entity. If the result - * of the hook involves reading from the database, it is highly recommended to - * statically cache the information. - * - * @param $widget - * The instance's widget properties. - * @param $context - * An associative array containing: - * - entity_type: The entity type; e.g., 'node' or 'user'. - * - entity: The entity object. - * - field: The field that the widget belongs to. - * - instance: The instance of the field. - * - * @see hook_field_widget_properties_ENTITY_TYPE_alter() - */ -function hook_field_widget_properties_alter(&$widget, $context) { - // Change a widget's type according to the time of day. - $field = $context['field']; - if ($context['entity_type'] == 'node' && $field['field_name'] == 'field_foo') { - $time = date('H'); - $widget['type'] = $time < 12 ? 'widget_am' : 'widget_pm'; - } -} - -/** * Alters the widget properties of a field instance on a given entity type * before it gets displayed. * @@ -2380,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. @@ -2391,11 +2468,7 @@ } /** - * @} End of "ingroup field_storage" - */ - -/** - * @ingroup field_crud + * @addtogroup field_crud * @{ */ @@ -2501,7 +2574,7 @@ * * @param $instance * The instance as it is post-update. - * @param $prior_$instance + * @param $prior_instance * The instance as it was pre-update. */ function hook_field_update_instance($instance, $prior_instance) { @@ -2589,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); @@ -2606,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') @@ -2627,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); @@ -2644,7 +2723,7 @@ } /** - * @} End of "ingroup field_crud" + * @} End of "addtogroup field_crud". */ /** @@ -2666,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') { @@ -2675,5 +2756,5 @@ } /** - * @} End of "addtogroup hooks" + * @} End of "addtogroup hooks". */ diff -Naur drupal-7.14/modules/field/field.attach.inc drupal-7.66/modules/field/field.attach.inc --- drupal-7.14/modules/field/field.attach.inc 2012-05-03 00:10:42.000000000 +0200 +++ drupal-7.66/modules/field/field.attach.inc 2019-04-17 22:20:46.000000000 +0200 @@ -65,7 +65,7 @@ define('FIELD_STORAGE_INSERT', 'insert'); /** - * @} End of "defgroup field_storage" + * @} End of "defgroup field_storage". */ /** @@ -283,7 +283,6 @@ 'language' => NULL, ); $options += $default_options; - $field_info = field_info_field_by_ids(); $fields = array(); $grouped_instances = array(); @@ -307,7 +306,7 @@ foreach ($instances as $instance) { $field_id = $instance['field_id']; $field_name = $instance['field_name']; - $field = $field_info[$field_id]; + $field = field_info_field_by_id($field_id); $function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op; if (function_exists($function)) { // Add the field to the list of fields to invoke the hook on. @@ -319,7 +318,7 @@ // Unless a language suggestion is provided we iterate on all the // available languages. $available_languages = field_available_languages($entity_type, $field); - $language = !empty($options['language'][$id]) ? $options['language'][$id] : $options['language']; + $language = is_array($options['language']) && !empty($options['language'][$id]) ? $options['language'][$id] : $options['language']; $languages = _field_language_suggestion($available_languages, $language, $field_name); foreach ($languages as $langcode) { $grouped_items[$field_id][$langcode][$id] = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array(); @@ -555,16 +554,23 @@ * @param $langcode * The language the field values are going to be entered, if no language * is provided the default site language will be used. + * @param array $options + * An associative array of additional options. See _field_invoke() for + * details. * * @see field_form_get_state() * @see field_form_set_state() */ -function field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode = NULL) { +function field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode = NULL, $options = array()) { + // Validate $options since this is a new parameter added after Drupal 7 was + // released. + $options = is_array($options) ? $options : array(); + // Set #parents to 'top-level' by default. $form += array('#parents' => array()); // If no language is provided use the default site language. - $options = array('language' => field_valid_language($langcode)); + $options['language'] = field_valid_language($langcode); $form += (array) _field_invoke_default('form', $entity_type, $entity, $form, $form_state, $options); // Add custom weight handling. @@ -614,7 +620,6 @@ * non-deleted fields are operated on. */ function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $options = array()) { - $field_info = field_info_field_by_ids(); $load_current = $age == FIELD_LOAD_CURRENT; // Merge default options. @@ -692,7 +697,7 @@ } // Collect the storage backend if the field has not been loaded yet. if (!isset($skip_fields[$field_id])) { - $field = $field_info[$field_id]; + $field = field_info_field_by_id($field_id); $storages[$field['storage']['type']][$field_id][] = $load_current ? $id : $vid; } } @@ -709,7 +714,7 @@ _field_invoke_multiple('load', $entity_type, $queried_entities, $age, $null, $options); // Invoke hook_field_attach_load(): let other modules act on loading the - // entitiy. + // entity. module_invoke_all('field_attach_load', $entity_type, $queried_entities, $age, $options); // Build cache data. @@ -769,13 +774,21 @@ * If validation errors are found, a FieldValidationException is thrown. The * 'errors' property contains the array of errors, keyed by field name, * language and delta. + * @param array $options + * An associative array of additional options. See _field_invoke() for + * details. */ -function field_attach_validate($entity_type, $entity) { +function field_attach_validate($entity_type, $entity, $options = array()) { + // Validate $options since this is a new parameter added after Drupal 7 was + // released. + $options = is_array($options) ? $options : array(); + $errors = array(); // Check generic, field-type-agnostic errors first. - _field_invoke_default('validate', $entity_type, $entity, $errors); + $null = NULL; + _field_invoke_default('validate', $entity_type, $entity, $errors, $null, $options); // Check field-type specific errors. - _field_invoke('validate', $entity_type, $entity, $errors); + _field_invoke('validate', $entity_type, $entity, $errors, $null, $options); // Let other modules validate the entity. // Avoid module_invoke_all() to let $errors be taken by reference. @@ -817,14 +830,21 @@ * full form structure, or a sub-element of a larger form. * @param $form_state * An associative array containing the current state of the form. + * @param array $options + * An associative array of additional options. See _field_invoke() for + * details. */ -function field_attach_form_validate($entity_type, $entity, $form, &$form_state) { +function field_attach_form_validate($entity_type, $entity, $form, &$form_state, $options = array()) { + // Validate $options since this is a new parameter added after Drupal 7 was + // released. + $options = is_array($options) ? $options : array(); + // Extract field values from submitted values. _field_invoke_default('extract_form_values', $entity_type, $entity, $form, $form_state); // Perform field_level validation. try { - field_attach_validate($entity_type, $entity); + field_attach_validate($entity_type, $entity, $options); } catch (FieldValidationException $e) { // Pass field-level validation errors back to widgets for accurate error @@ -836,7 +856,7 @@ field_form_set_state($form['#parents'], $field_name, $langcode, $form_state, $field_state); } } - _field_invoke_default('form_errors', $entity_type, $entity, $form, $form_state); + _field_invoke_default('form_errors', $entity_type, $entity, $form, $form_state, $options); } } @@ -857,12 +877,19 @@ * full form structure, or a sub-element of a larger form. * @param $form_state * An associative array containing the current state of the form. + * @param array $options + * An associative array of additional options. See _field_invoke() for + * details. */ -function field_attach_submit($entity_type, $entity, $form, &$form_state) { +function field_attach_submit($entity_type, $entity, $form, &$form_state, $options = array()) { + // Validate $options since this is a new parameter added after Drupal 7 was + // released. + $options = is_array($options) ? $options : array(); + // Extract field values from submitted values. - _field_invoke_default('extract_form_values', $entity_type, $entity, $form, $form_state); + _field_invoke_default('extract_form_values', $entity_type, $entity, $form, $form_state, $options); - _field_invoke_default('submit', $entity_type, $entity, $form, $form_state); + _field_invoke_default('submit', $entity_type, $entity, $form, $form_state, $options); // Let other modules act on submitting the entity. // Avoid module_invoke_all() to let $form_state be taken by reference. @@ -949,6 +976,12 @@ /** * Save field data for an existing entity. * + * When calling this function outside an entity save operation be sure to + * clear caches for the entity: + * @code + * entity_get_controller($entity_type)->resetCache(array($entity_id)) + * @endcode + * * @param $entity_type * The type of $entity; e.g. 'node' or 'user'. * @param $entity @@ -1093,9 +1126,16 @@ * @param $langcode * (Optional) The language the field values are to be shown in. If no language * is provided the current language is used. + * @param array $options + * An associative array of additional options. See _field_invoke() for + * details. */ -function field_attach_prepare_view($entity_type, $entities, $view_mode, $langcode = NULL) { - $options = array('language' => array()); +function field_attach_prepare_view($entity_type, $entities, $view_mode, $langcode = NULL, $options = array()) { + // Validate $options since this is a new parameter added after Drupal 7 was + // released. + $options = is_array($options) ? $options : array(); + + $options['language'] = array(); // To ensure hooks are only run once per entity, only process items without // the _field_view_prepared flag. @@ -1167,14 +1207,21 @@ * @param $langcode * The language the field values are to be shown in. If no language is * provided the current language is used. + * @param array $options + * An associative array of additional options. See _field_invoke() for + * details. * @return * A renderable array for the field values. */ -function field_attach_view($entity_type, $entity, $view_mode, $langcode = NULL) { +function field_attach_view($entity_type, $entity, $view_mode, $langcode = NULL, $options = array()) { + // Validate $options since this is a new parameter added after Drupal 7 was + // released. + $options = is_array($options) ? $options : array(); + // Determine the actual language to display for each field, given the // languages available in the field data. $display_language = field_language($entity_type, $entity, NULL, $langcode); - $options = array('language' => $display_language); + $options['language'] = $display_language; // Invoke field_default_view(). $null = NULL; @@ -1365,5 +1412,5 @@ /** - * @} End of "defgroup field_attach" + * @} End of "defgroup field_attach". */ diff -Naur drupal-7.14/modules/field/field.crud.inc drupal-7.66/modules/field/field.crud.inc --- drupal-7.14/modules/field/field.crud.inc 2012-05-03 00:10:42.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); @@ -244,9 +244,11 @@ // $prior_field may no longer be right. module_load_install($field['module']); $schema = (array) module_invoke($field['module'], 'field_schema', $field); - $schema += array('columns' => array(), 'indexes' => array()); + $schema += array('columns' => array(), 'indexes' => array(), 'foreign keys' => array()); // 'columns' are hardcoded in the field type. $field['columns'] = $schema['columns']; + // 'foreign keys' are hardcoded in the field type. + $field['foreign keys'] = $schema['foreign keys']; // 'indexes' can be both hardcoded in the field type, and specified in the // incoming $field definition. $field += array( @@ -286,7 +288,7 @@ drupal_write_record('field_config', $field, $primary_key); // Clear caches - field_cache_clear(TRUE); + field_cache_clear(); // Invoke external hooks after the cache is cleared for API consistency. module_invoke_all('field_update_field', $field, $prior_field, $has_data); @@ -319,7 +321,11 @@ * Reads in fields that match an array of conditions. * * @param array $params - * An array of conditions to match against. + * An array of conditions to match against. Keys are columns from the + * 'field_config' table, values are conditions to match. Additionally, + * conditions on the 'entity_type' and 'bundle' columns from the + * 'field_config_instance' table are supported (select fields having an + * instance on a given bundle). * @param array $include_additional * The default behavior of this function is to not return fields that * are inactive or have been deleted. Setting @@ -337,8 +343,21 @@ // Turn the conditions into a query. foreach ($params as $key => $value) { + // Allow filtering on the 'entity_type' and 'bundle' columns of the + // field_config_instance table. + if ($key == 'entity_type' || $key == 'bundle') { + if (empty($fci_join)) { + $fci_join = $query->join('field_config_instance', 'fci', 'fc.id = fci.field_id'); + } + $key = 'fci.' . $key; + } + else { + $key = 'fc.' . $key; + } + $query->condition($key, $value); } + if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) { $query ->condition('fc.active', 1) @@ -411,7 +430,7 @@ ->execute(); // Clear the cache. - field_cache_clear(TRUE); + field_cache_clear(); module_invoke_all('field_delete_field', $field); } @@ -505,17 +524,30 @@ * Updates an instance of a field. * * @param $instance - * An associative array representing an instance structure. The required - * keys and values are: + * An associative array representing an instance structure. The following + * required array elements specify which field instance is being updated: * - entity_type: The type of the entity the field is attached to. * - bundle: The bundle this field belongs to. * - field_name: The name of an existing field. - * Read-only_id properties are assigned automatically. Any other - * properties specified in $instance overwrite the existing values for - * the instance. + * The other array elements represent properties of the instance, and all + * properties must be specified or their default values will be used (except + * internal-use properties, which are assigned automatically). To avoid + * losing the previously stored properties of the instance when making a + * change, first load the instance with field_info_instance(), then override + * the values you want to override, and finally save using this function. + * Example: + * @code + * // Fetch an instance info array. + * $instance_info = field_info_instance($entity_type, $field_name, $bundle_name); + * // Change a single property in the instance definition. + * $instance_info['required'] = TRUE; + * // Write the changed definition back. + * field_update_instance($instance_info); + * @endcode * * @throws FieldException * + * @see field_info_instance() * @see field_create_instance() */ function field_update_instance($instance) { diff -Naur drupal-7.14/modules/field/field.form.inc drupal-7.66/modules/field/field.form.inc --- drupal-7.14/modules/field/field.form.inc 2012-05-03 00:10:42.000000000 +0200 +++ drupal-7.66/modules/field/field.form.inc 2019-04-17 22:20:46.000000000 +0200 @@ -6,7 +6,38 @@ */ /** - * Create a separate form element for each field. + * Creates a form element for a field and can populate it with a default value. + * + * If the form element is not associated with an entity (i.e., $entity is NULL) + * field_get_default_value will be called to supply the default value for the + * field. Also allows other modules to alter the form element by implementing + * their own hooks. + * + * @param $entity_type + * The type of entity (for example 'node' or 'user') that the field belongs + * to. + * @param $entity + * The entity object that the field belongs to. This may be NULL if creating a + * form element with a default value. + * @param $field + * An array representing the field whose editing element is being created. + * @param $instance + * An array representing the structure for $field in its current context. + * @param $langcode + * The language associated with the field. + * @param $items + * An array of the field values. When creating a new entity this may be NULL + * or an empty array to use default values. + * @param $form + * An array representing the form that the editing element will be attached + * to. + * @param $form_state + * An array containing the current state of the form. + * @param $get_delta + * Used to get only a specific delta value of a multiple value field. + * + * @return + * The form element array created for this field. */ function field_default_form($entity_type, $entity, $field, $instance, $langcode, $items, &$form, &$form_state, $get_delta = NULL) { // This could be called with no entity, as when a UI module creates a @@ -37,90 +68,86 @@ // Collect widget elements. $elements = array(); - if (field_access('edit', $field, $entity_type, $entity)) { - // Store field information in $form_state. - if (!field_form_get_state($parents, $field_name, $langcode, $form_state)) { - $field_state = array( - 'field' => $field, - 'instance' => $instance, - 'items_count' => count($items), - 'array_parents' => array(), - 'errors' => array(), - ); - field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state); - } - // If field module handles multiple values for this form element, and we - // are displaying an individual element, process the multiple value form. - if (!isset($get_delta) && field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) { - // Store the entity in the form. - $form['#entity'] = $entity; - $elements = field_multiple_value_form($field, $instance, $langcode, $items, $form, $form_state); - } - // If the widget is handling multiple values (e.g Options), or if we are - // displaying an individual element, just get a single form element and - // make it the $delta value. - else { - $delta = isset($get_delta) ? $get_delta : 0; - $function = $instance['widget']['module'] . '_field_widget_form'; - if (function_exists($function)) { - $element = array( - '#entity' => $entity, - '#entity_type' => $instance['entity_type'], - '#bundle' => $instance['bundle'], - '#field_name' => $field_name, - '#language' => $langcode, - '#field_parents' => $parents, - '#columns' => array_keys($field['columns']), - '#title' => check_plain($instance['label']), - '#description' => field_filter_xss($instance['description']), - // Only the first widget should be required. - '#required' => $delta == 0 && $instance['required'], - '#delta' => $delta, + // Store field information in $form_state. + if (!field_form_get_state($parents, $field_name, $langcode, $form_state)) { + $field_state = array( + 'field' => $field, + 'instance' => $instance, + 'items_count' => count($items), + 'array_parents' => array(), + 'errors' => array(), + ); + field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state); + } + + // If field module handles multiple values for this form element, and we are + // displaying an individual element, process the multiple value form. + if (!isset($get_delta) && field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) { + // Store the entity in the form. + $form['#entity'] = $entity; + $elements = field_multiple_value_form($field, $instance, $langcode, $items, $form, $form_state); + } + // If the widget is handling multiple values (e.g Options), or if we are + // displaying an individual element, just get a single form element and make + // it the $delta value. + else { + $delta = isset($get_delta) ? $get_delta : 0; + $function = $instance['widget']['module'] . '_field_widget_form'; + if (function_exists($function)) { + $element = array( + '#entity' => $entity, + '#entity_type' => $instance['entity_type'], + '#bundle' => $instance['bundle'], + '#field_name' => $field_name, + '#language' => $langcode, + '#field_parents' => $parents, + '#columns' => array_keys($field['columns']), + '#title' => check_plain($instance['label']), + '#description' => field_filter_xss($instance['description']), + // Only the first widget should be required. + '#required' => $delta == 0 && $instance['required'], + '#delta' => $delta, + ); + if ($element = $function($form, $form_state, $field, $instance, $langcode, $items, $delta, $element)) { + // Allow modules to alter the field widget form element. + $context = array( + 'form' => $form, + 'field' => $field, + 'instance' => $instance, + 'langcode' => $langcode, + 'items' => $items, + 'delta' => $delta, ); - if ($element = $function($form, $form_state, $field, $instance, $langcode, $items, $delta, $element)) { - // Allow modules to alter the field widget form element. - $context = array( - 'form' => $form, - 'field' => $field, - 'instance' => $instance, - 'langcode' => $langcode, - 'items' => $items, - 'delta' => $delta, - ); - drupal_alter(array('field_widget_form', 'field_widget_' . $instance['widget']['type'] . '_form'), $element, $form_state, $context); + drupal_alter(array('field_widget_form', 'field_widget_' . $instance['widget']['type'] . '_form'), $element, $form_state, $context); - // If we're processing a specific delta value for a field where the - // field module handles multiples, set the delta in the result. - // For fields that handle their own processing, we can't make - // assumptions about how the field is structured, just merge in the - // returned element. - if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) { - $elements[$delta] = $element; - } - else { - $elements = $element; - } + // If we're processing a specific delta value for a field where the + // field module handles multiples, set the delta in the result. + // For fields that handle their own processing, we can't make + // assumptions about how the field is structured, just merge in the + // returned element. + if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) { + $elements[$delta] = $element; + } + else { + $elements = $element; } } } } - if ($elements) { - // Also aid in theming of field widgets by rendering a classified - // container. - $addition[$field_name] = array( - '#type' => 'container', - '#attributes' => array( - 'class' => array( - 'field-type-' . drupal_html_class($field['type']), - 'field-name-' . drupal_html_class($field_name), - 'field-widget-' . drupal_html_class($instance['widget']['type']), - ), + // Also aid in theming of field widgets by rendering a classified container. + $addition[$field_name] = array( + '#type' => 'container', + '#attributes' => array( + 'class' => array( + 'field-type-' . drupal_html_class($field['type']), + 'field-name-' . drupal_html_class($field_name), + 'field-widget-' . drupal_html_class($instance['widget']['type']), ), - '#weight' => $instance['widget']['weight'], - ); - } + ), + '#weight' => $instance['widget']['weight'], + ); // Populate the 'array_parents' information in $form_state['field'] after // the form is built, so that we catch changes in the form structure performed @@ -136,6 +163,7 @@ // when $langcode is unknown. '#language' => $langcode, $langcode => $elements, + '#access' => field_access('edit', $field, $entity_type, $entity), ); return $addition; @@ -281,7 +309,7 @@ $header = array( array( - 'data' => '", + 'data' => '", 'colspan' => 2, 'class' => array('field-label'), ), @@ -362,31 +390,33 @@ $field_state = field_form_get_state($form['#parents'], $field['field_name'], $langcode, $form_state); if (!empty($field_state['errors'])) { - $function = $instance['widget']['module'] . '_field_widget_error'; - $function_exists = function_exists($function); - - // Locate the correct element in the the form. + // Locate the correct element in the form. $element = drupal_array_get_nested_value($form_state['complete form'], $field_state['array_parents']); - - $multiple_widget = field_behaviors_widget('multiple values', $instance) != FIELD_BEHAVIOR_DEFAULT; - foreach ($field_state['errors'] as $delta => $delta_errors) { - // For multiple single-value widgets, pass errors by delta. - // For a multiple-value widget, all errors are passed to the main widget. - $error_element = $multiple_widget ? $element : $element[$delta]; - foreach ($delta_errors as $error) { - if ($function_exists) { - $function($error_element, $error, $form, $form_state); - } - else { - // Make sure that errors are reported (even incorrectly flagged) if - // the widget module fails to implement hook_field_widget_error(). - form_error($error_element, $error['error']); + // Only set errors if the element is accessible. + if (!isset($element['#access']) || $element['#access']) { + $function = $instance['widget']['module'] . '_field_widget_error'; + $function_exists = function_exists($function); + + $multiple_widget = field_behaviors_widget('multiple values', $instance) != FIELD_BEHAVIOR_DEFAULT; + foreach ($field_state['errors'] as $delta => $delta_errors) { + // For multiple single-value widgets, pass errors by delta. + // For a multiple-value widget, pass all errors to the main widget. + $error_element = $multiple_widget ? $element : $element[$delta]; + foreach ($delta_errors as $error) { + if ($function_exists) { + $function($error_element, $error, $form, $form_state); + } + else { + // Make sure that errors are reported (even incorrectly flagged) if + // the widget module fails to implement hook_field_widget_error(). + form_error($error_element, $error['message']); + } } } + // Reinitialize the errors list for the next submit. + $field_state['errors'] = array(); + field_form_set_state($form['#parents'], $field['field_name'], $langcode, $form_state, $field_state); } - // Reinitialize the errors list for the next submit. - $field_state['errors'] = array(); - field_form_set_state($form['#parents'], $field['field_name'], $langcode, $form_state, $field_state); } } diff -Naur drupal-7.14/modules/field/field.info drupal-7.66/modules/field/field.info --- drupal-7.14/modules/field/field.info 2012-05-03 00:25:55.000000000 +0200 +++ drupal-7.66/modules/field/field.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,13 +5,13 @@ core = 7.x files[] = field.module files[] = field.attach.inc +files[] = field.info.class.inc files[] = tests/field.test dependencies[] = field_sql_storage required = TRUE stylesheets[all][] = theme/field.css -; Information added by drupal.org packaging script on 2012-05-02 -version = "7.14" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1335997555" - +datestamp = "1555533576" diff -Naur drupal-7.14/modules/field/field.info.class.inc drupal-7.66/modules/field/field.info.class.inc --- drupal-7.14/modules/field/field.info.class.inc 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/modules/field/field.info.class.inc 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1,686 @@ +fieldMap = NULL; + + $this->fieldsById = array(); + $this->fieldIdsByName = array(); + $this->loadedAllFields = FALSE; + $this->unknownFields = array(); + + $this->bundleInstances = array(); + $this->loadedAllInstances = FALSE; + $this->emptyBundles = array(); + + $this->bundleExtraFields = array(); + + cache_clear_all('field_info:', 'cache_field', TRUE); + } + + /** + * Collects a lightweight map of fields across bundles. + * + * @return + * An array keyed by field name. Each value is an array with two entries: + * - type: The field type. + * - bundles: The bundles in which the field appears, as an array with + * entity types as keys and the array of bundle names as values. + */ + public function getFieldMap() { + // Read from the "static" cache. + if ($this->fieldMap !== NULL) { + return $this->fieldMap; + } + + // Read from persistent cache. + if ($cached = cache_get('field_info:field_map', 'cache_field')) { + $map = $cached->data; + + // Save in "static" cache. + $this->fieldMap = $map; + + return $map; + } + + $map = array(); + + $query = db_query('SELECT fc.type, fci.field_name, fci.entity_type, fci.bundle FROM {field_config_instance} fci INNER JOIN {field_config} fc ON fc.id = fci.field_id WHERE fc.active = 1 AND fc.storage_active = 1 AND fc.deleted = 0 AND fci.deleted = 0'); + foreach ($query as $row) { + $map[$row->field_name]['bundles'][$row->entity_type][] = $row->bundle; + $map[$row->field_name]['type'] = $row->type; + } + + // Save in "static" and persistent caches. + $this->fieldMap = $map; + if (lock_acquire('field_info:field_map')) { + cache_set('field_info:field_map', $map, 'cache_field'); + lock_release('field_info:field_map'); + } + + return $map; + } + + /** + * Returns all active fields, including deleted ones. + * + * @return + * An array of field definitions, keyed by field ID. + */ + public function getFields() { + // Read from the "static" cache. + if ($this->loadedAllFields) { + return $this->fieldsById; + } + + // Read from persistent cache. + if ($cached = cache_get('field_info:fields', 'cache_field')) { + $this->fieldsById = $cached->data; + } + else { + // Collect and prepare fields. + foreach (field_read_fields(array(), array('include_deleted' => TRUE)) as $field) { + $this->fieldsById[$field['id']] = $this->prepareField($field); + } + + // Store in persistent cache. + if (lock_acquire('field_info:fields')) { + cache_set('field_info:fields', $this->fieldsById, 'cache_field'); + lock_release('field_info:fields'); + } + } + + // Fill the name/ID map. + foreach ($this->fieldsById as $field) { + if (!$field['deleted']) { + $this->fieldIdsByName[$field['field_name']] = $field['id']; + } + } + + $this->loadedAllFields = TRUE; + + return $this->fieldsById; + } + + /** + * Retrieves all active, non-deleted instances definitions. + * + * @param $entity_type + * (optional) The entity type. + * + * @return + * If $entity_type is not set, all instances keyed by entity type and bundle + * name. If $entity_type is set, all instances for that entity type, keyed + * by bundle name. + */ + public function getInstances($entity_type = NULL) { + // If the full list is not present in "static" cache yet. + if (!$this->loadedAllInstances) { + + // Read from persistent cache. + if ($cached = cache_get('field_info:instances', 'cache_field')) { + $this->bundleInstances = $cached->data; + } + else { + // Collect and prepare instances. + + // We also need to populate the static field cache, since it will not + // be set by subsequent getBundleInstances() calls. + $this->getFields(); + + // Initialize empty arrays for all existing entity types and bundles. + // This is not strictly needed, but is done to preserve the behavior of + // field_info_instances() before http://drupal.org/node/1915646. + foreach (field_info_bundles() as $existing_entity_type => $bundles) { + foreach ($bundles as $bundle => $bundle_info) { + $this->bundleInstances[$existing_entity_type][$bundle] = array(); + } + } + + foreach (field_read_instances() as $instance) { + $field = $this->getField($instance['field_name']); + $instance = $this->prepareInstance($instance, $field['type']); + $this->bundleInstances[$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance; + } + + // Store in persistent cache. + if (lock_acquire('field_info:instances')) { + cache_set('field_info:instances', $this->bundleInstances, 'cache_field'); + lock_release('field_info:instances'); + } + } + + $this->loadedAllInstances = TRUE; + } + + if (isset($entity_type)) { + return isset($this->bundleInstances[$entity_type]) ? $this->bundleInstances[$entity_type] : array(); + } + else { + return $this->bundleInstances; + } + } + + /** + * Returns a field definition from a field name. + * + * This method only retrieves active, non-deleted fields. + * + * @param $field_name + * The field name. + * + * @return + * The field definition, or NULL if no field was found. + */ + public function getField($field_name) { + // Read from the "static" cache. + if (isset($this->fieldIdsByName[$field_name])) { + $field_id = $this->fieldIdsByName[$field_name]; + return $this->fieldsById[$field_id]; + } + if (isset($this->unknownFields[$field_name])) { + return; + } + + // Do not check the (large) persistent cache, but read the definition. + + // Cache miss: read from definition. + if ($field = field_read_field($field_name)) { + $field = $this->prepareField($field); + + // Save in the "static" cache. + $this->fieldsById[$field['id']] = $field; + $this->fieldIdsByName[$field['field_name']] = $field['id']; + + return $field; + } + else { + $this->unknownFields[$field_name] = TRUE; + } + } + + /** + * Returns a field definition from a field ID. + * + * This method only retrieves active fields, deleted or not. + * + * @param $field_id + * The field ID. + * + * @return + * The field definition, or NULL if no field was found. + */ + public function getFieldById($field_id) { + // Read from the "static" cache. + if (isset($this->fieldsById[$field_id])) { + return $this->fieldsById[$field_id]; + } + if (isset($this->unknownFields[$field_id])) { + return; + } + + // No persistent cache, fields are only persistently cached as part of a + // bundle. + + // Cache miss: read from definition. + if ($fields = field_read_fields(array('id' => $field_id), array('include_deleted' => TRUE))) { + $field = current($fields); + $field = $this->prepareField($field); + + // Store in the static cache. + $this->fieldsById[$field['id']] = $field; + if (!$field['deleted']) { + $this->fieldIdsByName[$field['field_name']] = $field['id']; + } + + return $field; + } + else { + $this->unknownFields[$field_id] = TRUE; + } + } + + /** + * Retrieves the instances for a bundle. + * + * The function also populates the corresponding field definitions in the + * "static" cache. + * + * @param $entity_type + * The entity type. + * @param $bundle + * The bundle name. + * + * @return + * The array of instance definitions, keyed by field name. + */ + public function getBundleInstances($entity_type, $bundle) { + // Read from the "static" cache. + if (isset($this->bundleInstances[$entity_type][$bundle])) { + return $this->bundleInstances[$entity_type][$bundle]; + } + if (isset($this->emptyBundles[$entity_type][$bundle])) { + return array(); + } + + // Read from the persistent cache. + if ($cached = cache_get("field_info:bundle:$entity_type:$bundle", 'cache_field')) { + $info = $cached->data; + + // Extract the field definitions and save them in the "static" cache. + foreach ($info['fields'] as $field) { + if (!isset($this->fieldsById[$field['id']])) { + $this->fieldsById[$field['id']] = $field; + if (!$field['deleted']) { + $this->fieldIdsByName[$field['field_name']] = $field['id']; + } + } + } + unset($info['fields']); + + // Store the instance definitions in the "static" cache'. Empty (or + // non-existent) bundles are stored separately, so that they do not + // pollute the global list returned by getInstances(). + if ($info['instances']) { + $this->bundleInstances[$entity_type][$bundle] = $info['instances']; + } + else { + $this->emptyBundles[$entity_type][$bundle] = TRUE; + } + + return $info['instances']; + } + + // Cache miss: collect from the definitions. + + $instances = array(); + + // Collect the fields in the bundle. + $params = array('entity_type' => $entity_type, 'bundle' => $bundle); + $fields = field_read_fields($params); + + // This iterates on non-deleted instances, so deleted fields are kept out of + // the persistent caches. + foreach (field_read_instances($params) as $instance) { + $field = $fields[$instance['field_name']]; + + $instance = $this->prepareInstance($instance, $field['type']); + $instances[$field['field_name']] = $instance; + + // If the field is not in our global "static" list yet, add it. + if (!isset($this->fieldsById[$field['id']])) { + $field = $this->prepareField($field); + + $this->fieldsById[$field['id']] = $field; + $this->fieldIdsByName[$field['field_name']] = $field['id']; + } + } + + // Store in the 'static' cache'. Empty (or non-existent) bundles are stored + // separately, so that they do not pollute the global list returned by + // getInstances(). + if ($instances) { + $this->bundleInstances[$entity_type][$bundle] = $instances; + } + else { + $this->emptyBundles[$entity_type][$bundle] = TRUE; + } + + // The persistent cache additionally contains the definitions of the fields + // involved in the bundle. + $cache = array( + 'instances' => $instances, + 'fields' => array() + ); + foreach ($instances as $instance) { + $cache['fields'][] = $this->fieldsById[$instance['field_id']]; + } + + if (lock_acquire("field_info:bundle:$entity_type:$bundle")) { + cache_set("field_info:bundle:$entity_type:$bundle", $cache, 'cache_field'); + lock_release("field_info:bundle:$entity_type:$bundle"); + } + + return $instances; + } + + /** + * Retrieves the "extra fields" for a bundle. + * + * @param $entity_type + * The entity type. + * @param $bundle + * The bundle name. + * + * @return + * The array of extra fields. + */ + public function getBundleExtraFields($entity_type, $bundle) { + // Read from the "static" cache. + if (isset($this->bundleExtraFields[$entity_type][$bundle])) { + return $this->bundleExtraFields[$entity_type][$bundle]; + } + + // Read from the persistent cache. + if ($cached = cache_get("field_info:bundle_extra:$entity_type:$bundle", 'cache_field')) { + $this->bundleExtraFields[$entity_type][$bundle] = $cached->data; + return $this->bundleExtraFields[$entity_type][$bundle]; + } + + // Cache miss: read from hook_field_extra_fields(). Note: given the current + // shape of the hook, we have no other way than collecting extra fields on + // all bundles. + $info = array(); + $extra = module_invoke_all('field_extra_fields'); + drupal_alter('field_extra_fields', $extra); + // Merge in saved settings. + if (isset($extra[$entity_type][$bundle])) { + $info = $this->prepareExtraFields($extra[$entity_type][$bundle], $entity_type, $bundle); + } + + // Store in the 'static' and persistent caches. + $this->bundleExtraFields[$entity_type][$bundle] = $info; + if (lock_acquire("field_info:bundle_extra:$entity_type:$bundle")) { + cache_set("field_info:bundle_extra:$entity_type:$bundle", $info, 'cache_field'); + lock_release("field_info:bundle_extra:$entity_type:$bundle"); + } + + return $this->bundleExtraFields[$entity_type][$bundle]; + } + + /** + * Prepares a field definition for the current run-time context. + * + * @param $field + * The raw field structure as read from the database. + * + * @return + * The field definition completed for the current runtime context. + */ + public function prepareField($field) { + // Make sure all expected field settings are present. + $field['settings'] += field_info_field_settings($field['type']); + $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']); + + // Add storage details. + $details = (array) module_invoke($field['storage']['module'], 'field_storage_details', $field); + drupal_alter('field_storage_details', $details, $field); + $field['storage']['details'] = $details; + + // Populate the list of bundles using the field. + $field['bundles'] = array(); + if (!$field['deleted']) { + $map = $this->getFieldMap(); + if (isset($map[$field['field_name']])) { + $field['bundles'] = $map[$field['field_name']]['bundles']; + } + } + + return $field; + } + + /** + * Prepares an instance definition for the current run-time context. + * + * @param $instance + * The raw instance structure as read from the database. + * @param $field_type + * The field type. + * + * @return + * The field instance array completed for the current runtime context. + */ + public function prepareInstance($instance, $field_type) { + // Make sure all expected instance settings are present. + $instance['settings'] += field_info_instance_settings($field_type); + + // Set a default value for the instance. + if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT && !isset($instance['default_value'])) { + $instance['default_value'] = NULL; + } + + // Prepare widget settings. + $instance['widget'] = $this->prepareInstanceWidget($instance['widget'], $field_type); + + // Prepare display settings. + foreach ($instance['display'] as $view_mode => $display) { + $instance['display'][$view_mode] = $this->prepareInstanceDisplay($display, $field_type); + } + + // Fall back to 'hidden' for view modes configured to use custom display + // settings, and for which the instance has no explicit settings. + $entity_info = entity_get_info($instance['entity_type']); + $view_modes = array_merge(array('default'), array_keys($entity_info['view modes'])); + $view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']); + foreach ($view_modes as $view_mode) { + if ($view_mode == 'default' || !empty($view_mode_settings[$view_mode]['custom_settings'])) { + if (!isset($instance['display'][$view_mode])) { + $instance['display'][$view_mode] = array( + 'type' => 'hidden', + 'label' => 'above', + 'settings' => array(), + 'weight' => 0, + ); + } + } + } + + return $instance; + } + + /** + * Prepares widget properties for the current run-time context. + * + * @param $widget + * Widget specifications as found in $instance['widget']. + * @param $field_type + * The field type. + * + * @return + * The widget properties completed for the current runtime context. + */ + public function prepareInstanceWidget($widget, $field_type) { + $field_type_info = field_info_field_types($field_type); + + // Fill in default values. + $widget += array( + 'type' => $field_type_info['default_widget'], + 'settings' => array(), + 'weight' => 0, + ); + + $widget_type_info = field_info_widget_types($widget['type']); + // Fall back to default formatter if formatter type is not available. + if (!$widget_type_info) { + $widget['type'] = $field_type_info['default_widget']; + $widget_type_info = field_info_widget_types($widget['type']); + } + $widget['module'] = $widget_type_info['module']; + // Fill in default settings for the widget. + $widget['settings'] += field_info_widget_settings($widget['type']); + + return $widget; + } + + /** + * Adapts display specifications to the current run-time context. + * + * @param $display + * Display specifications as found in $instance['display']['a_view_mode']. + * @param $field_type + * The field type. + * + * @return + * The display properties completed for the current runtime context. + */ + public function prepareInstanceDisplay($display, $field_type) { + $field_type_info = field_info_field_types($field_type); + + // Fill in default values. + $display += array( + 'label' => 'above', + 'settings' => array(), + 'weight' => 0, + ); + if (empty($display['type'])) { + $display['type'] = $field_type_info['default_formatter']; + } + if ($display['type'] != 'hidden') { + $formatter_type_info = field_info_formatter_types($display['type']); + // Fall back to default formatter if formatter type is not available. + if (!$formatter_type_info) { + $display['type'] = $field_type_info['default_formatter']; + $formatter_type_info = field_info_formatter_types($display['type']); + } + $display['module'] = $formatter_type_info['module']; + // Fill in default settings for the formatter. + $display['settings'] += field_info_formatter_settings($display['type']); + } + + return $display; + } + + /** + * Prepares 'extra fields' for the current run-time context. + * + * @param $extra_fields + * The array of extra fields, as collected in hook_field_extra_fields(). + * @param $entity_type + * The entity type. + * @param $bundle + * The bundle name. + * + * @return + * The list of extra fields completed for the current runtime context. + */ + public function prepareExtraFields($extra_fields, $entity_type, $bundle) { + $entity_type_info = entity_get_info($entity_type); + $bundle_settings = field_bundle_settings($entity_type, $bundle); + $extra_fields += array('form' => array(), 'display' => array()); + + $result = array(); + // Extra fields in forms. + foreach ($extra_fields['form'] as $name => $field_data) { + $settings = isset($bundle_settings['extra_fields']['form'][$name]) ? $bundle_settings['extra_fields']['form'][$name] : array(); + if (isset($settings['weight'])) { + $field_data['weight'] = $settings['weight']; + } + $result['form'][$name] = $field_data; + } + + // Extra fields in displayed entities. + $data = $extra_fields['display']; + foreach ($extra_fields['display'] as $name => $field_data) { + $settings = isset($bundle_settings['extra_fields']['display'][$name]) ? $bundle_settings['extra_fields']['display'][$name] : array(); + $view_modes = array_merge(array('default'), array_keys($entity_type_info['view modes'])); + foreach ($view_modes as $view_mode) { + if (isset($settings[$view_mode])) { + $field_data['display'][$view_mode] = $settings[$view_mode]; + } + else { + $field_data['display'][$view_mode] = array( + 'weight' => $field_data['weight'], + 'visible' => TRUE, + ); + } + } + unset($field_data['weight']); + $result['display'][$name] = $field_data; + } + + return $result; + } +} diff -Naur drupal-7.14/modules/field/field.info.inc drupal-7.66/modules/field/field.info.inc --- drupal-7.14/modules/field/field.info.inc 2012-05-03 00:10:42.000000000 +0200 +++ drupal-7.66/modules/field/field.info.inc 2019-04-17 22:20:46.000000000 +0200 @@ -6,6 +6,32 @@ */ /** + * Retrieves the FieldInfo object for the current request. + * + * @return FieldInfo + * An instance of the FieldInfo class. + */ +function _field_info_field_cache() { + // Use the advanced drupal_static() pattern, since this is called very often. + static $drupal_static_fast; + + if (!isset($drupal_static_fast)) { + $drupal_static_fast['field_info_field_cache'] = &drupal_static(__FUNCTION__); + } + $field_info = &$drupal_static_fast['field_info_field_cache']; + + if (!isset($field_info)) { + // @todo The registry should save the need for an explicit include, but not + // a couple upgrade tests (DisabledNodeTypeTestCase, + // FilterFormatUpgradePathTestCase...) break in a strange way without it. + include_once dirname(__FILE__) . '/field.info.class.inc'; + $field_info = new FieldInfo(); + } + + return $field_info; +} + +/** * @defgroup field_info Field Info API * @{ * Obtain information about Field API configuration. @@ -34,7 +60,50 @@ entity_info_cache_clear(); _field_info_collate_types(TRUE); - _field_info_collate_fields(TRUE); + _field_info_field_cache()->flush(); +} + +/** + * Collates all information on existing fields and instances. + * + * Deprecated. This function is kept to ensure backwards compatibility, but has + * a serious performance impact, and should be absolutely avoided. + * See http://drupal.org/node/1915646. + * + * Use the regular field_info_*() API functions to access the information, or + * field_info_cache_clear() to clear the cached data. + */ +function _field_info_collate_fields($reset = FALSE) { + if ($reset) { + _field_info_field_cache()->flush(); + return; + } + + $cache = _field_info_field_cache(); + + // Collect fields, and build the array of IDs keyed by field_name. + $fields = $cache->getFields(); + $field_ids = array(); + foreach ($fields as $id => $field) { + if (!$field['deleted']) { + $field_ids[$field['field_name']] = $id; + } + } + + // Collect extra fields for all entity types. + $extra_fields = array(); + foreach (field_info_bundles() as $entity_type => $bundles) { + foreach ($bundles as $bundle => $info) { + $extra_fields[$entity_type][$bundle] = $cache->getBundleExtraFields($entity_type, $bundle); + } + } + + return array( + 'fields' => $fields, + 'field_ids' => $field_ids, + 'instances' => $cache->getInstances(), + 'extra_fields' => $extra_fields, + ); } /** @@ -54,8 +123,8 @@ * the field type. * - 'widget types': Array of hook_field_widget_info() results, keyed by * widget_type. Each element has the following components: label, field - * types, settings, and behaviors from hook_field_widget_info(), as well - * as module, giving the module that exposes the widget type. + * types, settings, weight, and behaviors from hook_field_widget_info(), + * as well as module, giving the module that exposes the widget type. * - 'formatter types': Array of hook_field_formatter_info() results, keyed by * formatter_type. Each element has the following components: label, field * types, and behaviors from hook_field_formatter_info(), as well as @@ -124,6 +193,7 @@ } } drupal_alter('field_widget_info', $info['widget types']); + uasort($info['widget types'], 'drupal_sort_weight'); // Populate formatter types. foreach (module_implements('field_formatter_info') as $module) { @@ -153,96 +223,11 @@ } drupal_alter('field_storage_info', $info['storage types']); - cache_set("field_info_types:$langcode", $info, 'cache_field'); - } - } - - return $info; -} - -/** - * Collates all information on existing fields and instances. - * - * @param $reset - * If TRUE, clear the cache. The information will be rebuilt from the - * database next time it is needed. Defaults to FALSE. - * - * @return - * If $reset is TRUE, nothing. - * If $reset is FALSE, an array containing the following elements: - * - fields: Array of existing fields, keyed by field ID. This element - * lists deleted and non-deleted fields, but not inactive ones. - * Each field has an additional element, 'bundles', which is an array - * of all non-deleted instances of that field. - * - field_ids: Array of field IDs, keyed by field name. This element - * only lists non-deleted, active fields. - * - instances: Array of existing instances, keyed by entity type, bundle - * name and field name. This element only lists non-deleted instances - * whose field is active. - */ -function _field_info_collate_fields($reset = FALSE) { - static $info; - - if ($reset) { - $info = NULL; - cache_clear_all('field_info_fields', 'cache_field'); - return; - } - - if (!isset($info)) { - if ($cached = cache_get('field_info_fields', 'cache_field')) { - $info = $cached->data; - } - else { - $definitions = array( - 'field_ids' => field_read_fields(array(), array('include_deleted' => 1)), - 'instances' => field_read_instances(), - ); - - // Populate 'fields' with all fields, keyed by ID. - $info['fields'] = array(); - foreach ($definitions['field_ids'] as $key => $field) { - $info['fields'][$key] = $definitions['field_ids'][$key] = _field_info_prepare_field($field); + // 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"); } - - // Build an array of field IDs for non-deleted fields, keyed by name. - $info['field_ids'] = array(); - foreach ($info['fields'] as $key => $field) { - if (!$field['deleted']) { - $info['field_ids'][$field['field_name']] = $key; - } - } - - // Populate 'instances'. Only non-deleted instances are considered. - $info['instances'] = array(); - foreach (field_info_bundles() as $entity_type => $bundles) { - foreach ($bundles as $bundle => $bundle_info) { - $info['instances'][$entity_type][$bundle] = array(); - } - } - foreach ($definitions['instances'] as $instance) { - $field = $info['fields'][$instance['field_id']]; - $instance = _field_info_prepare_instance($instance, $field); - $info['instances'][$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance; - // Enrich field definitions with the list of bundles where they have - // instances. NOTE: Deleted fields in $info['field_ids'] are not - // enriched because all of their instances are deleted, too, and - // are thus not in $definitions['instances']. - $info['fields'][$instance['field_id']]['bundles'][$instance['entity_type']][] = $instance['bundle']; - } - - // Populate 'extra_fields'. - $extra = module_invoke_all('field_extra_fields'); - drupal_alter('field_extra_fields', $extra); - // Merge in saved settings. - foreach ($extra as $entity_type => $bundles) { - foreach ($bundles as $bundle => $extra_fields) { - $extra_fields = _field_info_prepare_extra_fields($extra_fields, $entity_type, $bundle); - $info['extra_fields'][$entity_type][$bundle] = $extra_fields; - } - } - - cache_set('field_info_fields', $info, 'cache_field'); } } @@ -252,190 +237,66 @@ /** * Prepares a field definition for the current run-time context. * - * Since the field was last saved or updated, new field settings can be - * expected. + * The functionality has moved to the FieldInfo class. This function is kept as + * a backwards-compatibility layer. See http://drupal.org/node/1915646. * - * @param $field - * The raw field structure as read from the database. + * @see FieldInfo::prepareField() */ function _field_info_prepare_field($field) { - // Make sure all expected field settings are present. - $field['settings'] += field_info_field_settings($field['type']); - $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']); - - // Add storage details. - $details = (array) module_invoke($field['storage']['module'], 'field_storage_details', $field); - drupal_alter('field_storage_details', $details, $field, $instance); - $field['storage']['details'] = $details; - - // Initialize the 'bundles' list. - $field['bundles'] = array(); - - return $field; + $cache = _field_info_field_cache(); + return $cache->prepareField($field); } /** * Prepares an instance definition for the current run-time context. * - * Since the instance was last saved or updated, a number of things might have - * changed: widgets or formatters disabled, new settings expected, new view - * modes added... - * - * @param $instance - * The raw instance structure as read from the database. - * @param $field - * The field structure for the instance. + * The functionality has moved to the FieldInfo class. This function is kept as + * a backwards-compatibility layer. See http://drupal.org/node/1915646. * - * @return - * Field instance array. + * @see FieldInfo::prepareInstance() */ function _field_info_prepare_instance($instance, $field) { - // Make sure all expected instance settings are present. - $instance['settings'] += field_info_instance_settings($field['type']); - - // Set a default value for the instance. - if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT && !isset($instance['default_value'])) { - $instance['default_value'] = NULL; - } - - $instance['widget'] = _field_info_prepare_instance_widget($field, $instance['widget']); - - foreach ($instance['display'] as $view_mode => $display) { - $instance['display'][$view_mode] = _field_info_prepare_instance_display($field, $display); - } - - // Fallback to 'hidden' for view modes configured to use custom display - // settings, and for which the instance has no explicit settings. - $entity_info = entity_get_info($instance['entity_type']); - $view_modes = array_merge(array('default'), array_keys($entity_info['view modes'])); - $view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']); - foreach ($view_modes as $view_mode) { - if ($view_mode == 'default' || !empty($view_mode_settings[$view_mode]['custom_settings'])) { - if (!isset($instance['display'][$view_mode])) { - $instance['display'][$view_mode] = array( - 'type' => 'hidden', - 'label' => 'above', - 'settings' => array(), - 'weight' => 0, - ); - } - } - } - - return $instance; + $cache = _field_info_field_cache(); + return $cache->prepareInstance($instance, $field['type']); } /** * Adapts display specifications to the current run-time context. * - * @param $field - * The field structure for the instance. - * @param $display - * Display specifications as found in - * $instance['display']['some_view_mode']. + * The functionality has moved to the FieldInfo class. This function is kept as + * a backwards-compatibility layer. See http://drupal.org/node/1915646. + * + * @see FieldInfo::prepareInstanceDisplay() */ function _field_info_prepare_instance_display($field, $display) { - $field_type = field_info_field_types($field['type']); - - // Fill in default values. - $display += array( - 'label' => 'above', - 'type' => $field_type['default_formatter'], - 'settings' => array(), - 'weight' => 0, - ); - if ($display['type'] != 'hidden') { - $formatter_type = field_info_formatter_types($display['type']); - // Fallback to default formatter if formatter type is not available. - if (!$formatter_type) { - $display['type'] = $field_type['default_formatter']; - $formatter_type = field_info_formatter_types($display['type']); - } - $display['module'] = $formatter_type['module']; - // Fill in default settings for the formatter. - $display['settings'] += field_info_formatter_settings($display['type']); - } - - return $display; + $cache = _field_info_field_cache(); + return $cache->prepareInstanceDisplay($display, $field['type']); } /** * Prepares widget specifications for the current run-time context. * - * @param $field - * The field structure for the instance. - * @param $widget - * Widget specifications as found in $instance['widget']. + * The functionality has moved to the FieldInfo class. This function is kept as + * a backwards-compatibility layer. See http://drupal.org/node/1915646. + * + * @see FieldInfo::prepareInstanceWidget() */ function _field_info_prepare_instance_widget($field, $widget) { - $field_type = field_info_field_types($field['type']); - - // Fill in default values. - $widget += array( - 'type' => $field_type['default_widget'], - 'settings' => array(), - 'weight' => 0, - ); - - $widget_type = field_info_widget_types($widget['type']); - // Fallback to default formatter if formatter type is not available. - if (!$widget_type) { - $widget['type'] = $field_type['default_widget']; - $widget_type = field_info_widget_types($widget['type']); - } - $widget['module'] = $widget_type['module']; - // Fill in default settings for the widget. - $widget['settings'] += field_info_widget_settings($widget['type']); - - return $widget; + $cache = _field_info_field_cache(); + return $cache->prepareInstanceWidget($widget, $field['type']); } /** * Prepares 'extra fields' for the current run-time context. * - * @param $extra_fields - * The array of extra fields, as collected in hook_field_extra_fields(). - * @param $entity_type - * The entity type. - * @param $bundle - * The bundle name. + * The functionality has moved to the FieldInfo class. This function is kept as + * a backwards-compatibility layer. See http://drupal.org/node/1915646. + * + * @see FieldInfo::prepareExtraFields() */ function _field_info_prepare_extra_fields($extra_fields, $entity_type, $bundle) { - $entity_type_info = entity_get_info($entity_type); - $bundle_settings = field_bundle_settings($entity_type, $bundle); - $extra_fields += array('form' => array(), 'display' => array()); - - $result = array(); - // Extra fields in forms. - foreach ($extra_fields['form'] as $name => $field_data) { - $settings = isset($bundle_settings['extra_fields']['form'][$name]) ? $bundle_settings['extra_fields']['form'][$name] : array(); - if (isset($settings['weight'])) { - $field_data['weight'] = $settings['weight']; - } - $result['form'][$name] = $field_data; - } - - // Extra fields in displayed entities. - $data = $extra_fields['display']; - foreach ($extra_fields['display'] as $name => $field_data) { - $settings = isset($bundle_settings['extra_fields']['display'][$name]) ? $bundle_settings['extra_fields']['display'][$name] : array(); - $view_modes = array_merge(array('default'), array_keys($entity_type_info['view modes'])); - foreach ($view_modes as $view_mode) { - if (isset($settings[$view_mode])) { - $field_data['display'][$view_mode] = $settings[$view_mode]; - } - else { - $field_data['display'][$view_mode] = array( - 'weight' => $field_data['weight'], - 'visible' => TRUE, - ); - } - } - unset($field_data['weight']); - $result['display'][$name] = $field_data; - } - - return $result; + $cache = _field_info_field_cache(); + return $cache->prepareExtraFields($extra_fields, $entity_type, $bundle); } /** @@ -583,21 +444,61 @@ } /** + * Returns a lightweight map of fields across bundles. + * + * The function only returns active, non deleted fields. + * + * @return + * An array keyed by field name. Each value is an array with two entries: + * - type: The field type. + * - bundles: The bundles in which the field appears, as an array with entity + * types as keys and the array of bundle names as values. + * Example: + * @code + * array( + * 'body' => array( + * 'bundles' => array( + * 'node' => array('page', 'article'), + * ), + * 'type' => 'text_with_summary', + * ), + * ); + * @endcode + */ +function field_info_field_map() { + $cache = _field_info_field_cache(); + return $cache->getFieldMap(); +} + +/** * Returns all field definitions. * + * Use of this function should be avoided when possible, since it loads and + * statically caches a potentially large array of information. Use + * field_info_field_map() instead. + * + * When iterating over the fields present in a given bundle after a call to + * field_info_instances($entity_type, $bundle), it is recommended to use + * field_info_field() on each individual field instead. + * * @return * An array of field definitions, keyed by field name. Each field has an * additional property, 'bundles', which is an array of all the bundles to * which this field belongs keyed by entity type. + * + * @see field_info_field_map() */ function field_info_fields() { + $cache = _field_info_field_cache(); + $info = $cache->getFields(); + $fields = array(); - $info = _field_info_collate_fields(); - foreach ($info['fields'] as $key => $field) { + foreach ($info as $key => $field) { if (!$field['deleted']) { $fields[$field['field_name']] = $field; } } + return $fields; } @@ -613,15 +514,14 @@ * @return * The field array, as returned by field_read_fields(), with an * additional element 'bundles', whose value is an array of all the bundles - * this field belongs to keyed by entity type. + * this field belongs to keyed by entity type. NULL if the field was not + * found. * * @see field_info_field_by_id() */ function field_info_field($field_name) { - $info = _field_info_collate_fields(); - if (isset($info['field_ids'][$field_name])) { - return $info['fields'][$info['field_ids'][$field_name]]; - } + $cache = _field_info_field_cache(); + return $cache->getField($field_name); } /** @@ -639,17 +539,19 @@ * @see field_info_field() */ function field_info_field_by_id($field_id) { - $info = _field_info_collate_fields(); - if (isset($info['fields'][$field_id])) { - return $info['fields'][$field_id]; - } + $cache = _field_info_field_cache(); + return $cache->getFieldById($field_id); } /** * Returns the same data as field_info_field_by_id() for every field. * - * This function is typically used when handling all fields of some entities - * to avoid thousands of calls to field_info_field_by_id(). + * Use of this function should be avoided when possible, since it loads and + * statically caches a potentially large array of information. + * + * When iterating over the fields present in a given bundle after a call to + * field_info_instances($entity_type, $bundle), it is recommended to use + * field_info_field() on each individual field instead. * * @return * An array, each key is a field ID and the values are field arrays as @@ -660,52 +562,73 @@ * @see field_info_field_by_id() */ function field_info_field_by_ids() { - $info = _field_info_collate_fields(); - return $info['fields']; + $cache = _field_info_field_cache(); + return $cache->getFields(); } /** * Retrieves information about field instances. * + * Use of this function to retrieve instances across separate bundles (i.e. + * when the $bundle parameter is NULL) should be avoided when possible, since + * it loads and statically caches a potentially large array of information. Use + * field_info_field_map() instead. + * + * When retrieving the instances of a specific bundle (i.e. when both + * $entity_type and $bundle_name are provided), the function also populates a + * static cache with the corresponding field definitions, allowing fast + * retrieval of field_info_field() later in the request. + * * @param $entity_type - * The entity type for which to return instances. + * (optional) The entity type for which to return instances. * @param $bundle_name - * The bundle name for which to return instances. + * (optional) The bundle name for which to return instances. If $entity_type + * is NULL, the $bundle_name parameter is ignored. * * @return * If $entity_type is not set, return all instances keyed by entity type and * bundle name. If $entity_type is set, return all instances for that entity * type, keyed by bundle name. If $entity_type and $bundle_name are set, return * all instances for that bundle. + * + * @see field_info_field_map() */ function field_info_instances($entity_type = NULL, $bundle_name = NULL) { - $info = _field_info_collate_fields(); + $cache = _field_info_field_cache(); - if (isset($entity_type) && isset($bundle_name)) { - return isset($info['instances'][$entity_type][$bundle_name]) ? $info['instances'][$entity_type][$bundle_name] : array(); + if (!isset($entity_type)) { + return $cache->getInstances(); } - elseif (isset($entity_type)) { - return isset($info['instances'][$entity_type]) ? $info['instances'][$entity_type] : array(); - } - else { - return $info['instances']; + if (!isset($bundle_name)) { + return $cache->getInstances($entity_type); } + + return $cache->getBundleInstances($entity_type, $bundle_name); } /** * Returns an array of instance data for a specific field and bundle. * + * The function populates a static cache with all fields and instances used in + * the bundle, allowing fast retrieval of field_info_field() or + * field_info_instance() later in the request. + * * @param $entity_type * The entity type for the instance. * @param $field_name * The field name for the instance. * @param $bundle_name * The bundle name for the instance. + * + * @return + * An associative array of instance data for the specific field and bundle; + * NULL if the instance does not exist. */ function field_info_instance($entity_type, $field_name, $bundle_name) { - $info = _field_info_collate_fields(); - if (isset($info['instances'][$entity_type][$bundle_name][$field_name])) { - return $info['instances'][$entity_type][$bundle_name][$field_name]; + $cache = _field_info_field_cache(); + $info = $cache->getBundleInstances($entity_type, $bundle_name); + if (isset($info[$field_name])) { + return $info[$field_name]; } } @@ -763,11 +686,10 @@ * The array of pseudo-field elements in the bundle. */ function field_info_extra_fields($entity_type, $bundle, $context) { - $info = _field_info_collate_fields(); - if (isset($info['extra_fields'][$entity_type][$bundle][$context])) { - return $info['extra_fields'][$entity_type][$bundle][$context]; - } - return array(); + $cache = _field_info_field_cache(); + $info = $cache->getBundleExtraFields($entity_type, $bundle); + + return isset($info[$context]) ? $info[$context] : array(); } /** @@ -890,5 +812,5 @@ } /** - * @} End of "defgroup field_info" + * @} End of "defgroup field_info". */ diff -Naur drupal-7.14/modules/field/field.install drupal-7.66/modules/field/field.install --- drupal-7.14/modules/field/field.install 2012-05-03 00:10:42.000000000 +0200 +++ drupal-7.66/modules/field/field.install 2019-04-17 22:20:46.000000000 +0200 @@ -128,7 +128,7 @@ 'not null' => TRUE, 'default' => '' ), - 'entity_type' => array( + 'entity_type' => array( 'type' => 'varchar', 'length' => 32, 'not null' => TRUE, @@ -162,6 +162,7 @@ ), ); $schema['cache_field'] = drupal_get_schema_unprocessed('system', 'cache'); + $schema['cache_field']['description'] = 'Cache table for the Field module to store already built field information.'; return $schema; } @@ -172,7 +173,7 @@ * This function can be used for databases whose schema is at field module * version 7000 or higher. * - * @ingroup update-api-6.x-to-7.x + * @ingroup update_api */ function _update_7000_field_create_field(&$field) { // Merge in default values.` @@ -253,7 +254,7 @@ * @param $field_name * The field name to delete. * - * @ingroup update-api-6.x-to-7.x + * @ingroup update_api */ function _update_7000_field_delete_field($field_name) { $table_name = 'field_data_' . $field_name; @@ -284,7 +285,7 @@ * * This function is valid for a database schema version 7000. * - * @ingroup update-api-6.x-to-7.x + * @ingroup update_api */ function _update_7000_field_delete_instance($field_name, $entity_type, $bundle) { // Delete field instance configuration data. @@ -322,6 +323,8 @@ * @return * An array of fields matching $conditions, keyed by the property specified * by the $key parameter. + * + * @ingroup update_api */ function _update_7000_field_read_fields(array $conditions = array(), $key = 'id') { $fields = array(); @@ -356,7 +359,7 @@ * This function can be used for databases whose schema is at field module * version 7000 or higher. * - * @ingroup update-api-6.x-to-7.x + * @ingroup update_api */ function _update_7000_field_create_instance($field, &$instance) { // Merge in defaults. @@ -434,7 +437,7 @@ } /** - * @} End of "addtogroup updates-6.x-to-7.x" + * @} End of "addtogroup updates-6.x-to-7.x". */ /** @@ -458,5 +461,33 @@ } /** - * @} End of "addtogroup updates-7.x-extra" + * Add the FieldInfo class to the class registry. + */ +function field_update_7003() { + // Empty update to force a rebuild of the registry. +} + +/** + * Grant the new "administer fields" permission to trusted users. + */ +function field_update_7004() { + // Assign the permission to anyone that already has a trusted core permission + // that would have previously let them administer fields on an entity type. + $rids = array(); + $permissions = array( + 'administer site configuration', + 'administer content types', + 'administer users', + ); + foreach ($permissions as $permission) { + $rids = array_merge($rids, array_keys(user_roles(FALSE, $permission))); + } + $rids = array_unique($rids); + foreach ($rids as $rid) { + _update_7000_user_role_grant_permissions($rid, array('administer fields'), 'field'); + } +} + +/** + * @} End of "addtogroup updates-7.x-extra". */ diff -Naur drupal-7.14/modules/field/field.module drupal-7.66/modules/field/field.module --- drupal-7.14/modules/field/field.module 2012-05-03 00:10:42.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 @@ -376,7 +380,7 @@ } if ($non_deleted) { if (module_exists('field_ui')) { - $explanation = t('Field type(s) in use - see !link', array('!link' => l(t('Field list'), 'admin/reports/fields'))); + $explanation = t('Field type(s) in use - see Field list', array('@fields-page' => url('admin/reports/fields'))); } else { $explanation = t('Fields type(s) in use'); @@ -423,13 +427,9 @@ function field_sync_field_status() { // Refresh the 'active' and 'storage_active' columns according to the current // set of enabled modules. - $all_modules = system_rebuild_module_data(); - $modules = array(); - foreach ($all_modules as $module_name => $module) { - if ($module->status) { - $modules[] = $module_name; - field_associate_fields($module_name); - } + $modules = module_list(); + foreach ($modules as $module_name) { + field_associate_fields($module_name); } db_update('field_config') ->fields(array('active' => 0)) @@ -555,51 +555,33 @@ /** * Gets or sets administratively defined bundle settings. * - * For each bundle, settings are provided as a nested array with the following - * structure: - * @code - * array( - * 'view_modes' => array( - * // One sub-array per view mode for the entity type: - * 'full' => array( - * 'custom_display' => Whether the view mode uses custom display - * settings or settings of the 'default' mode, - * ), - * 'teaser' => ... - * ), - * 'extra_fields' => array( - * 'form' => array( - * // One sub-array per pseudo-field in displayed entities: - * 'extra_field_1' => array( - * 'weight' => The weight of the pseudo-field, - * ), - * 'extra_field_2' => ... - * ), - * 'display' => array( - * // One sub-array per pseudo-field in displayed entities: - * 'extra_field_1' => array( - * // One sub-array per view mode for the entity type, including - * // the 'default' mode: - * 'default' => array( - * 'weight' => The weight of the pseudo-field, - * 'visible' => TRUE if the pseudo-field is visible, FALSE if hidden, - * ), - * 'full' => ... - * ), - * 'extra_field_2' => ... - * ), - * ), - * ); - * @endcode - * - * @param $entity_type + * @param string $entity_type * The type of $entity; e.g., 'node' or 'user'. - * @param $bundle + * @param string $bundle * The bundle name. - * @param $settings - * (optional) The settings to store. + * @param array|null $settings + * (optional) The settings to store, an associative array with the following + * elements: + * - view_modes: An associative array keyed by view mode, with the following + * key/value pairs: + * - custom_settings: Boolean specifying whether the view mode uses a + * dedicated set of display options (TRUE), or the 'default' options + * (FALSE). Defaults to FALSE. + * - extra_fields: An associative array containing the form and display + * settings for extra fields (also known as pseudo-fields): + * - form: An associative array whose keys are the names of extra fields, + * and whose values are associative arrays with the following elements: + * - weight: The weight of the extra field, determining its position on an + * entity form. + * - display: An associative array whose keys are the names of extra fields, + * and whose values are associative arrays keyed by the name of view + * modes. This array must include an item for the 'default' view mode. + * Each view mode sub-array contains the following elements: + * - weight: The weight of the extra field, determining its position when + * an entity is viewed. + * - visible: TRUE if the extra field is visible, FALSE otherwise. * - * @return + * @return array|null * If no $settings are passed, the current settings are returned. */ function field_bundle_settings($entity_type, $bundle, $settings = NULL) { @@ -841,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. @@ -895,7 +877,8 @@ if ($field = field_info_field($field_name)) { if (is_array($display)) { // When using custom display settings, fill in default values. - $display = _field_info_prepare_instance_display($field, $display); + $cache = _field_info_field_cache(); + $display = $cache->prepareInstanceDisplay($display, $field["type"]); } // Hook invocations are done through the _field_invoke() functions in @@ -926,6 +909,7 @@ 'entity' => $entity, 'view_mode' => '_custom', 'display' => $display, + 'language' => $langcode, ); drupal_alter('field_attach_view', $result, $context); @@ -968,16 +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'); + + 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' @@ -1112,7 +1110,7 @@ } } /** - * @} End of "defgroup field" + * @} End of "defgroup field". */ /** @@ -1215,7 +1213,7 @@ * Use element_validate_integer_positive() instead. * * @deprecated - * @see element_validate_number_positive() + * @see element_validate_integer_positive() */ function _element_validate_integer_positive($element, &$form_state) { element_validate_integer_positive($element, $form_state); diff -Naur drupal-7.14/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.14/modules/field/modules/field_sql_storage/field_sql_storage.info 2012-05-03 00:25:55.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 2012-05-02 -version = "7.14" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1335997555" - +datestamp = "1555533576" diff -Naur drupal-7.14/modules/field/modules/field_sql_storage/field_sql_storage.install drupal-7.66/modules/field/modules/field_sql_storage/field_sql_storage.install --- drupal-7.14/modules/field/modules/field_sql_storage/field_sql_storage.install 2012-05-03 00:10:42.000000000 +0200 +++ drupal-7.66/modules/field/modules/field_sql_storage/field_sql_storage.install 2019-04-17 22:20:46.000000000 +0200 @@ -30,7 +30,7 @@ * This function can be used for databases whose schema is at field module * version 7000 or higher. * - * @ingroup update-api-6.x-to-7.x + * @ingroup update_api */ function _update_7000_field_sql_storage_write($entity_type, $bundle, $entity_id, $revision_id, $field_name, $data) { $table_name = "field_data_{$field_name}"; @@ -211,5 +211,5 @@ } /** - * @} End of "addtogroup updates-6.x-to-7.x" + * @} End of "addtogroup updates-6.x-to-7.x". */ diff -Naur drupal-7.14/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.14/modules/field/modules/field_sql_storage/field_sql_storage.module 2012-05-03 00:10:42.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); + } } } @@ -188,7 +241,7 @@ foreach ($field['foreign keys'] as $specifier => $specification) { $real_name = _field_sql_storage_indexname($field['field_name'], $specifier); $current['foreign keys'][$real_name]['table'] = $specification['table']; - foreach ($specification['columns'] as $column => $referenced) { + foreach ($specification['columns'] as $column_name => $referenced) { $sql_storage_column = _field_sql_storage_columnname($field['field_name'], $column_name); $current['foreign keys'][$real_name]['columns'][$sql_storage_column] = $referenced; } @@ -289,7 +342,17 @@ $real_name = _field_sql_storage_indexname($field['field_name'], $name); $real_columns = array(); foreach ($columns as $column_name) { - $real_columns[] = _field_sql_storage_columnname($field['field_name'], $column_name); + // Indexes can be specified as either a column name or an array with + // column name and length. Allow for either case. + if (is_array($column_name)) { + $real_columns[] = array( + _field_sql_storage_columnname($field['field_name'], $column_name[0]), + $column_name[1], + ); + } + else { + $real_columns[] = _field_sql_storage_columnname($field['field_name'], $column_name); + } } db_add_index($table, $real_name, $real_columns); db_add_index($revision_table, $real_name, $real_columns); @@ -324,11 +387,14 @@ * Implements hook_field_storage_load(). */ function field_sql_storage_field_storage_load($entity_type, $entities, $age, $fields, $options) { - $field_info = field_info_field_by_ids(); $load_current = $age == FIELD_LOAD_CURRENT; foreach ($fields as $field_id => $ids) { - $field = $field_info[$field_id]; + // By the time this hook runs, the relevant field definitions have been + // populated and cached in FieldInfo, so calling field_info_field_by_id() + // on each field individually is more efficient than loading all fields in + // memory upfront with field_info_field_by_ids(). + $field = field_info_field_by_id($field_id); $field_name = $field['field_name']; $table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field); @@ -419,7 +485,7 @@ $items = (array) $entity->{$field_name}[$langcode]; $delta_count = 0; foreach ($items as $delta => $item) { - // We now know we have someting to insert. + // We now know we have something to insert. $do_insert = TRUE; $record = array( 'entity_type' => $entity_type, @@ -501,18 +567,27 @@ $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); - $select_query->addTag('entity_field_access'); + // 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. + if (!isset($query->tags['DANGEROUS_ACCESS_CHECK_OPT_OUT'])) { + $select_query->addTag('entity_field_access'); + } $select_query->addMetaData('base_table', $tablename); $select_query->fields($table_alias, array('entity_type', 'entity_id', 'revision_id', 'bundle')); $field_base_table = $table_alias; diff -Naur drupal-7.14/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.14/modules/field/modules/field_sql_storage/field_sql_storage.test 2012-05-03 00:10:42.000000000 +0200 +++ drupal-7.66/modules/field/modules/field_sql_storage/field_sql_storage.test 2019-04-17 22:20:46.000000000 +0200 @@ -126,7 +126,7 @@ $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); foreach ($values as $delta => $value) { if ($delta < $this->field['cardinality']) { - $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Value $delta is inserted correctly")); + $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], format_string("Value %delta is inserted correctly", array('%delta' => $delta))); } else { $this->assertFalse(array_key_exists($delta, $rows), "No extraneous value gets inserted."); @@ -145,7 +145,7 @@ $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); foreach ($values as $delta => $value) { if ($delta < $this->field['cardinality']) { - $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Value $delta is updated correctly")); + $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], format_string("Value %delta is updated correctly", array('%delta' => $delta))); } else { $this->assertFalse(array_key_exists($delta, $rows), "No extraneous value gets updated."); @@ -175,7 +175,7 @@ $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); foreach ($values as $delta => $value) { if ($delta < $this->field['cardinality']) { - $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Update with no field_name entry leaves value $delta untouched")); + $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], format_string("Update with no field_name entry leaves value %delta untouched", array('%delta' => $delta))); } } @@ -183,7 +183,7 @@ $entity->{$this->field_name} = NULL; field_attach_update($entity_type, $entity); $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); - $this->assertEqual(count($rows), 0, t("Update with an empty field_name entry empties the field.")); + $this->assertEqual(count($rows), 0, "Update with an empty field_name entry empties the field."); } /** @@ -326,7 +326,7 @@ // Ensure that the field tables are still there. foreach (_field_sql_storage_schema($prior_field) as $table_name => $table_info) { - $this->assertTrue(db_table_exists($table_name), t('Table %table exists.', array('%table' => $table_name))); + $this->assertTrue(db_table_exists($table_name), format_string('Table %table exists.', array('%table' => $table_name))); } } @@ -345,8 +345,8 @@ // Verify the indexes we will create do not exist yet. foreach ($tables as $table) { - $this->assertFalse(Database::getConnection()->schema()->indexExists($table, 'value'), t("No index named value exists in $table")); - $this->assertFalse(Database::getConnection()->schema()->indexExists($table, 'value_format'), t("No index named value_format exists in $table")); + $this->assertFalse(Database::getConnection()->schema()->indexExists($table, 'value'), format_string("No index named value exists in %table", array('%table' => $table))); + $this->assertFalse(Database::getConnection()->schema()->indexExists($table, 'value_format'), format_string("No index named value_format exists in %table", array('%table' => $table))); } // Add data so the table cannot be dropped. @@ -355,24 +355,24 @@ field_attach_insert('test_entity', $entity); // Add an index - $field = array('field_name' => $field_name, 'indexes' => array('value' => array('value'))); + $field = array('field_name' => $field_name, 'indexes' => array('value' => array(array('value', 255)))); field_update_field($field); foreach ($tables as $table) { - $this->assertTrue(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value"), t("Index on value created in $table")); + $this->assertTrue(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value"), format_string("Index on value created in %table", array('%table' => $table))); } // Add a different index, removing the existing custom one. - $field = array('field_name' => $field_name, 'indexes' => array('value_format' => array('value', 'format'))); + $field = array('field_name' => $field_name, 'indexes' => array('value_format' => array(array('value', 127), array('format', 127)))); field_update_field($field); foreach ($tables as $table) { - $this->assertTrue(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value_format"), t("Index on value_format created in $table")); - $this->assertFalse(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value"), t("Index on value removed in $table")); + $this->assertTrue(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value_format"), format_string("Index on value_format created in %table", array('%table' => $table))); + $this->assertFalse(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value"), format_string("Index on value removed in %table", array('%table' => $table))); } // Verify that the tables were not dropped. $entity = field_test_create_stub_entity(0, 0, $instance['bundle']); field_attach_load('test_entity', array(0 => $entity)); - $this->assertEqual($entity->{$field_name}[LANGUAGE_NONE][0]['value'], 'field data', t("Index changes performed without dropping the tables")); + $this->assertEqual($entity->{$field_name}[LANGUAGE_NONE][0]['value'], 'field data', "Index changes performed without dropping the tables"); } /** @@ -387,19 +387,19 @@ $instance = field_info_instance($this->instance['entity_type'], $this->instance['field_name'], $this->instance['bundle']); // The storage details are indexed by a storage engine type. - $this->assertTrue(array_key_exists('sql', $field['storage']['details']), t('The storage type is SQL.')); + $this->assertTrue(array_key_exists('sql', $field['storage']['details']), 'The storage type is SQL.'); // The SQL details are indexed by table name. $details = $field['storage']['details']['sql']; - $this->assertTrue(array_key_exists($current, $details[FIELD_LOAD_CURRENT]), t('Table name is available in the instance array.')); - $this->assertTrue(array_key_exists($revision, $details[FIELD_LOAD_REVISION]), t('Revision table name is available in the instance array.')); + $this->assertTrue(array_key_exists($current, $details[FIELD_LOAD_CURRENT]), 'Table name is available in the instance array.'); + $this->assertTrue(array_key_exists($revision, $details[FIELD_LOAD_REVISION]), 'Revision table name is available in the instance array.'); // Test current and revision storage details together because the columns // are the same. foreach ((array) $this->field['columns'] as $column_name => $attributes) { $storage_column_name = _field_sql_storage_columnname($this->field['field_name'], $column_name); - $this->assertEqual($details[FIELD_LOAD_CURRENT][$current][$column_name], $storage_column_name, t('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => $current))); - $this->assertEqual($details[FIELD_LOAD_REVISION][$revision][$column_name], $storage_column_name, t('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => $revision))); + $this->assertEqual($details[FIELD_LOAD_CURRENT][$current][$column_name], $storage_column_name, format_string('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => $current))); + $this->assertEqual($details[FIELD_LOAD_REVISION][$revision][$column_name], $storage_column_name, format_string('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => $revision))); } } @@ -407,21 +407,180 @@ * Test foreign key support. */ function testFieldSqlStorageForeignKeys() { - // Create a decimal field. + // Create a 'shape' field, with a configurable foreign key (see + // field_test_field_schema()). $field_name = 'testfield'; - $field = array('field_name' => $field_name, 'type' => 'text'); - $field = field_create_field($field); - // Retrieve the field and instance with field_info and verify the foreign - // keys are in place. + $foreign_key_name = 'shape'; + $field = array('field_name' => $field_name, 'type' => 'shape', 'settings' => array('foreign_key_name' => $foreign_key_name)); + field_create_field($field); + + // Retrieve the field definition and check that the foreign key is in place. + $field = field_info_field($field_name); + $this->assertEqual($field['foreign keys'][$foreign_key_name]['table'], $foreign_key_name, 'Foreign key table name preserved through CRUD'); + $this->assertEqual($field['foreign keys'][$foreign_key_name]['columns'][$foreign_key_name], 'id', 'Foreign key column name preserved through CRUD'); + + // Update the field settings, it should update the foreign key definition + // too. + $foreign_key_name = 'color'; + $field['settings']['foreign_key_name'] = $foreign_key_name; + field_update_field($field); + + // Retrieve the field definition and check that the foreign key is in place. $field = field_info_field($field_name); - $this->assertEqual($field['foreign keys']['format']['table'], 'filter_format', t('Foreign key table name preserved through CRUD')); - $this->assertEqual($field['foreign keys']['format']['columns']['format'], 'format', t('Foreign key column name preserved through CRUD')); + $this->assertEqual($field['foreign keys'][$foreign_key_name]['table'], $foreign_key_name, 'Foreign key table name modified after update'); + $this->assertEqual($field['foreign keys'][$foreign_key_name]['columns'][$foreign_key_name], 'id', 'Foreign key column name modified after update'); + // Now grab the SQL schema and verify that too. - $schema = drupal_get_schema(_field_sql_storage_tablename($field)); - $this->assertEqual(count($schema['foreign keys']), 1, t("There is 1 foreign key in the schema")); + $schema = drupal_get_schema(_field_sql_storage_tablename($field), TRUE); + $this->assertEqual(count($schema['foreign keys']), 1, 'There is 1 foreign key in the schema'); $foreign_key = reset($schema['foreign keys']); - $filter_column = _field_sql_storage_columnname($field['field_name'], 'format'); - $this->assertEqual($foreign_key['table'], 'filter_format', t('Foreign key table name preserved in the schema')); - $this->assertEqual($foreign_key['columns'][$filter_column], 'format', t('Foreign key column name preserved in the schema')); + $foreign_key_column = _field_sql_storage_columnname($field['field_name'], $foreign_key_name); + $this->assertEqual($foreign_key['table'], $foreign_key_name, 'Foreign key table name preserved in the schema'); + $this->assertEqual($foreign_key['columns'][$foreign_key_column], 'id', 'Foreign key column name preserved in the schema'); + } + + /** + * Test handling multiple conditions on one column of a field. + * + * Tests both the result and the complexity of the query. + */ + function testFieldSqlStorageMultipleConditionsSameColumn() { + $entity = field_test_create_stub_entity(NULL, NULL); + $entity->{$this->field_name}[LANGUAGE_NONE][0] = array('value' => 1); + field_test_entity_save($entity); + + $entity = field_test_create_stub_entity(NULL, NULL); + $entity->{$this->field_name}[LANGUAGE_NONE][0] = array('value' => 2); + field_test_entity_save($entity); + + $entity = field_test_create_stub_entity(NULL, NULL); + $entity->{$this->field_name}[LANGUAGE_NONE][0] = array('value' => 3); + field_test_entity_save($entity); + + $query = new EntityFieldQuery(); + // This tag causes field_test_query_store_global_test_query_alter() to be + // invoked so that the query can be tested. + $query->addTag('store_global_test_query'); + $query->entityCondition('entity_type', 'test_entity'); + $query->entityCondition('bundle', 'test_bundle'); + $query->fieldCondition($this->field_name, 'value', 1, '<>', 0, LANGUAGE_NONE); + $query->fieldCondition($this->field_name, 'value', 2, '<>', 0, LANGUAGE_NONE); + $result = field_sql_storage_field_storage_query($query); + + // Test the results. + $this->assertEqual(1, count($result), format_string('One result should be returned, got @count', array('@count' => count($result)))); + + // Test the complexity of the query. + $query = $GLOBALS['test_query']; + $this->assertNotNull($query, 'Precondition: the query should be available'); + $tables = $query->getTables(); + $this->assertEqual(1, count($tables), 'The query contains just one table.'); + + // Clean up. + unset($GLOBALS['test_query']); + } + + /** + * Test handling multiple conditions on multiple columns of one field. + * + * Tests both the result and the complexity of the query. + */ + function testFieldSqlStorageMultipleConditionsDifferentColumns() { + // Create the multi-column shape field + $field_name = strtolower($this->randomName()); + $field = array('field_name' => $field_name, 'type' => 'shape', 'cardinality' => 4); + $field = field_create_field($field); + $instance = array( + 'field_name' => $field_name, + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle' + ); + $instance = field_create_instance($instance); + + $entity = field_test_create_stub_entity(NULL, NULL); + $entity->{$field_name}[LANGUAGE_NONE][0] = array('shape' => 'A', 'color' => 'X'); + field_test_entity_save($entity); + + $entity = field_test_create_stub_entity(NULL, NULL); + $entity->{$field_name}[LANGUAGE_NONE][0] = array('shape' => 'B', 'color' => 'X'); + field_test_entity_save($entity); + + $entity = field_test_create_stub_entity(NULL, NULL); + $entity->{$field_name}[LANGUAGE_NONE][0] = array('shape' => 'A', 'color' => 'Y'); + field_test_entity_save($entity); + + $query = new EntityFieldQuery(); + // This tag causes field_test_query_store_global_test_query_alter() to be + // invoked so that the query can be tested. + $query->addTag('store_global_test_query'); + $query->entityCondition('entity_type', 'test_entity'); + $query->entityCondition('bundle', 'test_bundle'); + $query->fieldCondition($field_name, 'shape', 'B', '=', 'something', LANGUAGE_NONE); + $query->fieldCondition($field_name, 'color', 'X', '=', 'something', LANGUAGE_NONE); + $result = field_sql_storage_field_storage_query($query); + + // Test the results. + $this->assertEqual(1, count($result), format_string('One result should be returned, got @count', array('@count' => count($result)))); + + // Test the complexity of the query. + $query = $GLOBALS['test_query']; + $this->assertNotNull($query, 'Precondition: the query should be available'); + $tables = $query->getTables(); + $this->assertEqual(1, count($tables), 'The query contains just one table.'); + + // Clean up. + unset($GLOBALS['test_query']); + } + + /** + * Test handling multiple conditions on multiple columns of one field for multiple languages. + * + * Tests both the result and the complexity of the query. + */ + function testFieldSqlStorageMultipleConditionsDifferentColumnsMultipleLanguages() { + field_test_entity_info_translatable('test_entity', TRUE); + + // Create the multi-column shape field + $field_name = strtolower($this->randomName()); + $field = array('field_name' => $field_name, 'type' => 'shape', 'cardinality' => 4, 'translatable' => TRUE); + $field = field_create_field($field); + $instance = array( + 'field_name' => $field_name, + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + 'settings' => array( + // Prevent warning from field_test_field_load(). + 'test_hook_field_load' => FALSE, + ), + ); + $instance = field_create_instance($instance); + + $entity = field_test_create_stub_entity(NULL, NULL); + $entity->{$field_name}[LANGUAGE_NONE][0] = array('shape' => 'A', 'color' => 'X'); + $entity->{$field_name}['en'][0] = array('shape' => 'B', 'color' => 'Y'); + field_test_entity_save($entity); + $entity = field_test_entity_test_load($entity->ftid); + + $query = new EntityFieldQuery(); + // This tag causes field_test_query_store_global_test_query_alter() to be + // invoked so that the query can be tested. + $query->addTag('store_global_test_query'); + $query->entityCondition('entity_type', 'test_entity'); + $query->entityCondition('bundle', 'test_bundle'); + $query->fieldCondition($field_name, 'color', 'X', '=', NULL, LANGUAGE_NONE); + $query->fieldCondition($field_name, 'shape', 'B', '=', NULL, 'en'); + $result = field_sql_storage_field_storage_query($query); + + // Test the results. + $this->assertEqual(1, count($result), format_string('One result should be returned, got @count', array('@count' => count($result)))); + + // Test the complexity of the query. + $query = $GLOBALS['test_query']; + $this->assertNotNull($query, 'Precondition: the query should be available'); + $tables = $query->getTables(); + $this->assertEqual(2, count($tables), 'The query contains two tables.'); + + // Clean up. + unset($GLOBALS['test_query']); } } diff -Naur drupal-7.14/modules/field/modules/list/list.info drupal-7.66/modules/field/modules/list/list.info --- drupal-7.14/modules/field/modules/list/list.info 2012-05-03 00:25:55.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 2012-05-02 -version = "7.14" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1335997555" - +datestamp = "1555533576" diff -Naur drupal-7.14/modules/field/modules/list/list.install drupal-7.66/modules/field/modules/list/list.install --- drupal-7.14/modules/field/modules/list/list.install 2012-05-03 00:10:42.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. @@ -117,6 +124,11 @@ } /** + * @addtogroup updates-7.x-extra + * @{ + */ + +/** * Re-apply list_update_7001() for deleted fields. */ function list_update_7002() { @@ -126,4 +138,8 @@ // list_update_7001() has the required checks to ensure it is reentrant, so // it can simply be executed once more.. list_update_7001(); -} \ No newline at end of file +} + +/** + * @} End of "addtogroup updates-7.x-extra". + */ diff -Naur drupal-7.14/modules/field/modules/list/tests/list.test drupal-7.66/modules/field/modules/list/tests/list.test --- drupal-7.14/modules/field/modules/list/tests/list.test 2012-05-03 00:10:42.000000000 +0200 +++ drupal-7.66/modules/field/modules/list/tests/list.test 2019-04-17 22:20:46.000000000 +0200 @@ -51,9 +51,9 @@ // All three options appear. $entity = field_test_create_stub_entity(); $form = drupal_get_form('field_test_entity_form', $entity); - $this->assertTrue(!empty($form[$this->field_name][$langcode][1]), t('Option 1 exists')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), t('Option 2 exists')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][3]), t('Option 3 exists')); + $this->assertTrue(!empty($form[$this->field_name][$langcode][1]), 'Option 1 exists'); + $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), 'Option 2 exists'); + $this->assertTrue(!empty($form[$this->field_name][$langcode][3]), 'Option 3 exists'); // Use one of the values in an actual entity, and check that this value // cannot be removed from the list. @@ -77,19 +77,19 @@ field_update_field($this->field); $entity = field_test_create_stub_entity(); $form = drupal_get_form('field_test_entity_form', $entity); - $this->assertTrue(empty($form[$this->field_name][$langcode][1]), t('Option 1 does not exist')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), t('Option 2 exists')); - $this->assertTrue(empty($form[$this->field_name][$langcode][3]), t('Option 3 does not exist')); + $this->assertTrue(empty($form[$this->field_name][$langcode][1]), 'Option 1 does not exist'); + $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), 'Option 2 exists'); + $this->assertTrue(empty($form[$this->field_name][$langcode][3]), 'Option 3 does not exist'); // Completely new options appear. $this->field['settings']['allowed_values'] = array(10 => 'Update', 20 => 'Twenty'); field_update_field($this->field); $form = drupal_get_form('field_test_entity_form', $entity); - $this->assertTrue(empty($form[$this->field_name][$langcode][1]), t('Option 1 does not exist')); - $this->assertTrue(empty($form[$this->field_name][$langcode][2]), t('Option 2 does not exist')); - $this->assertTrue(empty($form[$this->field_name][$langcode][3]), t('Option 3 does not exist')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][10]), t('Option 10 exists')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][20]), t('Option 20 exists')); + $this->assertTrue(empty($form[$this->field_name][$langcode][1]), 'Option 1 does not exist'); + $this->assertTrue(empty($form[$this->field_name][$langcode][2]), 'Option 2 does not exist'); + $this->assertTrue(empty($form[$this->field_name][$langcode][3]), 'Option 3 does not exist'); + $this->assertTrue(!empty($form[$this->field_name][$langcode][10]), 'Option 10 exists'); + $this->assertTrue(!empty($form[$this->field_name][$langcode][20]), 'Option 20 exists'); // Options are reset when a new field with the same name is created. field_delete_field($this->field_name); @@ -107,9 +107,9 @@ $this->instance = field_create_instance($this->instance); $entity = field_test_create_stub_entity(); $form = drupal_get_form('field_test_entity_form', $entity); - $this->assertTrue(!empty($form[$this->field_name][$langcode][1]), t('Option 1 exists')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), t('Option 2 exists')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][3]), t('Option 3 exists')); + $this->assertTrue(!empty($form[$this->field_name][$langcode][1]), 'Option 1 exists'); + $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), 'Option 2 exists'); + $this->assertTrue(!empty($form[$this->field_name][$langcode][3]), 'Option 3 exists'); } } @@ -212,7 +212,7 @@ parent::setUp('field_test', 'field_ui'); // Create test user. - $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy')); + $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy', 'administer fields')); $this->drupalLogin($admin_user); // Create content type, with underscores. @@ -233,20 +233,20 @@ // Flat list of textual values. $string = "Zero\nOne"; $array = array('0' => 'Zero', '1' => 'One'); - $this->assertAllowedValuesInput($string, $array, t('Unkeyed lists are accepted.')); + $this->assertAllowedValuesInput($string, $array, 'Unkeyed lists are accepted.'); // Explicit integer keys. $string = "0|Zero\n2|Two"; $array = array('0' => 'Zero', '2' => 'Two'); - $this->assertAllowedValuesInput($string, $array, t('Integer keys are accepted.')); + $this->assertAllowedValuesInput($string, $array, 'Integer keys are accepted.'); // Check that values can be added and removed. $string = "0|Zero\n1|One"; $array = array('0' => 'Zero', '1' => 'One'); - $this->assertAllowedValuesInput($string, $array, t('Values can be added and removed.')); + $this->assertAllowedValuesInput($string, $array, 'Values can be added and removed.'); // Non-integer keys. - $this->assertAllowedValuesInput("1.1|One", 'keys must be integers', t('Non integer keys are rejected.')); - $this->assertAllowedValuesInput("abc|abc", 'keys must be integers', t('Non integer keys are rejected.')); + $this->assertAllowedValuesInput("1.1|One", 'keys must be integers', 'Non integer keys are rejected.'); + $this->assertAllowedValuesInput("abc|abc", 'keys must be integers', 'Non integer keys are rejected.'); // Mixed list of keyed and unkeyed values. - $this->assertAllowedValuesInput("Zero\n1|One", 'invalid input', t('Mixed lists are rejected.')); + $this->assertAllowedValuesInput("Zero\n1|One", 'invalid input', 'Mixed lists are rejected.'); // Create a node with actual data for the field. $settings = array( @@ -256,22 +256,22 @@ $node = $this->drupalCreateNode($settings); // Check that a flat list of values is rejected once the field has data. - $this->assertAllowedValuesInput( "Zero\nOne", 'invalid input', t('Unkeyed lists are rejected once the field has data.')); + $this->assertAllowedValuesInput( "Zero\nOne", 'invalid input', 'Unkeyed lists are rejected once the field has data.'); // Check that values can be added but values in use cannot be removed. $string = "0|Zero\n1|One\n2|Two"; $array = array('0' => 'Zero', '1' => 'One', '2' => 'Two'); - $this->assertAllowedValuesInput($string, $array, t('Values can be added.')); + $this->assertAllowedValuesInput($string, $array, 'Values can be added.'); $string = "0|Zero\n1|One"; $array = array('0' => 'Zero', '1' => 'One'); - $this->assertAllowedValuesInput($string, $array, t('Values not in use can be removed.')); - $this->assertAllowedValuesInput("0|Zero", 'some values are being removed while currently in use', t('Values in use cannot be removed.')); + $this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.'); + $this->assertAllowedValuesInput("0|Zero", 'some values are being removed while currently in use', 'Values in use cannot be removed.'); // Delete the node, remove the value. node_delete($node->nid); $string = "0|Zero"; $array = array('0' => 'Zero'); - $this->assertAllowedValuesInput($string, $array, t('Values not in use can be removed.')); + $this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.'); } /** @@ -284,19 +284,19 @@ // Flat list of textual values. $string = "Zero\nOne"; $array = array('0' => 'Zero', '1' => 'One'); - $this->assertAllowedValuesInput($string, $array, t('Unkeyed lists are accepted.')); + $this->assertAllowedValuesInput($string, $array, 'Unkeyed lists are accepted.'); // Explicit numeric keys. $string = "0|Zero\n.5|Point five"; $array = array('0' => 'Zero', '0.5' => 'Point five'); - $this->assertAllowedValuesInput($string, $array, t('Integer keys are accepted.')); + $this->assertAllowedValuesInput($string, $array, 'Integer keys are accepted.'); // Check that values can be added and removed. $string = "0|Zero\n.5|Point five\n1.0|One"; $array = array('0' => 'Zero', '0.5' => 'Point five', '1' => 'One'); - $this->assertAllowedValuesInput($string, $array, t('Values can be added and removed.')); + $this->assertAllowedValuesInput($string, $array, 'Values can be added and removed.'); // Non-numeric keys. - $this->assertAllowedValuesInput("abc|abc\n", 'each key must be a valid integer or decimal', t('Non numeric keys are rejected.')); + $this->assertAllowedValuesInput("abc|abc\n", 'each key must be a valid integer or decimal', 'Non numeric keys are rejected.'); // Mixed list of keyed and unkeyed values. - $this->assertAllowedValuesInput("Zero\n1|One\n", 'invalid input', t('Mixed lists are rejected.')); + $this->assertAllowedValuesInput("Zero\n1|One\n", 'invalid input', 'Mixed lists are rejected.'); // Create a node with actual data for the field. $settings = array( @@ -306,22 +306,22 @@ $node = $this->drupalCreateNode($settings); // Check that a flat list of values is rejected once the field has data. - $this->assertAllowedValuesInput("Zero\nOne", 'invalid input', t('Unkeyed lists are rejected once the field has data.')); + $this->assertAllowedValuesInput("Zero\nOne", 'invalid input', 'Unkeyed lists are rejected once the field has data.'); // Check that values can be added but values in use cannot be removed. $string = "0|Zero\n.5|Point five\n2|Two"; $array = array('0' => 'Zero', '0.5' => 'Point five', '2' => 'Two'); - $this->assertAllowedValuesInput($string, $array, t('Values can be added.')); + $this->assertAllowedValuesInput($string, $array, 'Values can be added.'); $string = "0|Zero\n.5|Point five"; $array = array('0' => 'Zero', '0.5' => 'Point five'); - $this->assertAllowedValuesInput($string, $array, t('Values not in use can be removed.')); - $this->assertAllowedValuesInput("0|Zero", 'some values are being removed while currently in use', t('Values in use cannot be removed.')); + $this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.'); + $this->assertAllowedValuesInput("0|Zero", 'some values are being removed while currently in use', 'Values in use cannot be removed.'); // Delete the node, remove the value. node_delete($node->nid); $string = "0|Zero"; $array = array('0' => 'Zero'); - $this->assertAllowedValuesInput($string, $array, t('Values not in use can be removed.')); + $this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.'); } /** @@ -334,21 +334,21 @@ // Flat list of textual values. $string = "Zero\nOne"; $array = array('Zero' => 'Zero', 'One' => 'One'); - $this->assertAllowedValuesInput($string, $array, t('Unkeyed lists are accepted.')); + $this->assertAllowedValuesInput($string, $array, 'Unkeyed lists are accepted.'); // Explicit keys. $string = "zero|Zero\none|One"; $array = array('zero' => 'Zero', 'one' => 'One'); - $this->assertAllowedValuesInput($string, $array, t('Explicit keys are accepted.')); + $this->assertAllowedValuesInput($string, $array, 'Explicit keys are accepted.'); // Check that values can be added and removed. $string = "zero|Zero\ntwo|Two"; $array = array('zero' => 'Zero', 'two' => 'Two'); - $this->assertAllowedValuesInput($string, $array, t('Values can be added and removed.')); + $this->assertAllowedValuesInput($string, $array, 'Values can be added and removed.'); // Mixed list of keyed and unkeyed values. $string = "zero|Zero\nOne\n"; $array = array('zero' => 'Zero', 'One' => 'One'); - $this->assertAllowedValuesInput($string, $array, t('Mixed lists are accepted.')); + $this->assertAllowedValuesInput($string, $array, 'Mixed lists are accepted.'); // Overly long keys. - $this->assertAllowedValuesInput("zero|Zero\n" . $this->randomName(256) . "|One", 'each key must be a string at most 255 characters long', t('Overly long keys are rejected.')); + $this->assertAllowedValuesInput("zero|Zero\n" . $this->randomName(256) . "|One", 'each key must be a string at most 255 characters long', 'Overly long keys are rejected.'); // Create a node with actual data for the field. $settings = array( @@ -361,22 +361,22 @@ // data. $string = "Zero\nOne"; $array = array('Zero' => 'Zero', 'One' => 'One'); - $this->assertAllowedValuesInput($string, $array, t('Unkeyed lists are still accepted once the field has data.')); + $this->assertAllowedValuesInput($string, $array, 'Unkeyed lists are still accepted once the field has data.'); // Check that values can be added but values in use cannot be removed. $string = "Zero\nOne\nTwo"; $array = array('Zero' => 'Zero', 'One' => 'One', 'Two' => 'Two'); - $this->assertAllowedValuesInput($string, $array, t('Values can be added.')); + $this->assertAllowedValuesInput($string, $array, 'Values can be added.'); $string = "Zero\nOne"; $array = array('Zero' => 'Zero', 'One' => 'One'); - $this->assertAllowedValuesInput($string, $array, t('Values not in use can be removed.')); - $this->assertAllowedValuesInput("Zero", 'some values are being removed while currently in use', t('Values in use cannot be removed.')); + $this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.'); + $this->assertAllowedValuesInput("Zero", 'some values are being removed while currently in use', 'Values in use cannot be removed.'); // Delete the node, remove the value. node_delete($node->nid); $string = "Zero"; $array = array('Zero' => 'Zero'); - $this->assertAllowedValuesInput($string, $array, t('Values not in use can be removed.')); + $this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.'); } /** @@ -395,15 +395,15 @@ 'off' => $off, ); $this->drupalPost($this->admin_path, $edit, t('Save settings')); - $this->assertText("Saved field_list_boolean configuration.", t("The 'On' and 'Off' form fields work for boolean fields.")); + $this->assertText("Saved field_list_boolean configuration.", "The 'On' and 'Off' form fields work for boolean fields."); // Test the allowed_values on the field settings form. $this->drupalGet($this->admin_path); - $this->assertFieldByName('on', $on, t("The 'On' value is stored correctly.")); - $this->assertFieldByName('off', $off, t("The 'Off' value is stored correctly.")); + $this->assertFieldByName('on', $on, "The 'On' value is stored correctly."); + $this->assertFieldByName('off', $off, "The 'Off' value is stored correctly."); $field = field_info_field($this->field_name); - $this->assertEqual($field['settings']['allowed_values'], $allowed_values, t('The allowed value is correct')); - $this->assertFalse(isset($field['settings']['on']), t('The on value is not saved into settings')); - $this->assertFalse(isset($field['settings']['off']), t('The off value is not saved into settings')); + $this->assertEqual($field['settings']['allowed_values'], $allowed_values, 'The allowed value is correct'); + $this->assertFalse(isset($field['settings']['on']), 'The on value is not saved into settings'); + $this->assertFalse(isset($field['settings']['off']), 'The off value is not saved into settings'); } /** diff -Naur drupal-7.14/modules/field/modules/list/tests/list_test.info drupal-7.66/modules/field/modules/list/tests/list_test.info --- drupal-7.14/modules/field/modules/list/tests/list_test.info 2012-05-03 00:25:55.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 2012-05-02 -version = "7.14" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1335997555" - +datestamp = "1555533576" diff -Naur drupal-7.14/modules/field/modules/number/number.info drupal-7.66/modules/field/modules/number/number.info --- drupal-7.14/modules/field/modules/number/number.info 2012-05-03 00:25:55.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 2012-05-02 -version = "7.14" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1335997555" - +datestamp = "1555533576" diff -Naur drupal-7.14/modules/field/modules/number/number.module drupal-7.66/modules/field/modules/number/number.module --- drupal-7.14/modules/field/modules/number/number.module 2012-05-03 00:10:42.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.14/modules/field/modules/number/number.test drupal-7.66/modules/field/modules/number/number.test --- drupal-7.14/modules/field/modules/number/number.test 2012-05-03 00:10:42.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); } @@ -58,7 +58,7 @@ // Display creation form. $this->drupalGet('test-entity/add/test-bundle'); $langcode = LANGUAGE_NONE; - $this->assertFieldByName("{$this->field['field_name']}[$langcode][0][value]", '', t('Widget is displayed')); + $this->assertFieldByName("{$this->field['field_name']}[$langcode][0][value]", '', 'Widget is displayed'); // Submit a signed decimal value within the allowed precision and scale. $value = '-1234.5678'; @@ -68,8 +68,8 @@ $this->drupalPost(NULL, $edit, t('Save')); preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match); $id = $match[1]; - $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), t('Entity was created')); - $this->assertRaw(round($value, 2), t('Value is displayed.')); + $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created'); + $this->assertRaw($value, 'Value is displayed.'); // Try to create entries with more than one decimal separator; assert fail. $wrong_entries = array( @@ -89,7 +89,7 @@ $this->assertText( t('There should only be one decimal separator (@separator)', array('@separator' => $this->field['settings']['decimal_separator'])), - t('Correctly failed to save decimal value with more than one decimal point.') + 'Correctly failed to save decimal value with more than one decimal point.' ); } @@ -152,4 +152,50 @@ ); $this->drupalPost(NULL, $edit, t('Save')); } + + /** + * Test number_float field. + */ + function testNumberFloatField() { + $this->field = array( + 'field_name' => drupal_strtolower($this->randomName()), + 'type' => 'number_float', + 'settings' => array( + 'precision' => 8, 'scale' => 4, 'decimal_separator' => '.', + ) + ); + field_create_field($this->field); + $this->instance = array( + 'field_name' => $this->field['field_name'], + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + 'widget' => array( + 'type' => 'number', + ), + 'display' => array( + 'default' => array( + 'type' => 'number_float', + ), + ), + ); + field_create_instance($this->instance); + + $langcode = LANGUAGE_NONE; + $value = array( + '9.' => '9', + '.' => '0', + '123.55' => '123.55', + '.55' => '0.55', + '-0.55' => '-0.55', + ); + foreach($value as $key => $value) { + $edit = array( + "{$this->field['field_name']}[$langcode][0][value]" => $key, + ); + $this->drupalPost('test-entity/add/test-bundle', $edit, t('Save')); + $this->assertNoText("PDOException"); + $this->assertRaw($value, 'Correct value is displayed.'); + } + } + } diff -Naur drupal-7.14/modules/field/modules/options/options.info drupal-7.66/modules/field/modules/options/options.info --- drupal-7.14/modules/field/modules/options/options.info 2012-05-03 00:25:55.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 2012-05-02 -version = "7.14" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1335997555" - +datestamp = "1555533576" diff -Naur drupal-7.14/modules/field/modules/options/options.module drupal-7.66/modules/field/modules/options/options.module --- drupal-7.14/modules/field/modules/options/options.module 2012-05-03 00:10:42.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.14/modules/field/modules/options/options.test drupal-7.66/modules/field/modules/options/options.test --- drupal-7.14/modules/field/modules/options/options.test 2012-05-03 00:10:42.000000000 +0200 +++ drupal-7.66/modules/field/modules/options/options.test 2019-04-17 22:20:46.000000000 +0200 @@ -1,7 +1,7 @@ 'list_integer', 'cardinality' => 1, 'settings' => array( - // Make sure that 0 works as an option. - 'allowed_values' => array(0 => 'Zero', 1 => 'One', 2 => 'Some & unescaped markup'), + 'allowed_values' => array( + // Make sure that 0 works as an option. + 0 => 'Zero', + 1 => 'One', + // Make sure that option text is properly sanitized. + 2 => 'Some & unescaped markup', + // Make sure that HTML entities in option text are not double-encoded. + 3 => 'Some HTML encoded markup with < & >', + ), ), ); $this->card_1 = field_create_field($this->card_1); @@ -35,8 +42,13 @@ 'type' => 'list_integer', 'cardinality' => 2, 'settings' => array( - // Make sure that 0 works as an option. - 'allowed_values' => array(0 => 'Zero', 1 => 'One', 2 => 'Some & unescaped markup'), + 'allowed_values' => array( + // Make sure that 0 works as an option. + 0 => 'Zero', + 1 => 'One', + // Make sure that option text is properly sanitized. + 2 => 'Some & unescaped markup', + ), ), ); $this->card_2 = field_create_field($this->card_2); @@ -47,14 +59,18 @@ 'type' => 'list_boolean', 'cardinality' => 1, 'settings' => array( - // Make sure that 0 works as a 'on' value'. - 'allowed_values' => array(1 => 'Zero', 0 => 'Some & unescaped markup'), + 'allowed_values' => array( + // Make sure that 1 works as a 'on' value'. + 1 => 'Zero', + // Make sure that option text is properly sanitized. + 0 => 'Some & unescaped markup', + ), ), ); $this->bool = field_create_field($this->bool); // Create a web user. - $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content')); + $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content', 'administer fields')); $this->drupalLogin($this->web_user); } @@ -85,7 +101,7 @@ $this->assertNoFieldChecked("edit-card-1-$langcode-0"); $this->assertNoFieldChecked("edit-card-1-$langcode-1"); $this->assertNoFieldChecked("edit-card-1-$langcode-2"); - $this->assertRaw('Some dangerous & unescaped markup', t('Option text was properly filtered.')); + $this->assertRaw('Some dangerous & unescaped markup', 'Option text was properly filtered.'); // Select first option. $edit = array("card_1[$langcode]" => 0); @@ -139,7 +155,7 @@ $this->assertNoFieldChecked("edit-card-2-$langcode-0"); $this->assertNoFieldChecked("edit-card-2-$langcode-1"); $this->assertNoFieldChecked("edit-card-2-$langcode-2"); - $this->assertRaw('Some dangerous & unescaped markup', t('Option text was properly filtered.')); + $this->assertRaw('Some dangerous & unescaped markup', 'Option text was properly filtered.'); // Submit form: select first and third options. $edit = array( @@ -178,7 +194,7 @@ "card_2[$langcode][2]" => TRUE, ); $this->drupalPost(NULL, $edit, t('Save')); - $this->assertText('this field cannot hold more than 2 values', t('Validation error was displayed.')); + $this->assertText('this field cannot hold more than 2 values', 'Validation error was displayed.'); // Submit form: uncheck all options. $edit = array( @@ -225,19 +241,20 @@ // Display form. $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); // A required field without any value has a "none" option. - $this->assertTrue($this->xpath('//select[@id=:id]//option[@value="_none" and text()=:label]', array(':id' => 'edit-card-1-' . $langcode, ':label' => t('- Select a value -'))), t('A required select list has a "Select a value" choice.')); + $this->assertTrue($this->xpath('//select[@id=:id]//option[@value="_none" and text()=:label]', array(':id' => 'edit-card-1-' . $langcode, ':label' => t('- Select a value -'))), 'A required select list has a "Select a value" choice.'); // With no field data, nothing is selected. $this->assertNoOptionSelected("edit-card-1-$langcode", '_none'); $this->assertNoOptionSelected("edit-card-1-$langcode", 0); $this->assertNoOptionSelected("edit-card-1-$langcode", 1); $this->assertNoOptionSelected("edit-card-1-$langcode", 2); - $this->assertRaw('Some dangerous & unescaped markup', t('Option text was properly filtered.')); + $this->assertRaw('Some dangerous & unescaped markup', 'Option text was properly filtered.'); + $this->assertRaw('Some HTML encoded markup with < & >', 'HTML entities in option text were properly handled and not double-encoded'); // Submit form: select invalid 'none' option. $edit = array("card_1[$langcode]" => '_none'); $this->drupalPost(NULL, $edit, t('Save')); - $this->assertRaw(t('!title field is required.', array('!title' => $instance['field_name'])), t('Cannot save a required field when selecting "none" from the select list.')); + $this->assertRaw(t('!title field is required.', array('!title' => $instance['field_name'])), 'Cannot save a required field when selecting "none" from the select list.'); // Submit form: select first option. $edit = array("card_1[$langcode]" => 0); @@ -247,7 +264,7 @@ // Display form: check that the right options are selected. $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); // A required field with a value has no 'none' option. - $this->assertFalse($this->xpath('//select[@id=:id]//option[@value="_none"]', array(':id' => 'edit-card-1-' . $langcode)), t('A required select list with an actual value has no "none" choice.')); + $this->assertFalse($this->xpath('//select[@id=:id]//option[@value="_none"]', array(':id' => 'edit-card-1-' . $langcode)), 'A required select list with an actual value has no "none" choice.'); $this->assertOptionSelected("edit-card-1-$langcode", 0); $this->assertNoOptionSelected("edit-card-1-$langcode", 1); $this->assertNoOptionSelected("edit-card-1-$langcode", 2); @@ -259,7 +276,7 @@ // Display form. $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); // A non-required field has a 'none' option. - $this->assertTrue($this->xpath('//select[@id=:id]//option[@value="_none" and text()=:label]', array(':id' => 'edit-card-1-' . $langcode, ':label' => t('- None -'))), t('A non-required select list has a "None" choice.')); + $this->assertTrue($this->xpath('//select[@id=:id]//option[@value="_none" and text()=:label]', array(':id' => 'edit-card-1-' . $langcode, ':label' => t('- None -'))), 'A non-required select list has a "None" choice.'); // Submit form: Unselect the option. $edit = array("card_1[$langcode]" => '_none'); $this->drupalPost('test-entity/manage/' . $entity->ftid . '/edit', $edit, t('Save')); @@ -276,8 +293,8 @@ $this->assertNoOptionSelected("edit-card-1-$langcode", 0); $this->assertNoOptionSelected("edit-card-1-$langcode", 1); $this->assertNoOptionSelected("edit-card-1-$langcode", 2); - $this->assertRaw('Some dangerous & unescaped markup', t('Option text was properly filtered.')); - $this->assertRaw('Group 1', t('Option groups are displayed.')); + $this->assertRaw('Some dangerous & unescaped markup', 'Option text was properly filtered.'); + $this->assertRaw('Group 1', 'Option groups are displayed.'); // Submit form: select first option. $edit = array("card_1[$langcode]" => 0); @@ -323,7 +340,7 @@ $this->assertNoOptionSelected("edit-card-2-$langcode", 0); $this->assertNoOptionSelected("edit-card-2-$langcode", 1); $this->assertNoOptionSelected("edit-card-2-$langcode", 2); - $this->assertRaw('Some dangerous & unescaped markup', t('Option text was properly filtered.')); + $this->assertRaw('Some dangerous & unescaped markup', 'Option text was properly filtered.'); // Submit form: select first and third options. $edit = array("card_2[$langcode][]" => array(0 => 0, 2 => 2)); @@ -350,7 +367,7 @@ // Submit form: select the three options while the field accepts only 2. $edit = array("card_2[$langcode][]" => array(0 => 0, 1 => 1, 2 => 2)); $this->drupalPost(NULL, $edit, t('Save')); - $this->assertText('this field cannot hold more than 2 values', t('Validation error was displayed.')); + $this->assertText('this field cannot hold more than 2 values', 'Validation error was displayed.'); // Submit form: uncheck all options. $edit = array("card_2[$langcode][]" => array()); @@ -359,7 +376,7 @@ // Test the 'None' option. - // Check that the 'none' option has no efect if actual options are selected + // Check that the 'none' option has no effect if actual options are selected // as well. $edit = array("card_2[$langcode][]" => array('_none' => '_none', 0 => 0)); $this->drupalPost('test-entity/manage/' . $entity->ftid . '/edit', $edit, t('Save')); @@ -374,7 +391,7 @@ $instance['required'] = TRUE; field_update_instance($instance); $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - $this->assertFalse($this->xpath('//select[@id=:id]//option[@value=""]', array(':id' => 'edit-card-2-' . $langcode)), t('A required select list does not have an empty key.')); + $this->assertFalse($this->xpath('//select[@id=:id]//option[@value=""]', array(':id' => 'edit-card-2-' . $langcode)), 'A required select list does not have an empty key.'); // We do not have to test that a required select list with one option is // auto-selected because the browser does it for us. @@ -393,8 +410,8 @@ $this->assertNoOptionSelected("edit-card-2-$langcode", 0); $this->assertNoOptionSelected("edit-card-2-$langcode", 1); $this->assertNoOptionSelected("edit-card-2-$langcode", 2); - $this->assertRaw('Some dangerous & unescaped markup', t('Option text was properly filtered.')); - $this->assertRaw('Group 1', t('Option groups are displayed.')); + $this->assertRaw('Some dangerous & unescaped markup', 'Option text was properly filtered.'); + $this->assertRaw('Group 1', 'Option groups are displayed.'); // Submit form: select first option. $edit = array("card_2[$langcode][]" => array(0 => 0)); @@ -438,7 +455,7 @@ // Display form: with no field data, option is unchecked. $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); $this->assertNoFieldChecked("edit-bool-$langcode"); - $this->assertRaw('Some dangerous & unescaped markup', t('Option text was properly filtered.')); + $this->assertRaw('Some dangerous & unescaped markup', 'Option text was properly filtered.'); // Submit form: check the option. $edit = array("bool[$langcode]" => TRUE); @@ -459,7 +476,7 @@ $this->assertNoFieldChecked("edit-bool-$langcode"); // Create admin user. - $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy')); + $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy', 'administer fields')); $this->drupalLogin($admin_user); // Create a test field instance. @@ -483,13 +500,13 @@ $this->assertText( 'Use field label instead of the "On value" as label ', - t('Display setting checkbox available.') + 'Display setting checkbox available.' ); $this->assertFieldByXPath( '*//label[@for="edit-' . $this->bool['field_name'] . '-und" and text()="MyOnValue "]', TRUE, - t('Default case shows "On value"') + 'Default case shows "On value"' ); // Enable setting @@ -502,16 +519,16 @@ $this->drupalGet($fieldEditUrl); $this->assertText( 'Use field label instead of the "On value" as label ', - t('Display setting checkbox is available') + 'Display setting checkbox is available' ); $this->assertFieldChecked( 'edit-instance-widget-settings-display-label', - t('Display settings checkbox checked') + 'Display settings checkbox checked' ); $this->assertFieldByXPath( '*//label[@for="edit-' . $this->bool['field_name'] . '-und" and text()="' . $this->bool['field_name'] . ' "]', TRUE, - t('Display label changes label of the checkbox') + 'Display label changes label of the checkbox' ); } } diff -Naur drupal-7.14/modules/field/modules/text/text.info drupal-7.66/modules/field/modules/text/text.info --- drupal-7.14/modules/field/modules/text/text.info 2012-05-03 00:25:55.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 2012-05-02 -version = "7.14" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1335997555" - +datestamp = "1555533576" diff -Naur drupal-7.14/modules/field/modules/text/text.js drupal-7.66/modules/field/modules/text/text.js --- drupal-7.14/modules/field/modules/text/text.js 2012-05-03 00:10:42.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.14/modules/field/modules/text/text.module drupal-7.66/modules/field/modules/text/text.module --- drupal-7.14/modules/field/modules/text/text.module 2012-05-03 00:10:42.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.14/modules/field/modules/text/text.test drupal-7.66/modules/field/modules/text/text.test --- drupal-7.14/modules/field/modules/text/text.test 2012-05-03 00:10:42.000000000 +0200 +++ drupal-7.66/modules/field/modules/text/text.test 2019-04-17 22:20:46.000000000 +0200 @@ -110,8 +110,8 @@ // Display creation form. $this->drupalGet('test-entity/add/test-bundle'); - $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', t('Widget is displayed')); - $this->assertNoFieldByName("{$this->field_name}[$langcode][0][format]", '1', t('Format selector is not displayed')); + $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', 'Widget is displayed'); + $this->assertNoFieldByName("{$this->field_name}[$langcode][0][format]", '1', 'Format selector is not displayed'); // Submit with some value. $value = $this->randomName(); @@ -121,7 +121,7 @@ $this->drupalPost(NULL, $edit, t('Save')); preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match); $id = $match[1]; - $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), t('Entity was created')); + $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created'); // Display the entity. $entity = field_test_entity_test_load($id); @@ -179,8 +179,8 @@ // Display the creation form. Since the user only has access to one format, // no format selector will be displayed. $this->drupalGet('test-entity/add/test-bundle'); - $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', t('Widget is displayed')); - $this->assertNoFieldByName("{$this->field_name}[$langcode][0][format]", '', t('Format selector is not displayed')); + $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', 'Widget is displayed'); + $this->assertNoFieldByName("{$this->field_name}[$langcode][0][format]", '', 'Format selector is not displayed'); // Submit with data that should be filtered. $value = '' . $this->randomName() . ''; @@ -190,14 +190,14 @@ $this->drupalPost(NULL, $edit, t('Save')); preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match); $id = $match[1]; - $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), t('Entity was created')); + $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created'); // Display the entity. $entity = field_test_entity_test_load($id); $entity->content = field_attach_view($entity_type, $entity, 'full'); $this->content = drupal_render($entity->content); - $this->assertNoRaw($value, t('HTML tags are not displayed.')); - $this->assertRaw(check_plain($value), t('Escaped HTML is displayed correctly.')); + $this->assertNoRaw($value, 'HTML tags are not displayed.'); + $this->assertRaw(check_plain($value), 'Escaped HTML is displayed correctly.'); // Create a new text format that does not escape HTML, and grant the user // access to it. @@ -219,21 +219,21 @@ // Display edition form. // We should now have a 'text format' selector. $this->drupalGet('test-entity/manage/' . $id . '/edit'); - $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", NULL, t('Widget is displayed')); - $this->assertFieldByName("{$this->field_name}[$langcode][0][format]", NULL, t('Format selector is displayed')); + $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", NULL, 'Widget is displayed'); + $this->assertFieldByName("{$this->field_name}[$langcode][0][format]", NULL, 'Format selector is displayed'); // Edit and change the text format to the new one that was created. $edit = array( "{$this->field_name}[$langcode][0][format]" => $format_id, ); $this->drupalPost(NULL, $edit, t('Save')); - $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), t('Entity was updated')); + $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), 'Entity was updated'); // Display the entity. $entity = field_test_entity_test_load($id); $entity->content = field_attach_view($entity_type, $entity, 'full'); $this->content = drupal_render($entity->content); - $this->assertRaw($value, t('Value is displayed unfiltered')); + $this->assertRaw($value, 'Value is displayed unfiltered'); } } @@ -383,7 +383,7 @@ */ function callTextSummary($text, $expected, $format = NULL, $size = NULL) { $summary = text_summary($text, $format, $size); - $this->assertIdentical($summary, $expected, t('Generated summary "@summary" matches expected "@expected".', array('@summary' => $summary, '@expected' => $expected))); + $this->assertIdentical($summary, $expected, format_string('Generated summary "@summary" matches expected "@expected".', array('@summary' => $summary, '@expected' => $expected))); } /** @@ -401,7 +401,7 @@ $this->drupalPost('node/add/article', $edit, t('Save')); $node = $this->drupalGetNodeByTitle($edit['title']); - $this->assertIdentical($node->body['und'][0]['summary'], $summary, t('Article with with summary and no body has been submitted.')); + $this->assertIdentical($node->body['und'][0]['summary'], $summary, 'Article with with summary and no body has been submitted.'); } } @@ -424,6 +424,7 @@ 'administer content types', 'access administration pages', 'bypass node access', + 'administer fields', filter_permission_name($full_html_format), )); $this->translator = $this->drupalCreateUser(array('create article content', 'edit own article content', 'translate content')); @@ -436,7 +437,7 @@ // Set "Article" content type to use multilingual support with translation. $edit = array('language_content_type' => 2); $this->drupalPost('admin/structure/types/manage/article', $edit, t('Save content type')); - $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Article')), t('Article content type has been updated.')); + $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Article')), 'Article content type has been updated.'); } /** @@ -464,7 +465,7 @@ $node = $this->drupalGetNodeByTitle($edit['title']); $this->drupalGet("node/$node->nid/translate"); $this->clickLink(t('add translation')); - $this->assertFieldByXPath("//textarea[@name='body[$langcode][0][value]']", $body, t('The textfield widget is populated.')); + $this->assertFieldByXPath("//textarea[@name='body[$langcode][0][value]']", $body, 'The textfield widget is populated.'); } /** @@ -476,7 +477,7 @@ $edit = array('field[cardinality]' => -1); $this->drupalPost('admin/structure/types/manage/article/fields/body', $edit, t('Save settings')); $this->drupalGet('node/add/article'); - $this->assertFieldByXPath("//input[@name='body_add_more']", t('Add another item'), t('Body field cardinality set to multiple.')); + $this->assertFieldByXPath("//input[@name='body_add_more']", t('Add another item'), 'Body field cardinality set to multiple.'); $body = array( $this->randomName(), @@ -501,7 +502,7 @@ "body[$langcode][$delta][format]" => array_shift($formats), ); $this->drupalPost('node/1/edit', $edit, t('Save')); - $this->assertText($body[$delta], t('The body field with delta @delta has been saved.', array('@delta' => $delta))); + $this->assertText($body[$delta], format_string('The body field with delta @delta has been saved.', array('@delta' => $delta))); } // Login as translator. @@ -511,7 +512,7 @@ $node = $this->drupalGetNodeByTitle($title); $this->drupalGet("node/$node->nid/translate"); $this->clickLink(t('add translation')); - $this->assertNoText($body[0], t('The body field with delta @delta is hidden.', array('@delta' => 0))); - $this->assertText($body[1], t('The body field with delta @delta is shown.', array('@delta' => 1))); + $this->assertNoText($body[0], format_string('The body field with delta @delta is hidden.', array('@delta' => 0))); + $this->assertText($body[1], format_string('The body field with delta @delta is shown.', array('@delta' => 1))); } } diff -Naur drupal-7.14/modules/field/tests/field.test drupal-7.66/modules/field/tests/field.test --- drupal-7.14/modules/field/tests/field.test 2012-05-03 00:10:42.000000000 +0200 +++ drupal-7.66/modules/field/tests/field.test 2019-04-17 22:20:46.000000000 +0200 @@ -64,9 +64,9 @@ $e = clone $entity; field_attach_load('test_entity', array($e->ftid => $e)); $values = isset($e->{$field_name}[$langcode]) ? $e->{$field_name}[$langcode] : array(); - $this->assertEqual(count($values), count($expected_values), t('Expected number of values were saved.')); + $this->assertEqual(count($values), count($expected_values), 'Expected number of values were saved.'); foreach ($expected_values as $key => $value) { - $this->assertEqual($values[$key][$column], $value, t('Value @value was saved correctly.', array('@value' => $value))); + $this->assertEqual($values[$key][$column], $value, format_string('Value @value was saved correctly.', array('@value' => $value))); } } } @@ -85,12 +85,28 @@ } parent::setUp($modules); - $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); - $this->field = array('field_name' => $this->field_name, 'type' => 'test_field', 'cardinality' => 4); - $this->field = field_create_field($this->field); - $this->field_id = $this->field['id']; - $this->instance = array( - 'field_name' => $this->field_name, + $this->createFieldWithInstance(); + } + + /** + * Create a field and an instance of it. + * + * @param string $suffix + * (optional) A string that should only contain characters that are valid in + * PHP variable names as well. + */ + function createFieldWithInstance($suffix = '') { + $field_name = 'field_name' . $suffix; + $field = 'field' . $suffix; + $field_id = 'field_id' . $suffix; + $instance = 'instance' . $suffix; + + $this->$field_name = drupal_strtolower($this->randomName() . '_field_name' . $suffix); + $this->$field = array('field_name' => $this->$field_name, 'type' => 'test_field', 'cardinality' => 4); + $this->$field = field_create_field($this->$field); + $this->$field_id = $this->{$field}['id']; + $this->$instance = array( + 'field_name' => $this->$field_name, 'entity_type' => 'test_entity', 'bundle' => 'test_bundle', 'label' => $this->randomName() . '_label', @@ -107,7 +123,7 @@ ) ) ); - field_create_instance($this->instance); + field_create_instance($this->$instance); } } @@ -166,12 +182,12 @@ $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); field_attach_load($entity_type, array(0 => $entity)); // Number of values per field loaded equals the field cardinality. - $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], t('Current revision: expected number of values')); + $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], 'Current revision: expected number of values'); for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { // The field value loaded matches the one inserted or updated. - $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['value'] , $values[$current_revision][$delta]['value'], t('Current revision: expected value %delta was found.', array('%delta' => $delta))); + $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['value'] , $values[$current_revision][$delta]['value'], format_string('Current revision: expected value %delta was found.', array('%delta' => $delta))); // The value added in hook_field_load() is found. - $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['additional_key'], 'additional_value', t('Current revision: extra information for value %delta was found', array('%delta' => $delta))); + $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['additional_key'], 'additional_value', format_string('Current revision: extra information for value %delta was found', array('%delta' => $delta))); } // Confirm each revision loads the correct data. @@ -179,12 +195,12 @@ $entity = field_test_create_stub_entity(0, $revision_id, $this->instance['bundle']); field_attach_load_revision($entity_type, array(0 => $entity)); // Number of values per field loaded equals the field cardinality. - $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], t('Revision %revision_id: expected number of values.', array('%revision_id' => $revision_id))); + $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], format_string('Revision %revision_id: expected number of values.', array('%revision_id' => $revision_id))); for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { // The field value loaded matches the one inserted or updated. - $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['value'], $values[$revision_id][$delta]['value'], t('Revision %revision_id: expected value %delta was found.', array('%revision_id' => $revision_id, '%delta' => $delta))); + $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['value'], $values[$revision_id][$delta]['value'], format_string('Revision %revision_id: expected value %delta was found.', array('%revision_id' => $revision_id, '%delta' => $delta))); // The value added in hook_field_load() is found. - $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['additional_key'], 'additional_value', t('Revision %revision_id: extra information for value %delta was found', array('%revision_id' => $revision_id, '%delta' => $delta))); + $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['additional_key'], 'additional_value', format_string('Revision %revision_id: extra information for value %delta was found', array('%revision_id' => $revision_id, '%delta' => $delta))); } } } @@ -250,19 +266,19 @@ $instances = field_info_instances($entity_type, $bundles[$index]); foreach ($instances as $field_name => $instance) { // The field value loaded matches the one inserted. - $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], $values[$index][$field_name], t('Entity %index: expected value was found.', array('%index' => $index))); + $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], $values[$index][$field_name], format_string('Entity %index: expected value was found.', array('%index' => $index))); // The value added in hook_field_load() is found. - $this->assertEqual($entity->{$field_name}[$langcode][0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => $index))); + $this->assertEqual($entity->{$field_name}[$langcode][0]['additional_key'], 'additional_value', format_string('Entity %index: extra information was found', array('%index' => $index))); } } // Check that the single-field load option works. $entity = field_test_create_stub_entity(1, 1, $bundles[1]); field_attach_load($entity_type, array(1 => $entity), FIELD_LOAD_CURRENT, array('field_id' => $field_ids[1])); - $this->assertEqual($entity->{$field_names[1]}[$langcode][0]['value'], $values[1][$field_names[1]], t('Entity %index: expected value was found.', array('%index' => 1))); - $this->assertEqual($entity->{$field_names[1]}[$langcode][0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => 1))); - $this->assert(!isset($entity->{$field_names[2]}), t('Entity %index: field %field_name is not loaded.', array('%index' => 2, '%field_name' => $field_names[2]))); - $this->assert(!isset($entity->{$field_names[3]}), t('Entity %index: field %field_name is not loaded.', array('%index' => 3, '%field_name' => $field_names[3]))); + $this->assertEqual($entity->{$field_names[1]}[$langcode][0]['value'], $values[1][$field_names[1]], format_string('Entity %index: expected value was found.', array('%index' => 1))); + $this->assertEqual($entity->{$field_names[1]}[$langcode][0]['additional_key'], 'additional_value', format_string('Entity %index: extra information was found', array('%index' => 1))); + $this->assert(!isset($entity->{$field_names[2]}), format_string('Entity %index: field %field_name is not loaded.', array('%index' => 2, '%field_name' => $field_names[2]))); + $this->assert(!isset($entity->{$field_names[3]}), format_string('Entity %index: field %field_name is not loaded.', array('%index' => 3, '%field_name' => $field_names[3]))); } /** @@ -312,7 +328,7 @@ $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); foreach ($fields as $field) { - $this->assertEqual($values[$field['field_name']], $entity->{$field['field_name']}[$langcode], t('%storage storage: expected values were found.', array('%storage' => $field['storage']['type']))); + $this->assertEqual($values[$field['field_name']], $entity->{$field['field_name']}[$langcode], format_string('%storage storage: expected values were found.', array('%storage' => $field['storage']['type']))); } } @@ -341,20 +357,20 @@ $instance = field_info_instance($instance['entity_type'], $instance['field_name'], $instance['bundle']); // The storage details are indexed by a storage engine type. - $this->assertTrue(array_key_exists('drupal_variables', $field['storage']['details']), t('The storage type is Drupal variables.')); + $this->assertTrue(array_key_exists('drupal_variables', $field['storage']['details']), 'The storage type is Drupal variables.'); $details = $field['storage']['details']['drupal_variables']; // The field_test storage details are indexed by variable name. The details // are altered, so moon and mars are correct for this test. - $this->assertTrue(array_key_exists('moon', $details[FIELD_LOAD_CURRENT]), t('Moon is available in the instance array.')); - $this->assertTrue(array_key_exists('mars', $details[FIELD_LOAD_REVISION]), t('Mars is available in the instance array.')); + $this->assertTrue(array_key_exists('moon', $details[FIELD_LOAD_CURRENT]), 'Moon is available in the instance array.'); + $this->assertTrue(array_key_exists('mars', $details[FIELD_LOAD_REVISION]), 'Mars is available in the instance array.'); // Test current and revision storage details together because the columns // are the same. foreach ((array) $field['columns'] as $column_name => $attributes) { - $this->assertEqual($details[FIELD_LOAD_CURRENT]['moon'][$column_name], $column_name, t('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'moon[FIELD_LOAD_CURRENT]'))); - $this->assertEqual($details[FIELD_LOAD_REVISION]['mars'][$column_name], $column_name, t('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'mars[FIELD_LOAD_REVISION]'))); + $this->assertEqual($details[FIELD_LOAD_CURRENT]['moon'][$column_name], $column_name, format_string('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'moon[FIELD_LOAD_CURRENT]'))); + $this->assertEqual($details[FIELD_LOAD_REVISION]['mars'][$column_name], $column_name, format_string('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'mars[FIELD_LOAD_REVISION]'))); } } @@ -372,7 +388,7 @@ $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: missing field results in no value saved')); + $this->assertTrue(empty($entity->{$this->field_name}), 'Insert: missing field results in no value saved'); // Insert: Field is NULL. field_cache_clear(); @@ -382,7 +398,7 @@ $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: NULL field results in no value saved')); + $this->assertTrue(empty($entity->{$this->field_name}), 'Insert: NULL field results in no value saved'); // Add some real data. field_cache_clear(); @@ -393,7 +409,7 @@ $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Field data saved')); + $this->assertEqual($entity->{$this->field_name}[$langcode], $values, 'Field data saved'); // Update: Field is missing. Data should survive. field_cache_clear(); @@ -402,7 +418,7 @@ $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Update: missing field leaves existing values in place')); + $this->assertEqual($entity->{$this->field_name}[$langcode], $values, 'Update: missing field leaves existing values in place'); // Update: Field is NULL. Data should be wiped. field_cache_clear(); @@ -412,7 +428,7 @@ $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertTrue(empty($entity->{$this->field_name}), t('Update: NULL field removes existing values')); + $this->assertTrue(empty($entity->{$this->field_name}), 'Update: NULL field removes existing values'); // Re-add some data. field_cache_clear(); @@ -423,7 +439,7 @@ $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Field data saved')); + $this->assertEqual($entity->{$this->field_name}[$langcode], $values, 'Field data saved'); // Update: Field is empty array. Data should be wiped. field_cache_clear(); @@ -433,7 +449,7 @@ $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertTrue(empty($entity->{$this->field_name}), t('Update: empty array removes existing values')); + $this->assertTrue(empty($entity->{$this->field_name}), 'Update: empty array removes existing values'); } /** @@ -455,7 +471,7 @@ $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertTrue(empty($entity->{$this->field_name}[$langcode]), t('Insert: NULL field results in no value saved')); + $this->assertTrue(empty($entity->{$this->field_name}[$langcode]), 'Insert: NULL field results in no value saved'); // Insert: Field is missing. field_cache_clear(); @@ -465,7 +481,67 @@ $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); $values = field_test_default_value($entity_type, $entity, $this->field, $this->instance); - $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Insert: missing field results in default value saved')); + $this->assertEqual($entity->{$this->field_name}[$langcode], $values, 'Insert: missing field results in default value saved'); + } + + /** + * Test field_has_data(). + */ + function testFieldHasData() { + $entity_type = 'test_entity'; + $langcode = LANGUAGE_NONE; + + $field_name = 'field_1'; + $field = array('field_name' => $field_name, 'type' => 'test_field'); + $field = field_create_field($field); + + $this->assertFalse(field_has_data($field), "No data should be detected."); + + $instance = array( + 'field_name' => $field_name, + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle' + ); + $instance = field_create_instance($instance); + $table = _field_sql_storage_tablename($field); + $revision_table = _field_sql_storage_revision_tablename($field); + + $columns = array('entity_type', 'entity_id', 'revision_id', 'delta', 'language', $field_name . '_value'); + + $eid = 0; + + // Insert values into the field revision table. + $query = db_insert($revision_table)->fields($columns); + $query->values(array($entity_type, $eid, 0, 0, $langcode, 1)); + $query->execute(); + + $this->assertTrue(field_has_data($field), "Revision data only should be detected."); + + $field_name = 'field_2'; + $field = array('field_name' => $field_name, 'type' => 'test_field'); + $field = field_create_field($field); + + $this->assertFalse(field_has_data($field), "No data should be detected."); + + $instance = array( + 'field_name' => $field_name, + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle' + ); + $instance = field_create_instance($instance); + $table = _field_sql_storage_tablename($field); + $revision_table = _field_sql_storage_revision_tablename($field); + + $columns = array('entity_type', 'entity_id', 'revision_id', 'delta', 'language', $field_name . '_value'); + + $eid = 1; + + // Insert values into the field table. + $query = db_insert($table)->fields($columns); + $query->values(array($entity_type, $eid, 0, 0, $langcode, 1)); + $query->execute(); + + $this->assertTrue(field_has_data($field), "Values only in field table should be detected."); } /** @@ -520,7 +596,7 @@ } $read = field_test_create_stub_entity(0, 2, $this->instance['bundle']); field_attach_load($entity_type, array(0 => $read)); - $this->assertIdentical($read->{$this->field_name}, array(), t('The test entity current revision is deleted.')); + $this->assertIdentical($read->{$this->field_name}, array(), 'The test entity current revision is deleted.'); } /** @@ -641,13 +717,18 @@ * Test field_attach_view() and field_attach_prepare_view(). */ function testFieldAttachView() { + $this->createFieldWithInstance('_2'); + $entity_type = 'test_entity'; $entity_init = field_test_create_stub_entity(); $langcode = LANGUAGE_NONE; + $options = array('field_name' => $this->field_name_2); // Populate values to be displayed. $values = $this->_generateTestFieldValues($this->field['cardinality']); $entity_init->{$this->field_name}[$langcode] = $values; + $values_2 = $this->_generateTestFieldValues($this->field_2['cardinality']); + $entity_init->{$this->field_name_2}[$langcode] = $values_2; // Simple formatter, label displayed. $entity = clone($entity_init); @@ -662,15 +743,47 @@ ), ); field_update_instance($this->instance); + $formatter_setting_2 = $this->randomName(); + $this->instance_2['display'] = array( + 'full' => array( + 'label' => 'above', + 'type' => 'field_test_default', + 'settings' => array( + 'test_formatter_setting' => $formatter_setting_2, + ) + ), + ); + field_update_instance($this->instance_2); + // View all fields. field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); $entity->content = field_attach_view($entity_type, $entity, 'full'); $output = drupal_render($entity->content); $this->content = $output; - $this->assertRaw($this->instance['label'], "Label is displayed."); + $this->assertRaw($this->instance['label'], "First field's label is displayed."); foreach ($values as $delta => $value) { $this->content = $output; $this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied."); } + $this->assertRaw($this->instance_2['label'], "Second field's label is displayed."); + foreach ($values_2 as $delta => $value) { + $this->content = $output; + $this->assertRaw("$formatter_setting_2|{$value['value']}", "Value $delta is displayed, formatter settings are applied."); + } + // View single field (the second field). + field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full', $langcode, $options); + $entity->content = field_attach_view($entity_type, $entity, 'full', $langcode, $options); + $output = drupal_render($entity->content); + $this->content = $output; + $this->assertNoRaw($this->instance['label'], "First field's label is not displayed."); + foreach ($values as $delta => $value) { + $this->content = $output; + $this->assertNoRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied."); + } + $this->assertRaw($this->instance_2['label'], "Second field's label is displayed."); + foreach ($values_2 as $delta => $value) { + $this->content = $output; + $this->assertRaw("$formatter_setting_2|{$value['value']}", "Value $delta is displayed, formatter settings are applied."); + } // Label hidden. $entity = clone($entity_init); @@ -697,7 +810,7 @@ $this->content = $output; $this->assertNoRaw($this->instance['label'], "Hidden field: label is not displayed."); foreach ($values as $delta => $value) { - $this->assertNoRaw($value['value'], "Hidden field: value $delta is not displayed."); + $this->assertNoRaw("$formatter_setting|{$value['value']}", "Hidden field: value $delta is not displayed."); } // Multiple formatter. @@ -759,7 +872,7 @@ break; } } - $this->assertTrue($result, t('Variable $@field_name correctly populated.', array('@field_name' => $this->field_name))); + $this->assertTrue($result, format_string('Variable $@field_name correctly populated.', array('@field_name' => $this->field_name))); } /** @@ -825,18 +938,18 @@ $cid = "field:$entity_type:{$entity_init->ftid}"; // Check that no initial cache entry is present. - $this->assertFalse(cache_get($cid, 'cache_field'), t('Non-cached: no initial cache entry')); + $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no initial cache entry'); // Save, and check that no cache entry is present. $entity = clone($entity_init); $entity->{$this->field_name}[$langcode] = $values; field_attach_insert($entity_type, $entity); - $this->assertFalse(cache_get($cid, 'cache_field'), t('Non-cached: no cache entry on insert')); + $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no cache entry on insert'); // Load, and check that no cache entry is present. $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertFalse(cache_get($cid, 'cache_field'), t('Non-cached: no cache entry on load')); + $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no cache entry on load'); // Cacheable entity type. @@ -847,38 +960,38 @@ field_create_instance($instance); // Check that no initial cache entry is present. - $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no initial cache entry')); + $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no initial cache entry'); // Save, and check that no cache entry is present. $entity = clone($entity_init); $entity->{$this->field_name}[$langcode] = $values; field_attach_insert($entity_type, $entity); - $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on insert')); + $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on insert'); // Load a single field, and check that no cache entry is present. $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity), FIELD_LOAD_CURRENT, array('field_id' => $this->field_id)); $cache = cache_get($cid, 'cache_field'); - $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on loading a single field')); + $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on loading a single field'); // Load, and check that a cache entry is present with the expected values. $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); $cache = cache_get($cid, 'cache_field'); - $this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load')); + $this->assertEqual($cache->data[$this->field_name][$langcode], $values, 'Cached: correct cache entry on load'); // Update with different values, and check that the cache entry is wiped. $values = $this->_generateTestFieldValues($this->field['cardinality']); $entity = clone($entity_init); $entity->{$this->field_name}[$langcode] = $values; field_attach_update($entity_type, $entity); - $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on update')); + $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on update'); // Load, and check that a cache entry is present with the expected values. $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); $cache = cache_get($cid, 'cache_field'); - $this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load')); + $this->assertEqual($cache->data[$this->field_name][$langcode], $values, 'Cached: correct cache entry on load'); // Create a new revision, and check that the cache entry is wiped. $entity_init = field_test_create_stub_entity(1, 2, $this->instance['bundle']); @@ -887,17 +1000,17 @@ $entity->{$this->field_name}[$langcode] = $values; field_attach_update($entity_type, $entity); $cache = cache_get($cid, 'cache_field'); - $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on new revision creation')); + $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on new revision creation'); // Load, and check that a cache entry is present with the expected values. $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); $cache = cache_get($cid, 'cache_field'); - $this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load')); + $this->assertEqual($cache->data[$this->field_name][$langcode], $values, 'Cached: correct cache entry on load'); // Delete, and check that the cache entry is wiped. field_attach_delete($entity_type, $entity); - $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry after delete')); + $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry after delete'); } /** @@ -907,11 +1020,13 @@ * hook_field_validate. */ function testFieldAttachValidate() { + $this->createFieldWithInstance('_2'); + $entity_type = 'test_entity'; $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); $langcode = LANGUAGE_NONE; - // Set up values to generate errors + // Set up all but one values of the first field to generate errors. $values = array(); for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { $values[$delta]['value'] = -1; @@ -920,6 +1035,14 @@ $values[1]['value'] = 1; $entity->{$this->field_name}[$langcode] = $values; + // Set up all values of the second field to generate errors. + $values_2 = array(); + for ($delta = 0; $delta < $this->field_2['cardinality']; $delta++) { + $values_2[$delta]['value'] = -1; + } + $entity->{$this->field_name_2}[$langcode] = $values_2; + + // Validate all fields. try { field_attach_validate($entity_type, $entity); } @@ -929,26 +1052,57 @@ foreach ($values as $delta => $value) { if ($value['value'] != 1) { - $this->assertIdentical($errors[$this->field_name][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on value $delta"); - $this->assertEqual(count($errors[$this->field_name][$langcode][$delta]), 1, "Only one error set on value $delta"); + $this->assertIdentical($errors[$this->field_name][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on first field's value $delta"); + $this->assertEqual(count($errors[$this->field_name][$langcode][$delta]), 1, "Only one error set on first field's value $delta"); unset($errors[$this->field_name][$langcode][$delta]); } else { - $this->assertFalse(isset($errors[$this->field_name][$langcode][$delta]), "No error set on value $delta"); + $this->assertFalse(isset($errors[$this->field_name][$langcode][$delta]), "No error set on first field's value $delta"); } } - $this->assertEqual(count($errors[$this->field_name][$langcode]), 0, 'No extraneous errors set'); + foreach ($values_2 as $delta => $value) { + $this->assertIdentical($errors[$this->field_name_2][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on second field's value $delta"); + $this->assertEqual(count($errors[$this->field_name_2][$langcode][$delta]), 1, "Only one error set on second field's value $delta"); + unset($errors[$this->field_name_2][$langcode][$delta]); + } + $this->assertEqual(count($errors[$this->field_name][$langcode]), 0, 'No extraneous errors set for first field'); + $this->assertEqual(count($errors[$this->field_name_2][$langcode]), 0, 'No extraneous errors set for second field'); + + // Validate a single field. + $options = array('field_name' => $this->field_name_2); + try { + field_attach_validate($entity_type, $entity, $options); + } + catch (FieldValidationException $e) { + $errors = $e->errors; + } + + foreach ($values_2 as $delta => $value) { + $this->assertIdentical($errors[$this->field_name_2][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on second field's value $delta"); + $this->assertEqual(count($errors[$this->field_name_2][$langcode][$delta]), 1, "Only one error set on second field's value $delta"); + unset($errors[$this->field_name_2][$langcode][$delta]); + } + $this->assertFalse(isset($errors[$this->field_name]), 'No validation errors are set for the first field, despite it having errors'); + $this->assertEqual(count($errors[$this->field_name_2][$langcode]), 0, 'No extraneous errors set for second field'); // Check that cardinality is validated. - $entity->{$this->field_name}[$langcode] = $this->_generateTestFieldValues($this->field['cardinality'] + 1); + $entity->{$this->field_name_2}[$langcode] = $this->_generateTestFieldValues($this->field_2['cardinality'] + 1); + // When validating all fields. try { field_attach_validate($entity_type, $entity); } catch (FieldValidationException $e) { $errors = $e->errors; } - $this->assertEqual($errors[$this->field_name][$langcode][0][0]['error'], 'field_cardinality', t('Cardinality validation failed.')); - + $this->assertEqual($errors[$this->field_name_2][$langcode][0][0]['error'], 'field_cardinality', 'Cardinality validation failed.'); + // When validating a single field (the second field). + try { + field_attach_validate($entity_type, $entity, $options); + } + catch (FieldValidationException $e) { + $errors = $e->errors; + } + $this->assertEqual($errors[$this->field_name_2][$langcode][0][0]['error'], 'field_cardinality', 'Cardinality validation failed.'); } /** @@ -958,34 +1112,59 @@ * widgets show up. */ function testFieldAttachForm() { + $this->createFieldWithInstance('_2'); + $entity_type = 'test_entity'; $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + $langcode = LANGUAGE_NONE; + // When generating form for all fields. $form = array(); $form_state = form_state_defaults(); field_attach_form($entity_type, $entity, $form, $form_state); - $langcode = LANGUAGE_NONE; - $this->assertEqual($form[$this->field_name][$langcode]['#title'], $this->instance['label'], "Form title is {$this->instance['label']}"); + $this->assertEqual($form[$this->field_name][$langcode]['#title'], $this->instance['label'], "First field's form title is {$this->instance['label']}"); + $this->assertEqual($form[$this->field_name_2][$langcode]['#title'], $this->instance_2['label'], "Second field's form title is {$this->instance_2['label']}"); for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { // field_test_widget uses 'textfield' - $this->assertEqual($form[$this->field_name][$langcode][$delta]['value']['#type'], 'textfield', "Form delta $delta widget is textfield"); - } + $this->assertEqual($form[$this->field_name][$langcode][$delta]['value']['#type'], 'textfield', "First field's form delta $delta widget is textfield"); + } + for ($delta = 0; $delta < $this->field_2['cardinality']; $delta++) { + // field_test_widget uses 'textfield' + $this->assertEqual($form[$this->field_name_2][$langcode][$delta]['value']['#type'], 'textfield', "Second field's form delta $delta widget is textfield"); + } + + // When generating form for a single field (the second field). + $options = array('field_name' => $this->field_name_2); + $form = array(); + $form_state = form_state_defaults(); + field_attach_form($entity_type, $entity, $form, $form_state, NULL, $options); + + $this->assertFalse(isset($form[$this->field_name]), 'The first field does not exist in the form'); + $this->assertEqual($form[$this->field_name_2][$langcode]['#title'], $this->instance_2['label'], "Second field's form title is {$this->instance_2['label']}"); + for ($delta = 0; $delta < $this->field_2['cardinality']; $delta++) { + // field_test_widget uses 'textfield' + $this->assertEqual($form[$this->field_name_2][$langcode][$delta]['value']['#type'], 'textfield', "Second field's form delta $delta widget is textfield"); + } } /** * Test field_attach_submit(). */ function testFieldAttachSubmit() { + $this->createFieldWithInstance('_2'); + $entity_type = 'test_entity'; - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + $entity_init = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + $langcode = LANGUAGE_NONE; - // Build the form. + // Build the form for all fields. $form = array(); $form_state = form_state_defaults(); - field_attach_form($entity_type, $entity, $form, $form_state); + field_attach_form($entity_type, $entity_init, $form, $form_state); // Simulate incoming values. + // First field. $values = array(); $weights = array(); for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { @@ -999,22 +1178,59 @@ } // Leave an empty value. 'field_test' fields are empty if empty(). $values[1]['value'] = 0; - - $langcode = LANGUAGE_NONE; + // Second field. + $values_2 = array(); + $weights_2 = array(); + for ($delta = 0; $delta < $this->field_2['cardinality']; $delta++) { + $values_2[$delta]['value'] = mt_rand(1, 127); + // Assign random weight. + do { + $weight = mt_rand(0, $this->field_2['cardinality']); + } while (in_array($weight, $weights_2)); + $weights_2[$delta] = $weight; + $values_2[$delta]['_weight'] = $weight; + } + // Leave an empty value. 'field_test' fields are empty if empty(). + $values_2[1]['value'] = 0; // Pretend the form has been built. drupal_prepare_form('field_test_entity_form', $form, $form_state); drupal_process_form('field_test_entity_form', $form, $form_state); $form_state['values'][$this->field_name][$langcode] = $values; + $form_state['values'][$this->field_name_2][$langcode] = $values_2; + + // Call field_attach_submit() for all fields. + $entity = clone($entity_init); field_attach_submit($entity_type, $entity, $form, $form_state); asort($weights); + asort($weights_2); $expected_values = array(); + $expected_values_2 = array(); foreach ($weights as $key => $value) { if ($key != 1) { $expected_values[] = array('value' => $values[$key]['value']); } } $this->assertIdentical($entity->{$this->field_name}[$langcode], $expected_values, 'Submit filters empty values'); + foreach ($weights_2 as $key => $value) { + if ($key != 1) { + $expected_values_2[] = array('value' => $values_2[$key]['value']); + } + } + $this->assertIdentical($entity->{$this->field_name_2}[$langcode], $expected_values_2, 'Submit filters empty values'); + + // Call field_attach_submit() for a single field (the second field). + $options = array('field_name' => $this->field_name_2); + $entity = clone($entity_init); + field_attach_submit($entity_type, $entity, $form, $form_state, $options); + $expected_values_2 = array(); + foreach ($weights_2 as $key => $value) { + if ($key != 1) { + $expected_values_2[] = array('value' => $values_2[$key]['value']); + } + } + $this->assertFalse(isset($entity->{$this->field_name}), 'The first field does not exist in the entity object'); + $this->assertIdentical($entity->{$this->field_name_2}[$langcode], $expected_values_2, 'Submit filters empty values'); } } @@ -1046,42 +1262,42 @@ $info = field_info_field_types(); foreach ($field_test_info as $t_key => $field_type) { foreach ($field_type as $key => $val) { - $this->assertEqual($info[$t_key][$key], $val, t("Field type $t_key key $key is $val")); + $this->assertEqual($info[$t_key][$key], $val, format_string('Field type %t_key key %key is %value', array('%t_key' => $t_key, '%key' => $key, '%value' => print_r($val, TRUE)))); } - $this->assertEqual($info[$t_key]['module'], 'field_test', t("Field type field_test module appears")); + $this->assertEqual($info[$t_key]['module'], 'field_test', "Field type field_test module appears"); } $formatter_info = field_test_field_formatter_info(); $info = field_info_formatter_types(); foreach ($formatter_info as $f_key => $formatter) { foreach ($formatter as $key => $val) { - $this->assertEqual($info[$f_key][$key], $val, t("Formatter type $f_key key $key is $val")); + $this->assertEqual($info[$f_key][$key], $val, format_string('Formatter type %f_key key %key is %value', array('%f_key' => $f_key, '%key' => $key, '%value' => print_r($val, TRUE)))); } - $this->assertEqual($info[$f_key]['module'], 'field_test', t("Formatter type field_test module appears")); + $this->assertEqual($info[$f_key]['module'], 'field_test', "Formatter type field_test module appears"); } $widget_info = field_test_field_widget_info(); $info = field_info_widget_types(); foreach ($widget_info as $w_key => $widget) { foreach ($widget as $key => $val) { - $this->assertEqual($info[$w_key][$key], $val, t("Widget type $w_key key $key is $val")); + $this->assertEqual($info[$w_key][$key], $val, format_string('Widget type %w_key key %key is %value', array('%w_key' => $w_key, '%key' => $key, '%value' => print_r($val, TRUE)))); } - $this->assertEqual($info[$w_key]['module'], 'field_test', t("Widget type field_test module appears")); + $this->assertEqual($info[$w_key]['module'], 'field_test', "Widget type field_test module appears"); } $storage_info = field_test_field_storage_info(); $info = field_info_storage_types(); foreach ($storage_info as $s_key => $storage) { foreach ($storage as $key => $val) { - $this->assertEqual($info[$s_key][$key], $val, t("Storage type $s_key key $key is $val")); + $this->assertEqual($info[$s_key][$key], $val, format_string('Storage type %s_key key %key is %value', array('%s_key' => $s_key, '%key' => $key, '%value' => print_r($val, TRUE)))); } - $this->assertEqual($info[$s_key]['module'], 'field_test', t("Storage type field_test module appears")); + $this->assertEqual($info[$s_key]['module'], 'field_test', "Storage type field_test module appears"); } // Verify that no unexpected instances exist. $instances = field_info_instances('test_entity'); $expected = array('test_bundle' => array()); - $this->assertIdentical($instances, $expected, "field_info_instances('test_entity') returns " . var_export($expected, TRUE) . '.'); + $this->assertIdentical($instances, $expected, format_string("field_info_instances('test_entity') returns %expected.", array('%expected' => var_export($expected, TRUE)))); $instances = field_info_instances('test_entity', 'test_bundle'); $this->assertIdentical($instances, array(), "field_info_instances('test_entity', 'test_bundle') returns an empty array."); @@ -1093,16 +1309,16 @@ ); field_create_field($field); $fields = field_info_fields(); - $this->assertEqual(count($fields), count($core_fields) + 1, t('One new field exists')); - $this->assertEqual($fields[$field['field_name']]['field_name'], $field['field_name'], t('info fields contains field name')); - $this->assertEqual($fields[$field['field_name']]['type'], $field['type'], t('info fields contains field type')); - $this->assertEqual($fields[$field['field_name']]['module'], 'field_test', t('info fields contains field module')); + $this->assertEqual(count($fields), count($core_fields) + 1, 'One new field exists'); + $this->assertEqual($fields[$field['field_name']]['field_name'], $field['field_name'], 'info fields contains field name'); + $this->assertEqual($fields[$field['field_name']]['type'], $field['type'], 'info fields contains field type'); + $this->assertEqual($fields[$field['field_name']]['module'], 'field_test', 'info fields contains field module'); $settings = array('test_field_setting' => 'dummy test string'); foreach ($settings as $key => $val) { - $this->assertEqual($fields[$field['field_name']]['settings'][$key], $val, t("Field setting $key has correct default value $val")); + $this->assertEqual($fields[$field['field_name']]['settings'][$key], $val, format_string('Field setting %key has correct default value %value', array('%key' => $key, '%value' => $val))); } - $this->assertEqual($fields[$field['field_name']]['cardinality'], 1, t('info fields contains cardinality 1')); - $this->assertEqual($fields[$field['field_name']]['active'], 1, t('info fields contains active 1')); + $this->assertEqual($fields[$field['field_name']]['cardinality'], 1, 'info fields contains cardinality 1'); + $this->assertEqual($fields[$field['field_name']]['active'], 1, 'info fields contains active 1'); // Create an instance, verify that it shows up $instance = array( @@ -1119,9 +1335,12 @@ 'test_setting' => 999))); field_create_instance($instance); + $info = entity_get_info('test_entity'); $instances = field_info_instances('test_entity', $instance['bundle']); - $this->assertEqual(count($instances), 1, t('One instance shows up in info when attached to a bundle.')); - $this->assertTrue($instance < $instances[$instance['field_name']], t('Instance appears in info correctly')); + $this->assertEqual(count($instances), 1, format_string('One instance shows up in info when attached to a bundle on a @label.', array( + '@label' => $info['label'] + ))); + $this->assertTrue($instance < $instances[$instance['field_name']], 'Instance appears in info correctly'); // Test a valid entity type but an invalid bundle. $instances = field_info_instances('test_entity', 'invalid_bundle'); @@ -1138,9 +1357,19 @@ // Test with an entity type that has no bundles. $instances = field_info_instances('user'); $expected = array('user' => array()); - $this->assertIdentical($instances, $expected, "field_info_instances('user') returns " . var_export($expected, TRUE) . '.'); + $this->assertIdentical($instances, $expected, format_string("field_info_instances('user') returns %expected.", array('%expected' => var_export($expected, TRUE)))); $instances = field_info_instances('user', 'user'); $this->assertIdentical($instances, array(), "field_info_instances('user', 'user') returns an empty array."); + + // Test that querying for invalid entity types does not add entries in the + // list returned by field_info_instances(). + field_info_cache_clear(); + field_info_instances('invalid_entity', 'invalid_bundle'); + // Simulate new request by clearing static caches. + drupal_static_reset(); + field_info_instances('invalid_entity', 'invalid_bundle'); + $instances = field_info_instances(); + $this->assertFalse(isset($instances['invalid_entity']), 'field_info_instances() does not contain entries for the invalid entity type that was queried before'); } /** @@ -1171,7 +1400,7 @@ // Check that all expected settings are in place. $field_type = field_info_field_types($field_definition['type']); - $this->assertIdentical($field['settings'], $field_type['settings'], t('All expected default field settings are present.')); + $this->assertIdentical($field['settings'], $field_type['settings'], 'All expected default field settings are present.'); } /** @@ -1213,18 +1442,18 @@ // Check that all expected instance settings are in place. $field_type = field_info_field_types($field_definition['type']); - $this->assertIdentical($instance['settings'], $field_type['instance_settings'] , t('All expected instance settings are present.')); + $this->assertIdentical($instance['settings'], $field_type['instance_settings'] , 'All expected instance settings are present.'); // Check that the default widget is used and expected settings are in place. - $this->assertIdentical($instance['widget']['type'], $field_type['default_widget'], t('Unavailable widget replaced with default widget.')); + $this->assertIdentical($instance['widget']['type'], $field_type['default_widget'], 'Unavailable widget replaced with default widget.'); $widget_type = field_info_widget_types($instance['widget']['type']); - $this->assertIdentical($instance['widget']['settings'], $widget_type['settings'] , t('All expected widget settings are present.')); + $this->assertIdentical($instance['widget']['settings'], $widget_type['settings'] , 'All expected widget settings are present.'); // Check that display settings are set for the 'default' mode. $display = $instance['display']['default']; - $this->assertIdentical($display['type'], $field_type['default_formatter'], t("Formatter is set for the 'default' view mode")); + $this->assertIdentical($display['type'], $field_type['default_formatter'], "Formatter is set for the 'default' view mode"); $formatter_type = field_info_formatter_types($display['type']); - $this->assertIdentical($display['settings'], $formatter_type['settings'] , t("Formatter settings are set for the 'default' view mode")); + $this->assertIdentical($display['settings'], $formatter_type['settings'] , "Formatter settings are set for the 'default' view mode"); } /** @@ -1247,7 +1476,81 @@ // Disable coment module. This clears field_info cache. module_disable(array('comment')); - $this->assertNull(field_info_instance('comment', 'field', 'comment_node_article'), t('No instances are returned on disabled entity types.')); + $this->assertNull(field_info_instance('comment', 'field', 'comment_node_article'), 'No instances are returned on disabled entity types.'); + } + + /** + * Test field_info_field_map(). + */ + function testFieldMap() { + // We will overlook fields created by the 'standard' install profile. + $exclude = field_info_field_map(); + + // Create a new bundle for 'test_entity' entity type. + field_test_create_bundle('test_bundle_2'); + + // Create a couple fields. + $fields = array( + array( + 'field_name' => 'field_1', + 'type' => 'test_field', + ), + array( + 'field_name' => 'field_2', + 'type' => 'hidden_test_field', + ), + ); + foreach ($fields as $field) { + field_create_field($field); + } + + // Create a couple instances. + $instances = array( + array( + 'field_name' => 'field_1', + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + ), + array( + 'field_name' => 'field_1', + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle_2', + ), + array( + 'field_name' => 'field_2', + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + ), + array( + 'field_name' => 'field_2', + 'entity_type' => 'test_cacheable_entity', + 'bundle' => 'test_bundle', + ), + ); + foreach ($instances as $instance) { + field_create_instance($instance); + } + + $expected = array( + 'field_1' => array( + 'type' => 'test_field', + 'bundles' => array( + 'test_entity' => array('test_bundle', 'test_bundle_2'), + ), + ), + 'field_2' => array( + 'type' => 'hidden_test_field', + 'bundles' => array( + 'test_entity' => array('test_bundle'), + 'test_cacheable_entity' => array('test_bundle'), + ), + ), + ); + + // Check that the field map is correct. + $map = field_info_field_map(); + $map = array_diff_key($map, $exclude); + $this->assertEqual($map, $expected); } /** @@ -1260,20 +1563,45 @@ $info[$name]['instance_settings']['user_register_form'] = FALSE; } foreach ($info as $type => $data) { - $this->assertIdentical(field_info_field_settings($type), $data['settings'], "field_info_field_settings returns {$type}'s field settings"); - $this->assertIdentical(field_info_instance_settings($type), $data['instance_settings'], "field_info_field_settings returns {$type}'s field instance settings"); + $this->assertIdentical(field_info_field_settings($type), $data['settings'], format_string("field_info_field_settings returns %type's field settings", array('%type' => $type))); + $this->assertIdentical(field_info_instance_settings($type), $data['instance_settings'], format_string("field_info_field_settings returns %type's field instance settings", array('%type' => $type))); } $info = field_test_field_widget_info(); foreach ($info as $type => $data) { - $this->assertIdentical(field_info_widget_settings($type), $data['settings'], "field_info_widget_settings returns {$type}'s widget settings"); + $this->assertIdentical(field_info_widget_settings($type), $data['settings'], format_string("field_info_widget_settings returns %type's widget settings", array('%type' => $type))); } $info = field_test_field_formatter_info(); foreach ($info as $type => $data) { - $this->assertIdentical(field_info_formatter_settings($type), $data['settings'], "field_info_formatter_settings returns {$type}'s formatter settings"); + $this->assertIdentical(field_info_formatter_settings($type), $data['settings'], format_string("field_info_formatter_settings returns %type's formatter settings", array('%type' => $type))); } } + + /** + * Tests that the field info cache can be built correctly. + */ + function testFieldInfoCache() { + // Create a test field and ensure it's in the array returned by + // field_info_fields(). + $field_name = drupal_strtolower($this->randomName()); + $field = array( + 'field_name' => $field_name, + 'type' => 'test_field', + ); + field_create_field($field); + $fields = field_info_fields(); + $this->assertTrue(isset($fields[$field_name]), 'The test field is initially found in the array returned by field_info_fields().'); + + // Now rebuild the field info cache, and set a variable which will cause + // the cache to be cleared while it's being rebuilt; see + // field_test_entity_info(). Ensure the test field is still in the returned + // array. + field_info_cache_clear(); + variable_set('field_test_clear_info_cache_in_hook_entity_info', TRUE); + $fields = field_info_fields(); + $this->assertTrue(isset($fields[$field_name]), 'The test field is found in the array returned by field_info_fields() even if its cache is cleared while being rebuilt.'); + } } class FieldFormTestCase extends FieldTestCase { @@ -1597,7 +1925,7 @@ // Display creation form. $this->drupalGet('test-entity/add/test-bundle'); - $this->assertFieldByName("{$this->field_name}[$langcode]", '', t('Widget is displayed.')); + $this->assertFieldByName("{$this->field_name}[$langcode]", '', 'Widget is displayed.'); // Create entity with three values. $edit = array("{$this->field_name}[$langcode]" => '1, 2, 3'); @@ -1611,12 +1939,12 @@ // Display the form, check that the values are correctly filled in. $this->drupalGet('test-entity/manage/' . $id . '/edit'); - $this->assertFieldByName("{$this->field_name}[$langcode]", '1, 2, 3', t('Widget is displayed.')); + $this->assertFieldByName("{$this->field_name}[$langcode]", '1, 2, 3', 'Widget is displayed.'); // Submit the form with more values than the field accepts. $edit = array("{$this->field_name}[$langcode]" => '1, 2, 3, 4, 5'); $this->drupalPost(NULL, $edit, t('Save')); - $this->assertRaw('this field cannot hold more than 4 values', t('Form validation failed.')); + $this->assertRaw('this field cannot hold more than 4 values', 'Form validation failed.'); // Check that the field values were not submitted. $this->assertFieldValues($entity_init, $this->field_name, $langcode, array(1, 2, 3)); } @@ -1650,9 +1978,21 @@ $langcode = LANGUAGE_NONE; + // Test that the form structure includes full information for each delta + // apart from #access. + $entity_type = 'test_entity'; + $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + + $form = array(); + $form_state = form_state_defaults(); + field_attach_form($entity_type, $entity, $form, $form_state); + + $this->assertEqual($form[$field_name_no_access][$langcode][0]['value']['#entity_type'], $entity_type, 'The correct entity type is set in the field structure.'); + $this->assertFalse($form[$field_name_no_access]['#access'], 'Field #access is FALSE for the field without edit access.'); + // Display creation form. $this->drupalGet('test-entity/add/test-bundle'); - $this->assertNoFieldByName("{$field_name_no_access}[$langcode][0][value]", '', t('Widget is not displayed if field access is denied.')); + $this->assertNoFieldByName("{$field_name_no_access}[$langcode][0][value]", '', 'Widget is not displayed if field access is denied.'); // Create entity. $edit = array("{$field_name}[$langcode][0][value]" => 1); @@ -1662,8 +2002,8 @@ // Check that the default value was saved. $entity = field_test_entity_test_load($id); - $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, t('Default value was saved for the field with no edit access.')); - $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 1, t('Entered value vas saved for the field with edit access.')); + $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, 'Default value was saved for the field with no edit access.'); + $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 1, 'Entered value vas saved for the field with edit access.'); // Create a new revision. $edit = array("{$field_name}[$langcode][0][value]" => 2, 'revision' => TRUE); @@ -1671,13 +2011,13 @@ // Check that the new revision has the expected values. $entity = field_test_entity_test_load($id); - $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, t('New revision has the expected value for the field with no edit access.')); - $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 2, t('New revision has the expected value for the field with edit access.')); + $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, 'New revision has the expected value for the field with no edit access.'); + $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 2, 'New revision has the expected value for the field with edit access.'); // Check that the revision is also saved in the revisions table. $entity = field_test_entity_test_load($id, $entity->ftvid); - $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, t('New revision has the expected value for the field with no edit access.')); - $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 2, t('New revision has the expected value for the field with edit access.')); + $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, 'New revision has the expected value for the field with no edit access.'); + $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 2, 'New revision has the expected value for the field with edit access.'); } /** @@ -1709,10 +2049,10 @@ // Display the 'combined form'. $this->drupalGet('test-entity/nested/1/2'); - $this->assertFieldByName('field_single[und][0][value]', 0, t('Entity 1: field_single value appears correctly is the form.')); - $this->assertFieldByName('field_unlimited[und][0][value]', 1, t('Entity 1: field_unlimited value 0 appears correctly is the form.')); - $this->assertFieldByName('entity_2[field_single][und][0][value]', 10, t('Entity 2: field_single value appears correctly is the form.')); - $this->assertFieldByName('entity_2[field_unlimited][und][0][value]', 11, t('Entity 2: field_unlimited value 0 appears correctly is the form.')); + $this->assertFieldByName('field_single[und][0][value]', 0, 'Entity 1: field_single value appears correctly is the form.'); + $this->assertFieldByName('field_unlimited[und][0][value]', 1, 'Entity 1: field_unlimited value 0 appears correctly is the form.'); + $this->assertFieldByName('entity_2[field_single][und][0][value]', 10, 'Entity 2: field_single value appears correctly is the form.'); + $this->assertFieldByName('entity_2[field_unlimited][und][0][value]', 11, 'Entity 2: field_unlimited value 0 appears correctly is the form.'); // Submit the form and check that the entities are updated accordingly. $edit = array( @@ -1738,16 +2078,16 @@ 'field_unlimited[und][1][value]' => -1, ); $this->drupalPost('test-entity/nested/1/2', $edit, t('Save')); - $this->assertRaw(t('%label does not accept the value -1', array('%label' => 'Unlimited field')), t('Entity 1: the field validation error was reported.')); + $this->assertRaw(t('%label does not accept the value -1', array('%label' => 'Unlimited field')), 'Entity 1: the field validation error was reported.'); $error_field = $this->xpath('//input[@id=:id and contains(@class, "error")]', array(':id' => 'edit-field-unlimited-und-1-value')); - $this->assertTrue($error_field, t('Entity 1: the error was flagged on the correct element.')); + $this->assertTrue($error_field, 'Entity 1: the error was flagged on the correct element.'); $edit = array( 'entity_2[field_unlimited][und][1][value]' => -1, ); $this->drupalPost('test-entity/nested/1/2', $edit, t('Save')); - $this->assertRaw(t('%label does not accept the value -1', array('%label' => 'Unlimited field')), t('Entity 2: the field validation error was reported.')); + $this->assertRaw(t('%label does not accept the value -1', array('%label' => 'Unlimited field')), 'Entity 2: the field validation error was reported.'); $error_field = $this->xpath('//input[@id=:id and contains(@class, "error")]', array(':id' => 'edit-entity-2-field-unlimited-und-1-value')); - $this->assertTrue($error_field, t('Entity 2: the error was flagged on the correct element.')); + $this->assertTrue($error_field, 'Entity 2: the error was flagged on the correct element.'); // Test that reordering works on both entities. $edit = array( @@ -1767,10 +2107,10 @@ // 'Add more' button in the first entity: $this->drupalGet('test-entity/nested/1/2'); $this->drupalPostAJAX(NULL, array(), 'field_unlimited_add_more'); - $this->assertFieldByName('field_unlimited[und][0][value]', 3, t('Entity 1: field_unlimited value 0 appears correctly is the form.')); - $this->assertFieldByName('field_unlimited[und][1][value]', 2, t('Entity 1: field_unlimited value 1 appears correctly is the form.')); - $this->assertFieldByName('field_unlimited[und][2][value]', '', t('Entity 1: field_unlimited value 2 appears correctly is the form.')); - $this->assertFieldByName('field_unlimited[und][3][value]', '', t('Entity 1: an empty widget was added for field_unlimited value 3.')); + $this->assertFieldByName('field_unlimited[und][0][value]', 3, 'Entity 1: field_unlimited value 0 appears correctly is the form.'); + $this->assertFieldByName('field_unlimited[und][1][value]', 2, 'Entity 1: field_unlimited value 1 appears correctly is the form.'); + $this->assertFieldByName('field_unlimited[und][2][value]', '', 'Entity 1: field_unlimited value 2 appears correctly is the form.'); + $this->assertFieldByName('field_unlimited[und][3][value]', '', 'Entity 1: an empty widget was added for field_unlimited value 3.'); // 'Add more' button in the first entity (changing field values): $edit = array( 'entity_2[field_unlimited][und][0][value]' => 13, @@ -1778,10 +2118,10 @@ 'entity_2[field_unlimited][und][2][value]' => 15, ); $this->drupalPostAJAX(NULL, $edit, 'entity_2_field_unlimited_add_more'); - $this->assertFieldByName('entity_2[field_unlimited][und][0][value]', 13, t('Entity 2: field_unlimited value 0 appears correctly is the form.')); - $this->assertFieldByName('entity_2[field_unlimited][und][1][value]', 14, t('Entity 2: field_unlimited value 1 appears correctly is the form.')); - $this->assertFieldByName('entity_2[field_unlimited][und][2][value]', 15, t('Entity 2: field_unlimited value 2 appears correctly is the form.')); - $this->assertFieldByName('entity_2[field_unlimited][und][3][value]', '', t('Entity 2: an empty widget was added for field_unlimited value 3.')); + $this->assertFieldByName('entity_2[field_unlimited][und][0][value]', 13, 'Entity 2: field_unlimited value 0 appears correctly is the form.'); + $this->assertFieldByName('entity_2[field_unlimited][und][1][value]', 14, 'Entity 2: field_unlimited value 1 appears correctly is the form.'); + $this->assertFieldByName('entity_2[field_unlimited][und][2][value]', 15, 'Entity 2: field_unlimited value 2 appears correctly is the form.'); + $this->assertFieldByName('entity_2[field_unlimited][und][3][value]', '', 'Entity 2: an empty widget was added for field_unlimited value 3.'); // Save the form and check values are saved correclty. $this->drupalPost(NULL, array(), t('Save')); field_cache_clear(); @@ -1852,9 +2192,9 @@ $this->drupalSetContent(drupal_render($output)); $settings = field_info_formatter_settings('field_test_default'); $setting = $settings['test_formatter_setting']; - $this->assertText($this->label, t('Label was displayed.')); + $this->assertText($this->label, 'Label was displayed.'); foreach ($this->values as $delta => $value) { - $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); + $this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta))); } // Check that explicit display settings are used. @@ -1866,16 +2206,17 @@ 'alter' => TRUE, ), ); - $output = field_view_field('test_entity', $this->entity, $this->field_name, $display); + $output = field_view_field('test_entity', $this->entity, $this->field_name, $display, LANGUAGE_NONE); $this->drupalSetContent(drupal_render($output)); $setting = $display['settings']['test_formatter_setting_multiple']; - $this->assertNoText($this->label, t('Label was not displayed.')); - $this->assertText('field_test_field_attach_view_alter', t('Alter fired, display passed.')); + $this->assertNoText($this->label, 'Label was not displayed.'); + $this->assertText('field_test_field_attach_view_alter', 'Alter fired, display passed.'); + $this->assertText('field language is ' . LANGUAGE_NONE, 'Language is placed onto the context.'); $array = array(); foreach ($this->values as $delta => $value) { $array[] = $delta . ':' . $value['value']; } - $this->assertText($setting . '|' . implode('|', $array), t('Values were displayed with expected setting.')); + $this->assertText($setting . '|' . implode('|', $array), 'Values were displayed with expected setting.'); // Check the prepare_view steps are invoked. $display = array( @@ -1889,10 +2230,10 @@ $view = drupal_render($output); $this->drupalSetContent($view); $setting = $display['settings']['test_formatter_setting_additional']; - $this->assertNoText($this->label, t('Label was not displayed.')); - $this->assertNoText('field_test_field_attach_view_alter', t('Alter not fired.')); + $this->assertNoText($this->label, 'Label was not displayed.'); + $this->assertNoText('field_test_field_attach_view_alter', 'Alter not fired.'); foreach ($this->values as $delta => $value) { - $this->assertText($setting . '|' . $value['value'] . '|' . ($value['value'] + 1), t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); + $this->assertText($setting . '|' . $value['value'] . '|' . ($value['value'] + 1), format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta))); } // View mode: check that display settings specified in the instance are @@ -1900,9 +2241,9 @@ $output = field_view_field('test_entity', $this->entity, $this->field_name, 'teaser'); $this->drupalSetContent(drupal_render($output)); $setting = $this->instance['display']['teaser']['settings']['test_formatter_setting']; - $this->assertText($this->label, t('Label was displayed.')); + $this->assertText($this->label, 'Label was displayed.'); foreach ($this->values as $delta => $value) { - $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); + $this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta))); } // Unknown view mode: check that display settings for 'default' view mode @@ -1910,9 +2251,9 @@ $output = field_view_field('test_entity', $this->entity, $this->field_name, 'unknown_view_mode'); $this->drupalSetContent(drupal_render($output)); $setting = $this->instance['display']['default']['settings']['test_formatter_setting']; - $this->assertText($this->label, t('Label was displayed.')); + $this->assertText($this->label, 'Label was displayed.'); foreach ($this->values as $delta => $value) { - $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); + $this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta))); } } @@ -1927,7 +2268,7 @@ $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; $output = field_view_value('test_entity', $this->entity, $this->field_name, $item); $this->drupalSetContent(drupal_render($output)); - $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); + $this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta))); } // Check that explicit display settings are used. @@ -1943,7 +2284,7 @@ $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; $output = field_view_value('test_entity', $this->entity, $this->field_name, $item, $display); $this->drupalSetContent(drupal_render($output)); - $this->assertText($setting . '|0:' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); + $this->assertText($setting . '|0:' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta))); } // Check that prepare_view steps are invoked. @@ -1959,7 +2300,7 @@ $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; $output = field_view_value('test_entity', $this->entity, $this->field_name, $item, $display); $this->drupalSetContent(drupal_render($output)); - $this->assertText($setting . '|' . $value['value'] . '|' . ($value['value'] + 1), t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); + $this->assertText($setting . '|' . $value['value'] . '|' . ($value['value'] + 1), format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta))); } // View mode: check that display settings specified in the instance are @@ -1969,7 +2310,7 @@ $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; $output = field_view_value('test_entity', $this->entity, $this->field_name, $item, 'teaser'); $this->drupalSetContent(drupal_render($output)); - $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); + $this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta))); } // Unknown view mode: check that display settings for 'default' view mode @@ -1979,7 +2320,7 @@ $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; $output = field_view_value('test_entity', $this->entity, $this->field_name, $item, 'unknown_view_mode'); $this->drupalSetContent(drupal_render($output)); - $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); + $this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta))); } } } @@ -2022,18 +2363,18 @@ $record['data'] = unserialize($record['data']); // Ensure that basic properties are preserved. - $this->assertEqual($record['field_name'], $field_definition['field_name'], t('The field name is properly saved.')); - $this->assertEqual($record['type'], $field_definition['type'], t('The field type is properly saved.')); + $this->assertEqual($record['field_name'], $field_definition['field_name'], 'The field name is properly saved.'); + $this->assertEqual($record['type'], $field_definition['type'], 'The field type is properly saved.'); // Ensure that cardinality defaults to 1. - $this->assertEqual($record['cardinality'], 1, t('Cardinality defaults to 1.')); + $this->assertEqual($record['cardinality'], 1, 'Cardinality defaults to 1.'); // Ensure that default settings are present. $field_type = field_info_field_types($field_definition['type']); - $this->assertIdentical($record['data']['settings'], $field_type['settings'], t('Default field settings have been written.')); + $this->assertIdentical($record['data']['settings'], $field_type['settings'], 'Default field settings have been written.'); // Ensure that default storage was set. - $this->assertEqual($record['storage_type'], variable_get('field_storage_default'), t('The field type is properly saved.')); + $this->assertEqual($record['storage_type'], variable_get('field_storage_default'), 'The field type is properly saved.'); // Guarantee that the name is unique. try { @@ -2160,7 +2501,42 @@ // Read the field back. $field = field_read_field($field_definition['field_name']); - $this->assertTrue($field_definition < $field, t('The field was properly read.')); + $this->assertTrue($field_definition < $field, 'The field was properly read.'); + } + + /** + * Tests reading field definitions. + */ + function testReadFields() { + $field_definition = array( + 'field_name' => 'field_1', + 'type' => 'test_field', + ); + field_create_field($field_definition); + + // Check that 'single column' criteria works. + $fields = field_read_fields(array('field_name' => $field_definition['field_name'])); + $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.'); + + // Check that 'multi column' criteria works. + $fields = field_read_fields(array('field_name' => $field_definition['field_name'], 'type' => $field_definition['type'])); + $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.'); + $fields = field_read_fields(array('field_name' => $field_definition['field_name'], 'type' => 'foo')); + $this->assertTrue(empty($fields), 'No field was found.'); + + // Create an instance of the field. + $instance_definition = array( + 'field_name' => $field_definition['field_name'], + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + ); + field_create_instance($instance_definition); + + // Check that criteria spanning over the field_config_instance table work. + $fields = field_read_fields(array('entity_type' => $instance_definition['entity_type'], 'bundle' => $instance_definition['bundle'])); + $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.'); + $fields = field_read_fields(array('entity_type' => $instance_definition['entity_type'], 'field_name' => $instance_definition['field_name'])); + $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.'); } /** @@ -2175,7 +2551,7 @@ field_create_field($field_definition); $field = field_read_field($field_definition['field_name']); $expected_indexes = array('value' => array('value')); - $this->assertEqual($field['indexes'], $expected_indexes, t('Field type indexes saved by default')); + $this->assertEqual($field['indexes'], $expected_indexes, 'Field type indexes saved by default'); // Check that indexes specified by the field definition override the field // type indexes. @@ -2189,7 +2565,7 @@ field_create_field($field_definition); $field = field_read_field($field_definition['field_name']); $expected_indexes = array('value' => array()); - $this->assertEqual($field['indexes'], $expected_indexes, t('Field definition indexes override field type indexes')); + $this->assertEqual($field['indexes'], $expected_indexes, 'Field definition indexes override field type indexes'); // Check that indexes specified by the field definition add to the field // type indexes. @@ -2203,7 +2579,7 @@ field_create_field($field_definition); $field = field_read_field($field_definition['field_name']); $expected_indexes = array('value' => array('value'), 'value_2' => array('value')); - $this->assertEqual($field['indexes'], $expected_indexes, t('Field definition indexes are merged with field type indexes')); + $this->assertEqual($field['indexes'], $expected_indexes, 'Field definition indexes are merged with field type indexes'); } /** @@ -2234,41 +2610,41 @@ // Test that the first field is not deleted, and then delete it. $field = field_read_field($this->field['field_name'], array('include_deleted' => TRUE)); - $this->assertTrue(!empty($field) && empty($field['deleted']), t('A new field is not marked for deletion.')); + $this->assertTrue(!empty($field) && empty($field['deleted']), 'A new field is not marked for deletion.'); field_delete_field($this->field['field_name']); // Make sure that the field is marked as deleted when it is specifically // loaded. $field = field_read_field($this->field['field_name'], array('include_deleted' => TRUE)); - $this->assertTrue(!empty($field['deleted']), t('A deleted field is marked for deletion.')); + $this->assertTrue(!empty($field['deleted']), 'A deleted field is marked for deletion.'); // Make sure that this field's instance is marked as deleted when it is // specifically loaded. $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE)); - $this->assertTrue(!empty($instance['deleted']), t('An instance for a deleted field is marked for deletion.')); + $this->assertTrue(!empty($instance['deleted']), 'An instance for a deleted field is marked for deletion.'); // Try to load the field normally and make sure it does not show up. $field = field_read_field($this->field['field_name']); - $this->assertTrue(empty($field), t('A deleted field is not loaded by default.')); + $this->assertTrue(empty($field), 'A deleted field is not loaded by default.'); // Try to load the instance normally and make sure it does not show up. $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $this->assertTrue(empty($instance), t('An instance for a deleted field is not loaded by default.')); + $this->assertTrue(empty($instance), 'An instance for a deleted field is not loaded by default.'); // Make sure the other field (and its field instance) are not deleted. $another_field = field_read_field($this->another_field['field_name']); - $this->assertTrue(!empty($another_field) && empty($another_field['deleted']), t('A non-deleted field is not marked for deletion.')); + $this->assertTrue(!empty($another_field) && empty($another_field['deleted']), 'A non-deleted field is not marked for deletion.'); $another_instance = field_read_instance('test_entity', $this->another_instance_definition['field_name'], $this->another_instance_definition['bundle']); - $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), t('An instance of a non-deleted field is not marked for deletion.')); + $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), 'An instance of a non-deleted field is not marked for deletion.'); // Try to create a new field the same name as a deleted field and // write data into it. field_create_field($this->field); field_create_instance($this->instance_definition); $field = field_read_field($this->field['field_name']); - $this->assertTrue(!empty($field) && empty($field['deleted']), t('A new field with a previously used name is created.')); + $this->assertTrue(!empty($field) && empty($field['deleted']), 'A new field with a previously used name is created.'); $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $this->assertTrue(!empty($instance) && empty($instance['deleted']), t('A new instance for a previously used field name is created.')); + $this->assertTrue(!empty($instance) && empty($instance['deleted']), 'A new instance for a previously used field name is created.'); // Save an entity with data for the field $entity = field_test_create_stub_entity(0, 0, $instance['bundle']); @@ -2421,18 +2797,18 @@ // Read the field. $field = field_read_field($field_name); - $this->assertTrue($field_definition <= $field, t('The field was properly read.')); + $this->assertTrue($field_definition <= $field, 'The field was properly read.'); module_disable($modules, FALSE); $fields = field_read_fields(array('field_name' => $field_name), array('include_inactive' => TRUE)); - $this->assertTrue(isset($fields[$field_name]) && $field_definition < $field, t('The field is properly read when explicitly fetching inactive fields.')); + $this->assertTrue(isset($fields[$field_name]) && $field_definition < $field, 'The field is properly read when explicitly fetching inactive fields.'); // Re-enable modules one by one, and check that the field is still inactive // while some modules remain disabled. while ($modules) { $field = field_read_field($field_name); - $this->assertTrue(empty($field), t('%modules disabled. The field is marked inactive.', array('%modules' => implode(', ', $modules)))); + $this->assertTrue(empty($field), format_string('%modules disabled. The field is marked inactive.', array('%modules' => implode(', ', $modules)))); $module = array_shift($modules); module_enable(array($module), FALSE); @@ -2441,7 +2817,7 @@ // Check that the field is active again after all modules have been // enabled. $field = field_read_field($field_name); - $this->assertTrue($field_definition <= $field, t('The field was was marked active.')); + $this->assertTrue($field_definition <= $field, 'The field was was marked active.'); } } @@ -2493,17 +2869,17 @@ $formatter_type = field_info_formatter_types($field_type['default_formatter']); // Check that default values are set. - $this->assertIdentical($record['data']['required'], FALSE, t('Required defaults to false.')); - $this->assertIdentical($record['data']['label'], $this->instance_definition['field_name'], t('Label defaults to field name.')); - $this->assertIdentical($record['data']['description'], '', t('Description defaults to empty string.')); - $this->assertIdentical($record['data']['widget']['type'], $field_type['default_widget'], t('Default widget has been written.')); - $this->assertTrue(isset($record['data']['display']['default']), t('Display for "full" view_mode has been written.')); - $this->assertIdentical($record['data']['display']['default']['type'], $field_type['default_formatter'], t('Default formatter for "full" view_mode has been written.')); + $this->assertIdentical($record['data']['required'], FALSE, 'Required defaults to false.'); + $this->assertIdentical($record['data']['label'], $this->instance_definition['field_name'], 'Label defaults to field name.'); + $this->assertIdentical($record['data']['description'], '', 'Description defaults to empty string.'); + $this->assertIdentical($record['data']['widget']['type'], $field_type['default_widget'], 'Default widget has been written.'); + $this->assertTrue(isset($record['data']['display']['default']), 'Display for "full" view_mode has been written.'); + $this->assertIdentical($record['data']['display']['default']['type'], $field_type['default_formatter'], 'Default formatter for "full" view_mode has been written.'); // Check that default settings are set. - $this->assertIdentical($record['data']['settings'], $field_type['instance_settings'] , t('Default instance settings have been written.')); - $this->assertIdentical($record['data']['widget']['settings'], $widget_type['settings'] , t('Default widget settings have been written.')); - $this->assertIdentical($record['data']['display']['default']['settings'], $formatter_type['settings'], t('Default formatter settings for "full" view_mode have been written.')); + $this->assertIdentical($record['data']['settings'], $field_type['instance_settings'] , 'Default instance settings have been written.'); + $this->assertIdentical($record['data']['widget']['settings'], $widget_type['settings'] , 'Default widget settings have been written.'); + $this->assertIdentical($record['data']['display']['default']['settings'], $formatter_type['settings'], 'Default formatter settings for "full" view_mode have been written.'); // Guarantee that the field/bundle combination is unique. try { @@ -2568,7 +2944,7 @@ // Read the instance back. $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $this->assertTrue($this->instance_definition < $instance, t('The field was properly read.')); + $this->assertTrue($this->instance_definition < $instance, 'The field was properly read.'); } /** @@ -2591,13 +2967,13 @@ field_update_instance($instance); $instance_new = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $this->assertEqual($instance['required'], $instance_new['required'], t('"required" change is saved')); - $this->assertEqual($instance['label'], $instance_new['label'], t('"label" change is saved')); - $this->assertEqual($instance['description'], $instance_new['description'], t('"description" change is saved')); - $this->assertEqual($instance['widget']['settings']['test_widget_setting'], $instance_new['widget']['settings']['test_widget_setting'], t('Widget setting change is saved')); - $this->assertEqual($instance['widget']['weight'], $instance_new['widget']['weight'], t('Widget weight change is saved')); - $this->assertEqual($instance['display']['default']['settings']['test_formatter_setting'], $instance_new['display']['default']['settings']['test_formatter_setting'], t('Formatter setting change is saved')); - $this->assertEqual($instance['display']['default']['weight'], $instance_new['display']['default']['weight'], t('Widget weight change is saved')); + $this->assertEqual($instance['required'], $instance_new['required'], '"required" change is saved'); + $this->assertEqual($instance['label'], $instance_new['label'], '"label" change is saved'); + $this->assertEqual($instance['description'], $instance_new['description'], '"description" change is saved'); + $this->assertEqual($instance['widget']['settings']['test_widget_setting'], $instance_new['widget']['settings']['test_widget_setting'], 'Widget setting change is saved'); + $this->assertEqual($instance['widget']['weight'], $instance_new['widget']['weight'], 'Widget weight change is saved'); + $this->assertEqual($instance['display']['default']['settings']['test_formatter_setting'], $instance_new['display']['default']['settings']['test_formatter_setting'], 'Formatter setting change is saved'); + $this->assertEqual($instance['display']['default']['weight'], $instance_new['display']['default']['weight'], 'Widget weight change is saved'); // Check that changing widget and formatter types updates the default settings. $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); @@ -2606,13 +2982,13 @@ field_update_instance($instance); $instance_new = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $this->assertEqual($instance['widget']['type'], $instance_new['widget']['type'] , t('Widget type change is saved.')); + $this->assertEqual($instance['widget']['type'], $instance_new['widget']['type'] , 'Widget type change is saved.'); $settings = field_info_widget_settings($instance_new['widget']['type']); - $this->assertIdentical($settings, array_intersect_key($instance_new['widget']['settings'], $settings) , t('Widget type change updates default settings.')); - $this->assertEqual($instance['display']['default']['type'], $instance_new['display']['default']['type'] , t('Formatter type change is saved.')); + $this->assertIdentical($settings, array_intersect_key($instance_new['widget']['settings'], $settings) , 'Widget type change updates default settings.'); + $this->assertEqual($instance['display']['default']['type'], $instance_new['display']['default']['type'] , 'Formatter type change is saved.'); $info = field_info_formatter_types($instance_new['display']['default']['type']); $settings = $info['settings']; - $this->assertIdentical($settings, array_intersect_key($instance_new['display']['default']['settings'], $settings) , t('Changing formatter type updates default settings.')); + $this->assertIdentical($settings, array_intersect_key($instance_new['display']['default']['settings'], $settings) , 'Changing formatter type updates default settings.'); // Check that adding a new view mode is saved and gets default settings. $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); @@ -2620,11 +2996,11 @@ field_update_instance($instance); $instance_new = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $this->assertTrue(isset($instance_new['display']['teaser']), t('Display for the new view_mode has been written.')); - $this->assertIdentical($instance_new['display']['teaser']['type'], $field_type['default_formatter'], t('Default formatter for the new view_mode has been written.')); + $this->assertTrue(isset($instance_new['display']['teaser']), 'Display for the new view_mode has been written.'); + $this->assertIdentical($instance_new['display']['teaser']['type'], $field_type['default_formatter'], 'Default formatter for the new view_mode has been written.'); $info = field_info_formatter_types($instance_new['display']['teaser']['type']); $settings = $info['settings']; - $this->assertIdentical($settings, $instance_new['display']['teaser']['settings'] , t('Default formatter settings for the new view_mode have been written.')); + $this->assertIdentical($settings, $instance_new['display']['teaser']['settings'] , 'Default formatter settings for the new view_mode have been written.'); // TODO: test failures. } @@ -2646,26 +3022,26 @@ // Test that the first instance is not deleted, and then delete it. $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE)); - $this->assertTrue(!empty($instance) && empty($instance['deleted']), t('A new field instance is not marked for deletion.')); + $this->assertTrue(!empty($instance) && empty($instance['deleted']), 'A new field instance is not marked for deletion.'); field_delete_instance($instance); // Make sure the instance is marked as deleted when the instance is // specifically loaded. $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE)); - $this->assertTrue(!empty($instance['deleted']), t('A deleted field instance is marked for deletion.')); + $this->assertTrue(!empty($instance['deleted']), 'A deleted field instance is marked for deletion.'); // Try to load the instance normally and make sure it does not show up. $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $this->assertTrue(empty($instance), t('A deleted field instance is not loaded by default.')); + $this->assertTrue(empty($instance), 'A deleted field instance is not loaded by default.'); // Make sure the other field instance is not deleted. $another_instance = field_read_instance('test_entity', $this->another_instance_definition['field_name'], $this->another_instance_definition['bundle']); - $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), t('A non-deleted field instance is not marked for deletion.')); + $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), 'A non-deleted field instance is not marked for deletion.'); // Make sure the field is deleted when its last instance is deleted. field_delete_instance($another_instance); $field = field_read_field($another_instance['field_name'], array('include_deleted' => TRUE)); - $this->assertTrue(!empty($field['deleted']), t('A deleted field is marked for deletion after all its instances have been marked for deletion.')); + $this->assertTrue(!empty($field['deleted']), 'A deleted field is marked for deletion after all its instances have been marked for deletion.'); } } @@ -2732,17 +3108,17 @@ $available_languages = field_available_languages($this->entity_type, $this->field); foreach ($available_languages as $delta => $langcode) { if ($langcode != 'xx' && $langcode != 'en') { - $this->assertTrue(in_array($langcode, $enabled_languages), t('%language is an enabled language.', array('%language' => $langcode))); + $this->assertTrue(in_array($langcode, $enabled_languages), format_string('%language is an enabled language.', array('%language' => $langcode))); } } - $this->assertTrue(in_array('xx', $available_languages), t('%language was made available.', array('%language' => 'xx'))); - $this->assertFalse(in_array('en', $available_languages), t('%language was made unavailable.', array('%language' => 'en'))); + $this->assertTrue(in_array('xx', $available_languages), format_string('%language was made available.', array('%language' => 'xx'))); + $this->assertFalse(in_array('en', $available_languages), format_string('%language was made unavailable.', array('%language' => 'en'))); // Test field_available_languages() behavior for untranslatable fields. $this->field['translatable'] = FALSE; field_update_field($this->field); $available_languages = field_available_languages($this->entity_type, $this->field); - $this->assertTrue(count($available_languages) == 1 && $available_languages[0] === LANGUAGE_NONE, t('For untranslatable fields only LANGUAGE_NONE is available.')); + $this->assertTrue(count($available_languages) == 1 && $available_languages[0] === LANGUAGE_NONE, 'For untranslatable fields only LANGUAGE_NONE is available.'); } /** @@ -2777,10 +3153,10 @@ $hash = hash('sha256', serialize(array($entity_type, $entity, $this->field_name, $langcode, $values[$langcode]))); // Check whether the parameters passed to _field_invoke() were correctly // forwarded to the callback function. - $this->assertEqual($hash, $result, t('The result for %language is correctly stored.', array('%language' => $langcode))); + $this->assertEqual($hash, $result, format_string('The result for %language is correctly stored.', array('%language' => $langcode))); } - $this->assertEqual(count($results), count($available_languages), t('No unavailable language has been processed.')); + $this->assertEqual(count($results), count($available_languages), 'No unavailable language has been processed.'); } /** @@ -2794,7 +3170,7 @@ $options = array(); $entities = array(); $entity_type = 'test_entity'; - $entity_count = mt_rand(2, 5); + $entity_count = 5; $available_languages = field_available_languages($this->entity_type, $this->field); for ($id = 1; $id <= $entity_count; ++$id) { @@ -2841,17 +3217,17 @@ $hash = hash('sha256', serialize(array($entity_type, $entities[$id], $this->field_name, $langcode, $values[$id][$langcode]))); // Check whether the parameters passed to _field_invoke_multiple() // were correctly forwarded to the callback function. - $this->assertEqual($hash, $result, t('The result for entity %id/%language is correctly stored.', array('%id' => $id, '%language' => $langcode))); + $this->assertEqual($hash, $result, format_string('The result for entity %id/%language is correctly stored.', array('%id' => $id, '%language' => $langcode))); } } - $this->assertEqual(count($results), count($available_languages), t('No unavailable language has been processed for entity %id.', array('%id' => $id))); + $this->assertEqual(count($results), count($available_languages), format_string('No unavailable language has been processed for entity %id.', array('%id' => $id))); } $null = NULL; $grouped_results = _field_invoke_multiple('test_op_multiple', $entity_type, $entities, $null, $null, $options); foreach ($grouped_results as $id => $results) { foreach ($results as $langcode => $result) { - $this->assertTrue(isset($options['language'][$id]), t('The result language %language for entity %id was correctly suggested (display language: %display_language).', array('%id' => $id, '%language' => $langcode, '%display_language' => $display_language))); + $this->assertTrue(isset($options['language'][$id]), format_string('The result language %language for entity %id was correctly suggested (display language: %display_language).', array('%id' => $id, '%language' => $langcode, '%display_language' => $display_language))); } } } @@ -2863,7 +3239,7 @@ // Enable field translations for nodes. field_test_entity_info_translatable('node', TRUE); $entity_info = entity_get_info('node'); - $this->assertTrue(count($entity_info['translation']), t('Nodes are translatable.')); + $this->assertTrue(count($entity_info['translation']), 'Nodes are translatable.'); // Prepare the field translations. field_test_entity_info_translatable('test_entity', TRUE); @@ -2872,7 +3248,7 @@ $entity = field_test_create_stub_entity($eid, $evid, $this->instance['bundle']); $field_translations = array(); $available_languages = field_available_languages($entity_type, $this->field); - $this->assertTrue(count($available_languages) > 1, t('Field is translatable.')); + $this->assertTrue(count($available_languages) > 1, 'Field is translatable.'); foreach ($available_languages as $langcode) { $field_translations[$langcode] = $this->_generateTestFieldValues($this->field['cardinality']); } @@ -2889,7 +3265,7 @@ foreach ($items as $delta => $item) { $result = $result && $item['value'] == $entity->{$this->field_name}[$langcode][$delta]['value']; } - $this->assertTrue($result, t('%language translation correctly handled.', array('%language' => $langcode))); + $this->assertTrue($result, format_string('%language translation correctly handled.', array('%language' => $langcode))); } } @@ -2945,7 +3321,7 @@ $display_language = field_language($entity_type, $entity, NULL, $requested_language); foreach ($instances as $instance) { $field_name = $instance['field_name']; - $this->assertTrue($display_language[$field_name] == LANGUAGE_NONE, t('The display language for field %field_name is %language.', array('%field_name' => $field_name, '%language' => LANGUAGE_NONE))); + $this->assertTrue($display_language[$field_name] == LANGUAGE_NONE, format_string('The display language for field %field_name is %language.', array('%field_name' => $field_name, '%language' => LANGUAGE_NONE))); } // Test multiple-fields display languages for translatable entities. @@ -2959,20 +3335,20 @@ // As the requested language was not assinged to any field, if the // returned language is defined for the current field, core fallback rules // were successfully applied. - $this->assertTrue(isset($entity->{$field_name}[$langcode]) && $langcode != $requested_language, t('The display language for the field %field_name is %language.', array('%field_name' => $field_name, '%language' => $langcode))); + $this->assertTrue(isset($entity->{$field_name}[$langcode]) && $langcode != $requested_language, format_string('The display language for the field %field_name is %language.', array('%field_name' => $field_name, '%language' => $langcode))); } // Test single-field display language. drupal_static_reset('field_language'); $langcode = field_language($entity_type, $entity, $this->field_name, $requested_language); - $this->assertTrue(isset($entity->{$this->field_name}[$langcode]) && $langcode != $requested_language, t('The display language for the (single) field %field_name is %language.', array('%field_name' => $field_name, '%language' => $langcode))); + $this->assertTrue(isset($entity->{$this->field_name}[$langcode]) && $langcode != $requested_language, format_string('The display language for the (single) field %field_name is %language.', array('%field_name' => $field_name, '%language' => $langcode))); // Test field_language() basic behavior without language fallback. variable_set('field_test_language_fallback', FALSE); $entity->{$this->field_name}[$requested_language] = mt_rand(1, 127); drupal_static_reset('field_language'); $display_language = field_language($entity_type, $entity, $this->field_name, $requested_language); - $this->assertEqual($display_language, $requested_language, t('Display language behave correctly when language fallback is disabled')); + $this->assertEqual($display_language, $requested_language, 'Display language behave correctly when language fallback is disabled'); } /** @@ -3016,7 +3392,7 @@ $entity = field_test_entity_test_load($eid, $evid); foreach ($available_languages as $langcode => $value) { $passed = isset($entity->{$field_name}[$langcode]) && $entity->{$field_name}[$langcode][0]['value'] == $value + 1; - $this->assertTrue($passed, t('The @language translation for revision @revision was correctly stored', array('@language' => $langcode, '@revision' => $entity->ftvid))); + $this->assertTrue($passed, format_string('The @language translation for revision @revision was correctly stored', array('@language' => $langcode, '@revision' => $entity->ftvid))); } } } diff -Naur drupal-7.14/modules/field/tests/field_test.entity.inc drupal-7.66/modules/field/tests/field_test.entity.inc --- drupal-7.14/modules/field/tests/field_test.entity.inc 2012-05-03 00:10:42.000000000 +0200 +++ drupal-7.66/modules/field/tests/field_test.entity.inc 2019-04-17 22:20:46.000000000 +0200 @@ -9,6 +9,12 @@ * Implements hook_entity_info(). */ function field_test_entity_info() { + // If requested, clear the field cache while this hook is being called. See + // testFieldInfoCache(). + if (variable_get('field_test_clear_info_cache_in_hook_entity_info', FALSE)) { + field_info_cache_clear(); + } + $bundles = variable_get('field_test_bundles', array('test_bundle' => array('label' => 'Test Bundle'))); $test_entity_modes = array( 'full' => array( @@ -23,7 +29,7 @@ return array( 'test_entity' => array( - 'name' => t('Test Entity'), + 'label' => t('Test Entity'), 'fieldable' => TRUE, 'field cache' => FALSE, 'base table' => 'test_entity', @@ -38,7 +44,7 @@ ), // This entity type doesn't get form handling for now... 'test_cacheable_entity' => array( - 'name' => t('Test Entity, cacheable'), + 'label' => t('Test Entity, cacheable'), 'fieldable' => TRUE, 'field cache' => TRUE, 'entity keys' => array( @@ -50,7 +56,7 @@ 'view modes' => $test_entity_modes, ), 'test_entity_bundle_key' => array( - 'name' => t('Test Entity with a bundle key.'), + 'label' => t('Test Entity with a bundle key.'), 'base table' => 'test_entity_bundle_key', 'fieldable' => TRUE, 'field cache' => FALSE, @@ -63,7 +69,7 @@ ), // In this case, the bundle key is not stored in the database. 'test_entity_bundle' => array( - 'name' => t('Test Entity with a specified bundle.'), + 'label' => t('Test Entity with a specified bundle.'), 'base table' => 'test_entity_bundle', 'fieldable' => TRUE, 'controller class' => 'TestEntityBundleController', @@ -77,7 +83,7 @@ ), // @see EntityPropertiesTestCase::testEntityLabel() 'test_entity_no_label' => array( - 'name' => t('Test entity without label'), + 'label' => t('Test entity without label'), 'fieldable' => TRUE, 'field cache' => FALSE, 'base table' => 'test_entity', @@ -90,7 +96,7 @@ 'view modes' => $test_entity_modes, ), 'test_entity_label' => array( - 'name' => t('Test entity label'), + 'label' => t('Test entity label'), 'fieldable' => TRUE, 'field cache' => FALSE, 'base table' => 'test_entity', @@ -104,7 +110,7 @@ 'view modes' => $test_entity_modes, ), 'test_entity_label_callback' => array( - 'name' => t('Test entity label callback'), + 'label' => t('Test entity label callback'), 'fieldable' => TRUE, 'field cache' => FALSE, 'base table' => 'test_entity', diff -Naur drupal-7.14/modules/field/tests/field_test.field.inc drupal-7.66/modules/field/tests/field_test.field.inc --- drupal-7.14/modules/field/tests/field_test.field.inc 2012-05-03 00:10:42.000000000 +0200 +++ drupal-7.66/modules/field/tests/field_test.field.inc 2019-04-17 22:20:46.000000000 +0200 @@ -28,7 +28,9 @@ 'shape' => array( 'label' => t('Shape'), 'description' => t('Another dummy field type.'), - 'settings' => array(), + 'settings' => array( + 'foreign_key_name' => 'shape', + ), 'instance_settings' => array(), 'default_widget' => 'test_field_widget', 'default_formatter' => 'field_test_default', diff -Naur drupal-7.14/modules/field/tests/field_test.info drupal-7.66/modules/field/tests/field_test.info --- drupal-7.14/modules/field/tests/field_test.info 2012-05-03 00:25:55.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 2012-05-02 -version = "7.14" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1335997555" - +datestamp = "1555533576" diff -Naur drupal-7.14/modules/field/tests/field_test.install drupal-7.66/modules/field/tests/field_test.install --- drupal-7.14/modules/field/tests/field_test.install 2012-05-03 00:10:42.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, @@ -132,6 +132,18 @@ ); } else { + $foreign_keys = array(); + // The 'foreign keys' key is not always used in tests. + if (!empty($field['settings']['foreign_key_name'])) { + $foreign_keys['foreign keys'] = array( + // This is a dummy foreign key definition, references a table that + // doesn't exist, but that's not a problem. + $field['settings']['foreign_key_name'] => array( + 'table' => $field['settings']['foreign_key_name'], + 'columns' => array($field['settings']['foreign_key_name'] => 'id'), + ), + ); + } return array( 'columns' => array( 'shape' => array( @@ -145,6 +157,6 @@ 'not null' => FALSE, ), ), - ); + ) + $foreign_keys; } } diff -Naur drupal-7.14/modules/field/tests/field_test.module drupal-7.66/modules/field/tests/field_test.module --- drupal-7.14/modules/field/tests/field_test.module 2012-05-03 00:10:42.000000000 +0200 +++ drupal-7.66/modules/field/tests/field_test.module 2019-04-17 22:20:46.000000000 +0200 @@ -204,10 +204,7 @@ } /** - * Entity label callback. - * - * @param $entity - * The entity object. + * Implements callback_entity_info_label(). * * @return * The label of the entity prefixed with "label callback". @@ -223,6 +220,10 @@ if (!empty($context['display']['settings']['alter'])) { $output['test_field'][] = array('#markup' => 'field_test_field_attach_view_alter'); } + + if (isset($output['test_field'])) { + $output['test_field'][] = array('#markup' => 'field language is ' . $context['language']); + } } /** @@ -259,3 +260,25 @@ break; } } + +/** + * Implements hook_query_TAG_alter() for tag 'efq_table_prefixing_test'. + * + * @see EntityFieldQueryTestCase::testTablePrefixing() + */ +function field_test_query_efq_table_prefixing_test_alter(&$query) { + // Add an additional join onto the entity base table. This will cause an + // 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.14/modules/field/theme/field.tpl.php drupal-7.66/modules/field/theme/field.tpl.php --- drupal-7.14/modules/field/theme/field.tpl.php 2012-05-03 00:10:42.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. @@ -40,10 +42,12 @@ * * @see template_preprocess_field() * @see theme_field() + * + * @ingroup themeable */ ?> ' placeholder. if ($mode) { $content = $match[1]; - $hash = md5($content); + $hash = hash('sha256', $content); $comments[$hash] = $content; return ""; } @@ -1582,21 +1669,30 @@ } /** - * Filter tips callback for URL filter. + * Implements callback_filter_tips(). + * + * Provides help for the URL filter. + * + * @see filter_filter_info() */ function _filter_url_tips($filter, $format, $long = FALSE) { return t('Web page addresses and e-mail addresses turn into links automatically.'); } /** - * Scan input and make sure that all HTML tags are properly closed and nested. + * Implements callback_filter_process(). + * + * Scans the input and makes sure that HTML tags are properly closed. */ function _filter_htmlcorrector($text) { return filter_dom_serialize(filter_dom_load($text)); } /** - * Convert line breaks into

and
in an intelligent fashion. + * Implements callback_filter_process(). + * + * Converts line breaks into

and
in an intelligent fashion. + * * Based on: http://photomatt.net/scripts/autop */ function _filter_autop($text) { @@ -1662,7 +1758,11 @@ } /** - * Filter tips callback for auto-paragraph filter. + * Implements callback_filter_tips(). + * + * Provides help for the auto-paragraph filter. + * + * @see filter_filter_info() */ function _filter_autop_tips($filter, $format, $long = FALSE) { if ($long) { @@ -1674,6 +1774,8 @@ } /** + * Implements callback_filter_process(). + * * Escapes all HTML tags, so they will be visible instead of being effective. */ function _filter_html_escape($text) { @@ -1681,7 +1783,11 @@ } /** - * Filter tips callback for HTML escaping filter. + * Implements callback_filter_tips(). + * + * Provides help for the HTML escaping filter. + * + * @see filter_filter_info() */ function _filter_html_escape_tips($filter, $format, $long = FALSE) { return t('No HTML tags allowed.'); diff -Naur drupal-7.14/modules/filter/filter.pages.inc drupal-7.66/modules/filter/filter.pages.inc --- drupal-7.14/modules/filter/filter.pages.inc 2012-05-03 00:10:42.000000000 +0200 +++ drupal-7.66/modules/filter/filter.pages.inc 2019-04-17 22:20:46.000000000 +0200 @@ -2,17 +2,21 @@ /** * @file - * User page callbacks for the filter module. + * User page callbacks for the Filter module. */ - /** - * Menu callback; show a page with long filter tips. + * Page callback: Displays a page with long filter tips. + * + * @return string + * An HTML-formatted string. + * + * @see filter_menu() + * @see theme_filter_tips() */ -function filter_tips_long() { - $format_id = arg(2); - if ($format_id) { - $output = theme('filter_tips', array('tips' => _filter_tips($format_id, TRUE), 'long' => TRUE)); +function filter_tips_long($format = NULL) { + if (!empty($format)) { + $output = theme('filter_tips', array('tips' => _filter_tips($format->format, TRUE), 'long' => TRUE)); } else { $output = theme('filter_tips', array('tips' => _filter_tips(-1, TRUE), 'long' => TRUE)); @@ -20,13 +24,12 @@ return $output; } - /** * Returns HTML for a set of filter tips. * * @param $variables * An associative array containing: - * - tips: An array containing descriptions and a CSS id in the form of + * - tips: An array containing descriptions and a CSS ID in the form of * 'module-name/filter-id' (only used when $long is TRUE) for each * filter in one or more text formats. Example: * @code @@ -64,7 +67,7 @@ foreach ($tips as $name => $tiplist) { if ($multiple) { $output .= '

'; - $output .= '

' . $name . '

'; + $output .= '

' . check_plain($name) . '

'; } if (count($tiplist) > 0) { diff -Naur drupal-7.14/modules/filter/filter.test drupal-7.66/modules/filter/filter.test --- drupal-7.14/modules/filter/filter.test 2012-05-03 00:10:42.000000000 +0200 +++ drupal-7.66/modules/filter/filter.test 2019-04-17 22:20:46.000000000 +0200 @@ -22,7 +22,7 @@ } /** - * Test CRUD operations for text formats and filters. + * Tests CRUD operations for text formats and filters. */ function testTextFormatCRUD() { // Add a text format with minimum data only. @@ -67,13 +67,22 @@ filter_format_disable($format); $db_format = db_query("SELECT * FROM {filter_format} WHERE format = :format", array(':format' => $format->format))->fetchObject(); - $this->assertFalse($db_format->status, t('Database: Disabled text format is marked as disabled.')); + $this->assertFalse($db_format->status, 'Database: Disabled text format is marked as disabled.'); $formats = filter_formats(); - $this->assertTrue(!isset($formats[$format->format]), t('filter_formats: Disabled text format no longer exists.')); + $this->assertTrue(!isset($formats[$format->format]), 'filter_formats: Disabled text format no longer exists.'); + + // Add a new format to check for Xss in format name. + $format = new stdClass(); + $format->format = 'xss_format'; + $format->name = ''; + filter_format_save($format); + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array(filter_permission_name($format) => 1)); + $this->drupalGet('filter/tips'); + $this->assertNoRaw($format->name, 'Text format name contains no xss.'); } /** - * Verify that a text format is properly stored. + * Verifies that a text format is properly stored. */ function verifyTextFormat($format) { $t_args = array('%format' => $format->name); @@ -83,17 +92,17 @@ ->condition('format', $format->format) ->execute() ->fetchObject(); - $this->assertEqual($db_format->format, $format->format, t('Database: Proper format id for text format %format.', $t_args)); - $this->assertEqual($db_format->name, $format->name, t('Database: Proper title for text format %format.', $t_args)); - $this->assertEqual($db_format->cache, $format->cache, t('Database: Proper cache indicator for text format %format.', $t_args)); - $this->assertEqual($db_format->weight, $format->weight, t('Database: Proper weight for text format %format.', $t_args)); + $this->assertEqual($db_format->format, $format->format, format_string('Database: Proper format id for text format %format.', $t_args)); + $this->assertEqual($db_format->name, $format->name, format_string('Database: Proper title for text format %format.', $t_args)); + $this->assertEqual($db_format->cache, $format->cache, format_string('Database: Proper cache indicator for text format %format.', $t_args)); + $this->assertEqual($db_format->weight, $format->weight, format_string('Database: Proper weight for text format %format.', $t_args)); // Verify filter_format_load(). $filter_format = filter_format_load($format->format); - $this->assertEqual($filter_format->format, $format->format, t('filter_format_load: Proper format id for text format %format.', $t_args)); - $this->assertEqual($filter_format->name, $format->name, t('filter_format_load: Proper title for text format %format.', $t_args)); - $this->assertEqual($filter_format->cache, $format->cache, t('filter_format_load: Proper cache indicator for text format %format.', $t_args)); - $this->assertEqual($filter_format->weight, $format->weight, t('filter_format_load: Proper weight for text format %format.', $t_args)); + $this->assertEqual($filter_format->format, $format->format, format_string('filter_format_load: Proper format id for text format %format.', $t_args)); + $this->assertEqual($filter_format->name, $format->name, format_string('filter_format_load: Proper title for text format %format.', $t_args)); + $this->assertEqual($filter_format->cache, $format->cache, format_string('filter_format_load: Proper cache indicator for text format %format.', $t_args)); + $this->assertEqual($filter_format->weight, $format->weight, format_string('filter_format_load: Proper weight for text format %format.', $t_args)); // Verify the 'cache' text format property according to enabled filters. $filter_info = filter_get_filters(); @@ -107,11 +116,11 @@ break; } } - $this->assertEqual($filter_format->cache, $cacheable, t('Text format contains proper cache property.')); + $this->assertEqual($filter_format->cache, $cacheable, 'Text format contains proper cache property.'); } /** - * Verify that filters are properly stored for a text format. + * Verifies that filters are properly stored for a text format. */ function verifyFilters($format) { // Verify filter database records. @@ -121,20 +130,20 @@ $t_args = array('%format' => $format->name, '%filter' => $name); // Verify that filter status is properly stored. - $this->assertEqual($filter->status, $format_filters[$name]['status'], t('Database: Proper status for %filter in text format %format.', $t_args)); + $this->assertEqual($filter->status, $format_filters[$name]['status'], format_string('Database: Proper status for %filter in text format %format.', $t_args)); // Verify that filter settings were properly stored. - $this->assertEqual(unserialize($filter->settings), isset($format_filters[$name]['settings']) ? $format_filters[$name]['settings'] : array(), t('Database: Proper filter settings for %filter in text format %format.', $t_args)); + $this->assertEqual(unserialize($filter->settings), isset($format_filters[$name]['settings']) ? $format_filters[$name]['settings'] : array(), format_string('Database: Proper filter settings for %filter in text format %format.', $t_args)); // Verify that each filter has a module name assigned. - $this->assertTrue(!empty($filter->module), t('Database: Proper module name for %filter in text format %format.', $t_args)); + $this->assertTrue(!empty($filter->module), format_string('Database: Proper module name for %filter in text format %format.', $t_args)); // Remove the filter from the copy of saved $format to check whether all // filters have been processed later. unset($format_filters[$name]); } // Verify that all filters have been processed. - $this->assertTrue(empty($format_filters), t('Database contains values for all filters in the saved format.')); + $this->assertTrue(empty($format_filters), 'Database contains values for all filters in the saved format.'); // Verify filter_list_format(). $filters = filter_list_format($format->format); @@ -143,23 +152,26 @@ $t_args = array('%format' => $format->name, '%filter' => $name); // Verify that filter status is properly stored. - $this->assertEqual($filter->status, $format_filters[$name]['status'], t('filter_list_format: Proper status for %filter in text format %format.', $t_args)); + $this->assertEqual($filter->status, $format_filters[$name]['status'], format_string('filter_list_format: Proper status for %filter in text format %format.', $t_args)); // Verify that filter settings were properly stored. - $this->assertEqual($filter->settings, isset($format_filters[$name]['settings']) ? $format_filters[$name]['settings'] : array(), t('filter_list_format: Proper filter settings for %filter in text format %format.', $t_args)); + $this->assertEqual($filter->settings, isset($format_filters[$name]['settings']) ? $format_filters[$name]['settings'] : array(), format_string('filter_list_format: Proper filter settings for %filter in text format %format.', $t_args)); // Verify that each filter has a module name assigned. - $this->assertTrue(!empty($filter->module), t('filter_list_format: Proper module name for %filter in text format %format.', $t_args)); + $this->assertTrue(!empty($filter->module), format_string('filter_list_format: Proper module name for %filter in text format %format.', $t_args)); // Remove the filter from the copy of saved $format to check whether all // filters have been processed later. unset($format_filters[$name]); } // Verify that all filters have been processed. - $this->assertTrue(empty($format_filters), t('filter_list_format: Loaded filters contain values for all filters in the saved format.')); + $this->assertTrue(empty($format_filters), 'filter_list_format: Loaded filters contain values for all filters in the saved format.'); } } +/** + * Tests the administrative functionality of the Filter module. + */ class FilterAdminTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -185,6 +197,9 @@ $this->drupalLogin($this->admin_user); } + /** + * Tests the format administration functionality. + */ function testFormatAdmin() { // Add text format. $this->drupalGet('admin/config/content/formats'); @@ -199,14 +214,14 @@ // Verify default weight of the text format. $this->drupalGet('admin/config/content/formats'); - $this->assertFieldByName("formats[$format_id][weight]", 0, t('Text format weight was saved.')); + $this->assertFieldByName("formats[$format_id][weight]", 0, 'Text format weight was saved.'); // Change the weight of the text format. $edit = array( "formats[$format_id][weight]" => 5, ); $this->drupalPost('admin/config/content/formats', $edit, t('Save changes')); - $this->assertFieldByName("formats[$format_id][weight]", 5, t('Text format weight was saved.')); + $this->assertFieldByName("formats[$format_id][weight]", 5, 'Text format weight was saved.'); // Edit text format. $this->drupalGet('admin/config/content/formats'); @@ -216,7 +231,7 @@ // Verify that the custom weight of the text format has been retained. $this->drupalGet('admin/config/content/formats'); - $this->assertFieldByName("formats[$format_id][weight]", 5, t('Text format weight was retained.')); + $this->assertFieldByName("formats[$format_id][weight]", 5, 'Text format weight was retained.'); // Disable text format. $this->assertLinkByHref('admin/config/content/formats/' . $format_id . '/disable'); @@ -225,7 +240,7 @@ // Verify that disabled text format no longer exists. $this->drupalGet('admin/config/content/formats/' . $format_id); - $this->assertResponse(404, t('Disabled text format no longer exists.')); + $this->assertResponse(404, 'Disabled text format no longer exists.'); // Attempt to create a format of the same machine name as the disabled // format but with a different human readable name. @@ -249,7 +264,7 @@ } /** - * Test filter administration functionality. + * Tests filter administration functionality. */ function testFilterAdmin() { // URL filter. @@ -262,44 +277,44 @@ $plain = 'plain_text'; // Check that the fallback format exists and cannot be disabled. - $this->assertTrue($plain == filter_fallback_format(), t('The fallback format is set to plain text.')); + $this->assertTrue($plain == filter_fallback_format(), 'The fallback format is set to plain text.'); $this->drupalGet('admin/config/content/formats'); - $this->assertNoRaw('admin/config/content/formats/' . $plain . '/disable', t('Disable link for the fallback format not found.')); + $this->assertNoRaw('admin/config/content/formats/' . $plain . '/disable', 'Disable link for the fallback format not found.'); $this->drupalGet('admin/config/content/formats/' . $plain . '/disable'); - $this->assertResponse(403, t('The fallback format cannot be disabled.')); + $this->assertResponse(403, 'The fallback format cannot be disabled.'); // Verify access permissions to Full HTML format. - $this->assertTrue(filter_access(filter_format_load($full), $this->admin_user), t('Admin user may use Full HTML.')); - $this->assertFalse(filter_access(filter_format_load($full), $this->web_user), t('Web user may not use Full HTML.')); + $this->assertTrue(filter_access(filter_format_load($full), $this->admin_user), 'Admin user may use Full HTML.'); + $this->assertFalse(filter_access(filter_format_load($full), $this->web_user), 'Web user may not use Full HTML.'); // Add an additional tag. $edit = array(); $edit['filters[filter_html][settings][allowed_html]'] = '