From dc21b826646b6a36bf7db6bf6a06aae6b74634ea Mon Sep 17 00:00:00 2001 From: Bachir Soussi Chiadmi Date: Tue, 24 Apr 2018 23:28:54 +0200 Subject: [PATCH] updated core to 7.58, security update --- CHANGELOG.txt | 399 ++- INSTALL.mysql.txt | 45 + INSTALL.pgsql.txt | 44 + INSTALL.sqlite.txt | 31 + INSTALL.txt | 400 +++ MAINTAINERS.txt | 219 +- UPGRADE.txt | 10 + includes/ajax.inc | 41 +- includes/batch.inc | 4 +- includes/bootstrap.inc | 516 ++- includes/cache.inc | 15 +- includes/common.inc | 260 +- includes/database/database.inc | 72 +- includes/database/mysql/database.inc | 63 +- includes/database/mysql/schema.inc | 14 +- includes/database/pgsql/database.inc | 20 +- includes/database/pgsql/install.inc | 2 +- includes/database/pgsql/select.inc | 4 +- includes/database/query.inc | 8 +- includes/database/schema.inc | 10 +- includes/database/select.inc | 15 + includes/database/sqlite/database.inc | 8 + includes/database/sqlite/install.inc | 2 - includes/database/sqlite/query.inc | 13 +- includes/database/sqlite/schema.inc | 2 +- includes/date.inc | 20 +- includes/entity.inc | 86 +- includes/errors.inc | 17 +- includes/file.inc | 39 +- includes/form.inc | 174 +- includes/install.core.inc | 22 +- includes/install.inc | 10 +- includes/language.inc | 2 +- includes/locale.inc | 64 +- includes/mail.inc | 2 +- includes/menu.inc | 78 +- includes/module.inc | 90 +- includes/path.inc | 3 +- includes/registry.inc | 2 +- includes/request-sanitizer.inc | 82 + includes/session.inc | 6 +- includes/stream_wrappers.inc | 152 +- includes/theme.inc | 40 +- includes/update.inc | 12 + includes/xmlrpcs.inc | 8 + install.php | 26 + misc/ajax.js | 40 +- misc/autocomplete.js | 14 +- misc/drupal.js | 219 +- misc/favicon.ico | Bin 1150 -> 5430 bytes misc/states.js | 6 +- misc/tabledrag.js | 25 +- misc/tableselect.js | 6 +- misc/vertical-tabs.js | 6 + modules/aggregator/aggregator.info | 6 +- modules/aggregator/aggregator.module | 8 + modules/aggregator/aggregator.processor.inc | 2 +- modules/aggregator/aggregator.test | 38 +- modules/aggregator/tests/aggregator_test.info | 6 +- modules/block/block.api.php | 25 + modules/block/block.info | 6 +- modules/block/block.js | 6 +- modules/block/block.module | 76 +- modules/block/block.test | 4 +- modules/block/tests/block_test.info | 6 +- .../block_test_theme/block_test_theme.info | 6 +- modules/blog/blog.info | 6 +- modules/blog/blog.module | 2 +- modules/book/book.info | 6 +- modules/color/color.info | 6 +- modules/color/color.test | 2 +- modules/comment/comment.info | 6 +- modules/comment/comment.module | 7 +- modules/comment/comment.test | 47 +- modules/contact/contact.info | 6 +- modules/contact/contact.module | 9 +- modules/contact/contact.pages.inc | 4 +- modules/contact/contact.test | 22 + modules/contextual/contextual.info | 6 +- modules/dashboard/dashboard.info | 6 +- modules/dblog/dblog.admin.inc | 11 +- modules/dblog/dblog.info | 6 +- modules/dblog/dblog.install | 9 + modules/dblog/dblog.module | 38 +- modules/dblog/dblog.test | 63 +- modules/field/field.api.php | 2 +- modules/field/field.crud.inc | 6 +- modules/field/field.info | 6 +- modules/field/field.info.class.inc | 4 +- modules/field/field.install | 21 + modules/field/field.module | 16 + .../field_sql_storage/field_sql_storage.info | 6 +- .../field_sql_storage.module | 26 +- .../field_sql_storage/field_sql_storage.test | 4 +- modules/field/modules/list/list.info | 6 +- modules/field/modules/list/tests/list.test | 2 +- .../field/modules/list/tests/list_test.info | 6 +- modules/field/modules/number/number.info | 6 +- modules/field/modules/number/number.module | 15 +- modules/field/modules/number/number.test | 48 +- modules/field/modules/options/options.info | 6 +- modules/field/modules/options/options.module | 10 +- modules/field/modules/options/options.test | 33 +- modules/field/modules/text/text.info | 6 +- modules/field/modules/text/text.module | 6 +- modules/field/modules/text/text.test | 1 + modules/field/tests/field.test | 3 +- modules/field/tests/field_test.info | 6 +- modules/field/tests/field_test.module | 4 + modules/field/theme/field.tpl.php | 8 +- modules/field_ui/field_ui.admin.inc | 4 + modules/field_ui/field_ui.info | 6 +- modules/field_ui/field_ui.js | 4 +- modules/field_ui/field_ui.module | 22 +- modules/field_ui/field_ui.test | 17 +- modules/file/file.field.inc | 8 +- modules/file/file.info | 6 +- modules/file/file.module | 119 +- modules/file/tests/file.test | 573 +++- modules/file/tests/file_module_test.info | 6 +- modules/file/tests/file_module_test.module | 15 + modules/filter/filter.info | 6 +- modules/filter/filter.module | 40 +- modules/filter/filter.pages.inc | 7 +- modules/filter/filter.test | 62 +- modules/forum/forum.info | 6 +- modules/help/help.info | 6 +- modules/image/image.info | 6 +- modules/image/image.module | 26 +- modules/image/image.test | 99 +- modules/image/tests/image_module_test.info | 6 +- modules/image/tests/image_module_test.module | 3 + modules/locale/locale.admin.inc | 8 +- modules/locale/locale.info | 6 +- modules/locale/locale.test | 154 +- modules/locale/tests/locale_test.info | 6 +- modules/menu/menu.admin.inc | 3 +- modules/menu/menu.info | 6 +- modules/menu/menu.module | 1 + modules/menu/menu.test | 22 +- modules/node/content_types.inc | 2 +- modules/node/node.admin.inc | 15 +- modules/node/node.api.php | 29 +- modules/node/node.info | 6 +- modules/node/node.install | 28 + modules/node/node.module | 20 +- modules/node/node.pages.inc | 36 +- modules/node/node.test | 119 +- modules/node/tests/node_access_test.info | 6 +- modules/node/tests/node_access_test.module | 2 +- modules/node/tests/node_test.info | 6 +- modules/node/tests/node_test_exception.info | 6 +- modules/openid/openid.info | 6 +- modules/openid/openid.module | 14 +- modules/openid/openid.test | 10 +- modules/openid/tests/openid_test.info | 6 +- modules/openid/tests/openid_test.module | 1 + modules/overlay/overlay-parent.js | 36 +- modules/overlay/overlay.info | 6 +- modules/overlay/overlay.module | 14 + modules/path/path.info | 6 +- modules/path/path.module | 6 +- modules/php/php.info | 6 +- modules/poll/poll.info | 6 +- modules/poll/poll.module | 9 +- modules/profile/profile.info | 6 +- modules/profile/profile.test | 14 +- modules/rdf/rdf.info | 6 +- modules/rdf/tests/rdf_test.info | 7 +- modules/search/search.admin.inc | 10 + modules/search/search.api.php | 8 +- modules/search/search.extender.inc | 22 +- modules/search/search.info | 6 +- modules/search/search.install | 1 + modules/search/search.pages.inc | 9 +- modules/search/search.test | 88 +- .../search/tests/search_embedded_form.info | 6 +- modules/search/tests/search_extra_type.info | 6 +- modules/search/tests/search_node_tags.info | 12 + modules/search/tests/search_node_tags.module | 23 + modules/shortcut/shortcut.info | 6 +- modules/simpletest/drupal_web_test_case.php | 107 +- .../css_test_files/css_input_with_import.css | 2 + .../css_input_with_import.css.optimized.css | 2 +- .../css_input_with_import.css.unoptimized.css | 2 + .../files/image-test-no-transparency.gif | Bin 0 -> 964 bytes .../image-test-transparent-out-of-range.gif | Bin 0 -> 183 bytes modules/simpletest/simpletest.info | 7 +- modules/simpletest/simpletest.module | 112 +- modules/simpletest/simpletest.test | 57 +- modules/simpletest/src/Tests/PSR4WebTest.php | 18 + .../simpletest/tests/actions_loop_test.info | 6 +- modules/simpletest/tests/ajax.test | 2 +- modules/simpletest/tests/ajax_forms_test.info | 6 +- .../simpletest/tests/ajax_forms_test.module | 2 +- modules/simpletest/tests/ajax_test.info | 6 +- .../simpletest/tests/batch_test.callbacks.inc | 22 + modules/simpletest/tests/batch_test.info | 6 +- modules/simpletest/tests/boot.test | 38 + modules/simpletest/tests/boot_test_1.info | 12 + modules/simpletest/tests/boot_test_1.module | 21 + modules/simpletest/tests/boot_test_2.info | 12 + modules/simpletest/tests/boot_test_2.module | 13 + modules/simpletest/tests/bootstrap.test | 338 +- modules/simpletest/tests/common.test | 337 +- modules/simpletest/tests/common_test.info | 6 +- modules/simpletest/tests/common_test.module | 12 + .../tests/common_test_cron_helper.info | 6 +- modules/simpletest/tests/database_test.info | 6 +- modules/simpletest/tests/database_test.test | 55 +- .../drupal_autoload_test.info | 14 + .../drupal_autoload_test.module | 22 + .../drupal_autoload_test_class.inc | 11 + .../drupal_autoload_test_interface.inc | 11 + .../drupal_autoload_test_trait.sh | 16 + ...drupal_system_listing_compatible_test.info | 6 +- ...upal_system_listing_incompatible_test.info | 6 +- .../simpletest/tests/entity_cache_test.info | 6 +- .../tests/entity_cache_test_dependency.info | 6 +- .../tests/entity_crud_hook_test.info | 6 +- .../tests/entity_query_access_test.info | 6 +- modules/simpletest/tests/error_test.info | 6 +- modules/simpletest/tests/file.test | 10 - modules/simpletest/tests/file_test.info | 6 +- modules/simpletest/tests/filter_test.info | 6 +- modules/simpletest/tests/form.test | 119 + modules/simpletest/tests/form_test.info | 6 +- modules/simpletest/tests/form_test.module | 41 + modules/simpletest/tests/image.test | 115 +- modules/simpletest/tests/image_test.info | 6 +- modules/simpletest/tests/mail.test | 5 +- modules/simpletest/tests/menu_test.info | 6 +- modules/simpletest/tests/module.test | 42 + .../tests/module_test.implementations.inc | 10 + modules/simpletest/tests/module_test.info | 6 +- modules/simpletest/tests/module_test.module | 11 + modules/simpletest/tests/path_test.info | 6 +- .../tests/psr_0_test/psr_0_test.info | 6 +- .../tests/psr_4_test/psr_4_test.info | 12 + .../tests/psr_4_test/psr_4_test.module | 1 + .../psr_4_test/src/Tests/ExampleTest.php | 18 + .../src/Tests/Nested/NestedExampleTest.php | 18 + .../simpletest/tests/requirements1_test.info | 6 +- .../simpletest/tests/requirements2_test.info | 6 +- modules/simpletest/tests/session.test | 50 + modules/simpletest/tests/session_test.info | 6 +- .../tests/system_dependencies_test.info | 6 +- ...atible_core_version_dependencies_test.info | 6 +- ...system_incompatible_core_version_test.info | 6 +- ...ible_module_version_dependencies_test.info | 6 +- ...stem_incompatible_module_version_test.info | 6 +- .../tests/system_project_namespace_test.info | 13 + .../system_project_namespace_test.module | 1 + modules/simpletest/tests/system_test.info | 6 +- modules/simpletest/tests/system_test.install | 20 + modules/simpletest/tests/system_test.module | 149 + modules/simpletest/tests/taxonomy_test.info | 6 +- modules/simpletest/tests/taxonomy_test.module | 30 + modules/simpletest/tests/theme.test | 40 + modules/simpletest/tests/theme_test.info | 6 +- modules/simpletest/tests/theme_test.module | 37 + .../themes/engines/nyan_cat/nyan_cat.engine | 53 + .../themes/test_basetheme/test_basetheme.info | 6 +- .../themes/test_subtheme/test_subtheme.info | 6 +- .../tests/themes/test_theme/test_theme.info | 6 +- .../theme_test_template_test.nyan-cat.html | 1 + .../test_theme_nyan_cat.info | 11 + .../simpletest/tests/update_script_test.info | 6 +- .../tests/update_script_test.install | 13 + modules/simpletest/tests/update_test_1.info | 6 +- modules/simpletest/tests/update_test_2.info | 6 +- modules/simpletest/tests/update_test_3.info | 6 +- .../upgrade/drupal-6.filled.database.php | 48 +- .../upgrade/drupal-6.upload.database.php | 99 +- .../tests/upgrade/upgrade.taxonomy.test | 7 +- .../tests/upgrade/upgrade.upload.test | 23 + modules/simpletest/tests/url_alter_test.info | 6 +- modules/simpletest/tests/xmlrpc.test | 34 + modules/simpletest/tests/xmlrpc_test.info | 6 +- modules/statistics/statistics.admin.inc | 2 +- modules/statistics/statistics.info | 6 +- modules/statistics/statistics.module | 9 +- modules/statistics/statistics.php | 26 +- modules/statistics/statistics.test | 6 +- modules/syslog/syslog.info | 6 +- modules/system/image.gd.inc | 111 +- modules/system/system.admin.inc | 16 +- modules/system/system.api.php | 111 +- modules/system/system.info | 6 +- modules/system/system.install | 139 +- modules/system/system.js | 2 +- modules/system/system.module | 81 +- modules/system/system.queue.inc | 3 +- modules/system/system.tar.inc | 2901 ++++++++++------- modules/system/system.test | 244 +- modules/system/system.updater.inc | 8 +- modules/system/tests/cron_queue_test.info | 6 +- modules/system/tests/cron_queue_test.module | 12 + modules/system/tests/system_cron_test.info | 12 + modules/system/tests/system_cron_test.module | 15 + modules/taxonomy/taxonomy.api.php | 2 +- modules/taxonomy/taxonomy.info | 6 +- modules/taxonomy/taxonomy.install | 3 +- modules/taxonomy/taxonomy.module | 10 +- modules/taxonomy/taxonomy.pages.inc | 2 +- modules/taxonomy/taxonomy.test | 112 +- modules/toolbar/toolbar.info | 6 +- modules/tracker/tracker.info | 6 +- modules/tracker/tracker.test | 1 - .../translation/tests/translation_test.info | 6 +- modules/translation/translation.info | 6 +- modules/translation/translation.module | 2 +- modules/trigger/tests/trigger_test.info | 6 +- modules/trigger/trigger.info | 6 +- modules/trigger/trigger.test | 2 +- modules/update/tests/aaa_update_test.info | 6 +- modules/update/tests/bbb_update_test.info | 6 +- modules/update/tests/ccc_update_test.info | 6 +- .../update_test_admintheme.info | 10 + .../update_test_basetheme.info | 6 +- .../update_test_subtheme.info | 6 +- modules/update/tests/update_test.info | 6 +- modules/update/tests/update_test.module | 1 + modules/update/update.authorize.inc | 12 +- modules/update/update.compare.inc | 6 + modules/update/update.fetch.inc | 8 +- modules/update/update.info | 6 +- modules/update/update.manager.inc | 8 +- modules/update/update.module | 3 + modules/update/update.settings.inc | 11 +- modules/update/update.test | 51 +- modules/user/tests/user_form_test.info | 6 +- modules/user/tests/user_form_test.module | 18 + modules/user/user-picture.tpl.php | 2 +- modules/user/user.api.php | 30 +- modules/user/user.info | 6 +- modules/user/user.install | 26 +- modules/user/user.js | 2 + modules/user/user.module | 122 +- modules/user/user.pages.inc | 66 +- modules/user/user.test | 289 +- profiles/README.txt | 28 + profiles/minimal/minimal.info | 6 +- profiles/standard/standard.info | 6 +- ...drupal_system_listing_compatible_test.info | 6 +- ...upal_system_listing_incompatible_test.info | 6 +- profiles/testing/testing.info | 6 +- robots.txt | 36 +- scripts/generate-d6-content.sh | 1 + scripts/password-hash.sh | 2 +- scripts/run-tests.sh | 98 +- themes/bartik/bartik.info | 6 +- themes/garland/garland.info | 6 +- themes/garland/template.php | 78 +- themes/seven/seven.info | 6 +- themes/stark/stark.info | 6 +- web.config | 2 +- 357 files changed, 10830 insertions(+), 2674 deletions(-) create mode 100644 INSTALL.mysql.txt create mode 100644 INSTALL.pgsql.txt create mode 100644 INSTALL.sqlite.txt create mode 100644 INSTALL.txt create mode 100644 includes/request-sanitizer.inc create mode 100644 install.php create mode 100644 modules/search/tests/search_node_tags.info create mode 100644 modules/search/tests/search_node_tags.module create mode 100644 modules/simpletest/files/image-test-no-transparency.gif create mode 100644 modules/simpletest/files/image-test-transparent-out-of-range.gif create mode 100644 modules/simpletest/src/Tests/PSR4WebTest.php create mode 100644 modules/simpletest/tests/boot.test create mode 100644 modules/simpletest/tests/boot_test_1.info create mode 100644 modules/simpletest/tests/boot_test_1.module create mode 100644 modules/simpletest/tests/boot_test_2.info create mode 100644 modules/simpletest/tests/boot_test_2.module create mode 100644 modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info create mode 100644 modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.module create mode 100644 modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test_class.inc create mode 100644 modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test_interface.inc create mode 100644 modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test_trait.sh create mode 100644 modules/simpletest/tests/module_test.implementations.inc create mode 100644 modules/simpletest/tests/psr_4_test/psr_4_test.info create mode 100644 modules/simpletest/tests/psr_4_test/psr_4_test.module create mode 100644 modules/simpletest/tests/psr_4_test/src/Tests/ExampleTest.php create mode 100644 modules/simpletest/tests/psr_4_test/src/Tests/Nested/NestedExampleTest.php create mode 100644 modules/simpletest/tests/system_project_namespace_test.info create mode 100644 modules/simpletest/tests/system_project_namespace_test.module create mode 100644 modules/simpletest/tests/system_test.install create mode 100644 modules/simpletest/tests/themes/engines/nyan_cat/nyan_cat.engine create mode 100644 modules/simpletest/tests/themes/test_theme_nyan_cat/templates/theme_test_template_test.nyan-cat.html create mode 100644 modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info create mode 100644 modules/system/tests/system_cron_test.info create mode 100644 modules/system/tests/system_cron_test.module create mode 100644 modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info create mode 100644 profiles/README.txt diff --git a/CHANGELOG.txt b/CHANGELOG.txt index ef848fb..799b800 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,347 @@ +Drupal 7.58, 2018-03-28 +----------------------- +- Fixed security issues (multiple vulnerabilities). 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 @@ -69,11 +410,11 @@ Drupal 7.33, 2014-11-07 - 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 @@ -88,7 +429,7 @@ Drupal 7.30, 2014-07-24 - 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 @@ -134,11 +475,11 @@ Drupal 7.28, 2014-05-08 - 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 @@ -204,7 +545,7 @@ Drupal 7.25, 2014-01-02 - 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 @@ -458,8 +799,8 @@ Drupal 7.15, 2012-08-01 - Numerous API documentation improvements. - Additional automated test coverage. -Drupal 7.14 2012-05-02 ----------------------- +Drupal 7.14, 2012-05-02 +----------------------- - Fixed "integrity constraint" fatal errors when rebuilding registry. - Fixed custom logo and favicon functionality referencing incorrect paths. - Fixed DB Case Sensitivity: Allow BINARY attribute in MySQL. @@ -507,12 +848,12 @@ Drupal 7.14 2012-05-02 - 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. @@ -546,11 +887,11 @@ Drupal 7.12, 2012-02-01 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 @@ -903,7 +1244,7 @@ Drupal 7.0, 2011-01-05 requests. Drupal 6.23-dev, xxxx-xx-xx (development release) ------------------------ +--------------------------- Drupal 6.22, 2011-05-25 ----------------------- @@ -913,25 +1254,25 @@ Drupal 6.22, 2011-05-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 @@ -940,7 +1281,7 @@ Drupal 6.17, 2010-06-02 - 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. @@ -952,12 +1293,12 @@ Drupal 6.16, 2010-03-03 - 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 @@ -966,18 +1307,18 @@ Drupal 6.14, 2009-09-16 - 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 @@ -985,7 +1326,7 @@ Drupal 6.11, 2009-04-29 - 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. @@ -1381,7 +1722,7 @@ Drupal 4.7.9, 2007-12-05 - 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 @@ -1494,7 +1835,7 @@ Drupal 4.6.11, 2007-01-05 - 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 --git a/INSTALL.mysql.txt b/INSTALL.mysql.txt new file mode 100644 index 0000000..95a8734 --- /dev/null +++ b/INSTALL.mysql.txt @@ -0,0 +1,45 @@ + +CREATE THE MySQL DATABASE +-------------------------- + +This step is only necessary if you don't already have a database set up (e.g., +by your host). In the following examples, 'username' is an example MySQL user +which has the CREATE and GRANT privileges. Use the appropriate user name for +your system. + +First, you must create a new database for your Drupal site (here, 'databasename' +is the name of the new database): + + mysqladmin -u username -p create databasename + +MySQL will prompt for the 'username' database password and then create the +initial database files. Next you must log in and set the access database rights: + + mysql -u username -p + +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, + CREATE TEMPORARY TABLES ON databasename.* + TO 'username'@'localhost' IDENTIFIED BY 'password'; + +where: + + 'databasename' is the name of your database + '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 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: + + Query OK, 0 rows affected + +If the InnoDB storage engine is available, it will be used for all database +tables. InnoDB provides features over MyISAM such as transaction support, +row-level locks, and consistent non-locking reads. diff --git a/INSTALL.pgsql.txt b/INSTALL.pgsql.txt new file mode 100644 index 0000000..8fe8043 --- /dev/null +++ b/INSTALL.pgsql.txt @@ -0,0 +1,44 @@ + +CREATE THE PostgreSQL DATABASE +------------------------------ + +Note that the database must be created with UTF-8 (Unicode) encoding. + +1. CREATE DATABASE USER + + This step is only necessary if you don't already have a user set up (e.g., by + your host), or want to create a new user for use with Drupal only. The + following command creates a new user named 'username' and asks for a password + for that user: + + createuser --pwprompt --encrypted --no-createrole --no-createdb username + + If there are no errors, then the command was successful. + +2. CREATE DRUPAL DATABASE + + This step is only necessary if you don't already have a database set up + (e.g., by your host) or want to create a new database for use with Drupal + only. The following command creates a new database named 'databasename', + which is owned by the previously created 'username': + + createdb --encoding=UTF8 --owner=username databasename + + If there are no errors, then the command was successful. + +3. CREATE SCHEMA OR SCHEMAS (Optional advanced step) + + Drupal will run across different schemas within your database if you so wish. + By default, Drupal runs inside the 'public' schema but you can use $db_prefix + inside settings.php to define a schema for Drupal to run inside of, or + specify tables that are shared inside of a separate schema. Drupal will not + create schemas for you. In fact, the user that Drupal runs as should not be + allowed to do this. You'll need to execute the SQL below as a superuser, + replace 'username' with the username that Drupal uses to connect to + PostgreSQL, and replace 'schema_name' with a schema name you wish to use, + such as 'shared': + + CREATE SCHEMA schema_name AUTHORIZATION username; + + Do this for as many schemas as you need. See default.settings.php for + instructions on how to set which tables use which schemas. diff --git a/INSTALL.sqlite.txt b/INSTALL.sqlite.txt new file mode 100644 index 0000000..8e57d60 --- /dev/null +++ b/INSTALL.sqlite.txt @@ -0,0 +1,31 @@ + +SQLITE REQUIREMENTS +------------------- + +To use SQLite with your Drupal installation, the following requirements must be +met: Server has PHP 5.2 or later with PDO, and the PDO SQLite driver must be +enabled. + +SQLITE DATABASE CREATION +------------------------ + +The Drupal installer will create the SQLite database for you. The only +requirement is that the installer must have write permissions to the directory +where the database file resides. This directory (not just the database file) also +has to remain writeable by the web server going forward for SQLite to continue to +be able to operate. + +On the "Database configuration" form in the "Database file" field, you must +supply the exact path to where you wish your database file to reside. It is +strongly suggested that you choose a path that is outside of the webroot, yet +ensure that the directory is writeable by the web server. + +If you must place your database file in your webroot, you could try using the +following in your "Database file" field: + + sites/default/files/.ht.sqlite + +Note: The .ht in the name will tell Apache to prevent the database from being +downloaded. Please check that the file is, indeed, protected by your webserver. +If not, please consult the documentation of your webserver on how to protect a +file from downloading. diff --git a/INSTALL.txt b/INSTALL.txt new file mode 100644 index 0000000..e00c8ba --- /dev/null +++ b/INSTALL.txt @@ -0,0 +1,400 @@ + +CONTENTS OF THIS FILE +--------------------- + + * Requirements and notes + * Optional server requirements + * Installation + * Building and customizing your site + * Multisite configuration + * More information + +REQUIREMENTS AND NOTES +---------------------- + +Drupal requires: + +- A web server. Apache (version 2.0 or greater) is recommended. +- PHP 5.2.4 (or greater) (http://www.php.net/). +- One of the following databases: + - 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.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" +(http://drupal.org/requirements) in the Drupal.org online documentation. + +For detailed information on how to configure a test server environment using a +variety of operating systems and web servers, see "Local server setup" +(http://drupal.org/node/157602) in the Drupal.org online documentation. + +Note that all directories mentioned in this document are always relative to the +directory of your Drupal installation, and commands are meant to be run from +this directory (except for the initial commands that create that directory). + +OPTIONAL SERVER REQUIREMENTS +---------------------------- + +- If you want to use Drupal's "Clean URLs" feature on an Apache web server, you + will need the mod_rewrite module and the ability to use local .htaccess + files. For Clean URLs support on IIS, see "Clean URLs with IIS" + (http://drupal.org/node/3854) in the Drupal.org online documentation. + +- If you plan to use XML-based services such as RSS aggregation, you will need + PHP's XML extension. This extension is enabled by default on most PHP + installations. + +- To serve gzip compressed CSS and JS files on an Apache web server, you will + need the mod_headers module and the ability to use local .htaccess files. + +- Some Drupal functionality (e.g., checking whether Drupal and contributed + modules need updates, RSS aggregation, etc.) require that the web server be + able to go out to the web and download information. If you want to use this + functionality, you need to verify that your hosting provider or server + configuration allows the web server to initiate outbound connections. Most web + hosting setups allow this. + +INSTALLATION +------------ + +1. Download and extract Drupal. + + You can obtain the latest Drupal release from http://drupal.org -- the files + are available in .tar.gz and .zip formats and can be extracted using most + compression tools. + + To download and extract the files, on a typical Unix/Linux command line, use + the following commands (assuming you want version x.y of Drupal in .tar.gz + format): + + wget http://drupal.org/files/projects/drupal-x.y.tar.gz + tar -zxvf drupal-x.y.tar.gz + + This will create a new directory drupal-x.y/ containing all Drupal files and + directories. Then, to move the contents of that directory into a directory + within your web server's document root or your public HTML directory, + continue with this command: + + mv drupal-x.y/* drupal-x.y/.htaccess /path/to/your/installation + +2. Optionally, download a translation. + + By default, Drupal is installed in English, and further languages may be + installed later. If you prefer to install Drupal in another language + initially: + + - 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 installation profile, + move the .po file into the directory: + + profiles/standard/translations/ + + For detailed instructions, visit http://drupal.org/localize + +3. Create the Drupal database. + + Because Drupal stores all site information in a database, you must create + this database in order to install Drupal, and grant Drupal certain database + privileges (such as the ability to create tables). For details, consult + INSTALL.mysql.txt, INSTALL.pgsql.txt, or INSTALL.sqlite.txt. You may also + need to consult your web hosting provider for instructions specific to your + web host. + + Take note of the username, password, database name, and hostname as you + create the database. You will enter this information during the install. + +4. Run the install script. + + To run the install script, point your browser to the base URL of your + website (e.g., http://www.example.com). + + You will be guided through several screens to set up the database, add the + site maintenance account (the first user, also known as user/1), and provide + basic web site settings. + + During installation, several files and directories need to be created, which + the install script will try to do automatically. However, on some hosting + environments, manual steps are required, and the install script will tell + you that it cannot proceed until you fix certain issues. This is normal and + does not indicate a problem with your server. + + The most common steps you may need to perform are: + + a. Missing files directory. + + The install script will attempt to create a file storage directory in + the default location at sites/default/files (the location of the files + directory may be changed after Drupal is installed). + + If auto-creation fails, you can make it work by changing permissions on + the sites/default directory so that the web server can create the files + directory within it for you. (If you are creating a multisite + installation, substitute the correct sites directory for sites/default; + see the Multisite Configuration section of this file, below.) + + For example, on a Unix/Linux command line, you can grant everyone + (including the web server) permission to write to the sites/default + directory with this command: + + chmod a+w sites/default + + Be sure to set the permissions back after the installation is finished! + Sample command: + + chmod go-w sites/default + + Alternatively, instead of allowing the web server to create the files + directory for you as described above, you can create it yourself. Sample + commands from a Unix/Linux command line: + + mkdir sites/default/files + chmod a+w sites/default/files + + b. Missing settings file. + + Drupal will try to automatically create a settings.php configuration file, + which is normally in the directory sites/default (to avoid problems when + upgrading, Drupal is not packaged with this file). If auto-creation fails, + you will need to create this file yourself, using the file + sites/default/default.settings.php as a template. + + For example, on a Unix/Linux command line, you can make a copy of the + default.settings.php file with the command: + + cp sites/default/default.settings.php sites/default/settings.php + + Next, grant write privileges to the file to everyone (including the web + server) with the command: + + chmod a+w sites/default/settings.php + + Be sure to set the permissions back after the installation is finished! + Sample command: + + chmod go-w sites/default/settings.php + + c. Write permissions after install. + + The install script will attempt to write-protect the settings.php file and + the sites/default directory after saving your configuration. If this + fails, you will be notified, and you can do it manually. Sample commands + from a Unix/Linux command line: + + chmod go-w sites/default/settings.php + chmod go-w sites/default + +5. Verify that the site is working. + + When the install script finishes, you will be logged in with the site + maintenance account on a "Welcome" page. If the default Drupal theme is not + displaying properly and links on the page result in "Page Not Found" errors, + you may be experiencing problems with clean URLs. Visit + http://drupal.org/getting-started/clean-urls to troubleshoot. + +6. Change file system storage settings (optional). + + The files directory created in step 4 is the default file system path used to + store all uploaded files, as well as some temporary files created by + Drupal. After installation, you can modify the file system path to store + uploaded files in a different location. + + It is not necessary to modify this path, but you may wish to change it if: + + - Your site runs multiple Drupal installations from a single codebase (modify + the file system path of each installation to a different directory so that + uploads do not overlap between installations). + + - Your site runs on a number of web servers behind a load balancer or reverse + proxy (modify the file system path on each server to point to a shared file + repository). + + - You want to restrict access to uploaded files. + + To modify the file system path: + + a. Ensure that the new location for the path exists and is writable by the + web server. For example, to create a new directory named uploads and grant + write permissions, use the following commands on a Unix/Linux command + line: + + mkdir uploads + chmod a+w uploads + + b. Navigate to Administration > Configuration > Media > File system, and + enter the desired path. Note that if you want to use private file storage, + you need to first enter the path for private files and save the + configuration, and then change the "Default download method" setting and + save again. + + Changing the file system path after files have been uploaded may cause + unexpected problems on an existing site. If you modify the file system path + on an existing site, remember to copy all files from the original location + to the new location. + +7. Revoke documentation file permissions (optional). + + Some administrators suggest making the documentation files, especially + CHANGELOG.txt, non-readable so that the exact version of Drupal you are + running is slightly more difficult to determine. If you wish to implement + this optional security measure, from a Unix/Linux command line you can use + the following command: + + chmod a-r CHANGELOG.txt + + Note that the example only affects CHANGELOG.txt. To completely hide all + documentation files from public view, repeat this command for each of the + Drupal documentation files in the installation directory, substituting the + name of each file for CHANGELOG.txt in the example. + + For more information on setting file permissions, see "Modifying Linux, + Unix, and Mac file permissions" (http://drupal.org/node/202483) or + "Modifying Windows file permissions" (http://drupal.org/node/202491) in the + Drupal.org online documentation. + +8. Set up independent "cron" maintenance jobs. + + Many Drupal modules have tasks that must be run periodically, including the + Search module (building and updating the index used for keyword searching), + the Aggregator module (retrieving feeds from other sites), and the System + module (performing routine maintenance and pruning of database tables). These + tasks are known as "cron maintenance tasks", named after the Unix/Linux + "cron" utility. + + When you install Drupal, its built-in cron feature is enabled, which + automatically runs the cron tasks periodically, triggered by people visiting + pages of your site. You can configure the built-in cron feature by navigating + to Administration > Configuration > System > Cron. + + It is also possible to run the cron tasks independent of site visits; this is + recommended for most sites. To do this, you will need to set up an automated + process to visit the page cron.php on your site, which executes the cron + tasks. + + The URL of the cron.php page requires a "cron key" to protect against + unauthorized access. Your site's cron key is automatically generated during + installation and is specific to your site. The full URL of the page, with the + cron key, is available in the "Cron maintenance tasks" section of the Status + report page at Administration > Reports > Status report. + + As an example for how to set up this automated process, you can use the + crontab utility on Unix/Linux systems. The following crontab line uses the + wget command to visit the cron.php page, and runs each hour, on the hour: + + 0 * * * * wget -O - -q -t 1 http://example.com/cron.php?cron_key=YOURKEY + + Replace the text "http://example.com/cron.php?cron_key=YOURKEY" in the + example with the full URL displayed under "Cron maintenance tasks" on the + "Status report" page. + + More information about cron maintenance tasks is available at + http://drupal.org/cron, and sample cron shell scripts can be found in the + scripts/ directory. (Note that these scripts must be customized like the + above example, to add your site-specific cron key and domain name.) + +BUILDING AND CUSTOMIZING YOUR SITE +---------------------------------- + +A new installation of Drupal defaults to a very basic configuration. To extend +your site, you use "modules" and "themes". A module is a plugin that adds +functionality to Drupal, while a theme changes the look of your site. The core +of Drupal provides several optional modules and themes, and you can download +more at http://drupal.org/project/modules and http://drupal.org/project/themes + +Do not mix downloaded or custom modules and themes with Drupal's core modules +and themes. Drupal's modules and themes are located in the top-level modules and +themes directories, while the modules and themes you add to Drupal are normally +placed in the sites/all/modules and sites/all/themes directories. If you run a +multisite installation, you can also place modules and themes in the +site-specific directories -- see the Multisite Configuration section, below. + +Never edit Drupal's core modules and themes; instead, use the hooks available in +the Drupal API. To modify the behavior of Drupal, develop a module as described +at http://drupal.org/developing/modules. To modify the look of Drupal, create a +subtheme as described at http://drupal.org/node/225125, or a completely new +theme as described at http://drupal.org/documentation/theme + +MULTISITE CONFIGURATION +----------------------- + +A single Drupal installation can host several Drupal-powered sites, each with +its own individual configuration. + +Additional site configurations are created in subdirectories within the 'sites' +directory. Each subdirectory must have a 'settings.php' file, which specifies +the configuration settings. The easiest way to create additional sites is to +copy the 'default' directory and modify the 'settings.php' file as appropriate. +The new directory name is constructed from the site's URL. The configuration for +www.example.com could be in 'sites/example.com/settings.php' (note that 'www.' +should be omitted if users can access your site at http://example.com/). + +Sites do not have to have a different domain. You can also use subdomains and +subdirectories for Drupal sites. For example, example.com, sub.example.com, and +sub.example.com/site3 can all be defined as independent Drupal sites. The setup +for a configuration such as this would look like the following: + + sites/default/settings.php + sites/example.com/settings.php + sites/sub.example.com/settings.php + sites/sub.example.com.site3/settings.php + +When searching for a site configuration (for example www.sub.example.com/site3), +Drupal will search for configuration files in the following order, using the +first configuration it finds: + + sites/www.sub.example.com.site3/settings.php + sites/sub.example.com.site3/settings.php + sites/example.com.site3/settings.php + sites/www.sub.example.com/settings.php + sites/sub.example.com/settings.php + sites/example.com/settings.php + sites/default/settings.php + +If you are installing on a non-standard port, the port number is treated as the +deepest subdomain. For example: http://www.example.com:8080/ could be loaded +from sites/8080.www.example.com/. The port number will be removed according to +the pattern above if no port-specific configuration is found, just like a real +subdomain. + +Each site configuration can have its own site-specific modules and themes in +addition to those installed in the standard 'modules' and 'themes' directories. +To use site-specific modules or themes, simply create a 'modules' or 'themes' +directory within the site configuration directory. For example, if +sub.example.com has a custom theme and a custom module that should not be +accessible to other sites, the setup would look like this: + + sites/sub.example.com/ + settings.php + themes/custom_theme + modules/custom_module + +NOTE: for more information about multiple virtual hosts or the configuration +settings, consult http://drupal.org/getting-started/6/install/multi-site + +For more information on configuring Drupal's file system path in a multisite +configuration, see step 6 above. + +MORE INFORMATION +---------------- + +- See the Drupal.org online documentation: + http://drupal.org/documentation + +- For a list of security announcements, see the "Security advisories" page at + http://drupal.org/security (available as an RSS feed). This page also + describes how to subscribe to these announcements via e-mail. + +- For information about the Drupal security process, or to find out how to + report a potential security issue to the Drupal security team, see the + "Security team" page at http://drupal.org/security-team + +- For information about the wide range of available support options, visit + http://drupal.org and click on Community and Support in the top or bottom + navigation. diff --git a/MAINTAINERS.txt b/MAINTAINERS.txt index 3656314..5603a43 100644 --- a/MAINTAINERS.txt +++ b/MAINTAINERS.txt @@ -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,160 +10,154 @@ Branch maintainers The Drupal Core branch maintainers oversee the development of Drupal as a whole. The branch maintainers for Drupal 7 are: -- Dries Buytaert 'dries' http://drupal.org/user/1 -- Angela Byron 'webchick' http://drupal.org/user/24967 -- David Rothstein 'David_Rothstein' http://drupal.org/user/124982 +- Dries Buytaert 'dries' https://www.drupal.org/u/dries +- Angela Byron 'webchick' https://www.drupal.org/u/webchick +- Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx +- David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein +- Stefan Ruijsenaars 'stefan.r' https://www.drupal.org/u/stefanr-0 Component maintainers --------------------- The Drupal Core component maintainers oversee the development of Drupal -subsystems. See http://drupal.org/contribute/core-maintainers for more +subsystems. See https://www.drupal.org/contribute/core-maintainers for more information on their responsibilities, and to find out how to become a component maintainer. Current component maintainers for Drupal 7: Ajax system -- Alex Bronstein 'effulgentsia' http://drupal.org/user/78040 -- Earl Miles 'merlinofchaos' http://drupal.org/user/26979 +- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia +- Earl Miles 'merlinofchaos' https://www.drupal.org/u/merlinofchaos Base system -- Károly Négyesi 'chx' http://drupal.org/user/9446 -- Damien Tournoud 'DamZ' http://drupal.org/user/22211 -- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23 +- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud +- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman Batch system -- Yves Chedemois 'yched' http://drupal.org/user/39567 +- Yves Chedemois 'yched' https://www.drupal.org/u/yched Cache system -- Damien Tournoud 'DamZ' http://drupal.org/user/22211 -- Nathaniel Catchpole 'catch' http://drupal.org/user/35733 +- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud +- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch Cron system -- Károly Négyesi 'chx' http://drupal.org/user/9446 -- Derek Wright 'dww' http://drupal.org/user/46549 +- Derek Wright 'dww' https://www.drupal.org/u/dww Database system -- Larry Garfield 'Crell' http://drupal.org/user/26398 +- Larry Garfield 'Crell' https://www.drupal.org/u/crell - MySQL driver - - Larry Garfield 'Crell' http://drupal.org/user/26398 - - David Strauss 'David Strauss' http://drupal.org/user/93254 + - Larry Garfield 'Crell' https://www.drupal.org/u/crell + - David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss - PostgreSQL driver - - Damien Tournoud 'DamZ' http://drupal.org/user/22211 - - Josh Waihi 'fiasco' http://drupal.org/user/188162 + - Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud + - Josh Waihi 'fiasco' https://www.drupal.org/u/josh-waihi - Sqlite driver - - Damien Tournoud 'DamZ' http://drupal.org/user/22211 - - Károly Négyesi 'chx' http://drupal.org/user/9446 + - Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud Database update system -- Károly Négyesi 'chx' http://drupal.org/user/9446 -- Ashok Modi 'BTMash' http://drupal.org/user/60422 +- Ashok Modi 'BTMash' https://www.drupal.org/u/btmash Entity system -- Wolfgang Ziegler 'fago' http://drupal.org/user/16747 -- Nathaniel Catchpole 'catch' http://drupal.org/user/35733 -- Franz Heinzmann 'Frando' http://drupal.org/user/21850 +- Wolfgang Ziegler 'fago' https://www.drupal.org/u/fago +- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch +- Franz Heinzmann 'Frando' https://www.drupal.org/u/frando File system -- Andrew Morton 'drewish' http://drupal.org/user/34869 -- Aaron Winborn 'aaron' http://drupal.org/user/33420 +- Andrew Morton 'drewish' https://www.drupal.org/u/drewish +- Aaron Winborn 'aaron' https://www.drupal.org/u/aaron Form system -- Károly Négyesi 'chx' http://drupal.org/user/9446 -- Alex Bronstein 'effulgentsia' http://drupal.org/user/78040 -- Wolfgang Ziegler 'fago' http://drupal.org/user/16747 -- Daniel F. Kudwien 'sun' http://drupal.org/user/54136 -- Franz Heinzmann 'Frando' http://drupal.org/user/21850 +- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia +- Wolfgang Ziegler 'fago' https://www.drupal.org/u/fago +- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun +- Franz Heinzmann 'Frando' https://www.drupal.org/u/frando Image system -- Andrew Morton 'drewish' http://drupal.org/user/34869 -- Nathan Haug 'quicksketch' http://drupal.org/user/35821 +- Andrew Morton 'drewish' https://www.drupal.org/u/drewish +- Nathan Haug 'quicksketch' https://www.drupal.org/u/quicksketch Install system -- David Rothstein 'David_Rothstein' http://drupal.org/user/124982 +- David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein JavaScript -- Théodore Biadala 'nod_' http://drupal.org/user/598310 -- Steve De Jonghe 'seutje' http://drupal.org/user/264148 -- Jesse Renée Beach 'jessebeach' http://drupal.org/user/748566 +- Théodore Biadala 'nod_' https://www.drupal.org/u/nod_ +- Steve De Jonghe 'seutje' https://www.drupal.org/u/seutje Language system -- Francesco Placella 'plach' http://drupal.org/user/183211 -- Daniel F. Kudwien 'sun' http://drupal.org/user/54136 +- Francesco Placella 'plach' https://www.drupal.org/u/plach +- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun Lock system -- Damien Tournoud 'DamZ' http://drupal.org/user/22211 +- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud Mail system - ? Markup -- Jacine Luisi 'Jacine' http://drupal.org/user/88931 -- Daniel F. Kudwien 'sun' http://drupal.org/user/54136 +- Jacine Luisi 'Jacine' https://www.drupal.org/u/jacine +- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun Menu system -- Peter Wolanin 'pwolanin' http://drupal.org/user/49851 -- Károly Négyesi 'chx' http://drupal.org/user/9446 +- Peter Wolanin 'pwolanin' https://www.drupal.org/u/pwolanin Path system -- Dave Reid 'davereid' http://drupal.org/user/53892 -- Nathaniel Catchpole 'catch' http://drupal.org/user/35733 +- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid +- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch Render system -- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23 -- Alex Bronstein 'effulgentsia' http://drupal.org/user/78040 -- Franz Heinzmann 'Frando' http://drupal.org/user/21850 +- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman +- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia +- Franz Heinzmann 'Frando' https://www.drupal.org/u/frando Theme system -- Earl Miles 'merlinofchaos' http://drupal.org/user/26979 -- Alex Bronstein 'effulgentsia' http://drupal.org/user/78040 -- Joon Park 'dvessel' http://drupal.org/user/56782 -- John Albin Wilkins 'JohnAlbin' http://drupal.org/user/32095 +- Earl Miles 'merlinofchaos' https://www.drupal.org/u/merlinofchaos +- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia +- Joon Park 'dvessel' https://www.drupal.org/u/dvessel +- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin Token system -- Dave Reid 'davereid' http://drupal.org/user/53892 +- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid XML-RPC system -- Frederic G. Marand 'fgm' http://drupal.org/user/27985 +- Frederic G. Marand 'fgm' https://www.drupal.org/u/fgm Topic coordinators ------------------ Accessibility -- Everett Zufelt 'Everett Zufelt' http://drupal.org/user/406552 -- Brandon Bowersox-Johnson 'bowersox' http://drupal.org/user/186415 +- Everett Zufelt 'Everett Zufelt' https://www.drupal.org/u/everett-zufelt +- Brandon Bowersox-Johnson 'bowersox' https://www.drupal.org/u/bowersox Documentation -- Jennifer Hodgdon 'jhodgdon' http://drupal.org/user/155601 +- Jennifer Hodgdon 'jhodgdon' https://www.drupal.org/u/jhodgdon Translations -- Gerhard Killesreiter 'killes' http://drupal.org/user/83 +- Gerhard Killesreiter 'killes' https://www.drupal.org/u/gerhard-killesreiter User experience and usability -- Roy Scholten 'yoroy' http://drupal.org/user/41502 -- Bojhan Somers 'Bojhan' http://drupal.org/user/87969 +- Roy Scholten 'yoroy' https://www.drupal.org/u/yoroy +- Bojhan Somers 'Bojhan' https://www.drupal.org/u/bojhan Node Access -- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23 -- Ken Rickard 'agentrickard' http://drupal.org/user/20975 -- Jess Myrbo 'xjm' http://drupal.org/user/65776 +- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman +- Ken Rickard 'agentrickard' https://www.drupal.org/u/agentrickard Security team ----------------- -To report a security issue, see: https://drupal.org/security-team/report-issue +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 http://drupal.org/security-team for more information. The -security team lead is: +documentation. See https://www.drupal.org/security-team for more information. +The security team lead is: -- Michael Hess 'mlhess' https://drupal.org/user/102818 +- Michael Hess 'mlhess' https://www.drupal.org/u/mlhess Module maintainers @@ -172,143 +167,141 @@ Aggregator module - ? Block module -- John Albin Wilkins 'JohnAlbin' http://drupal.org/user/32095 +- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin Blog module - ? Book module -- Peter Wolanin 'pwolanin' http://drupal.org/user/49851 +- Peter Wolanin 'pwolanin' https://www.drupal.org/u/pwolanin Color module - ? Comment module -- Nathaniel Catchpole 'catch' http://drupal.org/user/35733 +- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch Contact module -- Dave Reid 'davereid' http://drupal.org/user/53892 +- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid Contextual module -- Daniel F. Kudwien 'sun' http://drupal.org/user/54136 +- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun Dashboard module - ? Database logging module -- Khalid Baheyeldin 'kbahey' http://drupal.org/user/4063 +- Khalid Baheyeldin 'kbahey' https://www.drupal.org/u/kbahey Field module -- Yves Chedemois 'yched' http://drupal.org/user/39567 -- Barry Jaspan 'bjaspan' http://drupal.org/user/46413 +- Yves Chedemois 'yched' https://www.drupal.org/u/yched +- Barry Jaspan 'bjaspan' https://www.drupal.org/u/bjaspan Field UI module -- Yves Chedemois 'yched' http://drupal.org/user/39567 +- Yves Chedemois 'yched' https://www.drupal.org/u/yched File module -- Aaron Winborn 'aaron' http://drupal.org/user/33420 +- Aaron Winborn 'aaron' https://www.drupal.org/u/aaron Filter module -- Daniel F. Kudwien 'sun' http://drupal.org/user/54136 +- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun Forum module -- Lee Rowlands 'larowlan' http://drupal.org/user/395439 +- Lee Rowlands 'larowlan' https://www.drupal.org/u/larowlan Help module - ? Image module -- Nathan Haug 'quicksketch' http://drupal.org/user/35821 +- Nathan Haug 'quicksketch' https://www.drupal.org/u/quicksketch Locale module -- Gábor Hojtsy 'Gábor Hojtsy' http://drupal.org/user/4166 +- Gábor Hojtsy 'Gábor Hojtsy' https://www.drupal.org/u/gábor-hojtsy Menu module - ? Node module -- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23 -- David Strauss 'David Strauss' http://drupal.org/user/93254 +- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman +- David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss OpenID module -- Vojtech Kusy 'wojtha' http://drupal.org/user/56154 -- Christian Schmidt 'c960657' http://drupal.org/user/216078 -- Damien Tournoud 'DamZ' http://drupal.org/user/22211 +- Vojtech Kusy 'wojtha' https://www.drupal.org/u/wojtha +- Christian Schmidt 'c960657' https://www.drupal.org/u/c960657 +- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud Overlay module -- Katherine Senzee 'ksenzee' http://drupal.org/user/139855 +- Katherine Senzee 'ksenzee' https://www.drupal.org/u/ksenzee Path module -- Dave Reid 'davereid' http://drupal.org/user/53892 +- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid PHP module - ? Poll module -- Andrei Mateescu 'amateescu' http://drupal.org/user/729614 +- Andrei Mateescu 'amateescu' https://www.drupal.org/u/amateescu Profile module - ? RDF module -- Stéphane Corlosquet 'scor' http://drupal.org/user/52142 +- Stéphane Corlosquet 'scor' https://www.drupal.org/u/scor Search module -- Doug Green 'douggreen' http://drupal.org/user/29191 +- Doug Green 'douggreen' https://www.drupal.org/u/douggreen Shortcut module -- David Rothstein 'David_Rothstein' http://drupal.org/user/124982 +- David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein Simpletest module -- Jimmy Berry 'boombatower' http://drupal.org/user/214218 -- Károly Négyesi 'chx' http://drupal.org/user/9446 +- Jimmy Berry 'boombatower' https://www.drupal.org/u/boombatower Statistics module -- Tim Millwood 'timmillwood' http://drupal.org/user/227849 +- Tim Millwood 'timmillwood' https://www.drupal.org/u/timmillwood Syslog module -- Khalid Baheyeldin 'kbahey' http://drupal.org/user/4063 +- Khalid Baheyeldin 'kbahey' https://www.drupal.org/u/kbahey System module - ? Taxonomy module -- Jess Myrbo 'xjm' http://drupal.org/user/65776 -- Nathaniel Catchpole 'catch' http://drupal.org/user/35733 -- Benjamin Doherty 'bangpound' http://drupal.org/user/100456 +- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch +- Benjamin Doherty 'bangpound' https://www.drupal.org/u/bangpound Toolbar module - ? Tracker module -- David Strauss 'David Strauss' http://drupal.org/user/93254 +- David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss Translation module -- Francesco Placella 'plach' http://drupal.org/user/183211 +- Francesco Placella 'plach' https://www.drupal.org/u/plach Trigger module - ? Update module -- Derek Wright 'dww' http://drupal.org/user/46549 +- Derek Wright 'dww' https://www.drupal.org/u/dww User module -- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23 -- David Strauss 'David Strauss' http://drupal.org/user/93254 +- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman +- David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss Theme maintainers ----------------- Bartik theme -- Jen Simmons 'jensimmons' http://drupal.org/user/140882 -- Jeff Burns 'Jeff Burnz' http://drupal.org/user/61393 +- Jen Simmons 'jensimmons' https://www.drupal.org/u/jensimmons +- Jeff Burns 'Jeff Burnz' https://www.drupal.org/u/jeff-burnz Garland theme -- John Albin Wilkins 'JohnAlbin' http://drupal.org/user/32095 +- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin Seven theme -- Jeff Burns 'Jeff Burnz' http://drupal.org/user/61393 +- Jeff Burns 'Jeff Burnz' https://www.drupal.org/u/jeff-burnz Stark theme -- John Albin Wilkins 'JohnAlbin' http://drupal.org/user/32095 +- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin diff --git a/UPGRADE.txt b/UPGRADE.txt index e870ff0..ae733ca 100644 --- a/UPGRADE.txt +++ b/UPGRADE.txt @@ -64,6 +64,9 @@ following the instructions in the INTRODUCTION section at the top of this file: 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 @@ following the instructions in the INTRODUCTION section at the top of this file: database information, and you will also want to copy in any other customizations you have added. + You can find the release notes for your version at + https://www.drupal.org/project/drupal. At bottom of the project page under + "Downloads" use the link for your version of Drupal to view the release + notes. If your version is not listed, use the 'View all releases' link. From + this page you can scroll down or use the filter to find your version and its + release notes. + 4. Download the latest Drupal 7.x release from http://drupal.org to a directory outside of your web root. Extract the archive and copy the files into your Drupal directory. diff --git a/includes/ajax.inc b/includes/ajax.inc index 10877a2..f059209 100644 --- a/includes/ajax.inc +++ b/includes/ajax.inc @@ -211,7 +211,7 @@ * * When returning an Ajax command array, it is often useful to have * status messages rendered along with other tasks in the command array. - * In that case the the Ajax commands array may be constructed like this: + * In that case the Ajax commands array may be constructed like this: * @code * $commands = array(); * $commands[] = ajax_command_replace(NULL, $output); @@ -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 @@ -390,7 +394,7 @@ function ajax_form_callback() { if (!empty($form_state['triggering_element'])) { $callback = $form_state['triggering_element']['#ajax']['callback']; } - if (!empty($callback) && function_exists($callback)) { + if (!empty($callback) && is_callable($callback)) { $result = $callback($form, $form_state); if (!(is_array($result) && isset($result['#type']) && $result['#type'] == 'ajax')) { @@ -487,6 +491,9 @@ function ajax_deliver($page_callback_result) { } } + // 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); @@ -576,6 +583,29 @@ function ajax_prepare_response($page_callback_result) { return $commands; } +/** + * 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. * @@ -764,7 +794,12 @@ function ajax_pre_render_element($element) { $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. diff --git a/includes/batch.inc b/includes/batch.inc index 061acd4..e89ab8d 100644 --- a/includes/batch.inc +++ b/includes/batch.inc @@ -460,10 +460,10 @@ function _batch_finished() { 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 --git a/includes/bootstrap.inc b/includes/bootstrap.inc index 744fc8f..06acf93 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -8,7 +8,7 @@ /** * The current system version. */ -define('VERSION', '7.34'); +define('VERSION', '7.58'); /** * Core API compatibility. @@ -254,8 +254,13 @@ define('DRUPAL_PHP_FUNCTION_PATTERN', '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*' * 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. */ -define('DATE_RFC7231', 'D, d M Y H:i:s \G\M\T'); +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. @@ -529,9 +534,8 @@ function timer_stop($name) { * Returns the appropriate configuration directory. * * Returns the configuration path based on the site's hostname, port, and - * pathname. Uses find_conf_path() to find the current configuration directory. - * See default.settings.php for examples on how the URL is converted to a - * directory. + * pathname. See default.settings.php for examples on how the URL is converted + * to a directory. * * @param bool $require_settings * Only configuration directories with an existing settings.php file @@ -719,6 +723,16 @@ function drupal_valid_http_host($host) { && 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'; +} + /** * Sets the base URL, cookie domain, and session name from configuration. */ @@ -732,7 +746,7 @@ function drupal_settings_initialize() { 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. @@ -829,14 +843,21 @@ function drupal_settings_initialize() { * @param $filename * The filename of the item if it is to be set explicitly rather * than by consulting the database. + * @param bool $trigger_error + * Whether to trigger an error when a file is missing or has unexpectedly + * moved. This defaults to TRUE, but can be set to FALSE by calling code that + * merely wants to check whether an item exists in the filesystem. * * @return * The filename of the requested item or NULL if the item is not found. */ -function drupal_get_filename($type, $name, $filename = NULL) { +function drupal_get_filename($type, $name, $filename = NULL, $trigger_error = TRUE) { + // The $files static variable will hold the locations of all requested files. + // We can be sure that any file listed in this static variable actually + // exists as all additions have gone through a file_exists() check. // The location of files will not change during the request, so do not use // drupal_static(). - static $files = array(), $dirs = array(); + static $files = array(); // Profiles are a special case: they have a fixed location and naming. if ($type == 'profile') { @@ -848,59 +869,41 @@ function drupal_get_filename($type, $name, $filename = NULL) { } 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 !== 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; - } - - 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; - } - } + $files[$type][$name] = _drupal_get_filename_fallback($type, $name, $trigger_error, $database_unavailable); } } @@ -909,6 +912,256 @@ function drupal_get_filename($type, $name, $filename = NULL) { } } +/** + * 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. + } + } + + 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'); + } +} + /** * Loads the persistent variable table. * @@ -1056,7 +1309,7 @@ function drupal_page_get_cache($check_only = FALSE) { * 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. @@ -1246,23 +1499,10 @@ function drupal_send_headers($default_headers = array(), $only_default = FALSE) * fresh page on every request. This prevents authenticated users from seeing * locally cached pages. * - * Also give each page a unique ETag. This will force clients to include both - * an If-Modified-Since header and an If-None-Match header when doing - * conditional requests for the page (required by RFC 2616, section 13.3.4), - * making the validation more robust. This is a workaround for a bug in Mozilla - * Firefox that is triggered when Drupal's caching is enabled and the user - * accesses Drupal via an HTTP proxy (see - * https://bugzilla.mozilla.org/show_bug.cgi?id=269303): When an authenticated - * user requests a page, and then logs out and requests the same page again, - * Firefox may send a conditional request based on the page that was cached - * locally when the user was logged in. If this page did not have an ETag - * header, the request only contains an If-Modified-Since header. The date will - * be recent, because with authenticated users the Last-Modified header always - * refers to the time of the request. If the user accesses Drupal via a proxy - * server, and the proxy already has a cached copy of the anonymous page with an - * older Last-Modified date, the proxy may respond with 304 Not Modified, making - * the client think that the anonymous and authenticated pageviews are - * identical. + * ETag and Last-Modified headers are not set per default for authenticated + * users so that browsers do not send If-Modified-Since headers from + * authenticated user pages. drupal_serve_page_from_cache() will set appropriate + * ETag and Last-Modified headers for cached pages. * * @see drupal_page_set_cache() */ @@ -1275,9 +1515,11 @@ function drupal_page_header() { $default_headers = array( 'Expires' => 'Sun, 19 Nov 1978 05:00:00 GMT', - 'Last-Modified' => gmdate(DATE_RFC7231, 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); } @@ -1451,6 +1693,23 @@ function drupal_unpack($obj, $field = 'data') { * 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 @@ -1461,8 +1720,9 @@ function drupal_unpack($obj, $field = 'data') { * 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. @@ -1659,14 +1919,14 @@ function request_uri() { * information about the passed-in exception is used. * @param $variables * Array of variables to replace in the message on display. Defaults to the - * return value of drupal_decode_exception(). + * return value of _drupal_decode_exception(). * @param $severity * The severity of the message, as per RFC 3164. * @param $link * A link to associate with the message. * * @see watchdog() - * @see drupal_decode_exception() + * @see _drupal_decode_exception() */ function watchdog_exception($type, Exception $exception, $message = NULL, $variables = array(), $severity = WATCHDOG_ERROR, $link = NULL) { @@ -1792,7 +2052,7 @@ function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NO * @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(); } @@ -2372,6 +2632,10 @@ function _drupal_bootstrap_configuration() { 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(); } /** @@ -2480,6 +2744,9 @@ function _drupal_bootstrap_database() { // 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'); + } } /** @@ -2497,6 +2764,26 @@ function _drupal_bootstrap_variables() { // Load bootstrap modules. require_once DRUPAL_ROOT . '/includes/module.inc'; module_load_all(TRUE); + + // Sanitize the destination parameter (which is often used for redirects) to + // prevent open redirect attacks leading to other domains. Sanitize both + // $_GET['destination'] and $_REQUEST['destination'] to protect code that + // relies on either, but do not sanitize $_POST to avoid interfering with + // unrelated form submissions. The sanitization happens here because + // url_is_external() requires the variable system to be available. + if (isset($_GET['destination']) || isset($_REQUEST['destination'])) { + require_once DRUPAL_ROOT . '/includes/common.inc'; + // If the destination is an external URL, remove it. + if (isset($_GET['destination']) && url_is_external($_GET['destination'])) { + unset($_GET['destination']); + unset($_REQUEST['destination']); + } + // If there's still something in $_REQUEST['destination'] that didn't come + // from $_GET, check it too. + if (isset($_REQUEST['destination']) && (!isset($_GET['destination']) || $_REQUEST['destination'] != $_GET['destination']) && url_is_external($_REQUEST['destination'])) { + unset($_REQUEST['destination']); + } + } } /** @@ -2633,7 +2920,7 @@ function drupal_installation_attempted() { * * This would include implementations of hook_install(), which could run * during the Drupal installation phase, and might also be run during - * non-installation time, such as while installing the module from the the + * non-installation time, such as while installing the module from the * module administration page. * * Example usage: @@ -2775,10 +3062,14 @@ function language_list($field = 'language') { } /** - * 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' => '')); @@ -2930,8 +3221,15 @@ function ip_address() { // 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); + } } } } @@ -2948,7 +3246,9 @@ function ip_address() { * 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. @@ -3103,6 +3403,22 @@ function drupal_autoload_class($class) { return _registry_check_code('class', $class); } +/** + * 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. * @@ -3121,7 +3437,7 @@ function drupal_autoload_class($class) { 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; } @@ -3154,7 +3470,7 @@ function _registry_check_code($type, $name = NULL) { $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]; } @@ -3162,10 +3478,13 @@ function _registry_check_code($type, $name = NULL) { // This function may get called when the default database is not active, but // there is no reason we'd ever want to not use the default database for // this query. - $file = Database::getConnection('default', 'default')->query("SELECT filename FROM {registry} WHERE name = :name AND type = :type", array( - ':name' => $name, - ':type' => $type, - )) + $file = Database::getConnection('default', 'default') + ->select('registry', 'r', array('target' => 'default')) + ->fields('r', array('filename')) + // Use LIKE here to make the query case-insensitive. + ->condition('r.name', db_like($name), 'LIKE') + ->condition('r.type', $type) + ->execute() ->fetchField(); // Flag that we've run a lookup query and need to update the cache. @@ -3176,7 +3495,7 @@ function _registry_check_code($type, $name = NULL) { $lookup_cache[$cache_key] = $file; if ($file) { - require_once DRUPAL_ROOT . '/' . $file; + include_once DRUPAL_ROOT . '/' . $file; return TRUE; } else { @@ -3503,3 +3822,34 @@ function drupal_check_memory_limit($required, $memory_limit = NULL) { // - The memory limit is greater than the memory required for the operation. return ((!$memory_limit) || ($memory_limit == -1) || (parse_size($memory_limit) >= parse_size($required))); } + +/** + * Invalidates a PHP file from any active opcode caches. + * + * If the opcode cache does not support the invalidation of individual files, + * the entire cache will be flushed. + * + * @param string $filepath + * The absolute path of the PHP file to invalidate. + */ +function drupal_clear_opcode_cache($filepath) { + if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50300) { + // Below PHP 5.3, clearstatcache does not accept any function parameters. + clearstatcache(); + } + else { + clearstatcache(TRUE, $filepath); + } + + // Zend OPcache. + if (function_exists('opcache_invalidate')) { + opcache_invalidate($filepath, TRUE); + } + // APC. + if (function_exists('apc_delete_file')) { + // apc_delete_file() throws a PHP warning in case the specified file was + // not compiled yet. + // @see http://php.net/apc-delete-file + @apc_delete_file($filepath); + } +} diff --git a/includes/cache.inc b/includes/cache.inc index 207bf66..945dd66 100644 --- a/includes/cache.inc +++ b/includes/cache.inc @@ -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. * @@ -121,7 +122,12 @@ function cache_get_multiple(array &$cids, $bin = 'cache') { * the administrator panel. * - cache_path: Stores the system paths that have an alias. * @param $expire - * (optional) 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 @@ -261,7 +267,12 @@ interface DrupalCacheInterface { * 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 - * (optional) One of the following values: + * (optional) Controls the maximum lifetime of this cache entry. Note that + * caches might be subject to clearing at any time, so this setting does not + * guarantee a minimum lifetime. With this in mind, the cache should not be + * used for data that must be kept during a cache clear, like sessions. + * + * Use one of the following values: * - CACHE_PERMANENT: Indicates that the item should never be removed unless * explicitly told to using cache_clear_all() with a cache ID. * - CACHE_TEMPORARY: Indicates that the item should be removed at the next diff --git a/includes/common.inc b/includes/common.inc index 20cc82b..d7dc47f 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -487,7 +487,7 @@ function drupal_http_build_query(array $query, $parent = '') { $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)) { @@ -688,6 +688,13 @@ function drupal_goto($path = '', array $options = array(), $http_response_code = $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. @@ -753,7 +760,8 @@ function drupal_access_denied() { * - 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 @@ -778,6 +786,8 @@ function drupal_access_denied() { * 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 @@ -1057,6 +1067,12 @@ function drupal_http_request($url, array $options = array()) { 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 @@ -1522,7 +1538,7 @@ function _filter_xss_split($m, $store = FALSE) { 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 ''; } @@ -1754,9 +1770,15 @@ function format_rss_item($title, $link, $description, $args = array()) { * - '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 = ''; @@ -1769,7 +1791,7 @@ function format_xml_elements($array) { } 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"; @@ -2214,14 +2236,11 @@ function url($path = NULL, array $options = array()) { '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. @@ -2259,6 +2278,11 @@ function url($path = NULL, array $options = array()) { return $path . $options['fragment']; } + // Strip leading slashes from internal paths to prevent them becoming external + // URLs without protocol. /example.com should not be turned into + // //example.com. + $path = ltrim($path, '/'); + global $base_url, $base_secure_url, $base_insecure_url; // The base_url might be rewritten from the language rewrite in domain mode. @@ -2336,10 +2360,21 @@ function url($path = NULL, array $options = array()) { */ function url_is_external($path) { $colonpos = strpos($path, ':'); - // Avoid calling drupal_strip_dangerous_protocols() if there is any - // slash (/), hash (#) or question_mark (?) before the colon (:) - // occurrence - if any - as this would clearly mean it is not a URL. - return $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $path; + // 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); } /** @@ -2621,6 +2656,15 @@ function drupal_deliver_html_page($page_callback_result) { 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? @@ -2636,7 +2680,10 @@ function drupal_deliver_html_page($page_callback_result) { // Keep old path for reference, and to allow forms to redirect to it. if (!isset($_GET['destination'])) { - $_GET['destination'] = $_GET['q']; + // Make sure that the current path is not interpreted as external URL. + if (!url_is_external($_GET['q'])) { + $_GET['destination'] = $_GET['q']; + } } $path = drupal_get_normal_path(variable_get('site_404', '')); @@ -2665,7 +2712,10 @@ function drupal_deliver_html_page($page_callback_result) { // Keep old path for reference, and to allow forms to redirect to it. if (!isset($_GET['destination'])) { - $_GET['destination'] = $_GET['q']; + // Make sure that the current path is not interpreted as external URL. + if (!url_is_external($_GET['q'])) { + $_GET['destination'] = $_GET['q']; + } } $path = drupal_get_normal_path(variable_get('site_403', '')); @@ -2729,6 +2779,7 @@ function drupal_page_footer() { _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE); drupal_cache_system_paths(); module_implements_write_cache(); + drupal_file_scan_write_cache(); system_run_automated_cron(); } @@ -2790,11 +2841,11 @@ function drupal_map_assoc($array, $function = NULL) { * 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 @@ -2811,7 +2862,11 @@ function drupal_map_assoc($array, $function = NULL) { */ 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); + } } } @@ -2992,6 +3047,13 @@ function drupal_add_html_head_link($attributes, $header = FALSE) { */ 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)) { @@ -3027,7 +3089,8 @@ function drupal_add_css($data = NULL, $options = NULL) { } // 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']) { @@ -3780,7 +3843,7 @@ function drupal_load_stylesheet_content($contents, $optimize = FALSE) { // 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; } @@ -3840,6 +3903,21 @@ function drupal_delete_file_if_stale($uri) { * 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); @@ -3911,7 +3989,11 @@ function drupal_html_id($id) { // 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 @@ -3956,7 +4038,10 @@ function drupal_html_id($id) { } } } - $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(' ' => '-', '_' => '-', '[' => '-', ']' => '')); @@ -4140,6 +4225,13 @@ function drupal_region_class($region) { * else being the same, JavaScript added by a call to drupal_add_js() that * happened later in the page request gets added to the page after one for * which drupal_add_js() happened earlier in the page request. + * - requires_jquery: Set this to FALSE if the JavaScript you are adding does + * not have a dependency on jQuery. Defaults to TRUE, except for JavaScript + * settings where it defaults to FALSE. This is used on sites that have the + * 'javascript_always_use_jquery' variable set to FALSE; on those sites, if + * all the JavaScript added to the page by drupal_add_js() does not have a + * dependency on jQuery, then for improved front-end performance Drupal + * will not add jQuery and related libraries and settings to the page. * - defer: If set to TRUE, the defer attribute is set on the 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 --git a/modules/field/field.api.php b/modules/field/field.api.php index e8361c2..f937091 100644 --- a/modules/field/field.api.php +++ b/modules/field/field.api.php @@ -1897,7 +1897,7 @@ function hook_field_storage_write($entity_type, $entity, $op, $fields) { $items = (array) $entity->{$field_name}[$langcode]; $delta_count = 0; foreach ($items as $delta => $item) { - // We now know we have someting to insert. + // We now know we have something to insert. $do_insert = TRUE; $record = array( 'entity_type' => $entity_type, diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc index ba37708..7c0e3a1 100644 --- a/modules/field/field.crud.inc +++ b/modules/field/field.crud.inc @@ -189,7 +189,7 @@ function field_create_field($field) { } // Clear caches - field_cache_clear(TRUE); + field_cache_clear(); // Invoke external hooks after the cache is cleared for API consistency. module_invoke_all('field_create_field', $field); @@ -288,7 +288,7 @@ function field_update_field($field) { drupal_write_record('field_config', $field, $primary_key); // Clear caches - field_cache_clear(TRUE); + field_cache_clear(); // Invoke external hooks after the cache is cleared for API consistency. module_invoke_all('field_update_field', $field, $prior_field, $has_data); @@ -430,7 +430,7 @@ function field_delete_field($field_name) { ->execute(); // Clear the cache. - field_cache_clear(TRUE); + field_cache_clear(); module_invoke_all('field_delete_field', $field); } diff --git a/modules/field/field.info b/modules/field/field.info index b885d60..e05108e 100644 --- a/modules/field/field.info +++ b/modules/field/field.info @@ -11,8 +11,8 @@ dependencies[] = field_sql_storage required = TRUE stylesheets[all][] = theme/field.css -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/field/field.info.class.inc b/modules/field/field.info.class.inc index f4f1f63..772cd45 100644 --- a/modules/field/field.info.class.inc +++ b/modules/field/field.info.class.inc @@ -612,10 +612,12 @@ class FieldInfo { // Fill in default values. $display += array( 'label' => 'above', - 'type' => $field_type_info['default_formatter'], 'settings' => array(), 'weight' => 0, ); + if (empty($display['type'])) { + $display['type'] = $field_type_info['default_formatter']; + } if ($display['type'] != 'hidden') { $formatter_type_info = field_info_formatter_types($display['type']); // Fall back to default formatter if formatter type is not available. diff --git a/modules/field/field.install b/modules/field/field.install index f6948e3..c5dd2dc 100644 --- a/modules/field/field.install +++ b/modules/field/field.install @@ -467,6 +467,27 @@ 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 --git a/modules/field/field.module b/modules/field/field.module index a593e51..8d66813 100644 --- a/modules/field/field.module +++ b/modules/field/field.module @@ -316,6 +316,21 @@ function field_help($path, $arg) { } } +/** + * 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(). */ @@ -894,6 +909,7 @@ function field_view_field($entity_type, $entity, $field_name, $display = array() 'entity' => $entity, 'view_mode' => '_custom', 'display' => $display, + 'language' => $langcode, ); drupal_alter('field_attach_view', $result, $context); diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.info b/modules/field/modules/field_sql_storage/field_sql_storage.info index c69226c..0543461 100644 --- a/modules/field/modules/field_sql_storage/field_sql_storage.info +++ b/modules/field/modules/field_sql_storage/field_sql_storage.info @@ -7,8 +7,8 @@ dependencies[] = field files[] = field_sql_storage.test required = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.module b/modules/field/modules/field_sql_storage/field_sql_storage.module index 7ab4ee5..842893a 100644 --- a/modules/field/modules/field_sql_storage/field_sql_storage.module +++ b/modules/field/modules/field_sql_storage/field_sql_storage.module @@ -223,7 +223,17 @@ function _field_sql_storage_schema($field) { 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); + } } } @@ -332,7 +342,17 @@ function field_sql_storage_field_storage_update_field($field, $prior_field, $has $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); @@ -465,7 +485,7 @@ function field_sql_storage_field_storage_write($entity_type, $entity, $op, $fiel $items = (array) $entity->{$field_name}[$langcode]; $delta_count = 0; foreach ($items as $delta => $item) { - // We now know we have someting to insert. + // We now know we have something to insert. $do_insert = TRUE; $record = array( 'entity_type' => $entity_type, diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.test b/modules/field/modules/field_sql_storage/field_sql_storage.test index 072739c..7c88ac7 100644 --- a/modules/field/modules/field_sql_storage/field_sql_storage.test +++ b/modules/field/modules/field_sql_storage/field_sql_storage.test @@ -355,14 +355,14 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase { field_attach_insert('test_entity', $entity); // Add an index - $field = array('field_name' => $field_name, 'indexes' => array('value' => array('value'))); + $field = array('field_name' => $field_name, 'indexes' => array('value' => array(array('value', 255)))); field_update_field($field); foreach ($tables as $table) { $this->assertTrue(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value"), format_string("Index on value created in %table", array('%table' => $table))); } // Add a different index, removing the existing custom one. - $field = array('field_name' => $field_name, 'indexes' => array('value_format' => array('value', 'format'))); + $field = array('field_name' => $field_name, 'indexes' => array('value_format' => array(array('value', 127), array('format', 127)))); field_update_field($field); foreach ($tables as $table) { $this->assertTrue(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value_format"), format_string("Index on value_format created in %table", array('%table' => $table))); diff --git a/modules/field/modules/list/list.info b/modules/field/modules/list/list.info index 3c9807f..bef7e4a 100644 --- a/modules/field/modules/list/list.info +++ b/modules/field/modules/list/list.info @@ -7,8 +7,8 @@ dependencies[] = field dependencies[] = options files[] = tests/list.test -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/field/modules/list/tests/list.test b/modules/field/modules/list/tests/list.test index 84de7e8..b476b5a 100644 --- a/modules/field/modules/list/tests/list.test +++ b/modules/field/modules/list/tests/list.test @@ -212,7 +212,7 @@ class ListFieldUITestCase extends FieldTestCase { parent::setUp('field_test', 'field_ui'); // Create test user. - $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy')); + $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy', 'administer fields')); $this->drupalLogin($admin_user); // Create content type, with underscores. diff --git a/modules/field/modules/list/tests/list_test.info b/modules/field/modules/list/tests/list_test.info index 09f553d..2d4d6cc 100644 --- a/modules/field/modules/list/tests/list_test.info +++ b/modules/field/modules/list/tests/list_test.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/field/modules/number/number.info b/modules/field/modules/number/number.info index 2652702..c65d94a 100644 --- a/modules/field/modules/number/number.info +++ b/modules/field/modules/number/number.info @@ -6,8 +6,8 @@ core = 7.x dependencies[] = field files[] = number.test -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/field/modules/number/number.module b/modules/field/modules/number/number.module index 6046544..2538bdd 100644 --- a/modules/field/modules/number/number.module +++ b/modules/field/modules/number/number.module @@ -164,6 +164,15 @@ function number_field_presave($entity_type, $entity, $field, $instance, $langcod } } } + 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 @@ function number_field_formatter_info() { '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 @@ function number_field_formatter_info() { '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 @@ function number_field_formatter_settings_form($field, $instance, $view_mode, $fo $display = $instance['display'][$view_mode]; $settings = $display['settings']; + $element = array(); + if ($display['type'] == 'number_decimal' || $display['type'] == 'number_integer') { $options = array( '' => t(''), diff --git a/modules/field/modules/number/number.test b/modules/field/modules/number/number.test index 88029cd..839da36 100644 --- a/modules/field/modules/number/number.test +++ b/modules/field/modules/number/number.test @@ -23,7 +23,7 @@ class NumberFieldTestCase extends DrupalWebTestCase { 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); } @@ -152,4 +152,50 @@ class NumberFieldTestCase extends DrupalWebTestCase { ); $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 --git a/modules/field/modules/options/options.info b/modules/field/modules/options/options.info index 17642a1..632ca24 100644 --- a/modules/field/modules/options/options.info +++ b/modules/field/modules/options/options.info @@ -6,8 +6,8 @@ core = 7.x dependencies[] = field files[] = options.test -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/field/modules/options/options.module b/modules/field/modules/options/options.module index 3862ba7..041b84a 100644 --- a/modules/field/modules/options/options.module +++ b/modules/field/modules/options/options.module @@ -185,6 +185,7 @@ function _options_properties($type, $multiple, $required, $has_value) { $base = array( 'filter_xss' => FALSE, 'strip_tags' => FALSE, + 'strip_tags_and_unescape' => FALSE, 'empty_option' => FALSE, 'optgroups' => FALSE, ); @@ -195,7 +196,7 @@ function _options_properties($type, $multiple, $required, $has_value) { 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 @@ function _options_prepare_options(&$options, $properties) { _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 --git a/modules/field/modules/options/options.test b/modules/field/modules/options/options.test index 7183311..321c2a4 100644 --- a/modules/field/modules/options/options.test +++ b/modules/field/modules/options/options.test @@ -23,8 +23,15 @@ class OptionsWidgetsTestCase extends FieldTestCase { 'type' => 'list_integer', 'cardinality' => 1, 'settings' => array( - // Make sure that 0 works as an option. - 'allowed_values' => array(0 => 'Zero', 1 => 'One', 2 => 'Some & unescaped markup'), + 'allowed_values' => array( + // Make sure that 0 works as an option. + 0 => 'Zero', + 1 => 'One', + // Make sure that option text is properly sanitized. + 2 => 'Some & unescaped markup', + // Make sure that HTML entities in option text are not double-encoded. + 3 => 'Some HTML encoded markup with < & >', + ), ), ); $this->card_1 = field_create_field($this->card_1); @@ -35,8 +42,13 @@ class OptionsWidgetsTestCase extends FieldTestCase { '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 @@ class OptionsWidgetsTestCase extends FieldTestCase { 'type' => 'list_boolean', 'cardinality' => 1, 'settings' => array( - // Make sure that 0 works as a 'on' value'. - 'allowed_values' => array(1 => 'Zero', 0 => 'Some & unescaped markup'), + 'allowed_values' => array( + // Make sure that 1 works as a 'on' value'. + 1 => 'Zero', + // Make sure that option text is properly sanitized. + 0 => 'Some & unescaped markup', + ), ), ); $this->bool = field_create_field($this->bool); // Create a web user. - $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content')); + $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content', 'administer fields')); $this->drupalLogin($this->web_user); } @@ -233,6 +249,7 @@ class OptionsWidgetsTestCase extends FieldTestCase { $this->assertNoOptionSelected("edit-card-1-$langcode", 1); $this->assertNoOptionSelected("edit-card-1-$langcode", 2); $this->assertRaw('Some dangerous & unescaped markup', 'Option text was properly filtered.'); + $this->assertRaw('Some HTML encoded markup with < & >', 'HTML entities in option text were properly handled and not double-encoded'); // Submit form: select invalid 'none' option. $edit = array("card_1[$langcode]" => '_none'); @@ -459,7 +476,7 @@ class OptionsWidgetsTestCase extends FieldTestCase { $this->assertNoFieldChecked("edit-bool-$langcode"); // Create admin user. - $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy')); + $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy', 'administer fields')); $this->drupalLogin($admin_user); // Create a test field instance. diff --git a/modules/field/modules/text/text.info b/modules/field/modules/text/text.info index 4d3afd3..fe93a35 100644 --- a/modules/field/modules/text/text.info +++ b/modules/field/modules/text/text.info @@ -7,8 +7,8 @@ dependencies[] = field files[] = text.test required = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/field/modules/text/text.module b/modules/field/modules/text/text.module index 68fc3cb..bf0d29d 100644 --- a/modules/field/modules/text/text.module +++ b/modules/field/modules/text/text.module @@ -223,11 +223,13 @@ function text_field_formatter_settings_form($field, $instance, $view_mode, $form 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 @@ function text_field_formatter_settings_summary($field, $instance, $view_mode) { $summary = ''; if (strpos($display['type'], '_trimmed') !== FALSE) { - $summary = t('Trim length') . ': ' . check_plain($settings['trim_length']); + $summary = t('Trimmed limit: @trim_length characters', array('@trim_length' => $settings['trim_length'])); } return $summary; diff --git a/modules/field/modules/text/text.test b/modules/field/modules/text/text.test index 2f14738..ad803cf 100644 --- a/modules/field/modules/text/text.test +++ b/modules/field/modules/text/text.test @@ -424,6 +424,7 @@ class TextTranslationTestCase extends DrupalWebTestCase { 'administer content types', 'access administration pages', 'bypass node access', + 'administer fields', filter_permission_name($full_html_format), )); $this->translator = $this->drupalCreateUser(array('create article content', 'edit own article content', 'translate content')); diff --git a/modules/field/tests/field.test b/modules/field/tests/field.test index b279d6a..adf2413 100644 --- a/modules/field/tests/field.test +++ b/modules/field/tests/field.test @@ -2206,11 +2206,12 @@ class FieldDisplayAPITestCase extends FieldTestCase { 'alter' => TRUE, ), ); - $output = field_view_field('test_entity', $this->entity, $this->field_name, $display); + $output = field_view_field('test_entity', $this->entity, $this->field_name, $display, LANGUAGE_NONE); $this->drupalSetContent(drupal_render($output)); $setting = $display['settings']['test_formatter_setting_multiple']; $this->assertNoText($this->label, 'Label was not displayed.'); $this->assertText('field_test_field_attach_view_alter', 'Alter fired, display passed.'); + $this->assertText('field language is ' . LANGUAGE_NONE, 'Language is placed onto the context.'); $array = array(); foreach ($this->values as $delta => $value) { $array[] = $delta . ':' . $value['value']; diff --git a/modules/field/tests/field_test.info b/modules/field/tests/field_test.info index a47d95f..ce3c725 100644 --- a/modules/field/tests/field_test.info +++ b/modules/field/tests/field_test.info @@ -6,8 +6,8 @@ files[] = field_test.entity.inc version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/field/tests/field_test.module b/modules/field/tests/field_test.module index 9daa2c3..7e9bba0 100644 --- a/modules/field/tests/field_test.module +++ b/modules/field/tests/field_test.module @@ -220,6 +220,10 @@ function field_test_field_attach_view_alter(&$output, $context) { if (!empty($context['display']['settings']['alter'])) { $output['test_field'][] = array('#markup' => 'field_test_field_attach_view_alter'); } + + if (isset($output['test_field'])) { + $output['test_field'][] = array('#markup' => 'field language is ' . $context['language']); + } } /** diff --git a/modules/field/theme/field.tpl.php b/modules/field/theme/field.tpl.php index f0f9d58..460fd2e 100644 --- a/modules/field/theme/field.tpl.php +++ b/modules/field/theme/field.tpl.php @@ -4,8 +4,10 @@ * @file field.tpl.php * Default template implementation to display the value of a field. * - * This file is not used and is here as a starting point for customization only. - * @see theme_field() + * This file is not used by Drupal core, which uses theme functions instead for + * performance reasons. The markup is the same, though, so if you want to use + * template files rather than functions to extend field theming, copy this to + * your custom theme. See theme_field() for a discussion of performance. * * Available variables: * - $items: An array of field values. Use render() to output them. @@ -45,7 +47,7 @@ */ ?> ' placeholder. if ($mode) { $content = $match[1]; - $hash = md5($content); + $hash = hash('sha256', $content); $comments[$hash] = $content; return ""; } diff --git a/modules/filter/filter.pages.inc b/modules/filter/filter.pages.inc index e602bce..0f13da8 100644 --- a/modules/filter/filter.pages.inc +++ b/modules/filter/filter.pages.inc @@ -14,10 +14,9 @@ * @see filter_menu() * @see theme_filter_tips() */ -function filter_tips_long() { - $format_id = arg(2); - if ($format_id) { - $output = theme('filter_tips', array('tips' => _filter_tips($format_id, TRUE), 'long' => TRUE)); +function filter_tips_long($format = NULL) { + if (!empty($format)) { + $output = theme('filter_tips', array('tips' => _filter_tips($format->format, TRUE), 'long' => TRUE)); } else { $output = theme('filter_tips', array('tips' => _filter_tips(-1, TRUE), 'long' => TRUE)); diff --git a/modules/filter/filter.test b/modules/filter/filter.test index fe9cfc3..34dcf04 100644 --- a/modules/filter/filter.test +++ b/modules/filter/filter.test @@ -555,6 +555,27 @@ class FilterFormatAccessTestCase extends DrupalWebTestCase { $this->assertTrue(isset($options[$this->allowed_format->format]), 'The allowed text format appears as an option when adding a new node.'); $this->assertFalse(isset($options[$this->disallowed_format->format]), 'The disallowed text format does not appear as an option when adding a new node.'); $this->assertTrue(isset($options[filter_fallback_format()]), 'The fallback format appears as an option when adding a new node.'); + + // Check regular user access to the filter tips pages. + $this->drupalGet('filter/tips/' . $this->allowed_format->format); + $this->assertResponse(200); + $this->drupalGet('filter/tips/' . $this->disallowed_format->format); + $this->assertResponse(403); + $this->drupalGet('filter/tips/' . filter_fallback_format()); + $this->assertResponse(200); + $this->drupalGet('filter/tips/invalid-format'); + $this->assertResponse(404); + + // Check admin user access to the filter tips pages. + $this->drupalLogin($this->admin_user); + $this->drupalGet('filter/tips/' . $this->allowed_format->format); + $this->assertResponse(200); + $this->drupalGet('filter/tips/' . $this->disallowed_format->format); + $this->assertResponse(200); + $this->drupalGet('filter/tips/' . filter_fallback_format()); + $this->assertResponse(200); + $this->drupalGet('filter/tips/invalid-format'); + $this->assertResponse(404); } /** @@ -1099,8 +1120,12 @@ class FilterUnitTestCase extends DrupalUnitTestCase { $f = filter_xss("", array('img')); $this->assertNoNormalized($f, 'cript', 'HTML scheme clearing evasion -- embedded nulls.'); - $f = filter_xss('', array('img')); - $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- spaces and metacharacters before scheme.'); + // @todo This dataset currently fails under 5.4 because of + // https://www.drupal.org/node/1210798. Restore after it's fixed. + if (version_compare(PHP_VERSION, '5.4.0', '<')) { + $f = filter_xss('', array('img')); + $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- spaces and metacharacters before scheme.'); + } $f = filter_xss('', array('img')); $this->assertNoNormalized($f, 'vbscript', 'HTML scheme clearing evasion -- another scheme.'); @@ -1148,7 +1173,7 @@ class FilterUnitTestCase extends DrupalUnitTestCase { // Setup dummy filter object. $filter = new stdClass(); $filter->settings = array( - 'allowed_html' => '
    1. ', + 'allowed_html' => '
        1. ', 'filter_html_help' => 1, 'filter_html_nofollow' => 0, ); @@ -1184,6 +1209,10 @@ class FilterUnitTestCase extends DrupalUnitTestCase { $f = _filter_html(' ', $filter); $this->assertNoNormalized($f, 'onerror', 'HTML filter should remove empty on* attributes on default.'); + + // Custom tags are supported and should be allowed through. + $f = _filter_html('', $filter); + $this->assertNormalized($f, 'test-element', 'HTML filter should allow custom elements.'); } /** @@ -1269,6 +1298,7 @@ class FilterUnitTestCase extends DrupalUnitTestCase { // Create a e-mail that is too long. $long_email = str_repeat('a', 254) . '@example.com'; $too_long_email = str_repeat('b', 255) . '@example.com'; + $email_with_plus_sign = 'one+two@example.com'; // Filter selection/pattern matching. @@ -1282,12 +1312,13 @@ http://example.com or www.example.com ), // MAILTO URLs. ' -person@example.com or mailto:person2@example.com or ' . $long_email . ' but not ' . $too_long_email . ' +person@example.com or mailto:person2@example.com or ' . $email_with_plus_sign . ' or ' . $long_email . ' but not ' . $too_long_email . ' ' => array( 'person@example.com' => TRUE, 'mailto:person2@example.com' => TRUE, '' . $long_email . '' => TRUE, '' . $too_long_email . '' => FALSE, + '' . $email_with_plus_sign . '' => TRUE, ), // URI parts and special characters. ' @@ -1979,3 +2010,26 @@ class FilterSettingsTestCase extends DrupalWebTestCase { } } } + +/** + * Tests DOMDocument serialization. + */ +class FilterDOMSerializeTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Serialization', + 'description' => 'Test serialization of DOMDocument objects.', + 'group' => 'Filter', + ); + } + + /** + * Tests empty DOMDocument object. + */ + function testFilterEmptyDOMSerialization() { + $document = new DOMDocument(); + $result = filter_dom_serialize($document); + $this->assertEqual('', $result); + } +} diff --git a/modules/forum/forum.info b/modules/forum/forum.info index 5687040..d5b1c76 100644 --- a/modules/forum/forum.info +++ b/modules/forum/forum.info @@ -9,8 +9,8 @@ files[] = forum.test configure = admin/structure/forum stylesheets[all][] = forum.css -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/help/help.info b/modules/help/help.info index 8db8096..2c851ea 100644 --- a/modules/help/help.info +++ b/modules/help/help.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x files[] = help.test -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/image/image.info b/modules/image/image.info index 99a2375..dd1200c 100644 --- a/modules/image/image.info +++ b/modules/image/image.info @@ -7,8 +7,8 @@ dependencies[] = file files[] = image.test configure = admin/config/media/image-styles -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/image/image.module b/modules/image/image.module index a2a0f41..dab8836 100644 --- a/modules/image/image.module +++ b/modules/image/image.module @@ -64,7 +64,7 @@ function image_help($path, $arg) { $effect = image_effect_definition_load($arg[7]); return isset($effect['help']) ? ('

          ' . $effect['help'] . '

          ') : NULL; case 'admin/config/media/image-styles/edit/%/effects/%': - $effect = ($arg[5] == 'add') ? image_effect_definition_load($arg[6]) : image_effect_load($arg[6], $arg[4]); + $effect = ($arg[5] == 'add') ? image_effect_definition_load($arg[6]) : image_effect_load($arg[7], $arg[5]); return isset($effect['help']) ? ('

          ' . $effect['help'] . '

          ') : NULL; } } @@ -801,6 +801,8 @@ function image_style_options($include_empty = TRUE, $output = CHECK_PLAIN) { * * @param $style * The image style + * @param $scheme + * The file scheme, for example 'public' for public files. */ function image_style_deliver($style, $scheme) { $args = func_get_args(); @@ -833,8 +835,8 @@ function image_style_deliver($style, $scheme) { file_download($scheme, file_uri_target($derivative_uri)); } else { - $headers = module_invoke_all('file_download', $image_uri); - if (in_array(-1, $headers) || empty($headers)) { + $headers = file_download_headers($image_uri); + if (empty($headers)) { return MENU_ACCESS_DENIED; } if (count($headers)) { @@ -1027,7 +1029,15 @@ function image_style_url($style_name, $path) { // The token query is added even if the 'image_allow_insecure_derivatives' // variable is TRUE, so that the emitted links remain valid if it is changed // back to the default FALSE. - $token_query = array(IMAGE_DERIVATIVE_TOKEN => image_style_path_token($style_name, $original_uri)); + // However, sites which need to prevent the token query from being emitted at + // all can additionally set the 'image_suppress_itok_output' variable to TRUE + // to achieve that (if both are set, the security token will neither be + // emitted in the image derivative URL nor checked for in + // image_style_deliver()). + $token_query = array(); + if (!variable_get('image_suppress_itok_output', FALSE)) { + $token_query = array(IMAGE_DERIVATIVE_TOKEN => image_style_path_token($style_name, $original_uri)); + } // If not using clean URLs, the image derivative callback is only available // with the query string. If the file does not exist, use url() to ensure @@ -1039,8 +1049,12 @@ function image_style_url($style_name, $path) { } $file_url = file_create_url($uri); - // Append the query string with the token. - return $file_url . (strpos($file_url, '?') !== FALSE ? '&' : '?') . drupal_http_build_query($token_query); + // Append the query string with the token, if necessary. + if ($token_query) { + $file_url .= (strpos($file_url, '?') !== FALSE ? '&' : '?') . drupal_http_build_query($token_query); + } + + return $file_url; } /** diff --git a/modules/image/image.test b/modules/image/image.test index 2387314..0c26ffa 100644 --- a/modules/image/image.test +++ b/modules/image/image.test @@ -32,7 +32,7 @@ class ImageFieldTestCase extends DrupalWebTestCase { function setUp() { parent::setUp('image'); - $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer content types', 'administer nodes', 'create article content', 'edit any article content', 'delete any article content', 'administer image styles')); + $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer content types', 'administer nodes', 'create article content', 'edit any article content', 'delete any article content', 'administer image styles', 'administer fields')); $this->drupalLogin($this->admin_user); } @@ -77,6 +77,24 @@ class ImageFieldTestCase extends DrupalWebTestCase { return field_create_instance($instance); } + /** + * Create a random style. + * + * @return array + * A list containing the details of the generated image style. + */ + function createRandomStyle() { + $style_name = strtolower($this->randomName(10)); + $style_label = $this->randomString(); + image_style_save(array('name' => $style_name, 'label' => $style_label)); + $style_path = 'admin/config/media/image-styles/edit/' . $style_name; + return array( + 'name' => $style_name, + 'label' => $style_label, + 'path' => $style_path, + ); + } + /** * Upload an image to a node. * @@ -183,6 +201,22 @@ class ImageStylesPathAndUrlTestCase extends DrupalWebTestCase { $this->assertResponse(404, 'Accessing an image style URL with a source image that does not exist provides a 404 error response.'); } + /** + * Test that we do not pass an array to drupal_add_http_header. + */ + function testImageContentTypeHeaders() { + $files = $this->drupalGetTestFiles('image'); + $file = array_shift($files); + // Copy the test file to private folder. + $private_file = file_copy($file, 'private://', FILE_EXISTS_RENAME); + // Tell image_module_test module to return the headers we want to test. + variable_set('image_module_test_invalid_headers', $private_file->uri); + // Invoke image_style_deliver so it will try to set headers. + $generated_url = image_style_url($this->style_name, $private_file->uri); + $this->drupalGet($generated_url); + variable_del('image_module_test_invalid_headers'); + } + /** * Test image_style_url(). */ @@ -251,7 +285,7 @@ class ImageStylesPathAndUrlTestCase extends DrupalWebTestCase { $this->assertEqual($this->drupalGetHeader('Content-Length'), $generated_image_info['file_size'], 'Expected Content-Length was reported.'); if ($scheme == 'private') { $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.'); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate, post-check=0, pre-check=0', 'Cache-Control header was set to prevent caching.'); + $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate', 'Cache-Control header was set to prevent caching.'); $this->assertEqual($this->drupalGetHeader('X-Image-Owned-By'), 'image_module_test', 'Expected custom header has been added.'); // Make sure that a second request to the already existing derivate works @@ -330,6 +364,15 @@ class ImageStylesPathAndUrlTestCase extends DrupalWebTestCase { $this->drupalGet($nested_url); $this->assertResponse(200, 'Image was accessible when a correct token was provided in the URL.'); + // Suppress the security token in the URL, then get the URL of a file. Check + // that the security token is not present in the URL but that the image is + // still accessible. + variable_set('image_suppress_itok_output', TRUE); + $generate_url = image_style_url($this->style_name, $original_uri); + $this->assertIdentical(strpos($generate_url, IMAGE_DERIVATIVE_TOKEN . '='), FALSE, 'The security token does not appear in the image style URL.'); + $this->drupalGet($generate_url); + $this->assertResponse(200, 'Image was accessible at the URL with a missing token.'); + // Check that requesting a nonexistent image does not create any new // directories in the file system. $directory = $scheme . '://styles/' . $this->style_name . '/' . $scheme . '/' . $this->randomName(); @@ -460,6 +503,58 @@ class ImageEffectsUnitTest extends ImageToolkitTestCase { } } +/** + * Tests the administrative user interface. + */ +class ImageAdminUiTestCase extends ImageFieldTestCase { + public static function getInfo() { + return array( + 'name' => 'Administrative user interface', + 'description' => 'Tests the forms used in the administrative user interface.', + 'group' => 'Image', + ); + } + + function setUp() { + parent::setUp(array('image')); + } + + /** + * Test if the help text is available on the add effect form. + */ + function testAddEffectHelpText() { + // Create a random image style. + $style = $this->createRandomStyle(); + + // Open the add effect form and check for the help text. + $this->drupalGet($style['path'] . '/add/image_crop'); + $this->assertText(t('Cropping will remove portions of an image to make it the specified dimensions.'), 'The image style effect help text was displayed on the add effect page.'); + } + + /** + * Test if the help text is available on the edit effect form. + */ + function testEditEffectHelpText() { + // Create a random image style. + $random_style = $this->createRandomStyle(); + + // Add the crop effect to the image style. + $edit = array(); + $edit['data[width]'] = 20; + $edit['data[height]'] = 20; + $this->drupalPost($random_style['path'] . '/add/image_crop', $edit, t('Add effect')); + + // Open the edit effect form and check for the help text. + drupal_static_reset('image_styles'); + $style = image_style_load($random_style['name']); + + foreach ($style['effects'] as $ieid => $effect) { + $this->drupalGet($random_style['path'] . '/effects/' . $ieid); + $this->assertText(t('Cropping will remove portions of an image to make it the specified dimensions.'), 'The image style effect help text was displayed on the edit effect page.'); + } + } +} + /** * Tests creation, deletion, and editing of image styles and effects. */ diff --git a/modules/image/tests/image_module_test.info b/modules/image/tests/image_module_test.info index aad8292..c6b3a6a 100644 --- a/modules/image/tests/image_module_test.info +++ b/modules/image/tests/image_module_test.info @@ -6,8 +6,8 @@ core = 7.x files[] = image_module_test.module hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/image/tests/image_module_test.module b/modules/image/tests/image_module_test.module index 8a322fb..fc66d9b 100644 --- a/modules/image/tests/image_module_test.module +++ b/modules/image/tests/image_module_test.module @@ -9,6 +9,9 @@ function image_module_test_file_download($uri) { if (variable_get('image_module_test_file_download', FALSE) == $uri) { return array('X-Image-Owned-By' => 'image_module_test'); } + if (variable_get('image_module_test_invalid_headers', FALSE) == $uri) { + return array('Content-Type' => 'image/png'); + } } /** diff --git a/modules/locale/locale.admin.inc b/modules/locale/locale.admin.inc index b736f79..acf6eb2 100644 --- a/modules/locale/locale.admin.inc +++ b/modules/locale/locale.admin.inc @@ -1139,11 +1139,11 @@ function locale_translate_edit_form($form, &$form_state, $lid) { '#value' => $source->location ); - // Include default form controls with empty values for all languages. - // This ensures that the languages are always in the same order in forms. + // Include both translated and not yet translated target languages in the + // list. The source language is English for built-in strings and the default + // language for other strings. $languages = language_list(); $default = language_default(); - // We don't need the default language value, that value is in $source. $omit = $source->textgroup == 'default' ? 'en' : $default->language; unset($languages[($omit)]); $form['translations'] = array('#tree' => TRUE); @@ -1194,7 +1194,7 @@ function locale_translate_edit_form_submit($form, &$form_state) { $translation = db_query("SELECT translation FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $key))->fetchField(); if (!empty($value)) { // Only update or insert if we have a value to use. - if (!empty($translation)) { + if (is_string($translation)) { db_update('locales_target') ->fields(array( 'translation' => $value, diff --git a/modules/locale/locale.info b/modules/locale/locale.info index 2f6e408..674d1f5 100644 --- a/modules/locale/locale.info +++ b/modules/locale/locale.info @@ -6,8 +6,8 @@ core = 7.x files[] = locale.test configure = admin/config/regional/language -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/locale/locale.test b/modules/locale/locale.test index 9ffec9f..db87e05 100644 --- a/modules/locale/locale.test +++ b/modules/locale/locale.test @@ -393,6 +393,16 @@ class LocaleTranslationFunctionalTest extends DrupalWebTestCase { // The indicator should not be here. $this->assertNoRaw($language_indicator, 'String is translated.'); + // Verify that a translation set which has an empty target string can be + // updated without any database error. + db_update('locales_target') + ->fields(array('translation' => '')) + ->condition('language', $langcode, '=') + ->condition('lid', $lid, '=') + ->execute(); + $this->drupalPost('admin/config/regional/translate/edit/' . $lid, $edit, t('Save translations')); + $this->assertText(t('The string has been saved.'), 'The string has been saved.'); + // Try to edit a non-existent string and ensure we're redirected correctly. // Assuming we don't have 999,999 strings already. $random_lid = 999999; @@ -809,7 +819,7 @@ class LocalePluralFormatTest extends DrupalWebTestCase { * Additional options to pass to the translation import form. */ function importPoFile($contents, array $options = array()) { - $name = tempnam('temporary://', "po_") . '.po'; + $name = drupal_tempnam('temporary://', "po_") . '.po'; file_put_contents($name, $contents); $options['files[file]'] = $name; $this->drupalPost('admin/config/regional/translate/import', $options, t('Import')); @@ -1103,7 +1113,7 @@ class LocaleImportFunctionalTest extends DrupalWebTestCase { * Additional options to pass to the translation import form. */ function importPoFile($contents, array $options = array()) { - $name = tempnam('temporary://', "po_") . '.po'; + $name = drupal_tempnam('temporary://', "po_") . '.po'; file_put_contents($name, $contents); $options['files[file]'] = $name; $this->drupalPost('admin/config/regional/translate/import', $options, t('Import')); @@ -1202,7 +1212,7 @@ EOF; * Helper function that returns a .po file with context. */ function getPoFileWithContext() { - // Croatian (code hr) is one the the languages that have a different + // Croatian (code hr) is one of the languages that have a different // form for the full name and the abbreviated name for the month May. return <<< EOF msgid "" @@ -1330,7 +1340,7 @@ class LocaleExportFunctionalTest extends DrupalWebTestCase { function testExportTranslation() { // First import some known translations. // This will also automatically enable the 'fr' language. - $name = tempnam('temporary://', "po_") . '.po'; + $name = drupal_tempnam('temporary://', "po_") . '.po'; file_put_contents($name, $this->getPoFile()); $this->drupalPost('admin/config/regional/translate/import', array( 'langcode' => 'fr', @@ -2237,6 +2247,37 @@ class LocaleContentFunctionalTest extends DrupalWebTestCase { $this->drupalLogout(); } + + /** + * Verifies that nodes may be created with different languages. + */ + function testNodeCreationWithLanguage() { + // Create an admin user and log them in. + $perms = array( + // Standard node permissions. + 'create page content', + 'administer content types', + 'administer nodes', + 'bypass node access', + // Locale. + 'administer languages', + ); + $web_user = $this->drupalCreateUser($perms); + $this->drupalLogin($web_user); + + // Create some test nodes using different langcodes. + foreach (array(LANGUAGE_NONE, 'en', 'fr') as $langcode) { + $node_args = array( + 'type' => 'page', + 'promote' => 1, + 'language' => $langcode, + ); + $node = $this->drupalCreateNode($node_args); + $node_reloaded = node_load($node->nid, NULL, TRUE); + $this->assertEqual($node_reloaded->language, $langcode, format_string('The language code of the node was successfully set to @langcode.', array('@langcode' => $langcode))); + } + } + } /** @@ -2629,6 +2670,68 @@ class LocaleUrlRewritingTest extends DrupalWebTestCase { $this->drupalGet("$prefix/$path"); $this->assertResponse(404, $message2); } + + /** + * Check URL rewriting when using a domain name and a non-standard port. + */ + function testDomainNameNegotiationPort() { + $language_domain = 'example.fr'; + $edit = array( + 'locale_language_negotiation_url_part' => 1, + ); + $this->drupalPost('admin/config/regional/language/configure/url', $edit, t('Save configuration')); + $edit = array( + 'prefix' => '', + 'domain' => $language_domain + ); + $this->drupalPost('admin/config/regional/language/edit/fr', $edit, t('Save language')); + + // Enable domain configuration. + variable_set('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN); + + // Reset static caching. + drupal_static_reset('language_list'); + drupal_static_reset('language_url_outbound_alter'); + drupal_static_reset('language_url_rewrite_url'); + + // In case index.php is part of the URLs, we need to adapt the asserted + // URLs as well. + $index_php = strpos(url('', array('absolute' => TRUE)), 'index.php') !== FALSE; + + // Remember current HTTP_HOST. + $http_host = $_SERVER['HTTP_HOST']; + + // Fake a different port. + $_SERVER['HTTP_HOST'] .= ':88'; + + // Create an absolute French link. + $languages = language_list(); + $language = $languages['fr']; + $url = url('', array( + 'absolute' => TRUE, + 'language' => $language + )); + + $expected = 'http://example.fr:88/'; + $expected .= $index_php ? 'index.php/' : ''; + + $this->assertEqual($url, $expected, 'The right port is used.'); + + // If we set the port explicitly in url(), it should not be overriden. + $url = url('', array( + 'absolute' => TRUE, + 'language' => $language, + 'base_url' => $GLOBALS['base_url'] . ':90', + )); + + $expected = 'http://example.fr:90/'; + $expected .= $index_php ? 'index.php/' : ''; + + $this->assertEqual($url, $expected, 'A given port is not overriden.'); + + // Restore HTTP_HOST. + $_SERVER['HTTP_HOST'] = $http_host; + } } /** @@ -3141,3 +3244,46 @@ class LocaleCSSAlterTest extends DrupalWebTestCase { $this->assertRaw('@import url("' . $base_url . '/modules/system/system.messages.css' . $query_string . '");' . "\n" . '@import url("' . $base_url . '/modules/system/system.messages-rtl.css' . $query_string . '");' . "\n", 'CSS: system.messages-rtl.css is added directly after system.messages.css.'); } } + +/** + * Tests locale translation safe string handling. + */ +class LocaleStringIsSafeTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Test if a string is safe', + 'description' => 'Tests locale translation safe string handling.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp('locale'); + } + + /** + * Tests for locale_string_is_safe(). + */ + public function testLocaleStringIsSafe() { + // Check a translatable string without HTML. + $string = 'Hello world!'; + $result = locale_string_is_safe($string); + $this->assertTrue($result); + + // Check a translatable string which includes trustable HTML. + $string = 'Hello world!'; + $result = locale_string_is_safe($string); + $this->assertTrue($result); + + // Check an untranslatable string which includes untrustable HTML (according + // to the locale_string_is_safe() function definition). + $string = 'Hello world!'; + $result = locale_string_is_safe($string); + $this->assertFalse($result); + + // Check a translatable string which includes a token in an href attribute. + $string = 'Hi user'; + $result = locale_string_is_safe($string); + $this->assertTrue($result); + } +} diff --git a/modules/locale/tests/locale_test.info b/modules/locale/tests/locale_test.info index 3daa74b..49da5e5 100644 --- a/modules/locale/tests/locale_test.info +++ b/modules/locale/tests/locale_test.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/menu/menu.admin.inc b/modules/menu/menu.admin.inc index 66bd6f3..a24703c 100644 --- a/modules/menu/menu.admin.inc +++ b/modules/menu/menu.admin.inc @@ -281,6 +281,7 @@ function menu_edit_item($form, &$form_state, $type, $item, $menu) { $form['link_title'] = array( '#type' => 'textfield', '#title' => t('Menu link title'), + '#maxlength' => 255, '#default_value' => $item['link_title'], '#description' => t('The text to be used for this link in the menu.'), '#required' => TRUE, @@ -305,7 +306,7 @@ function menu_edit_item($form, &$form_state, $type, $item, $menu) { '#title' => t('Path'), '#maxlength' => 255, '#default_value' => $path, - '#description' => t('The path for this menu link. This can be an internal Drupal path such as %add-node or an external URL such as %drupal. Enter %front to link to the front page.', array('%front' => '', '%add-node' => 'node/add', '%drupal' => 'http://drupal.org')), + '#description' => t('The path for this menu link. This can be an internal path such as %add-node or an external URL such as %example. Enter %front to link to the front page.', array('%front' => '', '%add-node' => 'node/add', '%example' => 'http://example.com')), '#required' => TRUE, ); $form['actions']['delete'] = array( diff --git a/modules/menu/menu.info b/modules/menu/menu.info index 14db656..9b7ef57 100644 --- a/modules/menu/menu.info +++ b/modules/menu/menu.info @@ -6,8 +6,8 @@ core = 7.x files[] = menu.test configure = admin/structure/menu -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/menu/menu.module b/modules/menu/menu.module index dc8f015..27b1675 100644 --- a/modules/menu/menu.module +++ b/modules/menu/menu.module @@ -674,6 +674,7 @@ function menu_form_node_form_alter(&$form, $form_state) { $form['menu']['link']['link_title'] = array( '#type' => 'textfield', '#title' => t('Menu link title'), + '#maxlength' => 255, '#default_value' => $link['link_title'], ); diff --git a/modules/menu/menu.test b/modules/menu/menu.test index a9bdb5f..bb792ee 100644 --- a/modules/menu/menu.test +++ b/modules/menu/menu.test @@ -72,6 +72,17 @@ class MenuTestCase extends DrupalWebTestCase { $saved_item = menu_link_load($item['mlid']); $this->assertEqual($description, $saved_item['options']['attributes']['title'], 'Saving an existing link updates the description (title attribute)'); $this->resetMenuLink($item, $old_title); + + // Test that the page title is correct when a local task appears in a + // top-level menu item. See https://www.drupal.org/node/1973262. + $item = $this->addMenuLink(0, 'user/register', 'user-menu'); + $this->drupalGet('user/password'); + $this->assertNoTitle('Home | Drupal'); + $this->drupalLogout(); + $this->drupalGet('user/register'); + $this->assertTitle($item['link_title'] . ' | Drupal'); + $this->drupalGet('user'); + $this->assertNoTitle('Home | Drupal'); } /** @@ -637,7 +648,12 @@ class MenuNodeTestCase extends DrupalWebTestCase { ); $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); - // Create a node. + // Verify that the menu link title on the node add form has the correct + // maxlength. + $this->drupalGet('node/add/page'); + $this->assertPattern('//', 'Menu link title field has correct maxlength in node add form.'); + + // Create a node with menu link disabled. $node_title = $this->randomName(); $language = LANGUAGE_NONE; $edit = array( @@ -673,6 +689,10 @@ class MenuNodeTestCase extends DrupalWebTestCase { $this->drupalGet('node/' . $node->nid . '/edit'); $this->assertOptionSelected('edit-menu-weight', 17, 'Menu weight correct in edit form'); + // Verify that the menu link title on the node edit form has the correct + // maxlength. + $this->assertPattern('//', 'Menu link title field has correct maxlength in node edit form.'); + // Edit the node and remove the menu link. $edit = array( 'menu[enabled]' => FALSE, diff --git a/modules/node/content_types.inc b/modules/node/content_types.inc index 55af667..c451dc7 100644 --- a/modules/node/content_types.inc +++ b/modules/node/content_types.inc @@ -11,7 +11,7 @@ function node_overview_types() { $types = node_type_get_types(); $names = node_type_get_names(); - $field_ui = module_exists('field_ui'); + $field_ui = module_exists('field_ui') && user_access('administer fields'); $header = array(t('Name'), array('data' => t('Operations'), 'colspan' => $field_ui ? '4' : '2')); $rows = array(); diff --git a/modules/node/node.admin.inc b/modules/node/node.admin.inc index 35f4c1d..eead4ea 100644 --- a/modules/node/node.admin.inc +++ b/modules/node/node.admin.inc @@ -329,6 +329,8 @@ function _node_mass_update_helper($nid, $updates) { } /** + * Implements callback_batch_operation(). + * * Executes a batch operation for node_mass_update(). * * @param array $nodes @@ -367,7 +369,9 @@ function _node_mass_update_batch_process($nodes, $updates, &$context) { } /** - * Menu callback: Reports the status of batch operation for node_mass_update(). + * Implements callback_batch_finished(). + * + * Reports the status of batch operation for node_mass_update(). * * @param bool $success * A boolean indicating whether the batch mass update operation successfully @@ -504,14 +508,17 @@ function node_admin_nodes() { $options = array(); foreach ($nodes as $node) { $langcode = entity_language('node', $node); - $l_options = $langcode != LANGUAGE_NONE && isset($languages[$langcode]) ? array('language' => $languages[$langcode]) : array(); + $uri = entity_uri('node', $node); + if ($langcode != LANGUAGE_NONE && isset($languages[$langcode])) { + $uri['options']['language'] = $languages[$langcode]; + } $options[$node->nid] = array( 'title' => array( 'data' => array( '#type' => 'link', '#title' => $node->title, - '#href' => 'node/' . $node->nid, - '#options' => $l_options, + '#href' => $uri['path'], + '#options' => $uri['options'], '#suffix' => ' ' . theme('mark', array('type' => node_mark($node->nid, $node->changed))), ), ), diff --git a/modules/node/node.api.php b/modules/node/node.api.php index a83dee9..c8176a7 100644 --- a/modules/node/node.api.php +++ b/modules/node/node.api.php @@ -950,7 +950,7 @@ function hook_node_info() { * 'recent', or 'comments'. The values should be arrays themselves, with the * following keys available: * - title: (required) The human readable name of the ranking mechanism. - * - join: (optional) The part of a query string to join to any additional + * - join: (optional) An array with information to join any additional * necessary table. This is not necessary if the table required is already * joined to by the base query, such as for the {node} table. Other tables * should use the full table name as an alias to avoid naming collisions. @@ -974,7 +974,12 @@ function hook_ranking() { 'title' => t('Average vote'), // Note that we use i.sid, the search index's search item id, rather than // n.nid. - 'join' => 'LEFT JOIN {vote_node_data} vote_node_data ON vote_node_data.nid = i.sid', + 'join' => array( + 'type' => 'LEFT', + 'table' => 'vote_node_data', + 'alias' => 'vote_node_data', + 'on' => 'vote_node_data.nid = i.sid', + ), // The highest possible score should be 1, and the lowest possible score, // always 0, should be 0. 'score' => 'vote_node_data.average / CAST(%f AS DECIMAL)', @@ -1079,19 +1084,9 @@ function hook_delete($node) { * @ingroup node_api_hooks */ function hook_prepare($node) { - $file = file_save_upload($field_name, _image_filename($file->filename, NULL, TRUE)); - if ($file) { - if (!image_get_info($file->uri)) { - form_set_error($field_name, t('Uploaded file is not a valid image')); - return; - } + if (!isset($node->mymodule_value)) { + $node->mymodule_value = 'foo'; } - else { - return; - } - $node->images['_original'] = $file->uri; - _image_build_derivatives($node, TRUE); - $node->new_file = TRUE; } /** @@ -1299,6 +1294,10 @@ function hook_validate($node, $form, &$form_state) { * The node to be displayed, as returned by node_load(). * @param $view_mode * View mode, e.g. 'full', 'teaser', ... + * @param $langcode + * (optional) A language code to use for rendering. Defaults to the global + * content language of the current request. + * * @return * The passed $node parameter should be modified as necessary and returned so * it can be properly presented. Nodes are prepared for display by assembling @@ -1312,7 +1311,7 @@ function hook_validate($node, $form, &$form_state) { * * @ingroup node_api_hooks */ -function hook_view($node, $view_mode) { +function hook_view($node, $view_mode, $langcode = NULL) { if ($view_mode == 'full' && node_is_page($node)) { $breadcrumb = array(); $breadcrumb[] = l(t('Home'), NULL); diff --git a/modules/node/node.info b/modules/node/node.info index 964ec40..11ae2cd 100644 --- a/modules/node/node.info +++ b/modules/node/node.info @@ -9,8 +9,8 @@ required = TRUE configure = admin/structure/types stylesheets[all][] = node.css -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/node/node.install b/modules/node/node.install index 76c2aec..3c4e7c2 100644 --- a/modules/node/node.install +++ b/modules/node/node.install @@ -410,6 +410,7 @@ function node_schema() { 'nid' => array( 'description' => 'The {node}.nid that was read.', 'type' => 'int', + 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, ), @@ -933,6 +934,33 @@ function node_update_7014() { db_add_index('node', 'language', array('language')); } +/** + * Enable node types that may have been erroneously disabled in Drupal 7.36. + */ +function node_update_7015() { + db_update('node_type') + ->fields(array('disabled' => 0)) + ->condition('base', 'node_content') + ->execute(); +} + +/** + * Change {history}.nid to an unsigned int in order to match {node}.nid. + */ +function node_update_7016() { + db_drop_primary_key('history'); + db_drop_index('history', 'nid'); + db_change_field('history', 'nid', 'nid', array( + 'description' => 'The {node}.nid that was read.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + )); + db_add_primary_key('history', array('uid', 'nid')); + db_add_index('history', 'nid', array('nid')); +} + /** * @} End of "addtogroup updates-7.x-extra". */ diff --git a/modules/node/node.module b/modules/node/node.module index dbb1a65..1d88834 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -506,7 +506,8 @@ function node_type_load($name) { * - custom: TRUE or FALSE indicating whether this type is defined by a module * (FALSE) or by a user (TRUE) via Add Content Type. * - modified: TRUE or FALSE indicating whether this type has been modified by - * an administrator. Currently not used in any way. + * an administrator. When modifying an existing node type, set to TRUE, or + * the change will be ignored on node_types_rebuild(). * - locked: TRUE or FALSE indicating whether the administrator can change the * machine name of this type. * - disabled: TRUE or FALSE indicating whether this type has been disabled. @@ -1580,9 +1581,7 @@ function node_permission() { ), 'access content overview' => array( 'title' => t('Access the content overview page'), - 'description' => user_access('access content overview') - ? t('Get an overview of all content.', array('@url' => url('admin/content'))) - : t('Get an overview of all content.'), + 'description' => t('Get an overview of all content.', array('@url' => url('admin/content'))), ), 'access content' => array( 'title' => t('View published content'), @@ -1610,7 +1609,7 @@ function node_permission() { } /** - * Gathers the rankings from the the hook_ranking() implementations. + * Gathers the rankings from the hook_ranking() implementations. * * @param $query * A query object that has been extended with the Search DB Extender. @@ -2954,7 +2953,10 @@ function node_search_validate($form, &$form_state) { * system. When adding a node listing to your module, be sure to use a dynamic * query created by db_select() and add a tag of "node_access". This will allow * modules dealing with node access to ensure only nodes to which the user has - * access are retrieved, through the use of hook_query_TAG_alter(). + * access are retrieved, through the use of hook_query_TAG_alter(). Tagging a + * query with "node_access" does not check the published/unpublished status of + * nodes, so the base query is responsible for ensuring that unpublished nodes + * are not displayed to inappropriate users. * * Note: Even a single module returning NODE_ACCESS_DENY from hook_node_access() * will block access to the node. Therefore, implementers should take care to @@ -3670,6 +3672,8 @@ function node_access_rebuild($batch_mode = FALSE) { } /** + * Implements callback_batch_operation(). + * * Performs batch operation for node_access_rebuild(). * * This is a multistep operation: we go through all nodes by packs of 20. The @@ -3684,7 +3688,7 @@ function _node_access_rebuild_batch_operation(&$context) { // Initiate multistep processing. $context['sandbox']['progress'] = 0; $context['sandbox']['current_node'] = 0; - $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT nid) FROM {node}')->fetchField(); + $context['sandbox']['max'] = db_query('SELECT COUNT(nid) FROM {node}')->fetchField(); } // Process the next 20 nodes. @@ -3708,6 +3712,8 @@ function _node_access_rebuild_batch_operation(&$context) { } /** + * Implements callback_batch_finished(). + * * Performs post-processing for node_access_rebuild(). * * @param bool $success diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc index 6267463..72b0ea7 100644 --- a/modules/node/node.pages.inc +++ b/modules/node/node.pages.inc @@ -371,35 +371,37 @@ function node_form_build_preview($form, &$form_state) { * @see node_form_build_preview() */ function node_preview($node) { - if (node_access('create', $node) || node_access('update', $node)) { - _field_invoke_multiple('load', 'node', array($node->nid => $node)); + // Clone the node before previewing it to prevent the node itself from being + // modified. + $cloned_node = clone $node; + if (node_access('create', $cloned_node) || node_access('update', $cloned_node)) { + _field_invoke_multiple('load', 'node', array($cloned_node->nid => $cloned_node)); // Load the user's name when needed. - if (isset($node->name)) { + if (isset($cloned_node->name)) { // The use of isset() is mandatory in the context of user IDs, because // user ID 0 denotes the anonymous user. - if ($user = user_load_by_name($node->name)) { - $node->uid = $user->uid; - $node->picture = $user->picture; + if ($user = user_load_by_name($cloned_node->name)) { + $cloned_node->uid = $user->uid; + $cloned_node->picture = $user->picture; } else { - $node->uid = 0; // anonymous user + $cloned_node->uid = 0; // anonymous user } } - elseif ($node->uid) { - $user = user_load($node->uid); - $node->name = $user->name; - $node->picture = $user->picture; + elseif ($cloned_node->uid) { + $user = user_load($cloned_node->uid); + $cloned_node->name = $user->name; + $cloned_node->picture = $user->picture; } - $node->changed = REQUEST_TIME; - $nodes = array($node->nid => $node); - field_attach_prepare_view('node', $nodes, 'full'); + $cloned_node->changed = REQUEST_TIME; + $nodes = array($cloned_node->nid => $cloned_node); // Display a preview of the node. if (!form_get_errors()) { - $node->in_preview = TRUE; - $output = theme('node_preview', array('node' => $node)); - unset($node->in_preview); + $cloned_node->in_preview = TRUE; + $output = theme('node_preview', array('node' => $cloned_node)); + unset($cloned_node->in_preview); } drupal_set_title(t('Preview'), PASS_THROUGH); diff --git a/modules/node/node.test b/modules/node/node.test index 5c9118e..e8eb459 100644 --- a/modules/node/node.test +++ b/modules/node/node.test @@ -457,10 +457,70 @@ class PagePreviewTestCase extends DrupalWebTestCase { } function setUp() { - parent::setUp(); + parent::setUp(array('taxonomy', 'node')); $web_user = $this->drupalCreateUser(array('edit own page content', 'create page content')); $this->drupalLogin($web_user); + + // Add a vocabulary so we can test different view modes. + $vocabulary = (object) array( + 'name' => $this->randomName(), + 'description' => $this->randomName(), + 'machine_name' => drupal_strtolower($this->randomName()), + 'help' => '', + 'nodes' => array('page' => 'page'), + ); + taxonomy_vocabulary_save($vocabulary); + + $this->vocabulary = $vocabulary; + + // Add a term to the vocabulary. + $term = (object) array( + 'name' => $this->randomName(), + 'description' => $this->randomName(), + // Use the first available text format. + 'format' => db_query_range('SELECT format FROM {filter_format}', 0, 1)->fetchField(), + 'vid' => $this->vocabulary->vid, + 'vocabulary_machine_name' => $vocabulary->machine_name, + ); + taxonomy_term_save($term); + + $this->term = $term; + + // Set up a field and instance. + $this->field_name = drupal_strtolower($this->randomName()); + $this->field = array( + 'field_name' => $this->field_name, + 'type' => 'taxonomy_term_reference', + 'settings' => array( + 'allowed_values' => array( + array( + 'vocabulary' => $this->vocabulary->machine_name, + 'parent' => '0', + ), + ), + ) + ); + + field_create_field($this->field); + $this->instance = array( + 'field_name' => $this->field_name, + 'entity_type' => 'node', + 'bundle' => 'page', + 'widget' => array( + 'type' => 'options_select', + ), + // Hide on full display but render on teaser. + 'display' => array( + 'default' => array( + 'type' => 'hidden', + ), + 'teaser' => array( + 'type' => 'taxonomy_term_reference_link', + ), + ), + ); + field_create_instance($this->instance); } /** @@ -470,21 +530,26 @@ class PagePreviewTestCase extends DrupalWebTestCase { $langcode = LANGUAGE_NONE; $title_key = "title"; $body_key = "body[$langcode][0][value]"; + $term_key = "{$this->field_name}[$langcode]"; // Fill in node creation form and preview node. $edit = array(); $edit[$title_key] = $this->randomName(8); $edit[$body_key] = $this->randomName(16); + $edit[$term_key] = $this->term->tid; $this->drupalPost('node/add/page', $edit, t('Preview')); - // Check that the preview is displaying the title and body. + // Check that the preview is displaying the title, body, and term. $this->assertTitle(t('Preview | Drupal'), 'Basic page title is preview.'); $this->assertText($edit[$title_key], 'Title displayed.'); $this->assertText($edit[$body_key], 'Body displayed.'); + $this->assertText($this->term->name, 'Term displayed.'); - // Check that the title and body fields are displayed with the correct values. + // Check that the title, body, and term fields are displayed with the + // correct values. $this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.'); $this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.'); + $this->assertFieldByName($term_key, $edit[$term_key], 'Term field displayed.'); } /** @@ -494,6 +559,7 @@ class PagePreviewTestCase extends DrupalWebTestCase { $langcode = LANGUAGE_NONE; $title_key = "title"; $body_key = "body[$langcode][0][value]"; + $term_key = "{$this->field_name}[$langcode]"; // Force revision on "Basic page" content. variable_set('node_options_page', array('status', 'revision')); @@ -501,17 +567,21 @@ class PagePreviewTestCase extends DrupalWebTestCase { $edit = array(); $edit[$title_key] = $this->randomName(8); $edit[$body_key] = $this->randomName(16); + $edit[$term_key] = $this->term->tid; $edit['log'] = $this->randomName(32); $this->drupalPost('node/add/page', $edit, t('Preview')); - // Check that the preview is displaying the title and body. + // Check that the preview is displaying the title, body, and term. $this->assertTitle(t('Preview | Drupal'), 'Basic page title is preview.'); $this->assertText($edit[$title_key], 'Title displayed.'); $this->assertText($edit[$body_key], 'Body displayed.'); + $this->assertText($this->term->name, 'Term displayed.'); - // Check that the title and body fields are displayed with the correct values. + // Check that the title, body, and term fields are displayed with the + // correct values. $this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.'); $this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.'); + $this->assertFieldByName($term_key, $edit[$term_key], 'Term field displayed.'); // Check that the log field has the correct value. $this->assertFieldByName('log', $edit['log'], 'Log field displayed.'); @@ -1448,7 +1518,7 @@ class NodeTypeTestCase extends DrupalWebTestCase { * Tests editing a node type using the UI. */ function testNodeTypeEditing() { - $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types')); + $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types', 'administer fields')); $this->drupalLogin($web_user); $instance = field_info_instance('node', 'body', 'page'); @@ -2698,8 +2768,8 @@ class NodeAccessFieldTestCase extends NodeWebTestCase { node_access_rebuild(); // Create some users. - $this->admin_user = $this->drupalCreateUser(array('access content', 'bypass node access')); - $this->content_admin_user = $this->drupalCreateUser(array('access content', 'administer content types')); + $this->admin_user = $this->drupalCreateUser(array('access content', 'bypass node access', 'administer fields')); + $this->content_admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer fields')); // Add a custom field to the page content type. $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); @@ -2916,3 +2986,36 @@ class NodePageCacheTest extends NodeWebTestCase { $this->assertResponse(404); } } + +/** + * Tests that multi-byte UTF-8 characters are stored and retrieved correctly. + */ +class NodeMultiByteUtf8Test extends NodeWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Multi-byte UTF-8', + 'description' => 'Test that multi-byte UTF-8 characters are stored and retrieved correctly.', + 'group' => 'Node', + ); + } + + /** + * Tests that multi-byte UTF-8 characters are stored and retrieved correctly. + */ + public function testMultiByteUtf8() { + $connection = Database::getConnection(); + // On MySQL, this test will only run if 'charset' is set to 'utf8mb4' in + // settings.php. + if (!($connection->utf8mb4IsSupported() && $connection->utf8mb4IsActive())) { + return; + } + $title = '🐙'; + $this->assertTrue(drupal_strlen($title, 'utf-8') < strlen($title), 'Title has multi-byte characters.'); + $node = $this->drupalCreateNode(array('title' => $title)); + $this->drupalGet('node/' . $node->nid); + $result = $this->xpath('//h1[@id="page-title"]'); + $this->assertEqual(trim((string) $result[0]), $title, 'The passed title was returned.'); + } + +} diff --git a/modules/node/tests/node_access_test.info b/modules/node/tests/node_access_test.info index 17946ef..0079290 100644 --- a/modules/node/tests/node_access_test.info +++ b/modules/node/tests/node_access_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/node/tests/node_access_test.module b/modules/node/tests/node_access_test.module index ec35c41..7932c55 100644 --- a/modules/node/tests/node_access_test.module +++ b/modules/node/tests/node_access_test.module @@ -211,7 +211,7 @@ function node_access_test_node_insert($node) { } /** - * Implements hook_nodeapi_update(). + * Implements hook_node_update(). */ function node_access_test_node_update($node) { _node_access_test_node_write($node); diff --git a/modules/node/tests/node_test.info b/modules/node/tests/node_test.info index b9b946e..f56e2e5 100644 --- a/modules/node/tests/node_test.info +++ b/modules/node/tests/node_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/node/tests/node_test_exception.info b/modules/node/tests/node_test_exception.info index 5db9629..a4c1185 100644 --- a/modules/node/tests/node_test_exception.info +++ b/modules/node/tests/node_test_exception.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/openid/openid.info b/modules/openid/openid.info index 1b97354..69d2663 100644 --- a/modules/openid/openid.info +++ b/modules/openid/openid.info @@ -5,8 +5,8 @@ package = Core core = 7.x files[] = openid.test -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/openid/openid.module b/modules/openid/openid.module index a28f452..a52dbc3 100644 --- a/modules/openid/openid.module +++ b/modules/openid/openid.module @@ -365,14 +365,20 @@ function openid_complete($response = array()) { // to the OpenID Provider, we need to do discovery on the returned // identififer to make sure that the provider is authorized to // respond on behalf of this. - if ($response_claimed_id != $claimed_id) { + if ($response_claimed_id != $claimed_id || $response_claimed_id != $response['openid.identity']) { $discovery = openid_discovery($response['openid.claimed_id']); + $uris = array(); if ($discovery && !empty($discovery['services'])) { - $uris = array(); foreach ($discovery['services'] as $discovered_service) { - if (in_array('http://specs.openid.net/auth/2.0/server', $discovered_service['types']) || in_array('http://specs.openid.net/auth/2.0/signon', $discovered_service['types'])) { - $uris[] = $discovered_service['uri']; + if (!in_array('http://specs.openid.net/auth/2.0/server', $discovered_service['types']) && !in_array('http://specs.openid.net/auth/2.0/signon', $discovered_service['types'])) { + continue; } + // The OP-Local Identifier (if different than the Claimed + // Identifier) must be present in the XRDS document. + if ($response_claimed_id != $response['openid.identity'] && (!isset($discovered_service['identity']) || $discovered_service['identity'] != $response['openid.identity'])) { + continue; + } + $uris[] = $discovered_service['uri']; } } if (!in_array($service['uri'], $uris)) { diff --git a/modules/openid/openid.test b/modules/openid/openid.test index 41af3f8..d0708e0 100644 --- a/modules/openid/openid.test +++ b/modules/openid/openid.test @@ -94,7 +94,7 @@ class OpenIDFunctionalTestCase extends OpenIDWebTestCase { $identity = url('openid-test/yadis/xrds/dummy-user', array('absolute' => TRUE, 'fragment' => $this->randomName())); // Tell openid_test.module to respond with this identifier. If the fragment // part is present in the identifier, it should be retained. - variable_set('openid_test_response', array('openid.claimed_id' => $identity)); + variable_set('openid_test_response', array('openid.claimed_id' => $identity, 'openid.identity' => openid_normalize($identity))); $this->addIdentity(url('openid-test/yadis/xrds/server', array('absolute' => TRUE)), 2, 'http://specs.openid.net/auth/2.0/identifier_select', $identity); variable_set('openid_test_response', array()); @@ -680,11 +680,11 @@ class OpenIDTestCase extends DrupalWebTestCase { * Test _openid_dh_XXX_to_XXX() functions. */ function testConversion() { - $this->assertEqual(_openid_dh_long_to_base64('12345678901234567890123456789012345678901234567890'), 'CHJ/Y2mq+DyhUCZ0evjH8ZbOPwrS', '_openid_dh_long_to_base64() returned expected result.'); - $this->assertEqual(_openid_dh_base64_to_long('BsH/g8Nrpn2dtBSdu/sr1y8hxwyx'), '09876543210987654321098765432109876543210987654321', '_openid_dh_base64_to_long() returned expected result.'); + $this->assertIdentical(_openid_dh_long_to_base64('12345678901234567890123456789012345678901234567890'), 'CHJ/Y2mq+DyhUCZ0evjH8ZbOPwrS', '_openid_dh_long_to_base64() returned expected result.'); + $this->assertIdentical(_openid_dh_base64_to_long('BsH/g8Nrpn2dtBSdu/sr1y8hxwyx'), '9876543210987654321098765432109876543210987654321', '_openid_dh_base64_to_long() returned expected result.'); - $this->assertEqual(_openid_dh_long_to_binary('12345678901234567890123456789012345678901234567890'), "\x08r\x7fci\xaa\xf8<\xa1P&tz\xf8\xc7\xf1\x96\xce?\x0a\xd2", '_openid_dh_long_to_binary() returned expected result.'); - $this->assertEqual(_openid_dh_binary_to_long("\x06\xc1\xff\x83\xc3k\xa6}\x9d\xb4\x14\x9d\xbb\xfb+\xd7/!\xc7\x0c\xb1"), '09876543210987654321098765432109876543210987654321', '_openid_dh_binary_to_long() returned expected result.'); + $this->assertIdentical(_openid_dh_long_to_binary('12345678901234567890123456789012345678901234567890'), "\x08r\x7fci\xaa\xf8<\xa1P&tz\xf8\xc7\xf1\x96\xce?\x0a\xd2", '_openid_dh_long_to_binary() returned expected result.'); + $this->assertIdentical(_openid_dh_binary_to_long("\x06\xc1\xff\x83\xc3k\xa6}\x9d\xb4\x14\x9d\xbb\xfb+\xd7/!\xc7\x0c\xb1"), '9876543210987654321098765432109876543210987654321', '_openid_dh_binary_to_long() returned expected result.'); } /** diff --git a/modules/openid/tests/openid_test.info b/modules/openid/tests/openid_test.info index a33d596..7186198 100644 --- a/modules/openid/tests/openid_test.info +++ b/modules/openid/tests/openid_test.info @@ -6,8 +6,8 @@ core = 7.x dependencies[] = openid hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/openid/tests/openid_test.module b/modules/openid/tests/openid_test.module index bcf9f42..3d6e292 100644 --- a/modules/openid/tests/openid_test.module +++ b/modules/openid/tests/openid_test.module @@ -150,6 +150,7 @@ function openid_test_yadis_xrds() { http://specs.openid.net/auth/2.0/server ' . url('openid-test/endpoint', array('absolute' => TRUE)) . ' + ' . url('openid-test/yadis/xrds/server', array('absolute' => TRUE)) . ' '; } elseif (arg(3) == 'delegate') { diff --git a/modules/overlay/overlay-parent.js b/modules/overlay/overlay-parent.js index 7452a51..efb2637 100644 --- a/modules/overlay/overlay-parent.js +++ b/modules/overlay/overlay-parent.js @@ -350,7 +350,7 @@ Drupal.overlay.setFocusBefore = function ($element, document) { * TRUE if the URL represents an administrative link, FALSE otherwise. */ Drupal.overlay.isAdminLink = function (url) { - if (Drupal.overlay.isExternalLink(url)) { + if (!Drupal.urlIsLocal(url)) { return false; } @@ -378,6 +378,8 @@ Drupal.overlay.isAdminLink = function (url) { /** * Determine whether a link is external to the site. * + * Deprecated. Use Drupal.urlIsLocal() instead. + * * @param url * The URL to be tested. * @@ -385,8 +387,28 @@ Drupal.overlay.isAdminLink = function (url) { * TRUE if the URL is external to the site, FALSE otherwise. */ Drupal.overlay.isExternalLink = function (url) { - var re = RegExp('^((f|ht)tps?:)?//(?!' + window.location.host + ')'); - return re.test(url); + return !Drupal.urlIsLocal(url); +}; + +/** + * Constructs an internal URL (relative to this site) from the provided path. + * + * For example, if the provided path is 'admin' and the site is installed at + * http://example.com/drupal, this function will return '/drupal/admin'. + * + * @param path + * The internal path, without any leading slash. + * + * @return + * The internal URL derived from the provided path, or null if a valid + * internal path cannot be constructed (for example, if an attempt to create + * an external link is detected). + */ +Drupal.overlay.getInternalUrl = function (path) { + var url = Drupal.settings.basePath + path; + if (Drupal.urlIsLocal(url)) { + return url; + } }; /** @@ -577,7 +599,7 @@ Drupal.overlay.eventhandlerOverrideLink = function (event) { // If the link contains the overlay-restore class and the overlay-context // state is set, also update the parent window's location. var parentLocation = ($target.hasClass('overlay-restore') && typeof $.bbq.getState('overlay-context') == 'string') - ? Drupal.settings.basePath + $.bbq.getState('overlay-context') + ? this.getInternalUrl($.bbq.getState('overlay-context')) : null; href = this.fragmentizeLink($target.get(0), parentLocation); // Only override default behavior when left-clicking and user is not @@ -657,11 +679,15 @@ Drupal.overlay.eventhandlerOperateByURLFragment = function (event) { } // Get the overlay URL from the current URL fragment. + var internalUrl = null; var state = $.bbq.getState('overlay'); if (state) { + internalUrl = this.getInternalUrl(state); + } + if (internalUrl) { // Append render variable, so the server side can choose the right // rendering and add child frame code to the page if needed. - var url = $.param.querystring(Drupal.settings.basePath + state, { render: 'overlay' }); + var url = $.param.querystring(internalUrl, { render: 'overlay' }); this.open(url); this.resetActiveClass(this.getPath(Drupal.settings.basePath + state)); diff --git a/modules/overlay/overlay.info b/modules/overlay/overlay.info index 269e323..1bf7e9e 100644 --- a/modules/overlay/overlay.info +++ b/modules/overlay/overlay.info @@ -4,8 +4,8 @@ package = Core version = VERSION core = 7.x -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/overlay/overlay.module b/modules/overlay/overlay.module index 7b2fc93..7e54734 100644 --- a/modules/overlay/overlay.module +++ b/modules/overlay/overlay.module @@ -78,6 +78,20 @@ function overlay_theme() { ); } +/** + * Implements hook_form_alter(). + */ +function overlay_form_alter(&$form, &$form_state) { + // Add a hidden element to prevent dropping out of the overlay when a form is + // submitted inside the overlay using a GET method. + if (isset($form['#method']) && $form['#method'] == 'get' && isset($_REQUEST['render']) && $_REQUEST['render'] == 'overlay' && !isset($form['render'])) { + $form['render'] = array( + '#type' => 'hidden', + '#value' => 'overlay', + ); + } +} + /** * Implements hook_form_FORM_ID_alter(). */ diff --git a/modules/path/path.info b/modules/path/path.info index 3445b7c..b5b0eb9 100644 --- a/modules/path/path.info +++ b/modules/path/path.info @@ -6,8 +6,8 @@ core = 7.x files[] = path.test configure = admin/config/search/path -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/path/path.module b/modules/path/path.module index 81c7bb2..4614b0f 100644 --- a/modules/path/path.module +++ b/modules/path/path.module @@ -185,7 +185,7 @@ function path_form_element_validate($element, &$form_state, $complete_form) { * Implements hook_node_insert(). */ function path_node_insert($node) { - if (isset($node->path)) { + if (isset($node->path) && isset($node->path['alias'])) { $path = $node->path; $path['alias'] = trim($path['alias']); // Only save a non-empty alias. @@ -205,9 +205,9 @@ function path_node_insert($node) { function path_node_update($node) { if (isset($node->path)) { $path = $node->path; - $path['alias'] = trim($path['alias']); + $path['alias'] = isset($path['alias']) ? trim($path['alias']) : ''; // Delete old alias if user erased it. - if (!empty($path['pid']) && empty($path['alias'])) { + if (!empty($path['pid']) && !$path['alias']) { path_delete($path['pid']); } path_node_insert($node); diff --git a/modules/php/php.info b/modules/php/php.info index 1fcb89d..236f931 100644 --- a/modules/php/php.info +++ b/modules/php/php.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x files[] = php.test -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/poll/poll.info b/modules/poll/poll.info index 101b99e..eeed31d 100644 --- a/modules/poll/poll.info +++ b/modules/poll/poll.info @@ -6,8 +6,8 @@ core = 7.x files[] = poll.test stylesheets[all][] = poll.css -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/poll/poll.module b/modules/poll/poll.module index d3d64b1..336e445 100644 --- a/modules/poll/poll.module +++ b/modules/poll/poll.module @@ -191,7 +191,6 @@ function poll_node_info() { 'base' => 'poll', 'description' => t('A poll is a question with a set of possible responses. A poll, once created, automatically provides a simple running count of the number of votes received for each response.'), 'title_label' => t('Question'), - 'has_body' => FALSE, ) ); } @@ -632,9 +631,6 @@ function poll_delete($node) { * The node object to load. */ function poll_block_latest_poll_view($node) { - global $user; - $output = ''; - // This is necessary for shared objects because PHP doesn't copy objects, but // passes them by reference. So when the objects are cached it can result in // the wrong output being displayed on subsequent calls. The cloning and @@ -675,9 +671,6 @@ function poll_block_latest_poll_view($node) { * Implements hook_view(). */ function poll_view($node, $view_mode) { - global $user; - $output = ''; - if (!empty($node->allowvotes) && empty($node->show_results)) { $node->content['poll_view_voting'] = drupal_get_form('poll_view_voting', $node); } @@ -695,7 +688,7 @@ function poll_view($node, $view_mode) { function poll_teaser($node) { $teaser = NULL; if (is_array($node->choice)) { - foreach ($node->choice as $k => $choice) { + foreach ($node->choice as $choice) { if ($choice['chtext'] != '') { $teaser .= '* ' . check_plain($choice['chtext']) . "\n"; } diff --git a/modules/profile/profile.info b/modules/profile/profile.info index 593be3e..1480c61 100644 --- a/modules/profile/profile.info +++ b/modules/profile/profile.info @@ -11,8 +11,8 @@ configure = admin/config/people/profile ; See user_system_info_alter(). hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/profile/profile.test b/modules/profile/profile.test index 42a1a42..1892471 100644 --- a/modules/profile/profile.test +++ b/modules/profile/profile.test @@ -339,12 +339,22 @@ class ProfileTestAutocomplete extends ProfileTestCase { $this->setProfileField($field, $field['value']); // Set some html for what we want to see in the page output later. - $autocomplete_html = ''; - $field_html = ''; + // Autocomplete always uses non-clean URLs. + $current_clean_url = isset($GLOBALS['conf']['clean_url']) ? $GLOBALS['conf']['clean_url'] : NULL; + $GLOBALS['conf']['clean_url'] = 0; + $autocomplete_url = url('profile/autocomplete/' . $field['fid'], array('absolute' => TRUE, 'script' => 'index.php')); + $GLOBALS['conf']['clean_url'] = $current_clean_url; + $autocomplete_id = drupal_html_id('edit-' . $field['form_name'] . '-autocomplete'); + $autocomplete_html = ''; // Check that autocompletion html is found on the user's profile edit page. $this->drupalGet('user/' . $this->admin_user->uid . '/edit/' . $category); $this->assertRaw($autocomplete_html, 'Autocomplete found.'); + $this->assertFieldByXPath( + '//input[@type="text" and @name="' . $field['form_name'] . '" and contains(@class, "form-autocomplete")]', + '', + 'Text input field found' + ); $this->assertRaw('misc/autocomplete.js', 'Autocomplete JavaScript found.'); $this->assertRaw('class="form-text form-autocomplete"', 'Autocomplete form element class found.'); diff --git a/modules/rdf/rdf.info b/modules/rdf/rdf.info index e5c963c..18e6297 100644 --- a/modules/rdf/rdf.info +++ b/modules/rdf/rdf.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x files[] = rdf.test -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/rdf/tests/rdf_test.info b/modules/rdf/tests/rdf_test.info index 7602a94..a302a7b 100644 --- a/modules/rdf/tests/rdf_test.info +++ b/modules/rdf/tests/rdf_test.info @@ -4,9 +4,10 @@ package = Testing version = VERSION core = 7.x hidden = TRUE +dependencies[] = blog -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/search/search.admin.inc b/modules/search/search.admin.inc index a609485..a37d37b 100644 --- a/modules/search/search.admin.inc +++ b/modules/search/search.admin.inc @@ -125,6 +125,16 @@ function search_admin_settings($form) { '#options' => $module_options, '#description' => t('Choose which search module is the default.') ); + $form['logging'] = array( + '#type' => 'fieldset', + '#title' => t('Logging') + ); + $form['logging']['search_logging'] = array( + '#type' => 'checkbox', + '#title' => t('Log searches'), + '#default_value' => variable_get('search_logging', 1), + '#description' => t('If checked, all searches will be logged. Uncheck to skip logging. Logging may affect performance.'), + ); $form['#validate'][] = 'search_admin_settings_validate'; $form['#submit'][] = 'search_admin_settings_submit'; diff --git a/modules/search/search.api.php b/modules/search/search.api.php index 62d53b8..8c17bb4 100644 --- a/modules/search/search.api.php +++ b/modules/search/search.api.php @@ -30,8 +30,9 @@ * * @return * Array with optional keys: - * - title: Title for the tab on the search page for this module. Defaults - * to the module name if not given. + * - title: Title for the tab on the search page for this module. Title must + * be untranslated. Outside of this return array, pass the title through the + * t() function to register it as a translatable string. * - path: Path component after 'search/' for searching with this module. * Defaults to the module name if not given. * - conditions_callback: An implementation of callback_search_conditions(). @@ -39,6 +40,9 @@ * @ingroup search */ function hook_search_info() { + // Make the title translatable. + t('Content'); + return array( 'title' => 'Content', 'path' => 'node', diff --git a/modules/search/search.extender.inc b/modules/search/search.extender.inc index 6709466..4074256 100644 --- a/modules/search/search.extender.inc +++ b/modules/search/search.extender.inc @@ -149,6 +149,17 @@ class SearchQuery extends SelectQueryExtender { $this->searchExpression = $expression; $this->type = $module; + // Add a search_* tag. This needs to be added before any preExecute methods + // for decorated queries are called, as $this->prepared will be set to TRUE + // and tags added in the execute method will never get used. For example, + // if $query is extended by 'SearchQuery' then 'PagerDefault', the + // search-specific tag will be added too late (when preExecute() has + // already been called from the PagerDefault extender), and as a + // consequence will not be available to hook_query_alter() implementations, + // nor will the correct hook_query_TAG_alter() implementations get invoked. + // See node_search_execute(). + $this->addTag('search_' . $module); + return $this; } @@ -398,10 +409,10 @@ class SearchQuery extends SelectQueryExtender { * used. However, if at least one call to addScore() has taken place, the * keyword relevance score is not automatically added. * - * Also note that if you call orderBy() directly on the query, search scores - * will not automatically be used to order search results. Your orderBy() - * expression can reference 'calculated_score', which will be the total - * calculated score value. + * Note that you must use this method to add ordering to your searches, and + * not call orderBy() directly, when using the SearchQuery extender. This is + * because of the two-pass system the SearchQuery class uses to normalize + * scores. * * @param $score * The score expression, which should evaluate to a number between 0 and 1. @@ -494,9 +505,8 @@ class SearchQuery extends SelectQueryExtender { $this->orderBy('calculated_score', 'DESC'); } - // Add tag and useful metadata. + // Add useful metadata. $this - ->addTag('search_' . $this->type) ->addMetaData('normalize', $this->normalize) ->fields('i', array('type', 'sid')); diff --git a/modules/search/search.info b/modules/search/search.info index 0a1bcc2..248f476 100644 --- a/modules/search/search.info +++ b/modules/search/search.info @@ -8,8 +8,8 @@ files[] = search.test configure = admin/config/search/settings stylesheets[all][] = search.css -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/search/search.install b/modules/search/search.install index f0113b3..c91283c 100644 --- a/modules/search/search.install +++ b/modules/search/search.install @@ -12,6 +12,7 @@ function search_uninstall() { variable_del('minimum_word_size'); variable_del('overlap_cjk'); variable_del('search_cron_limit'); + variable_del('search_logging'); } /** diff --git a/modules/search/search.pages.inc b/modules/search/search.pages.inc index 9dd00a6..b24de8e 100644 --- a/modules/search/search.pages.inc +++ b/modules/search/search.pages.inc @@ -49,7 +49,7 @@ function search_view($module = NULL, $keys = '') { // which will get us back to this page callback. In other words, the search // form submits with POST but redirects to GET. This way we can keep // the search query URL clean as a whistle. - if (empty($_POST['form_id']) || $_POST['form_id'] != 'search_form') { + if (empty($_POST['form_id']) || ($_POST['form_id'] != 'search_form' && $_POST['form_id'] != 'search_block_form')) { $conditions = NULL; if (isset($info['conditions_callback']) && function_exists($info['conditions_callback'])) { // Build an optional array of more search conditions. @@ -57,9 +57,10 @@ function search_view($module = NULL, $keys = '') { } // Only search if there are keywords or non-empty conditions. if ($keys || !empty($conditions)) { - // Log the search keys. - watchdog('search', 'Searched %type for %keys.', array('%keys' => $keys, '%type' => $info['title']), WATCHDOG_NOTICE, l(t('results'), 'search/' . $info['path'] . '/' . $keys)); - + if (variable_get('search_logging', TRUE)) { + // Log the search keys. + watchdog('search', 'Searched %type for %keys.', array('%keys' => $keys, '%type' => $info['title']), WATCHDOG_NOTICE, l(t('results'), 'search/' . $info['path'] . '/' . $keys)); + } // Collect the search results. $results = search_data($keys, $info['module'], $conditions); } diff --git a/modules/search/search.test b/modules/search/search.test index 19f4e55..d3a60b4 100644 --- a/modules/search/search.test +++ b/modules/search/search.test @@ -666,6 +666,24 @@ class SearchBlockTestCase extends DrupalWebTestCase { url('search/node/', array('absolute' => TRUE)), 'Redirected to correct url.' ); + + // Test that after entering a too-short keyword in the form, you can then + // search again with a longer keyword. First test using the block form. + $terms = array('search_block_form' => 'a'); + $this->drupalPost('node', $terms, t('Search')); + $this->assertText('You must include at least one positive keyword with 3 characters or more'); + $terms = array('search_block_form' => 'foo'); + $this->drupalPost(NULL, $terms, t('Search')); + $this->assertNoText('You must include at least one positive keyword with 3 characters or more'); + $this->assertText('Your search yielded no results'); + + // Same test again, using the search page form for the second search this time. + $terms = array('search_block_form' => 'a'); + $this->drupalPost('node', $terms, t('Search')); + $terms = array('keys' => 'foo'); + $this->drupalPost(NULL, $terms, t('Search')); + $this->assertNoText('You must include at least one positive keyword with 3 characters or more'); + $this->assertText('Your search yielded no results'); } } @@ -1435,7 +1453,7 @@ class SearchConfigSettingsForm extends DrupalWebTestCase { parent::setUp('search', 'search_extra_type'); // Login as a user that can create and search content. - $this->search_user = $this->drupalCreateUser(array('search content', 'administer search', 'administer nodes', 'bypass node access', 'access user profiles', 'administer users', 'administer blocks')); + $this->search_user = $this->drupalCreateUser(array('search content', 'administer search', 'administer nodes', 'bypass node access', 'access user profiles', 'administer users', 'administer blocks', 'access site reports')); $this->drupalLogin($this->search_user); // Add a single piece of content and index it. @@ -1484,6 +1502,19 @@ class SearchConfigSettingsForm extends DrupalWebTestCase { ); $this->drupalPost('admin/config/search/settings', $edit, t('Save configuration')); $this->assertNoText(t('The configuration options have been saved.'), 'Form does not save with an invalid word length.'); + + // Test logging setting. It should be on by default. + $text = $this->randomName(5); + $this->drupalPost('search/node', array('keys' => $text), t('Search')); + $this->drupalGet('admin/reports/dblog'); + $this->assertLink('Searched Content for ' . $text . '.', 0, 'Search was logged'); + + // Turn off logging. + variable_set('search_logging', FALSE); + $text = $this->randomName(5); + $this->drupalPost('search/node', array('keys' => $text), t('Search')); + $this->drupalGet('admin/reports/dblog'); + $this->assertNoLink('Searched Content for ' . $text . '.', 'Search was not logged'); } /** @@ -2029,10 +2060,11 @@ class SearchNodeAccessTest extends DrupalWebTestCase { } /** - * Tests that search returns results with punctuation in the search phrase. + * Tests that search works with punctuation and HTML entities. */ function testPhraseSearchPunctuation() { $node = $this->drupalCreateNode(array('body' => array(LANGUAGE_NONE => array(array('value' => "The bunny's ears were fuzzy."))))); + $node2 = $this->drupalCreateNode(array('body' => array(LANGUAGE_NONE => array(array('value' => 'Dignissim Aliquam & Quieligo meus natu quae quia te. Damnum© erat— neo pneum. Facilisi feugiat ibidem ratis.'))))); // Update the search index. module_invoke_all('update_index'); @@ -2045,6 +2077,58 @@ class SearchNodeAccessTest extends DrupalWebTestCase { $edit = array('keys' => '"bunny\'s"'); $this->drupalPost('search/node', $edit, t('Search')); $this->assertText($node->title); + + // Search for "&" and verify entities are not broken up in the output. + $edit = array('keys' => '&'); + $this->drupalPost('search/node', $edit, t('Search')); + $this->assertNoRaw('&amp;'); + $this->assertText('You must include at least one positive keyword'); + + $edit = array('keys' => '&'); + $this->drupalPost('search/node', $edit, t('Search')); + $this->assertNoRaw('&amp;'); + $this->assertText('You must include at least one positive keyword'); + } +} + +/** + * Tests node search with query tags. + */ +class SearchNodeTagTest extends DrupalWebTestCase { + public $test_user; + + public static function getInfo() { + return array( + 'name' => 'Node search query tags', + 'description' => 'Tests Node search tags functionality.', + 'group' => 'Search', + ); + } + + function setUp() { + parent::setUp('search', 'search_node_tags'); + node_access_rebuild(); + + // Create a test user and log in. + $this->test_user = $this->drupalCreateUser(array('search content')); + $this->drupalLogin($this->test_user); + } + + /** + * Tests that the correct tags are available and hooks invoked. + */ + function testNodeSearchQueryTags() { + $this->drupalCreateNode(array('body' => array(LANGUAGE_NONE => array(array('value' => 'testing testing testing.'))))); + + // Update the search index. + module_invoke_all('update_index'); + search_update_totals(); + + $edit = array('keys' => 'testing'); + $this->drupalPost('search/node', $edit, t('Search')); + + $this->assertTrue(variable_get('search_node_tags_test_query_tag', FALSE), 'hook_query_alter() was invoked and the query contained the "search_node" tag.'); + $this->assertTrue(variable_get('search_node_tags_test_query_tag_hook', FALSE), 'hook_query_search_node_alter() was invoked.'); } } diff --git a/modules/search/tests/search_embedded_form.info b/modules/search/tests/search_embedded_form.info index 2c789cc..7e1b736 100644 --- a/modules/search/tests/search_embedded_form.info +++ b/modules/search/tests/search_embedded_form.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/search/tests/search_extra_type.info b/modules/search/tests/search_extra_type.info index af8ba67..534edad 100644 --- a/modules/search/tests/search_extra_type.info +++ b/modules/search/tests/search_extra_type.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/search/tests/search_node_tags.info b/modules/search/tests/search_node_tags.info new file mode 100644 index 0000000..1643898 --- /dev/null +++ b/modules/search/tests/search_node_tags.info @@ -0,0 +1,12 @@ +name = "Test search node tags" +description = "Support module for Node search tags testing." +package = Testing +version = VERSION +core = 7.x +hidden = TRUE + +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" +project = "drupal" +datestamp = "1522264019" + diff --git a/modules/search/tests/search_node_tags.module b/modules/search/tests/search_node_tags.module new file mode 100644 index 0000000..b66dd9e --- /dev/null +++ b/modules/search/tests/search_node_tags.module @@ -0,0 +1,23 @@ +hasTag('search_node')) { + variable_set('search_node_tags_test_query_tag', TRUE); + } +} + +/** + * Implements hook_query_TAG_alter(). + */ +function search_node_tags_query_search_node_alter(QueryAlterableInterface $query) { + variable_set('search_node_tags_test_query_tag_hook', TRUE); +} diff --git a/modules/shortcut/shortcut.info b/modules/shortcut/shortcut.info index 2aa55da..c490374 100644 --- a/modules/shortcut/shortcut.info +++ b/modules/shortcut/shortcut.info @@ -6,8 +6,8 @@ core = 7.x files[] = shortcut.test configure = admin/config/user-interface/shortcut -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php index 8ecddb5..3124ffe 100644 --- a/modules/simpletest/drupal_web_test_case.php +++ b/modules/simpletest/drupal_web_test_case.php @@ -39,6 +39,13 @@ abstract class DrupalTestCase { */ protected $originalFileDirectory = NULL; + /** + * URL to the verbose output file directory. + * + * @var string + */ + protected $verboseDirectoryUrl; + /** * Time limit for the test. */ @@ -461,8 +468,11 @@ abstract class DrupalTestCase { protected function verbose($message) { if ($id = simpletest_verbose($message)) { $class_safe = str_replace('\\', '_', get_class($this)); - $url = file_create_url($this->originalFileDirectory . '/simpletest/verbose/' . $class_safe . '-' . $id . '.html'); - $this->error(l(t('Verbose message'), $url, array('attributes' => array('target' => '_blank'))), 'User notice'); + $url = $this->verboseDirectoryUrl . '/' . $class_safe . '-' . $id . '.html'; + // Not using l() to avoid invoking the theme system, so that unit tests + // can use verbose() as well. + $link = '' . t('Verbose message') . ''; + $this->error($link, 'User notice'); } } @@ -719,10 +729,17 @@ class DrupalUnitTestCase extends DrupalTestCase { * method. */ protected function setUp() { - global $conf; + global $conf, $language; // Store necessary current values before switching to the test environment. $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files'); + $this->verboseDirectoryUrl = file_create_url($this->originalFileDirectory . '/simpletest/verbose'); + + // Set up English language. + $this->originalLanguage = $language; + $this->originalLanguageDefault = variable_get('language_default'); + unset($conf['language_default']); + $language = language_default(); // Reset all statics so that test is performed with a clean environment. drupal_static_reset(); @@ -764,7 +781,7 @@ class DrupalUnitTestCase extends DrupalTestCase { } protected function tearDown() { - global $conf; + global $conf, $language; // Get back to the original connection. Database::removeConnection('default'); @@ -775,6 +792,12 @@ class DrupalUnitTestCase extends DrupalTestCase { if (isset($this->originalModuleList)) { module_list(TRUE, FALSE, FALSE, $this->originalModuleList); } + + // Reset language. + $language = $this->originalLanguage; + if ($this->originalLanguageDefault) { + $GLOBALS['conf']['language_default'] = $this->originalLanguageDefault; + } } } @@ -853,6 +876,13 @@ class DrupalWebTestCase extends DrupalTestCase { */ protected $cookieFile = NULL; + /** + * The cookies of the page currently loaded in the internal browser. + * + * @var array + */ + protected $cookies = array(); + /** * Additional cURL options. * @@ -942,7 +972,6 @@ class DrupalWebTestCase extends DrupalTestCase { protected function drupalCreateNode($settings = array()) { // Populate defaults array. $settings += array( - 'body' => array(LANGUAGE_NONE => array(array())), 'title' => $this->randomName(8), 'comment' => 2, 'changed' => REQUEST_TIME, @@ -957,6 +986,12 @@ class DrupalWebTestCase extends DrupalTestCase { 'language' => LANGUAGE_NONE, ); + // Add the body after the language is defined so that it may be set + // properly. + $settings += array( + 'body' => array($settings['language'] => array(array())), + ); + // Use the original node's created time for existing nodes. if (isset($settings['created']) && !isset($settings['date'])) { $settings['date'] = format_date($settings['created'], 'custom', 'Y-m-d H:i:s O'); @@ -1015,9 +1050,7 @@ class DrupalWebTestCase extends DrupalTestCase { 'description' => '', 'help' => '', 'title_label' => 'Title', - 'body_label' => 'Body', 'has_title' => 1, - 'has_body' => 1, ); // Imposed values for a custom type. $forced = array( @@ -1067,7 +1100,7 @@ class DrupalWebTestCase extends DrupalTestCase { $lines = array(16, 256, 1024, 2048, 20480); $count = 0; foreach ($lines as $line) { - simpletest_generate_file('text-' . $count++, 64, $line); + simpletest_generate_file('text-' . $count++, 64, $line, 'text'); } // Copy other test files from simpletest. @@ -1364,12 +1397,14 @@ class DrupalWebTestCase extends DrupalTestCase { * @see DrupalWebTestCase::tearDown() */ protected function prepareEnvironment() { - global $user, $language, $conf; + global $user, $language, $language_url, $conf; // Store necessary current values before switching to prefixed database. $this->originalLanguage = $language; + $this->originalLanguageUrl = $language_url; $this->originalLanguageDefault = variable_get('language_default'); $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files'); + $this->verboseDirectoryUrl = file_create_url($this->originalFileDirectory . '/simpletest/verbose'); $this->originalProfile = drupal_get_profile(); $this->originalCleanUrl = variable_get('clean_url', 0); $this->originalUser = $user; @@ -1377,7 +1412,7 @@ class DrupalWebTestCase extends DrupalTestCase { // Set to English to prevent exceptions from utf8_truncate() from t() // during install if the current language is not 'en'. // The following array/object conversion is copied from language_default(). - $language = (object) array('language' => 'en', 'name' => 'English', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => '', 'weight' => 0, 'javascript' => ''); + $language_url = $language = (object) array('language' => 'en', 'name' => 'English', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => '', 'weight' => 0, 'javascript' => ''); // Save and clean the shutdown callbacks array because it is static cached // and will be changed by the test run. Otherwise it will contain callbacks @@ -1435,7 +1470,7 @@ class DrupalWebTestCase extends DrupalTestCase { * @see DrupalWebTestCase::prepareEnvironment() */ protected function setUp() { - global $user, $language, $conf; + global $user, $language, $language_url, $conf; // Create the database prefix for this test. $this->prepareDatabasePrefix(); @@ -1532,7 +1567,7 @@ class DrupalWebTestCase extends DrupalTestCase { // Set up English language. unset($conf['language_default']); - $language = language_default(); + $language_url = $language = language_default(); // Use the test mail class instead of the default mail handler class. variable_set('mail_system', array('default-system' => 'TestingMailSystem')); @@ -1626,7 +1661,7 @@ class DrupalWebTestCase extends DrupalTestCase { * and reset the database prefix. */ protected function tearDown() { - global $user, $language; + global $user, $language, $language_url; // In case a fatal error occurred that was not in the test process read the // log to pick up any fatal errors. @@ -1691,12 +1726,15 @@ class DrupalWebTestCase extends DrupalTestCase { // Reset language. $language = $this->originalLanguage; + $language_url = $this->originalLanguageUrl; if ($this->originalLanguageDefault) { $GLOBALS['conf']['language_default'] = $this->originalLanguageDefault; } - // Close the CURL handler. + // Close the CURL handler and reset the cookies array so test classes + // containing multiple tests are not polluted. $this->curlClose(); + $this->cookies = array(); } /** @@ -1769,14 +1807,24 @@ class DrupalWebTestCase extends DrupalTestCase { protected function curlExec($curl_options, $redirect = FALSE) { $this->curlInitialize(); - // cURL incorrectly handles URLs with a fragment by including the - // fragment in the request to the server, causing some web servers - // to reject the request citing "400 - Bad Request". To prevent - // this, we strip the fragment from the request. - // TODO: Remove this for Drupal 8, since fixed in curl 7.20.0. - if (!empty($curl_options[CURLOPT_URL]) && strpos($curl_options[CURLOPT_URL], '#')) { - $original_url = $curl_options[CURLOPT_URL]; - $curl_options[CURLOPT_URL] = strtok($curl_options[CURLOPT_URL], '#'); + if (!empty($curl_options[CURLOPT_URL])) { + // Forward XDebug activation if present. + if (isset($_COOKIE['XDEBUG_SESSION'])) { + $options = drupal_parse_url($curl_options[CURLOPT_URL]); + $options += array('query' => array()); + $options['query'] += array('XDEBUG_SESSION_START' => $_COOKIE['XDEBUG_SESSION']); + $curl_options[CURLOPT_URL] = url($options['path'], $options); + } + + // cURL incorrectly handles URLs with a fragment by including the + // fragment in the request to the server, causing some web servers + // to reject the request citing "400 - Bad Request". To prevent + // this, we strip the fragment from the request. + // TODO: Remove this for Drupal 8, since fixed in curl 7.20.0. + if (strpos($curl_options[CURLOPT_URL], '#')) { + $original_url = $curl_options[CURLOPT_URL]; + $curl_options[CURLOPT_URL] = strtok($curl_options[CURLOPT_URL], '#'); + } } $url = empty($curl_options[CURLOPT_URL]) ? curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL) : $curl_options[CURLOPT_URL]; @@ -2211,6 +2259,7 @@ class DrupalWebTestCase extends DrupalTestCase { // Submit the POST request. $return = drupal_json_decode($this->drupalPost(NULL, $edit, array('path' => $ajax_path, 'triggering_element' => $triggering_element), $options, $headers, $form_html_id, $extra_post)); + $this->assertIdentical($this->drupalGetHeader('X-Drupal-Ajax-Token'), '1', 'Ajax response header found.'); // Change the page content by applying the returned commands. if (!empty($ajax_settings) && !empty($return)) { @@ -2247,8 +2296,13 @@ class DrupalWebTestCase extends DrupalTestCase { if ($wrapperNode) { // ajax.js adds an enclosing DIV to work around a Safari bug. $newDom = new DOMDocument(); + // DOM can load HTML soup. But, HTML soup can throw warnings, + // suppress them. $newDom->loadHTML('
          ' . $command['data'] . '
          '); - $newNode = $dom->importNode($newDom->documentElement->firstChild->firstChild, TRUE); + // Suppress warnings thrown when duplicate HTML IDs are + // encountered. This probably means we are replacing an element + // with the same ID. + $newNode = @$dom->importNode($newDom->documentElement->firstChild->firstChild, TRUE); $method = isset($command['method']) ? $command['method'] : $ajax_settings['method']; // The "method" is a jQuery DOM manipulation function. Emulate // each one using PHP's DOMNode API. @@ -2570,6 +2624,11 @@ class DrupalWebTestCase extends DrupalTestCase { * * @param $xpath * The xpath string to use in the search. + * @param array $arguments + * An array of arguments with keys in the form ':name' matching the + * placeholders in the query. The values may be either strings or numeric + * values. + * * @return * The return value of the xpath search. For details on the xpath string * format and return values see the SimpleXML documentation, @@ -2741,7 +2800,7 @@ class DrupalWebTestCase extends DrupalTestCase { $path = substr($path, $length); } // Ensure that we have an absolute path. - if ($path[0] !== '/') { + if (empty($path) || $path[0] !== '/') { $path = '/' . $path; } // Finally, prepend the $base_url. diff --git a/modules/simpletest/files/css_test_files/css_input_with_import.css b/modules/simpletest/files/css_test_files/css_input_with_import.css index 87afcb3..484db83 100644 --- a/modules/simpletest/files/css_test_files/css_input_with_import.css +++ b/modules/simpletest/files/css_test_files/css_input_with_import.css @@ -1,5 +1,7 @@ +@import url("http://example.com/style.css"); +@import url("//example.com/style.css"); @import "import1.css"; @import "import2.css"; diff --git a/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css b/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css index a05f939..a2af7b3 100644 --- a/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css +++ b/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css @@ -1,4 +1,4 @@ -ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);} +@import url("http://example.com/style.css");@import url("//example.com/style.css");ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);} p,select{font:1em/160% Verdana,sans-serif;color:#494949;} body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this .is diff --git a/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css b/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css index b8c7778..bc3c7b6 100644 --- a/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css +++ b/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css @@ -1,5 +1,7 @@ +@import url("http://example.com/style.css"); +@import url("//example.com/style.css"); ul, select { font: 1em/160% Verdana, sans-serif; diff --git a/modules/simpletest/files/image-test-no-transparency.gif b/modules/simpletest/files/image-test-no-transparency.gif new file mode 100644 index 0000000000000000000000000000000000000000..15ae7772dc4ab73ef2ccdca1a2fb7bc463b9aea3 GIT binary patch literal 964 zcmZ?wbhEHb)L;-{_|5pC)2&n-wrVv_4qMH3(_zXW2@d=fo}7VP3pPyL^pDUj?uHx)~O) Qla95@+Rs~Irodng04Q`+2LJ#7 literal 0 HcmV?d00001 diff --git a/modules/simpletest/files/image-test-transparent-out-of-range.gif b/modules/simpletest/files/image-test-transparent-out-of-range.gif new file mode 100644 index 0000000000000000000000000000000000000000..a54df7a93ce2346e19414536a3a8eb555f64f8d1 GIT binary patch literal 183 zcmZ?wbhEHb)L;-{IK%)3|AFLxAOXbxK{N;`{$ycfU|?g=0dYWT8JKGb=t5n!+_BT}-q<=e+CBR^E^YIlLQph@XE_%@DZj;qn)Ttdkecd;6*8 z&7vbKTMxVyio3Ti!AH!1F|}w0cii&h&$lQ1y?XKKNAtb1$K0P(m*wSGW;TYIbQ;uG YWoxN4^!3Y5Oq@KWciMF6B2ETt0P&+sLI3~& literal 0 HcmV?d00001 diff --git a/modules/simpletest/simpletest.info b/modules/simpletest/simpletest.info index 4c6fcbe..26b3485 100644 --- a/modules/simpletest/simpletest.info +++ b/modules/simpletest/simpletest.info @@ -11,6 +11,7 @@ configure = admin/config/development/testing/settings files[] = tests/actions.test files[] = tests/ajax.test files[] = tests/batch.test +files[] = tests/boot.test files[] = tests/bootstrap.test files[] = tests/cache.test files[] = tests/common.test @@ -56,8 +57,8 @@ files[] = tests/upgrade/update.trigger.test files[] = tests/upgrade/update.field.test files[] = tests/upgrade/update.user.test -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/simpletest.module b/modules/simpletest/simpletest.module index 3103af0..cf83047 100644 --- a/modules/simpletest/simpletest.module +++ b/modules/simpletest/simpletest.module @@ -154,7 +154,7 @@ function simpletest_run_tests($test_list, $reporter = 'drupal') { } /** - * Batch operation callback. + * Implements callback_batch_operation(). */ function _simpletest_batch_operation($test_list_init, $test_id, &$context) { simpletest_classloader_register(); @@ -205,6 +205,9 @@ function _simpletest_batch_operation($test_list_init, $test_id, &$context) { $context['finished'] = 1 - $size / $max; } +/** + * Implements callback_batch_finished(). + */ function _simpletest_batch_finished($success, $results, $operations, $elapsed) { if ($success) { drupal_set_message(t('The test run finished in @elapsed.', array('@elapsed' => $elapsed))); @@ -328,25 +331,32 @@ function simpletest_test_get_all() { // Also discover PSR-0 test classes, if the PHP version allows it. if (version_compare(PHP_VERSION, '5.3') > 0) { - // Select all PSR-0 classes in the Tests namespace of all modules. + // Select all PSR-0 and PSR-4 classes in the Tests namespace of all + // modules. $system_list = db_query("SELECT name, filename FROM {system}")->fetchAllKeyed(); foreach ($system_list as $name => $filename) { - // Build directory in which the test files would reside. - $tests_dir = DRUPAL_ROOT . '/' . dirname($filename) . '/lib/Drupal/' . $name . '/Tests'; - // Scan it for test files if it exists. - if (is_dir($tests_dir)) { - $files = file_scan_directory($tests_dir, '/.*\.php/'); - if (!empty($files)) { - $basedir = DRUPAL_ROOT . '/' . dirname($filename) . '/lib/'; - foreach ($files as $file) { - // Convert the file name into the namespaced class name. - $replacements = array( - '/' => '\\', - $basedir => '', - '.php' => '', - ); - $classes[] = strtr($file->uri, $replacements); + $module_dir = DRUPAL_ROOT . '/' . dirname($filename); + // Search both the 'lib/Drupal/mymodule' directory (for PSR-0 classes) + // and the 'src' directory (for PSR-4 classes). + foreach(array('lib/Drupal/' . $name, 'src') as $subdir) { + // Build directory in which the test files would reside. + $tests_dir = $module_dir . '/' . $subdir . '/Tests'; + // Scan it for test files if it exists. + if (is_dir($tests_dir)) { + $files = file_scan_directory($tests_dir, '/.*\.php/'); + if (!empty($files)) { + foreach ($files as $file) { + // Convert the file name into the namespaced class name. + $replacements = array( + '/' => '\\', + $module_dir . '/' => '', + 'lib/' => '', + 'src/' => 'Drupal\\' . $name . '\\', + '.php' => '', + ); + $classes[] = strtr($file->uri, $replacements); + } } } } @@ -364,7 +374,10 @@ function simpletest_test_get_all() { // If this test class requires a non-existing module, skip it. if (!empty($info['dependencies'])) { foreach ($info['dependencies'] as $module) { - if (!drupal_get_filename('module', $module)) { + // Pass FALSE as fourth argument so no error gets created for + // the missing file. + $found_module = drupal_get_filename('module', $module, NULL, FALSE); + if (!$found_module) { continue 2; } } @@ -406,17 +419,20 @@ function simpletest_classloader_register() { // Only register PSR-0 class loading if we are on PHP 5.3 or higher. if (version_compare(PHP_VERSION, '5.3') > 0) { - spl_autoload_register('_simpletest_autoload_psr0'); + spl_autoload_register('_simpletest_autoload_psr4_psr0'); } } /** - * Autoload callback to find PSR-0 test classes. + * Autoload callback to find PSR-4 and PSR-0 test classes. + * + * Looks in the 'src/Tests' and in the 'lib/Drupal/mymodule/Tests' directory of + * modules for the class. * * This will only work on classes where the namespace is of the pattern * "Drupal\$extension\Tests\.." */ -function _simpletest_autoload_psr0($class) { +function _simpletest_autoload_psr4_psr0($class) { // Static cache for extension paths. // This cache is lazily filled as soon as it is needed. @@ -446,14 +462,26 @@ function _simpletest_autoload_psr0($class) { $namespace = substr($class, 0, $nspos); $classname = substr($class, $nspos + 1); - // Build the filepath where we expect the class to be defined. - $path = dirname($extensions[$extension]) . '/lib/' . - str_replace('\\', '/', $namespace) . '/' . + // Try the PSR-4 location first, and the PSR-0 location as a fallback. + // Build the PSR-4 filepath where we expect the class to be defined. + $psr4_path = dirname($extensions[$extension]) . '/src/' . + str_replace('\\', '/', substr($namespace, strlen('Drupal\\' . $extension . '\\'))) . '/' . str_replace('_', '/', $classname) . '.php'; // Include the file, if it does exist. - if (file_exists($path)) { - include $path; + if (file_exists($psr4_path)) { + include $psr4_path; + } + else { + // Build the PSR-0 filepath where we expect the class to be defined. + $psr0_path = dirname($extensions[$extension]) . '/lib/' . + str_replace('\\', '/', $namespace) . '/' . + str_replace('_', '/', $classname) . '.php'; + + // Include the file, if it does exist. + if (file_exists($psr0_path)) { + include $psr0_path; + } } } } @@ -487,25 +515,25 @@ function simpletest_registry_files_alter(&$files, $modules) { * Generate test file. */ function simpletest_generate_file($filename, $width, $lines, $type = 'binary-text') { - $size = $width * $lines - $lines; - - // Generate random text $text = ''; - for ($i = 0; $i < $size; $i++) { - switch ($type) { - case 'text': - $text .= chr(rand(32, 126)); - break; - case 'binary': - $text .= chr(rand(0, 31)); - break; - case 'binary-text': - default: - $text .= rand(0, 1); - break; + for ($i = 0; $i < $lines; $i++) { + // Generate $width - 1 characters to leave space for the "\n" character. + for ($j = 0; $j < $width - 1; $j++) { + switch ($type) { + case 'text': + $text .= chr(rand(32, 126)); + break; + case 'binary': + $text .= chr(rand(0, 31)); + break; + case 'binary-text': + default: + $text .= rand(0, 1); + break; + } } + $text .= "\n"; } - $text = wordwrap($text, $width - 1, "\n", TRUE) . "\n"; // Add \n for symmetrical file. // Create filename. file_put_contents('public://' . $filename . '.txt', $text); diff --git a/modules/simpletest/simpletest.test b/modules/simpletest/simpletest.test index dde162e..5d1c718 100644 --- a/modules/simpletest/simpletest.test +++ b/modules/simpletest/simpletest.test @@ -322,6 +322,14 @@ class SimpleTestFunctionalTest extends DrupalWebTestCase { * Test internal testing framework browser. */ class SimpleTestBrowserTestCase extends DrupalWebTestCase { + + /** + * A flag indicating whether a cookie has been set in a test. + * + * @var bool + */ + protected static $cookieSet = FALSE; + public static function getInfo() { return array( 'name' => 'SimpleTest browser', @@ -380,6 +388,46 @@ EOF; $urls = $this->xpath('//a[text()=:text]', array(':text' => 'A second "even more weird" link, in memory of George O\'Malley')); $this->assertEqual($urls[0]['href'], 'link2', 'Match with mixed single and double quotes.'); } + + /** + * Tests that cookies set during a request are available for testing. + */ + public function testCookies() { + // Check that the $this->cookies property is populated when a user logs in. + $user = $this->drupalCreateUser(); + $edit = array('name' => $user->name, 'pass' => $user->pass_raw); + $this->drupalPost('', $edit, t('Log in')); + $this->assertEqual(count($this->cookies), 1, 'A cookie is set when the user logs in.'); + + // Check that the name and value of the cookie match the request data. + $cookie_header = $this->drupalGetHeader('set-cookie', TRUE); + + // The name and value are located at the start of the string, separated by + // an equals sign and ending in a semicolon. + preg_match('/^([^=]+)=([^;]+)/', $cookie_header, $matches); + $name = $matches[1]; + $value = $matches[2]; + + $this->assertTrue(array_key_exists($name, $this->cookies), 'The cookie name is correct.'); + $this->assertEqual($value, $this->cookies[$name]['value'], 'The cookie value is correct.'); + + // Set a flag indicating that a cookie has been set in this test. + // @see SimpleTestBrowserTestCase::testCookieDoesNotBleed(). + self::$cookieSet = TRUE; + } + + /** + * Tests that the cookies from a previous test do not bleed into a new test. + * + * @see SimpleTestBrowserTestCase::testCookies(). + */ + public function testCookieDoesNotBleed() { + // In order for this test to be effective it should always run after the + // testCookies() test. + $this->assertTrue(self::$cookieSet, 'Tests have been executed in the expected order.'); + $this->assertEqual(count($this->cookies), 0, 'No cookies are present at the start of a new test.'); + } + } class SimpleTestMailCaptureTestCase extends DrupalWebTestCase { @@ -703,7 +751,9 @@ class SimpleTestDiscoveryTestCase extends DrupalWebTestCase { $classes_all = simpletest_test_get_all(); foreach (array( 'Drupal\\simpletest\\Tests\\PSR0WebTest', + 'Drupal\\simpletest\\Tests\\PSR4WebTest', 'Drupal\\psr_0_test\\Tests\\ExampleTest', + 'Drupal\\psr_4_test\\Tests\\ExampleTest', ) as $class) { $this->assert(!empty($classes_all['SimpleTest'][$class]), t('Class @class must be discovered by simpletest_test_get_all().', array('@class' => $class))); } @@ -726,15 +776,20 @@ class SimpleTestDiscoveryTestCase extends DrupalWebTestCase { // Don't expect PSR-0 tests to be discovered on older PHP versions. return; } - // This one is provided by simpletest itself via PSR-0. + // These are provided by simpletest itself via PSR-0 and PSR-4. $this->assertText('PSR0 web test'); + $this->assertText('PSR4 web test'); $this->assertText('PSR0 example test: PSR-0 in disabled modules.'); + $this->assertText('PSR4 example test: PSR-4 in disabled modules.'); $this->assertText('PSR0 example test: PSR-0 in nested subfolders.'); + $this->assertText('PSR4 example test: PSR-4 in nested subfolders.'); // Test each test individually. foreach (array( 'Drupal\\psr_0_test\\Tests\\ExampleTest', 'Drupal\\psr_0_test\\Tests\\Nested\\NestedExampleTest', + 'Drupal\\psr_4_test\\Tests\\ExampleTest', + 'Drupal\\psr_4_test\\Tests\\Nested\\NestedExampleTest', ) as $class) { $this->drupalGet('admin/config/development/testing'); $edit = array($class => TRUE); diff --git a/modules/simpletest/src/Tests/PSR4WebTest.php b/modules/simpletest/src/Tests/PSR4WebTest.php new file mode 100644 index 0000000..24c8d89 --- /dev/null +++ b/modules/simpletest/src/Tests/PSR4WebTest.php @@ -0,0 +1,18 @@ + 'PSR4 web test', + 'description' => 'We want to assert that this PSR-4 test case is being discovered.', + 'group' => 'SimpleTest', + ); + } + + function testArithmetics() { + $this->assert(1 + 1 == 2, '1 + 1 == 2'); + } +} diff --git a/modules/simpletest/tests/actions_loop_test.info b/modules/simpletest/tests/actions_loop_test.info index e338438..2edf253 100644 --- a/modules/simpletest/tests/actions_loop_test.info +++ b/modules/simpletest/tests/actions_loop_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/ajax.test b/modules/simpletest/tests/ajax.test index c38a325..afe0230 100644 --- a/modules/simpletest/tests/ajax.test +++ b/modules/simpletest/tests/ajax.test @@ -293,7 +293,7 @@ class AJAXCommandsTestCase extends AJAXTestCase { $this->assertCommand($commands, $expected, "'changed' AJAX command (with asterisk) issued with correct selector"); // Tests the 'css' command. - $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("Set the the '#box' div to be blue."))); + $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("Set the '#box' div to be blue."))); $expected = array( 'command' => 'css', 'selector' => '#css_div', diff --git a/modules/simpletest/tests/ajax_forms_test.info b/modules/simpletest/tests/ajax_forms_test.info index db582d4..9736647 100644 --- a/modules/simpletest/tests/ajax_forms_test.info +++ b/modules/simpletest/tests/ajax_forms_test.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/ajax_forms_test.module b/modules/simpletest/tests/ajax_forms_test.module index 260d911..de2fa0b 100644 --- a/modules/simpletest/tests/ajax_forms_test.module +++ b/modules/simpletest/tests/ajax_forms_test.module @@ -157,7 +157,7 @@ function ajax_forms_test_ajax_commands_form($form, &$form_state) { // Shows the Ajax 'css' command. $form['css_command_example'] = array( - '#value' => t("Set the the '#box' div to be blue."), + '#value' => t("Set the '#box' div to be blue."), '#type' => 'submit', '#ajax' => array( 'callback' => 'ajax_forms_test_advanced_commands_css_callback', diff --git a/modules/simpletest/tests/ajax_test.info b/modules/simpletest/tests/ajax_test.info index 736c080..fe2f90b 100644 --- a/modules/simpletest/tests/ajax_test.info +++ b/modules/simpletest/tests/ajax_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/batch_test.callbacks.inc b/modules/simpletest/tests/batch_test.callbacks.inc index 75e6655..6564413 100644 --- a/modules/simpletest/tests/batch_test.callbacks.inc +++ b/modules/simpletest/tests/batch_test.callbacks.inc @@ -7,6 +7,8 @@ */ /** + * Implements callback_batch_operation(). + * * Simple batch operation. */ function _batch_test_callback_1($id, $sleep, &$context) { @@ -20,6 +22,8 @@ function _batch_test_callback_1($id, $sleep, &$context) { } /** + * Implements callback_batch_operation(). + * * Multistep batch operation. */ function _batch_test_callback_2($start, $total, $sleep, &$context) { @@ -53,6 +57,8 @@ function _batch_test_callback_2($start, $total, $sleep, &$context) { } /** + * Implements callback_batch_operation(). + * * Simple batch operation. */ function _batch_test_callback_5($id, $sleep, &$context) { @@ -68,6 +74,8 @@ function _batch_test_callback_5($id, $sleep, &$context) { } /** + * Implements callback_batch_operation(). + * * Batch operation setting up its own batch. */ function _batch_test_nested_batch_callback() { @@ -76,6 +84,8 @@ function _batch_test_nested_batch_callback() { } /** + * Implements callback_batch_finished(). + * * Common 'finished' callbacks for batches 1 to 4. */ function _batch_test_finished_helper($batch_id, $success, $results, $operations) { @@ -99,6 +109,8 @@ function _batch_test_finished_helper($batch_id, $success, $results, $operations) } /** + * Implements callback_batch_finished(). + * * 'finished' callback for batch 0. */ function _batch_test_finished_0($success, $results, $operations) { @@ -106,6 +118,8 @@ function _batch_test_finished_0($success, $results, $operations) { } /** + * Implements callback_batch_finished(). + * * 'finished' callback for batch 1. */ function _batch_test_finished_1($success, $results, $operations) { @@ -113,6 +127,8 @@ function _batch_test_finished_1($success, $results, $operations) { } /** + * Implements callback_batch_finished(). + * * 'finished' callback for batch 2. */ function _batch_test_finished_2($success, $results, $operations) { @@ -120,6 +136,8 @@ function _batch_test_finished_2($success, $results, $operations) { } /** + * Implements callback_batch_finished(). + * * 'finished' callback for batch 3. */ function _batch_test_finished_3($success, $results, $operations) { @@ -127,6 +145,8 @@ function _batch_test_finished_3($success, $results, $operations) { } /** + * Implements callback_batch_finished(). + * * 'finished' callback for batch 4. */ function _batch_test_finished_4($success, $results, $operations) { @@ -134,6 +154,8 @@ function _batch_test_finished_4($success, $results, $operations) { } /** + * Implements callback_batch_finished(). + * * 'finished' callback for batch 5. */ function _batch_test_finished_5($success, $results, $operations) { diff --git a/modules/simpletest/tests/batch_test.info b/modules/simpletest/tests/batch_test.info index 4ee2142..a53e215 100644 --- a/modules/simpletest/tests/batch_test.info +++ b/modules/simpletest/tests/batch_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/boot.test b/modules/simpletest/tests/boot.test new file mode 100644 index 0000000..562b082 --- /dev/null +++ b/modules/simpletest/tests/boot.test @@ -0,0 +1,38 @@ + 'Early bootstrap test', + 'description' => 'Confirm that calling module_implements() during early bootstrap does not pollute the module_implements() cache.', + 'group' => 'System', + ); + } + + function setUp() { + parent::setUp('boot_test_1', 'boot_test_2'); + } + + /** + * Test hook_boot() on both regular and "early exit" pages. + */ + public function testHookBoot() { + $paths = array('', 'early_exit'); + foreach ($paths as $path) { + // Empty the module_implements() caches. + module_implements(NULL, FALSE, TRUE); + // Do a request to the front page, which will call module_implements() + // during hook_boot(). + $this->drupalGet($path); + // Reset the static cache so we get implementation data from the persistent + // cache. + drupal_static_reset(); + // Make sure we get a full list of all modules implementing hook_help(). + $modules = module_implements('help'); + $this->assertTrue(in_array('boot_test_2', $modules)); + } + } +} diff --git a/modules/simpletest/tests/boot_test_1.info b/modules/simpletest/tests/boot_test_1.info new file mode 100644 index 0000000..873825d --- /dev/null +++ b/modules/simpletest/tests/boot_test_1.info @@ -0,0 +1,12 @@ +name = Early bootstrap tests +description = A support module for hook_boot testing. +core = 7.x +package = Testing +version = VERSION +hidden = TRUE + +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" +project = "drupal" +datestamp = "1522264019" + diff --git a/modules/simpletest/tests/boot_test_1.module b/modules/simpletest/tests/boot_test_1.module new file mode 100644 index 0000000..a452e28 --- /dev/null +++ b/modules/simpletest/tests/boot_test_1.module @@ -0,0 +1,21 @@ +proxy_ip; + $_SERVER['HTTP_X_FORWARDED_FOR'] = $this->proxy_ip; + drupal_static_reset('ip_address'); + $this->assertTrue( + ip_address() == $this->proxy_ip, + 'Visiting from trusted proxy got proxy IP address.' + ); + // Multi-tier architecture with comma separated values in header. $_SERVER['REMOTE_ADDR'] = $this->proxy_ip; $_SERVER['HTTP_X_FORWARDED_FOR'] = implode(', ', array($this->untrusted_ip, $this->forwarded_ip, $this->proxy2_ip)); @@ -152,7 +161,9 @@ class BootstrapPageCacheTestCase extends DrupalWebTestCase { $this->drupalLogin($user); $this->drupalGet('', array(), array('If-Modified-Since: ' . $last_modified, 'If-None-Match: ' . $etag)); $this->assertResponse(200, 'Conditional request returned 200 OK for authenticated user.'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Absense of Page was not cached.'); + $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Absence of Page was not cached.'); + $this->assertFalse($this->drupalGetHeader('ETag'), 'ETag HTTP headers are not present for logged in users.'); + $this->assertFalse($this->drupalGetHeader('Last-Modified'), 'Last-Modified HTTP headers are not present for logged in users.'); } /** @@ -189,7 +200,7 @@ class BootstrapPageCacheTestCase extends DrupalWebTestCase { $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar'))); $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Caching was bypassed.'); $this->assertTrue(strpos($this->drupalGetHeader('Vary'), 'Cookie') === FALSE, 'Vary: Cookie header was not sent.'); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate, post-check=0, pre-check=0', 'Cache-Control header was sent.'); + $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate', 'Cache-Control header was sent.'); $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.'); $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.'); @@ -286,6 +297,39 @@ class BootstrapVariableTestCase extends DrupalWebTestCase { } +/** + * Tests the auto-loading behavior of the code registry. + */ +class BootstrapAutoloadTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Code registry', + 'description' => 'Test that the code registry functions correctly.', + 'group' => 'Bootstrap', + ); + } + + function setUp() { + parent::setUp('drupal_autoload_test'); + } + + /** + * Tests that autoloader name matching is not case sensitive. + */ + function testAutoloadCase() { + // Test interface autoloader. + $this->assertTrue(drupal_autoload_interface('drupalautoloadtestinterface'), 'drupal_autoload_interface() recognizes DrupalAutoloadTestInterface in lower case.'); + // Test class autoloader. + $this->assertTrue(drupal_autoload_class('drupalautoloadtestclass'), 'drupal_autoload_class() recognizes DrupalAutoloadTestClass in lower case.'); + // Test trait autoloader. + if (version_compare(PHP_VERSION, '5.4') >= 0) { + $this->assertTrue(drupal_autoload_trait('drupalautoloadtesttrait'), 'drupal_autoload_trait() recognizes DrupalAutoloadTestTrait in lower case.'); + } + } + +} + /** * Test hook_boot() and hook_exit(). */ @@ -344,12 +388,19 @@ class BootstrapGetFilenameTestCase extends DrupalUnitTestCase { public static function getInfo() { return array( - 'name' => 'Get filename test', - 'description' => 'Test that drupal_get_filename() works correctly when the file is not found in the database.', + 'name' => 'Get filename test (without the system table)', + 'description' => 'Test that drupal_get_filename() works correctly when the database is not available.', 'group' => 'Bootstrap', ); } + /** + * The last file-related error message triggered by the filename test. + * + * Used by BootstrapGetFilenameTestCase::testDrupalGetFilename(). + */ + protected $getFilenameTestTriggeredError; + /** * Test that drupal_get_filename() works correctly when the file is not found in the database. */ @@ -379,6 +430,203 @@ class BootstrapGetFilenameTestCase extends DrupalUnitTestCase { // automatically check there for 'script' files, just as it does for (e.g.) // 'module' files in modules. $this->assertIdentical(drupal_get_filename('script', 'test'), 'scripts/test.script', t('Retrieve test script location.')); + + // When searching for a module that does not exist, drupal_get_filename() + // should return NULL and trigger an appropriate error message. + $this->getFilenameTestTriggeredError = NULL; + set_error_handler(array($this, 'fileNotFoundErrorHandler')); + $non_existing_module = $this->randomName(); + $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that does not exist returns NULL.'); + $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) === 0, 'Searching for an item that does not exist triggers the correct error.'); + restore_error_handler(); + + // Check that the result is stored in the file system scan cache. + $file_scans = _drupal_file_scan_cache(); + $this->assertIdentical($file_scans['module'][$non_existing_module], FALSE, 'Searching for a module that does not exist creates a record in the missing and moved files static variable.'); + + // Performing the search again in the same request still should not find + // the file, but the error message should not be repeated (therefore we do + // not override the error handler here). + $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that does not exist returns NULL during the second search.'); + } + + /** + * Skips handling of "file not found" errors. + */ + public function fileNotFoundErrorHandler($error_level, $message, $filename, $line, $context) { + // Skip error handling if this is a "file not found" error. + if (strpos($message, 'is missing from the file system:') !== FALSE || strpos($message, 'has moved within the file system:') !== FALSE) { + $this->getFilenameTestTriggeredError = $message; + return; + } + _drupal_error_handler($error_level, $message, $filename, $line, $context); + } +} + +/** + * Test drupal_get_filename() in the context of a full Drupal installation. + */ +class BootstrapGetFilenameWebTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Get filename test (full installation)', + 'description' => 'Test that drupal_get_filename() works correctly in the context of a full Drupal installation.', + 'group' => 'Bootstrap', + ); + } + + function setUp() { + parent::setUp('system_test'); + } + + /** + * The last file-related error message triggered by the filename test. + * + * Used by BootstrapGetFilenameWebTestCase::testDrupalGetFilename(). + */ + protected $getFilenameTestTriggeredError; + + /** + * Test that drupal_get_filename() works correctly with a full Drupal site. + */ + function testDrupalGetFilename() { + // Search for a module that exists in the file system and the {system} + // table and make sure that it is found. + $this->assertIdentical(drupal_get_filename('module', 'node'), 'modules/node/node.module', 'Module found at expected location.'); + + // Search for a module that does not exist in either the file system or the + // {system} table. Make sure that an appropriate error is triggered and + // that the module winds up in the static and persistent cache. + $this->getFilenameTestTriggeredError = NULL; + set_error_handler(array($this, 'fileNotFoundErrorHandler')); + $non_existing_module = $this->randomName(); + $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that does not exist returns NULL.'); + $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) === 0, 'Searching for a module that does not exist triggers the correct error.'); + restore_error_handler(); + $file_scans = _drupal_file_scan_cache(); + $this->assertIdentical($file_scans['module'][$non_existing_module], FALSE, 'Searching for a module that does not exist creates a record in the missing and moved files static variable.'); + drupal_file_scan_write_cache(); + $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap'); + $this->assertIdentical($cache->data['module'][$non_existing_module], FALSE, 'Searching for a module that does not exist creates a record in the missing and moved files persistent cache.'); + + // Simulate moving a module to a location that does not match the location + // in the {system} table and perform similar tests as above. + db_update('system') + ->fields(array('filename' => 'modules/simpletest/tests/fake_location/module_test.module')) + ->condition('name', 'module_test') + ->condition('type', 'module') + ->execute(); + $this->getFilenameTestTriggeredError = NULL; + set_error_handler(array($this, 'fileNotFoundErrorHandler')); + $this->assertIdentical(drupal_get_filename('module', 'module_test'), 'modules/simpletest/tests/module_test.module', 'Searching for a module that has moved finds the module at its new location.'); + $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module has moved within the file system: %name', array('%name' => 'module_test'))) === 0, 'Searching for a module that has moved triggers the correct error.'); + restore_error_handler(); + $file_scans = _drupal_file_scan_cache(); + $this->assertIdentical($file_scans['module']['module_test'], 'modules/simpletest/tests/module_test.module', 'Searching for a module that has moved creates a record in the missing and moved files static variable.'); + drupal_file_scan_write_cache(); + $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap'); + $this->assertIdentical($cache->data['module']['module_test'], 'modules/simpletest/tests/module_test.module', 'Searching for a module that has moved creates a record in the missing and moved files persistent cache.'); + + // Simulate a module that exists in the {system} table but does not exist + // in the file system and perform similar tests as above. + $non_existing_module = $this->randomName(); + db_update('system') + ->fields(array('name' => $non_existing_module)) + ->condition('name', 'module_test') + ->condition('type', 'module') + ->execute(); + $this->getFilenameTestTriggeredError = NULL; + set_error_handler(array($this, 'fileNotFoundErrorHandler')); + $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that exists in the system table but not in the file system returns NULL.'); + $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) === 0, 'Searching for a module that exists in the system table but not in the file system triggers the correct error.'); + restore_error_handler(); + $file_scans = _drupal_file_scan_cache(); + $this->assertIdentical($file_scans['module'][$non_existing_module], FALSE, 'Searching for a module that exists in the system table but not in the file system creates a record in the missing and moved files static variable.'); + drupal_file_scan_write_cache(); + $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap'); + $this->assertIdentical($cache->data['module'][$non_existing_module], FALSE, 'Searching for a module that exists in the system table but not in the file system creates a record in the missing and moved files persistent cache.'); + + // Simulate a module that exists in the file system but not in the {system} + // table and perform similar tests as above. + db_delete('system') + ->condition('name', 'common_test') + ->condition('type', 'module') + ->execute(); + system_list_reset(); + $this->getFilenameTestTriggeredError = NULL; + set_error_handler(array($this, 'fileNotFoundErrorHandler')); + $this->assertIdentical(drupal_get_filename('module', 'common_test'), 'modules/simpletest/tests/common_test.module', 'Searching for a module that does not exist in the system table finds the module at its actual location.'); + $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module has moved within the file system: %name', array('%name' => 'common_test'))) === 0, 'Searching for a module that does not exist in the system table triggers the correct error.'); + restore_error_handler(); + $file_scans = _drupal_file_scan_cache(); + $this->assertIdentical($file_scans['module']['common_test'], 'modules/simpletest/tests/common_test.module', 'Searching for a module that does not exist in the system table creates a record in the missing and moved files static variable.'); + drupal_file_scan_write_cache(); + $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap'); + $this->assertIdentical($cache->data['module']['common_test'], 'modules/simpletest/tests/common_test.module', 'Searching for a module that does not exist in the system table creates a record in the missing and moved files persistent cache.'); + } + + /** + * Skips handling of "file not found" errors. + */ + public function fileNotFoundErrorHandler($error_level, $message, $filename, $line, $context) { + // Skip error handling if this is a "file not found" error. + if (strpos($message, 'is missing from the file system:') !== FALSE || strpos($message, 'has moved within the file system:') !== FALSE) { + $this->getFilenameTestTriggeredError = $message; + return; + } + _drupal_error_handler($error_level, $message, $filename, $line, $context); + } + + /** + * Test that watchdog messages about missing files are correctly recorded. + */ + public function testWatchdog() { + // Search for a module that does not exist in either the file system or the + // {system} table. Make sure that an appropriate warning is recorded in the + // logs. + $non_existing_module = $this->randomName(); + $query_parameters = array( + ':type' => 'php', + ':severity' => WATCHDOG_WARNING, + ); + $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND severity = :severity', $query_parameters)->fetchField(), 0, 'No warning message appears in the logs before searching for a module that does not exist.'); + // Trigger the drupal_get_filename() call. This must be done via a request + // to a separate URL since the watchdog() will happen in a shutdown + // function, and so that SimpleTest can be told to ignore (and not fail as + // a result of) the expected PHP warnings generated during this process. + variable_set('system_test_drupal_get_filename_test_module_name', $non_existing_module); + $this->drupalGet('system-test/drupal-get-filename'); + $message_variables = db_query('SELECT variables FROM {watchdog} WHERE type = :type AND severity = :severity', $query_parameters)->fetchCol(); + $this->assertEqual(count($message_variables), 1, 'A single warning message appears in the logs after searching for a module that does not exist.'); + $variables = reset($message_variables); + $variables = unserialize($variables); + $this->assertTrue(isset($variables['!message']) && strpos($variables['!message'], format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) !== FALSE, 'The warning message that appears in the logs after searching for a module that does not exist contains the expected text.'); + } + + /** + * Test that drupal_get_filename() does not break recursive rebuilds. + */ + public function testRecursiveRebuilds() { + // Ensure that the drupal_get_filename() call due to a missing module does + // not break the data returned by an attempted recursive rebuild. The code + // path which is tested is as follows: + // - Call drupal_get_schema(). + // - Within a hook_schema() implementation, trigger a drupal_get_filename() + // search for a nonexistent module. + // - In the watchdog() call that results from that, trigger + // drupal_get_schema() again. + // Without some kind of recursion protection, this could cause the second + // drupal_get_schema() call to return incomplete results. This test ensures + // that does not happen. + $non_existing_module = $this->randomName(); + variable_set('system_test_drupal_get_filename_test_module_name', $non_existing_module); + $this->drupalGet('system-test/drupal-get-filename-with-schema-rebuild'); + $original_drupal_get_schema_tables = variable_get('system_test_drupal_get_filename_with_schema_rebuild_original_tables'); + $final_drupal_get_schema_tables = variable_get('system_test_drupal_get_filename_with_schema_rebuild_final_tables'); + $this->assertTrue(!empty($original_drupal_get_schema_tables)); + $this->assertTrue(!empty($final_drupal_get_schema_tables)); + $this->assertEqual($original_drupal_get_schema_tables, $final_drupal_get_schema_tables); } } @@ -546,3 +794,85 @@ class BootstrapOverrideServerVariablesTestCase extends DrupalUnitTestCase { } } } + +/** + * Tests for $_GET['destination'] and $_REQUEST['destination'] validation. + */ +class BootstrapDestinationTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'URL destination validation', + 'description' => 'Test that $_GET[\'destination\'] and $_REQUEST[\'destination\'] cannot contain external URLs.', + 'group' => 'Bootstrap', + ); + } + + function setUp() { + parent::setUp('system_test'); + } + + /** + * Tests that $_GET/$_REQUEST['destination'] only contain internal URLs. + * + * @see _drupal_bootstrap_variables() + * @see system_test_get_destination() + * @see system_test_request_destination() + */ + public function testDestination() { + $test_cases = array( + array( + 'input' => 'node', + 'output' => 'node', + 'message' => "Standard internal example node path is present in the 'destination' parameter.", + ), + array( + 'input' => '/example.com', + 'output' => '/example.com', + 'message' => 'Internal path with one leading slash is allowed.', + ), + array( + 'input' => '//example.com/test', + 'output' => '', + 'message' => 'External URL without scheme is not allowed.', + ), + array( + 'input' => 'example:test', + 'output' => 'example:test', + 'message' => 'Internal URL using a colon is allowed.', + ), + array( + 'input' => 'http://example.com', + 'output' => '', + 'message' => 'External URL is not allowed.', + ), + array( + 'input' => 'javascript:alert(0)', + 'output' => 'javascript:alert(0)', + 'message' => 'Javascript URL is allowed because it is treated as an internal URL.', + ), + ); + foreach ($test_cases as $test_case) { + // Test $_GET['destination']. + $this->drupalGet('system-test/get-destination', array('query' => array('destination' => $test_case['input']))); + $this->assertIdentical($test_case['output'], $this->drupalGetContent(), $test_case['message']); + // Test $_REQUEST['destination']. There's no form to submit to, so + // drupalPost() won't work here; this just tests a direct $_POST request + // instead. + $curl_parameters = array( + CURLOPT_URL => $this->getAbsoluteUrl('system-test/request-destination'), + CURLOPT_POST => TRUE, + CURLOPT_POSTFIELDS => 'destination=' . urlencode($test_case['input']), + CURLOPT_HTTPHEADER => array(), + ); + $post_output = $this->curlExec($curl_parameters); + $this->assertIdentical($test_case['output'], $post_output, $test_case['message']); + } + + // Make sure that 404 pages do not populate $_GET['destination'] with + // external URLs. + variable_set('site_404', 'system-test/get-destination'); + $this->drupalGet('http://example.com', array('external' => FALSE)); + $this->assertIdentical('', $this->drupalGetContent(), 'External URL is not allowed on 404 pages.'); + } +} diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test index eebfdbe..0490dd5 100644 --- a/modules/simpletest/tests/common.test +++ b/modules/simpletest/tests/common.test @@ -76,7 +76,7 @@ class DrupalAlterTestCase extends DrupalWebTestCase { class CommonURLUnitTest extends DrupalWebTestCase { public static function getInfo() { return array( - 'name' => 'URL generation tests', + 'name' => 'URL generation unit tests', 'description' => 'Confirm that url(), drupal_get_query_parameters(), drupal_http_build_query(), and l() work correctly with various input.', 'group' => 'System', ); @@ -169,7 +169,7 @@ class CommonURLUnitTest extends DrupalWebTestCase { $this->assertEqual(drupal_http_build_query(array('a' => ' &#//+%20@۞')), 'a=%20%26%23//%2B%2520%40%DB%9E', 'Value was properly encoded.'); $this->assertEqual(drupal_http_build_query(array(' &#//+%20@۞' => 'a')), '%20%26%23%2F%2F%2B%2520%40%DB%9E=a', 'Key was properly encoded.'); $this->assertEqual(drupal_http_build_query(array('a' => '1', 'b' => '2', 'c' => '3')), 'a=1&b=2&c=3', 'Multiple values were properly concatenated.'); - $this->assertEqual(drupal_http_build_query(array('a' => array('b' => '2', 'c' => '3'), 'd' => 'foo')), 'a[b]=2&a[c]=3&d=foo', 'Nested array was properly encoded.'); + $this->assertEqual(drupal_http_build_query(array('a' => array('b' => '2', 'c' => '3'), 'd' => 'foo')), 'a%5Bb%5D=2&a%5Bc%5D=3&d=foo', 'Nested array was properly encoded.'); } /** @@ -209,7 +209,16 @@ class CommonURLUnitTest extends DrupalWebTestCase { // Test that drupal can recognize an absolute URL. Used to prevent attack vectors. $this->assertTrue(url_is_external($url), 'Correctly identified an external URL.'); + // External URL without an explicit protocol. + $url = '//drupal.org/foo/bar?foo=bar&bar=baz&baz#foo'; + $this->assertTrue(url_is_external($url), 'Correctly identified an external URL without a protocol part.'); + + // Internal URL starting with a slash. + $url = '/drupal.org'; + $this->assertFalse(url_is_external($url), 'Correctly identified an internal URL with a leading slash.'); + // Test the parsing of absolute URLs. + $url = 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo'; $result = array( 'path' => 'http://drupal.org/foo/bar', 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), @@ -349,6 +358,108 @@ class CommonURLUnitTest extends DrupalWebTestCase { $query = array($this->randomName(5) => $this->randomName(5)); $result = url($url, array('query' => $query)); $this->assertEqual($url . '&' . http_build_query($query, '', '&'), $result, 'External URL query string can be extended with a custom query string in $options.'); + + // Verify that an internal URL does not result in an external URL without + // protocol part. + $url = '/drupal.org'; + $result = url($url); + $this->assertTrue(strpos($result, '//') === FALSE, 'Internal URL does not turn into an external URL.'); + + // Verify that an external URL without protocol part is recognized as such. + $url = '//drupal.org'; + $result = url($url); + $this->assertEqual($url, $result, 'External URL without protocol is not altered.'); + } +} + +/** + * Web tests for URL generation functions. + */ +class CommonURLWebTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'URL generation web tests', + 'description' => 'Confirm that URL-generating functions work correctly on specific site paths.', + 'group' => 'System', + ); + } + + function setUp() { + parent::setUp('common_test'); + } + + /** + * Tests the url() function on internal paths which mimic external URLs. + */ + function testInternalPathMimicsExternal() { + // Ensure that calling url(current_path()) on "/http://example.com" (an + // internal path which mimics an external URL) always links to the internal + // path, not the external URL. This helps protect against external URL link + // injection vulnerabilities. + variable_set('common_test_link_to_current_path', TRUE); + $this->drupalGet('/http://example.com'); + $this->clickLink('link which should point to the current path'); + $this->assertUrl('/http://example.com'); + $this->assertText('link which should point to the current path'); + } +} + +/** + * Tests url_is_external(). + */ +class UrlIsExternalUnitTest extends DrupalUnitTestCase { + + public static function getInfo() { + return array( + 'name' => 'External URL checking', + 'description' => 'Performs tests on url_is_external().', + 'group' => 'System', + ); + } + + /** + * Tests if each URL is external or not. + */ + function testUrlIsExternal() { + foreach ($this->examples() as $path => $expected) { + $this->assertIdentical(url_is_external($path), $expected, $path); + } + } + + /** + * Provides data for testUrlIsExternal(). + * + * @return array + * An array of test data, keyed by a path, with the expected value where + * TRUE is external, and FALSE is not external. + */ + protected function examples() { + return array( + // Simple external URLs. + 'http://example.com' => TRUE, + 'https://example.com' => TRUE, + 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo' => TRUE, + '//drupal.org' => TRUE, + // Some browsers ignore or strip leading control characters. + "\x00//www.example.com" => TRUE, + "\x08//www.example.com" => TRUE, + "\x1F//www.example.com" => TRUE, + "\n//www.example.com" => TRUE, + // JSON supports decoding directly from UTF-8 code points. + json_decode('"\u00AD"') . "//www.example.com" => TRUE, + json_decode('"\u200E"') . "//www.example.com" => TRUE, + json_decode('"\uE0020"') . "//www.example.com" => TRUE, + json_decode('"\uE000"') . "//www.example.com" => TRUE, + // Backslashes should be normalized to forward. + '\\\\example.com' => TRUE, + // Local URLs. + 'node' => FALSE, + '/system/ajax' => FALSE, + '?q=foo:bar' => FALSE, + 'node/edit:me' => FALSE, + '/drupal.org' => FALSE, + '' => FALSE, + ); } } @@ -868,6 +979,31 @@ class DrupalHTMLIdentifierTestCase extends DrupalUnitTestCase { // Verify that invalid characters (including non-breaking space) are stripped from the identifier. $this->assertIdentical(drupal_clean_css_identifier('invalid !"#$%&\'()*+,./:;<=>?@[\\]^`{|}~ identifier', array()), 'invalididentifier', 'Strip invalid characters.'); + + // Verify that double underscores are replaced in the identifier by default. + $identifier = 'css__identifier__with__double__underscores'; + $expected = 'css--identifier--with--double--underscores'; + $this->assertIdentical(drupal_clean_css_identifier($identifier), $expected, 'Verify double underscores are replaced with double hyphens by default.'); + + // Verify that double underscores are preserved in the identifier if the + // variable allow_css_double_underscores is set to TRUE. + $this->setAllowCSSDoubleUnderscores(TRUE); + $this->assertIdentical(drupal_clean_css_identifier($identifier), $identifier, 'Verify double underscores are preserved if the allow_css_double_underscores set to TRUE.'); + + // To avoid affecting other test cases, set the variable + // allow_css_double_underscores to FALSE which is the default value. + $this->setAllowCSSDoubleUnderscores(FALSE); + } + + /** + * Set the variable allow_css_double_underscores and reset the cache. + * + * @param $value bool + * A new value to be set to allow_css_double_underscores. + */ + function setAllowCSSDoubleUnderscores($value) { + $GLOBALS['conf']['allow_css_double_underscores'] = $value; + drupal_static_reset('drupal_clean_css_identifier:allow_css_double_underscores'); } /** @@ -922,6 +1058,7 @@ class CascadingStylesheetsUnitTest extends DrupalUnitTestCase { * - Proper URLs in imported files. (https://drupal.org/node/265719) * - Retain pseudo-selectors. (https://drupal.org/node/460448) * - Don't adjust data URIs. (https://drupal.org/node/2142441) + * - Files imported from external URLs. (https://drupal.org/node/2014851) */ function testLoadCssBasic() { // Array of files to test living in 'simpletest/files/css_test_files/'. @@ -1174,7 +1311,7 @@ class DrupalSetContentTestCase extends DrupalWebTestCase { function testRegions() { global $theme_key; - $block_regions = array_keys(system_region_list($theme_key)); + $block_regions = system_region_list($theme_key, REGIONS_ALL, FALSE); $delimiter = $this->randomName(32); $values = array(); // Set some random content for each region available. @@ -1235,6 +1372,15 @@ class DrupalGotoTest extends DrupalWebTestCase { $this->assertText('drupal_goto', 'Drupal goto redirect succeeded.'); $this->assertEqual($this->getUrl(), url('common-test/drupal_goto', array('query' => array('foo' => '123'), 'absolute' => TRUE)), 'Drupal goto redirected to expected URL.'); + // Test that calling drupal_goto() on the current path is not dangerous. + variable_set('common_test_redirect_current_path', TRUE); + $this->drupalGet('', array('query' => array('q' => 'http://www.example.com/'))); + $headers = $this->drupalGetHeaders(TRUE); + list(, $status) = explode(' ', $headers[0][':status'], 3); + $this->assertEqual($status, 302, 'Expected response code was sent.'); + $this->assertNotEqual($this->getUrl(), 'http://www.example.com/', 'Drupal goto did not redirect to external URL.'); + $this->assertTrue(strpos($this->getUrl(), url('', array('absolute' => TRUE))) === 0, 'Drupal redirected to itself.'); + variable_del('common_test_redirect_current_path'); // Test that drupal_goto() respects ?destination=xxx. Use an complicated URL // to test that the path is encoded and decoded properly. $destination = 'common-test/drupal_goto/destination?foo=%2525&bar=123'; @@ -1420,6 +1566,127 @@ class JavaScriptTestCase extends DrupalWebTestCase { $this->assertTrue(strpos($javascript, $inline) > 0, 'Rendered JavaScript footer returns the inline code.'); } + /** + * Test the 'javascript_always_use_jquery' variable. + */ + function testJavaScriptAlwaysUseJQuery() { + // The default front page of the site should use jQuery and other standard + // scripts and settings. + $this->drupalGet(''); + $this->assertRaw('misc/jquery.js', 'Default behavior: The front page of the site includes jquery.js.'); + $this->assertRaw('misc/drupal.js', 'Default behavior: The front page of the site includes drupal.js.'); + $this->assertRaw('Drupal.settings', 'Default behavior: The front page of the site includes Drupal settings.'); + $this->assertRaw('basePath', 'Default behavior: The front page of the site includes the basePath Drupal setting.'); + + // The default front page should not use jQuery and other standard scripts + // and settings when the 'javascript_always_use_jquery' variable is set to + // FALSE. + variable_set('javascript_always_use_jquery', FALSE); + $this->drupalGet(''); + $this->assertNoRaw('misc/jquery.js', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include jquery.js.'); + $this->assertNoRaw('misc/drupal.js', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include drupal.js.'); + $this->assertNoRaw('Drupal.settings', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include Drupal settings.'); + $this->assertNoRaw('basePath', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include the basePath Drupal setting.'); + variable_del('javascript_always_use_jquery'); + + // When only settings have been added via drupal_add_js(), drupal_get_js() + // should still return jQuery and other standard scripts and settings. + $this->resetStaticVariables(); + drupal_add_js(array('testJavaScriptSetting' => 'test'), 'setting'); + $javascript = drupal_get_js(); + $this->assertTrue(strpos($javascript, 'misc/jquery.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes jquery.js.'); + $this->assertTrue(strpos($javascript, 'misc/drupal.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes drupal.js.'); + $this->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes Drupal.settings.'); + $this->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes the basePath Drupal setting.'); + $this->assertTrue(strpos($javascript, 'testJavaScriptSetting') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes the added Drupal settings.'); + + // When only settings have been added via drupal_add_js() and the + // 'javascript_always_use_jquery' variable is set to FALSE, drupal_get_js() + // should not return jQuery and other standard scripts and settings, nor + // should it return the requested settings (since they cannot actually be + // addded to the page without jQuery). + $this->resetStaticVariables(); + variable_set('javascript_always_use_jquery', FALSE); + drupal_add_js(array('testJavaScriptSetting' => 'test'), 'setting'); + $javascript = drupal_get_js(); + $this->assertTrue(strpos($javascript, 'misc/jquery.js') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include jquery.js.'); + $this->assertTrue(strpos($javascript, 'misc/drupal.js') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include drupal.js.'); + $this->assertTrue(strpos($javascript, 'Drupal.settings') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include Drupal.settings.'); + $this->assertTrue(strpos($javascript, 'basePath') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include the basePath Drupal setting.'); + $this->assertTrue(strpos($javascript, 'testJavaScriptSetting') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include the added Drupal settings.'); + variable_del('javascript_always_use_jquery'); + + // When a regular file has been added via drupal_add_js(), drupal_get_js() + // should return jQuery and other standard scripts and settings. + $this->resetStaticVariables(); + drupal_add_js('misc/collapse.js'); + $javascript = drupal_get_js(); + $this->assertTrue(strpos($javascript, 'misc/jquery.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes jquery.js.'); + $this->assertTrue(strpos($javascript, 'misc/drupal.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes drupal.js.'); + $this->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes Drupal.settings.'); + $this->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes the basePath Drupal setting.'); + $this->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes the custom file.'); + + // When a regular file has been added via drupal_add_js() and the + // 'javascript_always_use_jquery' variable is set to FALSE, drupal_get_js() + // should still return jQuery and other standard scripts and settings + // (since the file is assumed to require jQuery by default). + $this->resetStaticVariables(); + variable_set('javascript_always_use_jquery', FALSE); + drupal_add_js('misc/collapse.js'); + $javascript = drupal_get_js(); + $this->assertTrue(strpos($javascript, 'misc/jquery.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes jquery.js.'); + $this->assertTrue(strpos($javascript, 'misc/drupal.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes drupal.js.'); + $this->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes Drupal.settings.'); + $this->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes the basePath Drupal setting.'); + $this->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes the custom file.'); + variable_del('javascript_always_use_jquery'); + + // When a file that does not require jQuery has been added via + // drupal_add_js(), drupal_get_js() should still return jQuery and other + // standard scripts and settings by default. + $this->resetStaticVariables(); + drupal_add_js('misc/collapse.js', array('requires_jquery' => FALSE)); + $javascript = drupal_get_js(); + $this->assertTrue(strpos($javascript, 'misc/jquery.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes jquery.js.'); + $this->assertTrue(strpos($javascript, 'misc/drupal.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes drupal.js.'); + $this->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes Drupal.settings.'); + $this->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes the basePath Drupal setting.'); + $this->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes the custom file.'); + + // When a file that does not require jQuery has been added via + // drupal_add_js() and the 'javascript_always_use_jquery' variable is set + // to FALSE, drupal_get_js() should not return jQuery and other standard + // scripts and setting, but it should still return the requested file. + $this->resetStaticVariables(); + variable_set('javascript_always_use_jquery', FALSE); + drupal_add_js('misc/collapse.js', array('requires_jquery' => FALSE)); + $javascript = drupal_get_js(); + $this->assertTrue(strpos($javascript, 'misc/jquery.js') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added does not include jquery.js.'); + $this->assertTrue(strpos($javascript, 'misc/drupal.js') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added does not include drupal.js.'); + $this->assertTrue(strpos($javascript, 'Drupal.settings') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added does not include Drupal.settings.'); + $this->assertTrue(strpos($javascript, 'basePath') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added does not include the basePath Drupal setting.'); + $this->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes the custom file.'); + variable_del('javascript_always_use_jquery'); + + // When 'javascript_always_use_jquery' is set to FALSE and a file that does + // not require jQuery is added, followed by one that does, drupal_get_js() + // should return jQuery and other standard scripts and settings, in + // addition to both of the requested files. + $this->resetStaticVariables(); + variable_set('javascript_always_use_jquery', FALSE); + drupal_add_js('misc/collapse.js', array('requires_jquery' => FALSE)); + drupal_add_js('misc/ajax.js'); + $javascript = drupal_get_js(); + $this->assertTrue(strpos($javascript, 'misc/jquery.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes jquery.js.'); + $this->assertTrue(strpos($javascript, 'misc/drupal.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes drupal.js.'); + $this->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes Drupal.settings.'); + $this->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes the basePath Drupal setting.'); + $this->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes the first custom file.'); + $this->assertTrue(strpos($javascript, 'misc/ajax.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes the second custom file.'); + variable_del('javascript_always_use_jquery'); + } + /** * Test drupal_add_js() sets preproccess to false when cache is set to false. */ @@ -1648,6 +1915,15 @@ class JavaScriptTestCase extends DrupalWebTestCase { $query_string = variable_get('css_js_query_string', '0'); $this->assertRaw(drupal_get_path('module', 'node') . '/node.js?' . $query_string, 'Query string was appended correctly to js.'); } + + /** + * Resets static variables related to adding JavaScript to a page. + */ + function resetStaticVariables() { + drupal_static_reset('drupal_add_js'); + drupal_static_reset('drupal_add_library'); + drupal_static_reset('drupal_get_library'); + } } /** @@ -1966,7 +2242,7 @@ class DrupalRenderTestCase extends DrupalWebTestCase { } /** - * Tests caching of an empty render item. + * Tests caching of render items. */ function testDrupalRenderCache() { // Force a request via GET. @@ -1992,6 +2268,59 @@ class DrupalRenderTestCase extends DrupalWebTestCase { drupal_render($element); $this->assertFalse(isset($element['#printed']), 'Cache hit'); + // Test that user 1 does not share the cache with other users who have the + // same roles, even when DRUPAL_CACHE_PER_ROLE is used. + $user1 = user_load(1); + $first_authenticated_user = $this->drupalCreateUser(); + $second_authenticated_user = $this->drupalCreateUser(); + $user1->roles = array_intersect_key($user1->roles, array(DRUPAL_AUTHENTICATED_RID => TRUE)); + user_save($user1); + // Load all the accounts again, to make sure we have complete account + // objects. + $user1 = user_load(1); + $first_authenticated_user = user_load($first_authenticated_user->uid); + $second_authenticated_user = user_load($second_authenticated_user->uid); + $this->assertEqual($user1->roles, $first_authenticated_user->roles, 'User 1 has the same roles as an authenticated user.'); + // Impersonate user 1 and render content that only user 1 should have + // permission to see. + $original_user = $GLOBALS['user']; + $original_session_state = drupal_save_session(); + drupal_save_session(FALSE); + $GLOBALS['user'] = $user1; + $test_element = array( + '#cache' => array( + 'keys' => array('test'), + 'granularity' => DRUPAL_CACHE_PER_ROLE, + ), + ); + $element = $test_element; + $element['#markup'] = 'content for user 1'; + $output = drupal_render($element); + $this->assertEqual($output, 'content for user 1'); + // Verify the cache is working by rendering the same element but with + // different markup passed in; the result should be the same. + $element = $test_element; + $element['#markup'] = 'should not be used'; + $output = drupal_render($element); + $this->assertEqual($output, 'content for user 1'); + // Verify that the first authenticated user does not see the same content + // as user 1. + $GLOBALS['user'] = $first_authenticated_user; + $element = $test_element; + $element['#markup'] = 'content for authenticated users'; + $output = drupal_render($element); + $this->assertEqual($output, 'content for authenticated users'); + // Verify that the second authenticated user shares the cache with the + // first authenticated user. + $GLOBALS['user'] = $second_authenticated_user; + $element = $test_element; + $element['#markup'] = 'should not be used'; + $output = drupal_render($element); + $this->assertEqual($output, 'content for authenticated users'); + // Restore the original logged-in user. + $GLOBALS['user'] = $original_user; + drupal_save_session($original_session_state); + // Restore the previous request method. $_SERVER['REQUEST_METHOD'] = $request_method; } diff --git a/modules/simpletest/tests/common_test.info b/modules/simpletest/tests/common_test.info index c8f4aea..8c56f80 100644 --- a/modules/simpletest/tests/common_test.info +++ b/modules/simpletest/tests/common_test.info @@ -7,8 +7,8 @@ stylesheets[all][] = common_test.css stylesheets[print][] = common_test.print.css hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/common_test.module b/modules/simpletest/tests/common_test.module index 674a494..d092c92 100644 --- a/modules/simpletest/tests/common_test.module +++ b/modules/simpletest/tests/common_test.module @@ -92,6 +92,18 @@ function common_test_drupal_goto_alter(&$path, &$options, &$http_response_code) } } +/** + * Implements hook_init(). + */ +function common_test_init() { + if (variable_get('common_test_redirect_current_path', FALSE)) { + drupal_goto(current_path()); + } + if (variable_get('common_test_link_to_current_path', FALSE)) { + drupal_set_message(l('link which should point to the current path', current_path())); + } +} + /** * Print destination query parameter. */ diff --git a/modules/simpletest/tests/common_test_cron_helper.info b/modules/simpletest/tests/common_test_cron_helper.info index 780e544..bf8a729 100644 --- a/modules/simpletest/tests/common_test_cron_helper.info +++ b/modules/simpletest/tests/common_test_cron_helper.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/database_test.info b/modules/simpletest/tests/database_test.info index d755deb..3ff31ed 100644 --- a/modules/simpletest/tests/database_test.info +++ b/modules/simpletest/tests/database_test.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/database_test.test b/modules/simpletest/tests/database_test.test index 12ddb34..59d2e5d 100644 --- a/modules/simpletest/tests/database_test.test +++ b/modules/simpletest/tests/database_test.test @@ -238,7 +238,7 @@ class DatabaseConnectionTestCase extends DatabaseTestCase { // Open the default target so we have an object to compare. $db1 = Database::getConnection('default', 'default'); - // Try to close the the default connection, then open a new one. + // Try to close the default connection, then open a new one. Database::closeConnection('default', 'default'); $db2 = Database::getConnection('default', 'default'); @@ -1414,10 +1414,47 @@ class DatabaseSelectTestCase extends DatabaseTestCase { } $query = (string)$query; - $expected = "/* Testing query comments SELECT nid FROM {node}; -- */ SELECT test.name AS name, test.age AS age\nFROM \n{test} test"; + $expected = "/* Testing query comments * / SELECT nid FROM {node}; -- */ SELECT test.name AS name, test.age AS age\nFROM \n{test} test"; $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); $this->assertEqual($query, $expected, 'The flattened query contains the sanitised comment string.'); + + $connection = Database::getConnection(); + foreach ($this->makeCommentsProvider() as $test_set) { + list($expected, $comments) = $test_set; + $this->assertEqual($expected, $connection->makeComment($comments)); + } + } + + /** + * Provides expected and input values for testVulnerableComment(). + */ + function makeCommentsProvider() { + return array( + array( + '/* */ ', + array(''), + ), + // Try and close the comment early. + array( + '/* Exploit * / DROP TABLE node; -- */ ', + array('Exploit */ DROP TABLE node; --'), + ), + // Variations on comment closing. + array( + '/* Exploit * / * / DROP TABLE node; -- */ ', + array('Exploit */*/ DROP TABLE node; --'), + ), + array( + '/* Exploit * * // DROP TABLE node; -- */ ', + array('Exploit **// DROP TABLE node; --'), + ), + // Try closing the comment in the second string which is appended. + array( + '/* Exploit * / DROP TABLE node; --; Another try * / DROP TABLE node; -- */ ', + array('Exploit */ DROP TABLE node; --', 'Another try */ DROP TABLE node; --'), + ), + ); } /** @@ -3454,12 +3491,14 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { } /** - * Helper method for transaction unit test. This "outer layer" transaction - * starts and then encapsulates the "inner layer" transaction. This nesting - * is used to evaluate whether the the database transaction API properly - * supports nesting. By "properly supports," we mean the outer transaction - * continues to exist regardless of what functions are called and whether - * those functions start their own transactions. + * Helper method for transaction unit test. + * + * This "outer layer" transaction starts and then encapsulates the + * "inner layer" transaction. This nesting is used to evaluate whether the + * database transaction API properly supports nesting. By "properly supports," + * we mean the outer transaction continues to exist regardless of what + * functions are called and whether those functions start their own + * transactions. * * In contrast, a typical database would commit the outer transaction, start * a new transaction for the inner layer, commit the inner layer transaction, diff --git a/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info new file mode 100644 index 0000000..b971535 --- /dev/null +++ b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info @@ -0,0 +1,14 @@ +name = "Drupal code registry test" +description = "Support module for testing the code registry." +files[] = drupal_autoload_test_interface.inc +files[] = drupal_autoload_test_class.inc +package = Testing +version = VERSION +core = 7.x +hidden = TRUE + +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" +project = "drupal" +datestamp = "1522264019" + diff --git a/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.module b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.module new file mode 100644 index 0000000..edd5d77 --- /dev/null +++ b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.module @@ -0,0 +1,22 @@ +name == 'drupal_autoload_test' && version_compare(PHP_VERSION, '5.4') >= 0) { + $files["$module->dir/drupal_autoload_test_trait.sh"] = array( + 'module' => $module->name, + 'weight' => $module->weight, + ); + } + } +} diff --git a/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test_class.inc b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test_class.inc new file mode 100644 index 0000000..afd72fe --- /dev/null +++ b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test_class.inc @@ -0,0 +1,11 @@ +drupalCreateUser(); - // Create a file with a size of 1000 bytes, and quotas of only 1 byte. $file = new stdClass(); $file->filesize = 1000; @@ -498,9 +491,6 @@ class FileValidatorTest extends DrupalWebTestCase { $this->assertEqual(count($errors), 1, 'Error for the user being over their limit.', 'File'); $errors = file_validate_size($file, 1, 1); $this->assertEqual(count($errors), 2, 'Errors for both the file and their limit.', 'File'); - - $user = $original_user; - drupal_save_session(TRUE); } } diff --git a/modules/simpletest/tests/file_test.info b/modules/simpletest/tests/file_test.info index dbeb750..3ceb3eb 100644 --- a/modules/simpletest/tests/file_test.info +++ b/modules/simpletest/tests/file_test.info @@ -6,8 +6,8 @@ core = 7.x files[] = file_test.module hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/filter_test.info b/modules/simpletest/tests/filter_test.info index d30cfa5..96c6c4b 100644 --- a/modules/simpletest/tests/filter_test.info +++ b/modules/simpletest/tests/filter_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/form.test b/modules/simpletest/tests/form.test index f90b854..a441cee 100644 --- a/modules/simpletest/tests/form.test +++ b/modules/simpletest/tests/form.test @@ -470,6 +470,64 @@ class FormsTestCase extends DrupalWebTestCase { $this->drupalPost(NULL, array('checkboxes[one]' => TRUE, 'checkboxes[two]' => TRUE), t('Submit')); $this->assertText('An illegal choice has been detected.', 'Input forgery was detected.'); } + + /** + * Tests that submitted values are converted to scalar strings for textfields. + */ + public function testTextfieldStringValue() { + // Check multivalued submissions. + $multivalue = array('evil' => 'multivalue', 'not so' => 'good'); + $this->checkFormValue('textfield', $multivalue, ''); + $this->checkFormValue('password', $multivalue, ''); + $this->checkFormValue('textarea', $multivalue, ''); + $this->checkFormValue('machine_name', $multivalue, ''); + $this->checkFormValue('password_confirm', $multivalue, array('pass1' => '', 'pass2' => '')); + // Check integer submissions. + $integer = 5; + $string = '5'; + $this->checkFormValue('textfield', $integer, $string); + $this->checkFormValue('password', $integer, $string); + $this->checkFormValue('textarea', $integer, $string); + $this->checkFormValue('machine_name', $integer, $string); + $this->checkFormValue('password_confirm', array('pass1' => $integer, 'pass2' => $integer), array('pass1' => $string, 'pass2' => $string)); + // Check that invalid array keys are ignored for password confirm elements. + $this->checkFormValue('password_confirm', array('pass1' => 'test', 'pass2' => 'test', 'extra' => 'invalid'), array('pass1' => 'test', 'pass2' => 'test')); + } + + /** + * Checks that a given form input value is sanitized to the expected result. + * + * @param string $element_type + * The form element type. Example: textfield. + * @param mixed $input_value + * The submitted user input value for the form element. + * @param mixed $expected_value + * The sanitized result value in the form state after calling + * form_builder(). + */ + protected function checkFormValue($element_type, $input_value, $expected_value) { + $form_id = $this->randomName(); + $form = array(); + $form_state = form_state_defaults(); + $form['op'] = array('#type' => 'submit', '#value' => t('Submit')); + $form[$element_type] = array( + '#type' => $element_type, + '#title' => 'test', + ); + + $form_state['input'][$element_type] = $input_value; + $form_state['input']['form_id'] = $form_id; + $form_state['method'] = 'post'; + $form_state['values'] = array(); + drupal_prepare_form($form_id, $form, $form_state); + + // This is the main function we want to test: it is responsible for + // populating user supplied $form_state['input'] to sanitized + // $form_state['values']. + form_builder($form_id, $form, $form_state); + + $this->assertIdentical($form_state['values'][$element_type], $expected_value, format_string('Form submission for the "@element_type" element type has been correctly sanitized.', array('@element_type' => $element_type))); + } } /** @@ -632,6 +690,14 @@ class FormValidationTestCase extends DrupalWebTestCase { $this->assertText('The form has become outdated. Copy any unsaved work in the form below'); } + /** + * Tests that a form with a disabled CSRF token can be validated. + */ + function testDisabledToken() { + $this->drupalPost('form-test/validate-no-token', array(), 'Save'); + $this->assertText('The form_test_validate_no_token form has been submitted successfully.'); + } + /** * Tests partial form validation through #limit_validation_errors. */ @@ -936,6 +1002,26 @@ class FormsElementsTableSelectFunctionalTest extends DrupalWebTestCase { $this->assertTrue(isset($errors['tableselect']), 'Option checker disallows invalid values for radio buttons.'); } + /** + * Test presence of ajax functionality + */ + function testAjax() { + $rows = array('row1', 'row2', 'row3'); + // Test checkboxes (#multiple == TRUE). + foreach ($rows as $row) { + $element = 'tableselect[' . $row . ']'; + $edit = array($element => TRUE); + $result = $this->drupalPostAJAX('form_test/tableselect/multiple-true', $edit, $element); + $this->assertFalse(empty($result), t('Ajax triggers on checkbox for @row.', array('@row' => $row))); + } + // Test radios (#multiple == FALSE). + $element = 'tableselect'; + foreach ($rows as $row) { + $edit = array($element => $row); + $result = $this->drupalPostAjax('form_test/tableselect/multiple-false', $edit, $element); + $this->assertFalse(empty($result), t('Ajax triggers on radio for @row.', array('@row' => $row))); + } + } /** * Helper function for the option check test to submit a form while collecting errors. @@ -2041,3 +2127,36 @@ class HTMLIdTestCase extends DrupalWebTestCase { $this->assertNoDuplicateIds('There are no duplicate IDs'); } } + +/** + * Tests for form textarea. + */ +class FormTextareaTestCase extends DrupalUnitTestCase { + + public static function getInfo() { + return array( + 'name' => 'Form textarea', + 'description' => 'Tests form textarea related functions.', + 'group' => 'Form API', + ); + } + + /** + * Tests that textarea value is properly set. + */ + public function testValueCallback() { + $element = array(); + $form_state = array(); + $test_cases = array( + array(NULL, FALSE), + array(NULL, NULL), + array('', array('test')), + array('test', 'test'), + array('123', 123), + ); + foreach ($test_cases as $test_case) { + list($expected, $input) = $test_case; + $this->assertIdentical($expected, form_type_textarea_value($element, $input, $form_state)); + } + } +} diff --git a/modules/simpletest/tests/form_test.info b/modules/simpletest/tests/form_test.info index 406bcd2..7706be2 100644 --- a/modules/simpletest/tests/form_test.info +++ b/modules/simpletest/tests/form_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/form_test.module b/modules/simpletest/tests/form_test.module index 602b409..097e588 100644 --- a/modules/simpletest/tests/form_test.module +++ b/modules/simpletest/tests/form_test.module @@ -37,6 +37,13 @@ function form_test_menu() { 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); + $items['form-test/validate-no-token'] = array( + 'title' => 'Form validation without a CSRF token', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('form_test_validate_no_token'), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); $items['form-test/limit-validation-errors'] = array( 'title' => 'Form validation with some error suppression', 'page callback' => 'drupal_get_form', @@ -454,6 +461,27 @@ function form_test_validate_required_form_no_title_submit($form, &$form_state) { drupal_set_message('The form_test_validate_required_form_no_title form was submitted successfully.'); } +/** + * Form builder for testing submission of a form without a CSRF token. + */ +function form_test_validate_no_token($form, &$form_state) { + $form['submit'] = array( + '#type' => 'submit', + '#value' => 'Save', + ); + + $form['#token'] = FALSE; + + return $form; +} + +/** + * Form submission handler for form_test_validate_no_token(). + */ +function form_test_validate_no_token_submit($form, &$form_state) { + drupal_set_message('The form_test_validate_no_token form has been submitted successfully.'); +} + /** * Builds a simple form with a button triggering partial validation. */ @@ -589,11 +617,17 @@ function _form_test_tableselect_form_builder($form, $form_state, $element_proper $form['tableselect'] = $element_properties; $form['tableselect'] += array( + '#prefix' => '
          ', + '#suffix' => '
          ', '#type' => 'tableselect', '#header' => $header, '#options' => $options, '#multiple' => FALSE, '#empty' => t('Empty text.'), + '#ajax' => array( + 'callback' => '_form_test_tableselect_ajax_callback', + 'wrapper' => 'tableselect-wrapper', + ), ); $form['submit'] = array( @@ -697,6 +731,13 @@ function _form_test_vertical_tabs_form($form, &$form_state) { return $form; } +/** +* Ajax callback that returns the form element. +*/ +function _form_test_tableselect_ajax_callback($form, &$form_state) { + return $form['tableselect']; +} + /** * A multistep form for testing the form storage. * diff --git a/modules/simpletest/tests/image.test b/modules/simpletest/tests/image.test index dc95a6e..7ca1d3a 100644 --- a/modules/simpletest/tests/image.test +++ b/modules/simpletest/tests/image.test @@ -207,9 +207,11 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase { protected $green = array(0, 255, 0, 0); protected $blue = array(0, 0, 255, 0); protected $yellow = array(255, 255, 0, 0); - protected $fuchsia = array(255, 0, 255, 0); // Used as background colors. - protected $transparent = array(0, 0, 0, 127); protected $white = array(255, 255, 255, 0); + protected $transparent = array(0, 0, 0, 127); + // Used as rotate background colors. + protected $fuchsia = array(255, 0, 255, 0); + protected $rotate_transparent = array(255, 255, 255, 127); protected $width = 40; protected $height = 20; @@ -261,6 +263,7 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase { */ function testManipulations() { // If GD isn't available don't bother testing this. + module_load_include('inc', 'system', 'image.gd'); if (!function_exists('image_gd_check_settings') || !image_gd_check_settings()) { $this->pass(t('Image manipulations for the GD toolkit were skipped because the GD toolkit is not available.')); return; @@ -274,6 +277,7 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase { $files = array( 'image-test.png', 'image-test.gif', + 'image-test-no-transparency.gif', 'image-test.jpg', ); @@ -333,13 +337,6 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase { // Systems using non-bundled GD2 don't have imagerotate. Test if available. if (function_exists('imagerotate')) { $operations += array( - 'rotate_5' => array( - 'function' => 'rotate', - 'arguments' => array(5, 0xFF00FF), // Fuchsia background. - 'width' => 42, - 'height' => 24, - 'corners' => array_fill(0, 4, $this->fuchsia), - ), 'rotate_90' => array( 'function' => 'rotate', 'arguments' => array(90, 0xFF00FF), // Fuchsia background. @@ -347,13 +344,6 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase { 'height' => 40, 'corners' => array($this->fuchsia, $this->red, $this->green, $this->blue), ), - 'rotate_transparent_5' => array( - 'function' => 'rotate', - 'arguments' => array(5), - 'width' => 42, - 'height' => 24, - 'corners' => array_fill(0, 4, $this->transparent), - ), 'rotate_transparent_90' => array( 'function' => 'rotate', 'arguments' => array(90), @@ -362,6 +352,49 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase { 'corners' => array($this->transparent, $this->red, $this->green, $this->blue), ), ); + // As of PHP version 5.5, GD uses a different algorithm to rotate images + // than version 5.4 and below, resulting in different dimensions. + // See https://bugs.php.net/bug.php?id=65148. + // For the 40x20 test images, the dimensions resulting from rotation will + // be 1 pixel smaller in both width and height in PHP 5.5 and above. + // @todo: If and when the PHP bug gets solved, add an upper limit + // version check. + if (version_compare(PHP_VERSION, '5.5', '>=')) { + $operations += array( + 'rotate_5' => array( + 'function' => 'rotate', + 'arguments' => array(5, 0xFF00FF), // Fuchsia background. + 'width' => 41, + 'height' => 23, + 'corners' => array_fill(0, 4, $this->fuchsia), + ), + 'rotate_transparent_5' => array( + 'function' => 'rotate', + 'arguments' => array(5), + 'width' => 41, + 'height' => 23, + 'corners' => array_fill(0, 4, $this->rotate_transparent), + ), + ); + } + else { + $operations += array( + 'rotate_5' => array( + 'function' => 'rotate', + 'arguments' => array(5, 0xFF00FF), // Fuchsia background. + 'width' => 42, + 'height' => 24, + 'corners' => array_fill(0, 4, $this->fuchsia), + ), + 'rotate_transparent_5' => array( + 'function' => 'rotate', + 'arguments' => array(5), + 'width' => 42, + 'height' => 24, + 'corners' => array_fill(0, 4, $this->rotate_transparent), + ), + ); + } } // Systems using non-bundled GD2 don't have imagefilter. Test if available. @@ -379,7 +412,7 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase { array_fill(0, 3, 76) + array(3 => 0), array_fill(0, 3, 149) + array(3 => 0), array_fill(0, 3, 29) + array(3 => 0), - array_fill(0, 3, 0) + array(3 => 127) + array_fill(0, 3, 225) + array(3 => 127) ), ), ); @@ -394,11 +427,14 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase { continue 2; } - // Transparent GIFs and the imagefilter function don't work together. - // There is a todo in image.gd.inc to correct this. + // All images should be converted to truecolor when loaded. + $image_truecolor = imageistruecolor($image->resource); + $this->assertTrue($image_truecolor, format_string('Image %file after load is a truecolor image.', array('%file' => $file))); + if ($image->info['extension'] == 'gif') { if ($op == 'desaturate') { - $values['corners'][3] = $this->white; + // Transparent GIFs and the imagefilter function don't work together. + $values['corners'][3][3] = 0; } } @@ -426,6 +462,11 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase { } // Now check each of the corners to ensure color correctness. foreach ($values['corners'] as $key => $corner) { + // The test gif that does not have transparency has yellow where the + // others have transparent. + if ($file === 'image-test-no-transparency.gif' && $corner === $this->transparent) { + $corner = $this->yellow; + } // Get the location of the corner. switch ($key) { case 0: @@ -451,7 +492,8 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase { $directory = file_default_scheme() . '://imagetests'; file_prepare_directory($directory, FILE_CREATE_DIRECTORY); - image_save($image, $directory . '/' . $op . '.' . $image->info['extension']); + $file_path = $directory . '/' . $op . '.' . $image->info['extension']; + image_save($image, $file_path); $this->assertTrue($correct_dimensions_real, format_string('Image %file after %action action has proper dimensions.', array('%file' => $file, '%action' => $op))); $this->assertTrue($correct_dimensions_object, format_string('Image %file object after %action action is reporting the proper height and width values.', array('%file' => $file, '%action' => $op))); @@ -460,8 +502,37 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase { $this->assertTrue($correct_colors, format_string('Image %file object after %action action has the correct color placement.', array('%file' => $file, '%action' => $op))); } } - } + // Check that saved image reloads without raising PHP errors. + $image_reloaded = image_load($file_path); + } + } + + /** + * Tests loading an image whose transparent color index is out of range. + */ + function testTransparentColorOutOfRange() { + // This image was generated by taking an initial image with a palette size + // of 6 colors, and setting the transparent color index to 6 (one higher + // than the largest allowed index), as follows: + // @code + // $image = imagecreatefromgif('modules/simpletest/files/image-test.gif'); + // imagecolortransparent($image, 6); + // imagegif($image, 'modules/simpletest/files/image-test-transparent-out-of-range.gif'); + // @endcode + // This allows us to test that an image with an out-of-range color index + // can be loaded correctly. + $file = 'image-test-transparent-out-of-range.gif'; + $image = image_load(drupal_get_path('module', 'simpletest') . '/files/' . $file); + + if (!$image) { + $this->fail(format_string('Could not load image %file.', array('%file' => $file))); + } + else { + // All images should be converted to truecolor when loaded. + $image_truecolor = imageistruecolor($image->resource); + $this->assertTrue($image_truecolor, format_string('Image %file after load is a truecolor image.', array('%file' => $file))); + } } } diff --git a/modules/simpletest/tests/image_test.info b/modules/simpletest/tests/image_test.info index b851329..82d2a3b 100644 --- a/modules/simpletest/tests/image_test.info +++ b/modules/simpletest/tests/image_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/mail.test b/modules/simpletest/tests/mail.test index 70a43cb..3e40e13 100644 --- a/modules/simpletest/tests/mail.test +++ b/modules/simpletest/tests/mail.test @@ -441,7 +441,7 @@ class DrupalHtmlToTextTestCase extends DrupalWebTestCase { * is 1000 characters." */ function testVeryLongLineWrap() { - $input = 'Drupal

          ' . str_repeat('x', 2100) . '
          Drupal'; + $input = 'Drupal

          ' . str_repeat('x', 2100) . '


          Drupal'; $output = drupal_html_to_text($input); // This awkward construct comes from includes/mail.inc lines 8-13. $eol = variable_get('mail_line_endings', MAIL_LINE_ENDINGS); @@ -455,7 +455,6 @@ class DrupalHtmlToTextTestCase extends DrupalWebTestCase { $maximum_line_length = max($maximum_line_length, strlen($line . $eol)); } $verbose = 'Maximum line length found was ' . $maximum_line_length . ' octets.'; - // @todo This should assert that $maximum_line_length <= 1000. - $this->pass($verbose); + $this->assertTrue($maximum_line_length <= 1000, $verbose); } } diff --git a/modules/simpletest/tests/menu_test.info b/modules/simpletest/tests/menu_test.info index e7509b5..26d70dc 100644 --- a/modules/simpletest/tests/menu_test.info +++ b/modules/simpletest/tests/menu_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/module.test b/modules/simpletest/tests/module.test index 371339f..eea3b51 100644 --- a/modules/simpletest/tests/module.test +++ b/modules/simpletest/tests/module.test @@ -302,3 +302,45 @@ class ModuleUninstallTestCase extends DrupalWebTestCase { $this->assertEqual(0, $count, 'Permissions were all removed.'); } } + +class ModuleImplementsAlterTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Module implements alter', + 'description' => 'Tests hook_module_implements_alter().', + 'group' => 'Module', + ); + } + + /** + * Tests hook_module_implements_alter() adding an implementation. + */ + function testModuleImplementsAlter() { + module_enable(array('module_test'), FALSE); + $this->assertTrue(module_exists('module_test'), 'Test module is enabled.'); + + // Assert that module_test.module is now included. + $this->assertTrue(function_exists('module_test_permission'), + 'The file module_test.module was successfully included.'); + + $modules = module_implements('permission'); + $this->assertTrue(in_array('module_test', $modules), 'module_test implements hook_permission.'); + + $modules = module_implements('module_implements_alter'); + $this->assertTrue(in_array('module_test', $modules), 'module_test implements hook_module_implements_alter().'); + + // Assert that module_test.implementations.inc is not included yet. + $this->assertFalse(function_exists('module_test_altered_test_hook'), + 'The file module_test.implementations.inc is not included yet.'); + + // Assert that module_test_module_implements_alter(*, 'altered_test_hook') + // has added an implementation + $this->assertTrue(in_array('module_test', module_implements('altered_test_hook')), + 'module_test implements hook_altered_test_hook().'); + + // Assert that module_test.implementations.inc was included as part of the process. + $this->assertTrue(function_exists('module_test_altered_test_hook'), + 'The file module_test.implementations.inc was included.'); + } + +} diff --git a/modules/simpletest/tests/module_test.implementations.inc b/modules/simpletest/tests/module_test.implementations.inc new file mode 100644 index 0000000..63c866e --- /dev/null +++ b/modules/simpletest/tests/module_test.implementations.inc @@ -0,0 +1,10 @@ + 'PSR4 example test: PSR-4 in disabled modules.', + 'description' => 'We want to assert that this test case is being discovered.', + 'group' => 'SimpleTest', + ); + } + + function testArithmetics() { + $this->assert(1 + 1 == 2, '1 + 1 == 2'); + } +} diff --git a/modules/simpletest/tests/psr_4_test/src/Tests/Nested/NestedExampleTest.php b/modules/simpletest/tests/psr_4_test/src/Tests/Nested/NestedExampleTest.php new file mode 100644 index 0000000..ff3ac29 --- /dev/null +++ b/modules/simpletest/tests/psr_4_test/src/Tests/Nested/NestedExampleTest.php @@ -0,0 +1,18 @@ + 'PSR4 example test: PSR-4 in nested subfolders.', + 'description' => 'We want to assert that this PSR-4 test case is being discovered.', + 'group' => 'SimpleTest', + ); + } + + function testArithmetics() { + $this->assert(1 + 1 == 2, '1 + 1 == 2'); + } +} diff --git a/modules/simpletest/tests/requirements1_test.info b/modules/simpletest/tests/requirements1_test.info index 7d6b011..c5b3a60 100644 --- a/modules/simpletest/tests/requirements1_test.info +++ b/modules/simpletest/tests/requirements1_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/requirements2_test.info b/modules/simpletest/tests/requirements2_test.info index af0032d..b12197e 100644 --- a/modules/simpletest/tests/requirements2_test.info +++ b/modules/simpletest/tests/requirements2_test.info @@ -7,8 +7,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/session.test b/modules/simpletest/tests/session.test index 097503b..893d03e 100644 --- a/modules/simpletest/tests/session.test +++ b/modules/simpletest/tests/session.test @@ -477,6 +477,56 @@ class SessionHttpsTestCase extends DrupalWebTestCase { $this->assertResponse(200); } + /** + * Tests that empty session IDs do not cause unrelated sessions to load. + */ + public function testEmptySessionId() { + global $is_https; + + if ($is_https) { + $secure_session_name = session_name(); + } + else { + $secure_session_name = 'S' . session_name(); + } + + // Enable mixed mode for HTTP and HTTPS. + variable_set('https', TRUE); + + $admin_user = $this->drupalCreateUser(array('access administration pages')); + $standard_user = $this->drupalCreateUser(array('access content')); + + // First log in as the admin user on HTTP. + // We cannot use $this->drupalLogin() here because we need to use the + // special http.php URLs. + $edit = array( + 'name' => $admin_user->name, + 'pass' => $admin_user->pass_raw + ); + $this->drupalGet('user'); + $form = $this->xpath('//form[@id="user-login"]'); + $form[0]['action'] = $this->httpUrl('user'); + $this->drupalPost(NULL, $edit, t('Log in')); + + $this->curlClose(); + + // Now start a session for the standard user on HTTPS. + $edit = array( + 'name' => $standard_user->name, + 'pass' => $standard_user->pass_raw + ); + $this->drupalGet('user'); + $form = $this->xpath('//form[@id="user-login"]'); + $form[0]['action'] = $this->httpsUrl('user'); + $this->drupalPost(NULL, $edit, t('Log in')); + + // Make the secure session cookie blank. + curl_setopt($this->curlHandle, CURLOPT_COOKIE, "$secure_session_name="); + $this->drupalGet($this->httpsUrl('user')); + $this->assertNoText($admin_user->name, 'User is not logged in as admin'); + $this->assertNoText($standard_user->name, "The user's own name is not displayed because the invalid session cookie has logged them out."); + } + /** * Test that there exists a session with two specific session IDs. * diff --git a/modules/simpletest/tests/session_test.info b/modules/simpletest/tests/session_test.info index 5851c3b..4987dea 100644 --- a/modules/simpletest/tests/session_test.info +++ b/modules/simpletest/tests/session_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/system_dependencies_test.info b/modules/simpletest/tests/system_dependencies_test.info index 8c198a0..4b03f29 100644 --- a/modules/simpletest/tests/system_dependencies_test.info +++ b/modules/simpletest/tests/system_dependencies_test.info @@ -6,8 +6,8 @@ core = 7.x hidden = TRUE dependencies[] = _missing_dependency -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info b/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info index cfa6983..cb2749e 100644 --- a/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info +++ b/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info @@ -6,8 +6,8 @@ core = 7.x hidden = TRUE dependencies[] = system_incompatible_core_version_test -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/system_incompatible_core_version_test.info b/modules/simpletest/tests/system_incompatible_core_version_test.info index 976bfe2..338521f 100644 --- a/modules/simpletest/tests/system_incompatible_core_version_test.info +++ b/modules/simpletest/tests/system_incompatible_core_version_test.info @@ -5,8 +5,8 @@ version = VERSION core = 5.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info b/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info index 3eb9849..edf6dd1 100644 --- a/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info +++ b/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info @@ -7,8 +7,8 @@ hidden = TRUE ; system_incompatible_module_version_test declares version 1.0 dependencies[] = system_incompatible_module_version_test (>2.0) -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/system_incompatible_module_version_test.info b/modules/simpletest/tests/system_incompatible_module_version_test.info index be297a3..51f3efc 100644 --- a/modules/simpletest/tests/system_incompatible_module_version_test.info +++ b/modules/simpletest/tests/system_incompatible_module_version_test.info @@ -5,8 +5,8 @@ version = 1.0 core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/system_project_namespace_test.info b/modules/simpletest/tests/system_project_namespace_test.info new file mode 100644 index 0000000..1507739 --- /dev/null +++ b/modules/simpletest/tests/system_project_namespace_test.info @@ -0,0 +1,13 @@ +name = "System project namespace test" +description = "Support module for testing project namespace dependencies." +package = Testing +version = VERSION +core = 7.x +hidden = TRUE +dependencies[] = drupal:filter + +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" +project = "drupal" +datestamp = "1522264019" + diff --git a/modules/simpletest/tests/system_project_namespace_test.module b/modules/simpletest/tests/system_project_namespace_test.module new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/modules/simpletest/tests/system_project_namespace_test.module @@ -0,0 +1 @@ + MENU_CALLBACK, ); + $items['system-test/drupal-set-message'] = array( + 'title' => 'Set messages with drupal_set_message()', + 'page callback' => 'system_test_drupal_set_message', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + $items['system-test/main-content-handling'] = array( 'title' => 'Test main content handling', 'page callback' => 'system_test_main_content_fallback', @@ -106,6 +113,34 @@ function system_test_menu() { 'type' => MENU_CALLBACK, ); + $items['system-test/get-destination'] = array( + 'title' => 'Test $_GET[\'destination\']', + 'page callback' => 'system_test_get_destination', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + + $items['system-test/request-destination'] = array( + 'title' => 'Test $_REQUEST[\'destination\']', + 'page callback' => 'system_test_request_destination', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + + $items['system-test/drupal-get-filename'] = array( + 'title' => 'Test drupal_get_filename()', + 'page callback' => 'system_test_drupal_get_filename', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + + $items['system-test/drupal-get-filename-with-schema-rebuild'] = array( + 'title' => 'Test drupal_get_filename() with a schema rebuild', + 'page callback' => 'system_test_drupal_get_filename_with_schema_rebuild', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + return $items; } @@ -275,6 +310,9 @@ function system_test_system_info_alter(&$info, $file, $type) { } } + if ($file->name == 'system_project_namespace_test') { + $info['hidden'] = FALSE; + } // Make the system_dependencies_test visible by default. if ($file->name == 'system_dependencies_test') { $info['hidden'] = FALSE; @@ -420,3 +458,114 @@ function system_test_authorize_init_page($page_title) { system_authorized_init('system_test_authorize_run', drupal_get_path('module', 'system_test') . '/system_test.module', array(), $page_title); drupal_goto($authorize_url); } + +/** + * Sets two messages and removes the first one before the messages are displayed. + */ +function system_test_drupal_set_message() { + // Set two messages. + drupal_set_message('First message (removed).'); + drupal_set_message('Second message (not removed).'); + + // Remove the first. + unset($_SESSION['messages']['status'][0]); + + return ''; +} + +/** + * Page callback to print out $_GET['destination'] for testing. + */ +function system_test_get_destination() { + if (isset($_GET['destination'])) { + print $_GET['destination']; + } + // No need to render the whole page, we are just interested in this bit of + // information. + exit; +} + +/** + * Page callback to print out $_REQUEST['destination'] for testing. + */ +function system_test_request_destination() { + if (isset($_REQUEST['destination'])) { + print $_REQUEST['destination']; + } + // No need to render the whole page, we are just interested in this bit of + // information. + exit; +} + +/** + * Page callback to run drupal_get_filename() on a particular module. + */ +function system_test_drupal_get_filename() { + // Prevent SimpleTest from failing as a result of the expected PHP warnings + // this function causes. Any warnings will be recorded in the database logs + // for examination by the tests. + define('SIMPLETEST_COLLECT_ERRORS', FALSE); + + $module_name = variable_get('system_test_drupal_get_filename_test_module_name'); + drupal_get_filename('module', $module_name); + + return ''; +} + +/** + * Page callback to run drupal_get_filename() and do a schema rebuild. + */ +function system_test_drupal_get_filename_with_schema_rebuild() { + // Prevent SimpleTest from failing as a result of the expected PHP warnings + // this function causes. + define('SIMPLETEST_COLLECT_ERRORS', FALSE); + + // Record the original database tables from drupal_get_schema(). + variable_set('system_test_drupal_get_filename_with_schema_rebuild_original_tables', array_keys(drupal_get_schema(NULL, TRUE))); + + // Trigger system_test_schema() and system_test_watchdog() to perform an + // attempted recursive rebuild when drupal_get_schema() is called. See + // BootstrapGetFilenameWebTestCase::testRecursiveRebuilds(). + variable_set('system_test_drupal_get_filename_attempt_recursive_rebuild', TRUE); + drupal_get_schema(NULL, TRUE); + + return ''; +} + +/** + * Implements hook_watchdog(). + */ +function system_test_watchdog($log_entry) { + // If an attempted recursive schema rebuild has been triggered by + // system_test_drupal_get_filename_with_schema_rebuild(), perform the rebuild + // in response to the missing file message triggered by system_test_schema(). + if (!variable_get('system_test_drupal_get_filename_attempt_recursive_rebuild')) { + return; + } + if ($log_entry['type'] != 'php' || $log_entry['severity'] != WATCHDOG_WARNING) { + return; + } + $module_name = variable_get('system_test_drupal_get_filename_test_module_name'); + if (!isset($log_entry['variables']['!message']) || strpos($log_entry['variables']['!message'], format_string('The following module is missing from the file system: %name', array('%name' => $module_name))) === FALSE) { + return; + } + variable_set('system_test_drupal_get_filename_with_schema_rebuild_final_tables', array_keys(drupal_get_schema())); +} + +/** + * Implements hook_module_implements_alter(). + */ +function system_test_module_implements_alter(&$implementations, $hook) { + // For BootstrapGetFilenameWebTestCase::testRecursiveRebuilds() to work + // correctly, this module's hook_schema() implementation cannot be either the + // first implementation (since that would trigger a potential recursive + // rebuild before anything is in the drupal_get_schema() cache) or the last + // implementation (since that would trigger a potential recursive rebuild + // after the cache is already complete). So put it somewhere in the middle. + if ($hook == 'schema') { + $group = $implementations['system_test']; + unset($implementations['system_test']); + $count = count($implementations); + $implementations = array_merge(array_slice($implementations, 0, $count / 2, TRUE), array('system_test' => $group), array_slice($implementations, $count / 2, NULL, TRUE)); + } +} diff --git a/modules/simpletest/tests/taxonomy_test.info b/modules/simpletest/tests/taxonomy_test.info index e6d525f..3ccf229 100644 --- a/modules/simpletest/tests/taxonomy_test.info +++ b/modules/simpletest/tests/taxonomy_test.info @@ -6,8 +6,8 @@ core = 7.x hidden = TRUE dependencies[] = taxonomy -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/taxonomy_test.module b/modules/simpletest/tests/taxonomy_test.module index f82950c..f414438 100644 --- a/modules/simpletest/tests/taxonomy_test.module +++ b/modules/simpletest/tests/taxonomy_test.module @@ -109,3 +109,33 @@ function taxonomy_test_get_antonym($tid) { ->execute() ->fetchField(); } + +/** + * Implements hook_query_alter(). + */ +function taxonomy_test_query_alter(QueryAlterableInterface $query) { + $value = variable_get(__FUNCTION__); + if (isset($value)) { + variable_set(__FUNCTION__, ++$value); + } +} + +/** + * Implements hook_query_TAG_alter(). + */ +function taxonomy_test_query_term_access_alter(QueryAlterableInterface $query) { + $value = variable_get(__FUNCTION__); + if (isset($value)) { + variable_set(__FUNCTION__, ++$value); + } +} + +/** + * Implements hook_query_TAG_alter(). + */ +function taxonomy_test_query_taxonomy_term_access_alter(QueryAlterableInterface $query) { + $value = variable_get(__FUNCTION__); + if (isset($value)) { + variable_set(__FUNCTION__, ++$value); + } +} diff --git a/modules/simpletest/tests/theme.test b/modules/simpletest/tests/theme.test index f1a743e..5f095bd 100644 --- a/modules/simpletest/tests/theme.test +++ b/modules/simpletest/tests/theme.test @@ -155,6 +155,15 @@ class ThemeTestCase extends DrupalWebTestCase { $this->assertNotEqual(theme_get_setting('subtheme_override', 'test_basetheme'), theme_get_setting('subtheme_override', 'test_subtheme'), 'Base theme\'s default settings values can be overridden by subtheme.'); $this->assertIdentical(theme_get_setting('basetheme_only', 'test_subtheme'), 'base theme value', 'Base theme\'s default settings values are inherited by subtheme.'); } + + /** + * Test the drupal_add_region_content() function. + */ + function testDrupalAddRegionContent() { + $this->drupalGet('theme-test/drupal-add-region-content'); + $this->assertText('Hello'); + $this->assertText('World'); + } } /** @@ -637,3 +646,34 @@ class ThemeDebugMarkupTestCase extends DrupalWebTestCase { } } + +/** + * Tests module-provided theme engines. + */ +class ModuleProvidedThemeEngineTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Theme engine test', + 'description' => 'Tests module-provided theme engines.', + 'group' => 'Theme', + ); + } + + function setUp() { + parent::setUp('theme_test'); + theme_enable(array('test_theme', 'test_theme_nyan_cat')); + } + + /** + * Ensures that the module provided theme engine is found and used by core. + */ + function testEngineIsFoundAndWorking() { + variable_set('theme_default', 'test_theme_nyan_cat'); + variable_set('admin_theme', 'test_theme_nyan_cat'); + + $this->drupalGet('theme-test/engine-info-test'); + $this->assertText('Miaou'); + } + +} diff --git a/modules/simpletest/tests/theme_test.info b/modules/simpletest/tests/theme_test.info index 4969662..f5cb1ea 100644 --- a/modules/simpletest/tests/theme_test.info +++ b/modules/simpletest/tests/theme_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/theme_test.module b/modules/simpletest/tests/theme_test.module index 61a12bb..1dbc3b9 100644 --- a/modules/simpletest/tests/theme_test.module +++ b/modules/simpletest/tests/theme_test.module @@ -27,9 +27,18 @@ function theme_test_system_theme_info() { $themes['test_theme'] = drupal_get_path('module', 'theme_test') . '/themes/test_theme/test_theme.info'; $themes['test_basetheme'] = drupal_get_path('module', 'theme_test') . '/themes/test_basetheme/test_basetheme.info'; $themes['test_subtheme'] = drupal_get_path('module', 'theme_test') . '/themes/test_subtheme/test_subtheme.info'; + $themes['test_theme_nyan_cat'] = drupal_get_path('module', 'theme_test') . '/themes/test_theme_nyan_cat/test_theme_nyan_cat.info'; return $themes; } +/** + * Implements hook_system_theme_engine_info(). + */ +function theme_test_system_theme_engine_info() { + $theme_engines['nyan_cat'] = drupal_get_path('module', 'theme_test') . '/themes/engines/nyan_cat/nyan_cat.engine'; + return $theme_engines; +} + /** * Implements hook_menu(). */ @@ -53,6 +62,17 @@ function theme_test_menu() { 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); + $items['theme-test/drupal-add-region-content'] = array( + 'page callback' => '_theme_test_drupal_add_region_content', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + $items['theme-test/engine-info-test'] = array( + 'description' => "Serves a simple page rendered using a Nyan Cat theme engine template.", + 'page callback' => '_theme_test_engine_info_test', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); return $items; } @@ -126,6 +146,23 @@ function _theme_test_suggestion() { return theme(array('theme_test__suggestion', 'theme_test'), array()); } +/** + * Page callback, calls drupal_add_region_content. + */ +function _theme_test_drupal_add_region_content() { + drupal_add_region_content('content', 'World'); + return 'Hello'; +} + +/** + * Serves a simple page renderered using a Nyan Cat theme engine template. + */ +function _theme_test_engine_info_test() { + return array( + '#markup' => theme('theme_test_template_test'), + ); +} + /** * Theme function for testing theme('theme_test_foo'). */ diff --git a/modules/simpletest/tests/themes/engines/nyan_cat/nyan_cat.engine b/modules/simpletest/tests/themes/engines/nyan_cat/nyan_cat.engine new file mode 100644 index 0000000..1b8ffef --- /dev/null +++ b/modules/simpletest/tests/themes/engines/nyan_cat/nyan_cat.engine @@ -0,0 +1,53 @@ +filename) . '/template.theme'; + if (file_exists($file)) { + include_once DRUPAL_ROOT . '/' . $file; + } +} + +/** + * Implements hook_theme(). + */ +function nyan_cat_theme($existing, $type, $theme, $path) { + $templates = drupal_find_theme_functions($existing, array($theme)); + $templates += drupal_find_theme_templates($existing, '.nyan-cat.html', $path); + return $templates; +} + +/** + * Implements hook_extension(). + */ +function nyan_cat_extension() { + return '.nyan-cat.html'; +} + +/** + * Implements hook_render_template(). + * + * @param string $template_file + * The filename of the template to render. + * @param mixed[] $variables + * A keyed array of variables that will appear in the output. + * + * @return string + * The output generated by the template. + */ +function nyan_cat_render_template($template_file, $variables) { + $output = str_replace('div', 'nyancat', file_get_contents(DRUPAL_ROOT . '/' . $template_file)); + foreach ($variables as $key => $variable) { + if (strpos($output, '9' . $key) !== FALSE) { + $output = str_replace('9' . $key, $variable, $output); + } + } + return $output; +} diff --git a/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info b/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info index 534537e..ed247b7 100644 --- a/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info +++ b/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info @@ -6,8 +6,8 @@ hidden = TRUE settings[basetheme_only] = base theme value settings[subtheme_override] = base theme value -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info b/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info index 3797325..dfd8bd8 100644 --- a/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info +++ b/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info @@ -6,8 +6,8 @@ hidden = TRUE settings[subtheme_override] = subtheme value -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/themes/test_theme/test_theme.info b/modules/simpletest/tests/themes/test_theme/test_theme.info index c6bf340..c132c91 100644 --- a/modules/simpletest/tests/themes/test_theme/test_theme.info +++ b/modules/simpletest/tests/themes/test_theme/test_theme.info @@ -17,8 +17,8 @@ stylesheets[all][] = system.base.css settings[theme_test_setting] = default value -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/themes/test_theme_nyan_cat/templates/theme_test_template_test.nyan-cat.html b/modules/simpletest/tests/themes/test_theme_nyan_cat/templates/theme_test_template_test.nyan-cat.html new file mode 100644 index 0000000..7202dd4 --- /dev/null +++ b/modules/simpletest/tests/themes/test_theme_nyan_cat/templates/theme_test_template_test.nyan-cat.html @@ -0,0 +1 @@ +Miaou \ No newline at end of file diff --git a/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info b/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info new file mode 100644 index 0000000..a4f5649 --- /dev/null +++ b/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info @@ -0,0 +1,11 @@ +name = Nyan cat engine based test theme +description = Theme for testing the module-provided theme engines. +core = 7.x +hidden = TRUE +engine = nyan_cat + +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" +project = "drupal" +datestamp = "1522264019" + diff --git a/modules/simpletest/tests/update_script_test.info b/modules/simpletest/tests/update_script_test.info index ab974c5..f3bfc50 100644 --- a/modules/simpletest/tests/update_script_test.info +++ b/modules/simpletest/tests/update_script_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/update_script_test.install b/modules/simpletest/tests/update_script_test.install index 6955ef1..4024fb4 100644 --- a/modules/simpletest/tests/update_script_test.install +++ b/modules/simpletest/tests/update_script_test.install @@ -31,6 +31,19 @@ function update_script_test_requirements($phase) { 'severity' => REQUIREMENT_ERROR, ); break; + case REQUIREMENT_INFO: + $requirements['update_script_test_stop'] = array( + 'title' => 'Update script test stop', + 'value' => 'Error', + 'description' => 'This is a requirements error provided by the update_script_test module to stop the page redirect for the info.', + 'severity' => REQUIREMENT_ERROR, + ); + $requirements['update_script_test'] = array( + 'title' => 'Update script test', + 'description' => 'This is a requirements info provided by the update_script_test module.', + 'severity' => REQUIREMENT_INFO, + ); + break; } } diff --git a/modules/simpletest/tests/update_test_1.info b/modules/simpletest/tests/update_test_1.info index 01763a3..a7431d5 100644 --- a/modules/simpletest/tests/update_test_1.info +++ b/modules/simpletest/tests/update_test_1.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/update_test_2.info b/modules/simpletest/tests/update_test_2.info index 01763a3..a7431d5 100644 --- a/modules/simpletest/tests/update_test_2.info +++ b/modules/simpletest/tests/update_test_2.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/update_test_3.info b/modules/simpletest/tests/update_test_3.info index 01763a3..a7431d5 100644 --- a/modules/simpletest/tests/update_test_3.info +++ b/modules/simpletest/tests/update_test_3.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/upgrade/drupal-6.filled.database.php b/modules/simpletest/tests/upgrade/drupal-6.filled.database.php index a916281..10b9040 100644 --- a/modules/simpletest/tests/upgrade/drupal-6.filled.database.php +++ b/modules/simpletest/tests/upgrade/drupal-6.filled.database.php @@ -19919,7 +19919,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '1', 'name' => 'vocabulary 1 (i=0)', 'description' => 'description of vocabulary 1 (i=0)', - 'help' => '', + 'help' => 'help for vocabulary 1 (i=0)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '0', @@ -19932,7 +19932,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '2', 'name' => 'vocabulary 2 (i=1)', 'description' => 'description of vocabulary 2 (i=1)', - 'help' => '', + 'help' => 'help for vocabulary 2 (i=1)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '1', @@ -19945,7 +19945,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '3', 'name' => 'vocabulary 3 (i=2)', 'description' => 'description of vocabulary 3 (i=2)', - 'help' => '', + 'help' => 'help for vocabulary 3 (i=2)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '0', @@ -19958,7 +19958,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '4', 'name' => 'vocabulary 4 (i=3)', 'description' => 'description of vocabulary 4 (i=3)', - 'help' => '', + 'help' => 'help for vocabulary 4 (i=3)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '1', @@ -19971,7 +19971,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '5', 'name' => 'vocabulary 5 (i=4)', 'description' => 'description of vocabulary 5 (i=4)', - 'help' => '', + 'help' => 'help for vocabulary 5 (i=4)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '0', @@ -19984,7 +19984,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '6', 'name' => 'vocabulary 6 (i=5)', 'description' => 'description of vocabulary 6 (i=5)', - 'help' => '', + 'help' => 'help for vocabulary 6 (i=5)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '1', @@ -19997,7 +19997,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '7', 'name' => 'vocabulary 7 (i=6)', 'description' => 'description of vocabulary 7 (i=6)', - 'help' => '', + 'help' => 'help for vocabulary 7 (i=6)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '0', @@ -20010,7 +20010,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '8', 'name' => 'vocabulary 8 (i=7)', 'description' => 'description of vocabulary 8 (i=7)', - 'help' => '', + 'help' => 'help for vocabulary 8 (i=7)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '1', @@ -20023,7 +20023,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '9', 'name' => 'vocabulary 9 (i=8)', 'description' => 'description of vocabulary 9 (i=8)', - 'help' => '', + 'help' => 'help for vocabulary 9 (i=8)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '0', @@ -20036,7 +20036,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '10', 'name' => 'vocabulary 10 (i=9)', 'description' => 'description of vocabulary 10 (i=9)', - 'help' => '', + 'help' => 'help for vocabulary 10 (i=9)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '1', @@ -20049,7 +20049,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '11', 'name' => 'vocabulary 11 (i=10)', 'description' => 'description of vocabulary 11 (i=10)', - 'help' => '', + 'help' => 'help for vocabulary 11 (i=10)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '0', @@ -20062,7 +20062,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '12', 'name' => 'vocabulary 12 (i=11)', 'description' => 'description of vocabulary 12 (i=11)', - 'help' => '', + 'help' => 'help for vocabulary 12 (i=11)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '1', @@ -20075,7 +20075,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '13', 'name' => 'vocabulary 13 (i=12)', 'description' => 'description of vocabulary 13 (i=12)', - 'help' => '', + 'help' => 'help for vocabulary 13 (i=12)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '0', @@ -20088,7 +20088,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '14', 'name' => 'vocabulary 14 (i=13)', 'description' => 'description of vocabulary 14 (i=13)', - 'help' => '', + 'help' => 'help for vocabulary 14 (i=13)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '1', @@ -20101,7 +20101,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '15', 'name' => 'vocabulary 15 (i=14)', 'description' => 'description of vocabulary 15 (i=14)', - 'help' => '', + 'help' => 'help for vocabulary 15 (i=14)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '0', @@ -20114,7 +20114,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '16', 'name' => 'vocabulary 16 (i=15)', 'description' => 'description of vocabulary 16 (i=15)', - 'help' => '', + 'help' => 'help for vocabulary 16 (i=15)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '1', @@ -20127,7 +20127,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '17', 'name' => 'vocabulary 17 (i=16)', 'description' => 'description of vocabulary 17 (i=16)', - 'help' => '', + 'help' => 'help for vocabulary 17 (i=16)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '0', @@ -20140,7 +20140,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '18', 'name' => 'vocabulary 18 (i=17)', 'description' => 'description of vocabulary 18 (i=17)', - 'help' => '', + 'help' => 'help for vocabulary 18 (i=17)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '1', @@ -20153,7 +20153,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '19', 'name' => 'vocabulary 19 (i=18)', 'description' => 'description of vocabulary 19 (i=18)', - 'help' => '', + 'help' => 'help for vocabulary 19 (i=18)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '0', @@ -20166,7 +20166,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '20', 'name' => 'vocabulary 20 (i=19)', 'description' => 'description of vocabulary 20 (i=19)', - 'help' => '', + 'help' => 'help for vocabulary 20 (i=19)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '1', @@ -20179,7 +20179,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '21', 'name' => 'vocabulary 21 (i=20)', 'description' => 'description of vocabulary 21 (i=20)', - 'help' => '', + 'help' => 'help for vocabulary 21 (i=20)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '0', @@ -20192,7 +20192,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '22', 'name' => 'vocabulary 22 (i=21)', 'description' => 'description of vocabulary 22 (i=21)', - 'help' => '', + 'help' => 'help for vocabulary 22 (i=21)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '1', @@ -20205,7 +20205,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '23', 'name' => 'vocabulary 23 (i=22)', 'description' => 'description of vocabulary 23 (i=22)', - 'help' => '', + 'help' => 'help for vocabulary 23 (i=22)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '0', @@ -20218,7 +20218,7 @@ db_insert('vocabulary')->fields(array( 'vid' => '24', 'name' => 'vocabulary 24 (i=23)', 'description' => 'description of vocabulary 24 (i=23)', - 'help' => '', + 'help' => 'help for vocabulary 24 (i=23)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '1', diff --git a/modules/simpletest/tests/upgrade/drupal-6.upload.database.php b/modules/simpletest/tests/upgrade/drupal-6.upload.database.php index 46ebe2c..3fd602a 100644 --- a/modules/simpletest/tests/upgrade/drupal-6.upload.database.php +++ b/modules/simpletest/tests/upgrade/drupal-6.upload.database.php @@ -127,6 +127,38 @@ db_insert('files')->fields(array( 'status' => '1', 'timestamp' => '1285708958', )) +// On some Drupal 6 sites, more than one file can have the same filepath. See +// https://www.drupal.org/node/1260938. +->values(array( + 'fid' => '12', + 'uid' => '1', + 'filename' => 'duplicate-name.png', + 'filepath' => 'sites/default/files/duplicate-name.png', + 'filemime' => 'image/png', + 'filesize' => '314', + 'status' => '1', + 'timestamp' => '1285708958', +)) +->values(array( + 'fid' => '13', + 'uid' => '1', + 'filename' => 'duplicate-name.png', + 'filepath' => 'sites/default/files/duplicate-name.png', + 'filemime' => 'image/png', + 'filesize' => '315', + 'status' => '1', + 'timestamp' => '1285708958', +)) +->values(array( + 'fid' => '14', + 'uid' => '1', + 'filename' => 'duplicate-name.png', + 'filepath' => 'sites/default/files/duplicate-name.png', + 'filemime' => 'image/png', + 'filesize' => '316', + 'status' => '1', + 'timestamp' => '1285708958', +)) ->execute(); db_insert('node')->fields(array( @@ -196,6 +228,23 @@ db_insert('node')->fields(array( 'sticky' => '0', 'tnid' => '0', 'translate' => '0', +)) +->values(array( + 'nid' => '41', + 'vid' => '55', + 'type' => 'page', + 'language' => '', + 'title' => 'node title 41 revision 55', + 'uid' => '1', + 'status' => '1', + 'created' => '1285709012', + 'changed' => '1285709012', + 'comment' => '0', + 'promote' => '0', + 'moderate' => '0', + 'sticky' => '0', + 'tnid' => '0', + 'translate' => '0', )) ->execute(); @@ -253,6 +302,28 @@ db_insert('node_revisions')->fields(array( 'log' => '', 'timestamp' => '1285709012', 'format' => '1', +)) +->values(array( + 'nid' => '41', + 'vid' => '54', + 'uid' => '1', + 'title' => 'node title 41 revision 54', + 'body' => "Attachments:\r\nduplicate-name.png", + 'teaser' => "Attachments:\r\nduplicate-name.png", + 'log' => '', + 'timestamp' => '1285709012', + 'format' => '1', +)) +->values(array( + 'nid' => '41', + 'vid' => '55', + 'uid' => '1', + 'title' => 'node title 41 revision 55', + 'body' => "Attachments:\r\nduplicate-name.png\r\nduplicate-name.png", + 'teaser' => "Attachments:\r\nduplicate-name.png\r\nduplicate-name.png", + 'log' => '', + 'timestamp' => '1285709012', + 'format' => '1', )) ->execute(); @@ -415,6 +486,30 @@ db_insert('upload')->fields(array( 'list' => '1', 'weight' => '0', )) +->values(array( + 'fid' => '12', + 'nid' => '41', + 'vid' => '54', + 'description' => 'duplicate-name.png', + 'list' => '1', + 'weight' => '0', +)) +->values(array( + 'fid' => '13', + 'nid' => '41', + 'vid' => '55', + 'description' => 'first description', + 'list' => '0', + 'weight' => '0', +)) +->values(array( + 'fid' => '14', + 'nid' => '41', + 'vid' => '55', + 'description' => 'second description', + 'list' => '1', + 'weight' => '0', +)) ->execute(); // Add series of entries for invalid node vids to the {upload} table. @@ -431,7 +526,7 @@ for ($i = 30; $i < 250; $i += 2) { ->values(array( 'fid' => $i, 'nid' => '40', - 'vid' => 24 + $i, + 'vid' => 26 + $i, 'description' => 'crazy-basename.png', 'list' => '1', 'weight' => '0', @@ -440,7 +535,7 @@ for ($i = 30; $i < 250; $i += 2) { ->values(array( 'fid' => 2, 'nid' => '40', - 'vid' => 24 + $i + 1, + 'vid' => 26 + $i + 1, 'description' => 'crazy-basename.png', 'list' => '1', 'weight' => '0', diff --git a/modules/simpletest/tests/upgrade/upgrade.taxonomy.test b/modules/simpletest/tests/upgrade/upgrade.taxonomy.test index 58a4d5c..51402ed 100644 --- a/modules/simpletest/tests/upgrade/upgrade.taxonomy.test +++ b/modules/simpletest/tests/upgrade/upgrade.taxonomy.test @@ -74,9 +74,10 @@ class UpgradePathTaxonomyTestCase extends UpgradePathTestCase { $this->assertEqual($voc_keys, $inst_keys, 'Node type page has instances for every vocabulary.'); // Ensure instance variables are getting through. - foreach ($instances as $instance) { - $this->assertTrue(isset($instance['required']), 'The required setting was preserved during the upgrade path.'); - $this->assertTrue($instance['description'], 'The description was preserved during the upgrade path'); + foreach (array_unique($instances) as $instance) { + $field_instance = field_info_instance('node', $instance, 'page'); + $this->assertTrue(isset($field_instance['required']), 'The required setting was preserved during the upgrade path.'); + $this->assertTrue($field_instance['description'], 'The description was preserved during the upgrade path'); } // Node type 'story' was not explicitly in $vocabulary->nodes but diff --git a/modules/simpletest/tests/upgrade/upgrade.upload.test b/modules/simpletest/tests/upgrade/upgrade.upload.test index be352bd..dfa94a0 100644 --- a/modules/simpletest/tests/upgrade/upgrade.upload.test +++ b/modules/simpletest/tests/upgrade/upgrade.upload.test @@ -64,12 +64,35 @@ class UploadUpgradePathTestCase extends UpgradePathTestCase { } $this->assertIdentical($filenames, $recorded_filenames, 'The uploaded files are present in the same order after the upgrade.'); } + // Test for the file with repeating basename to only have the streaming // path replaced. $node = node_load(40, 53); $repeated_basename_file = $node->upload[LANGUAGE_NONE][4]; $this->assertEqual($repeated_basename_file['uri'], 'private://drupal-6/file/directory/path/crazy-basename.png', "The file with the repeated basename path only had the stream portion replaced"); + // Ensure that filepaths are deduplicated. + $node0 = node_load(41, 54); + $node1 = node_load(41, 55); + // Ensure that both revisions point to the same file ID. + $items0 = field_get_items('node', $node0, 'upload'); + $this->assertEqual(count($items0), 1); + $items1 = field_get_items('node', $node1, 'upload'); + $this->assertEqual(count($items1), 2); + $this->assertEqual($items0[0]['fid'], $items1[0]['fid']); + $this->assertEqual($items0[0]['fid'], $items1[1]['fid']); + // The revision with more than one reference to the same file should retain + // the original settings for each reference. + $this->assertEqual($items1[0]['description'], 'first description'); + $this->assertEqual($items1[0]['display'], 0); + $this->assertEqual($items1[1]['description'], 'second description'); + $this->assertEqual($items1[1]['display'], 1); + // Ensure that the latest version of the files are used. + $this->assertEqual($items1[0]['filesize'], 316); + $this->assertEqual($items1[1]['filesize'], 316); + // No duplicate files should remain on the Drupal 7 site. + $this->assertEqual(0, db_query("SELECT COUNT(*) FROM {file_managed} GROUP BY uri HAVING COUNT(fid) > 1")->fetchField()); + // Make sure the file settings were properly migrated. $d6_file_directory_temp = '/drupal-6/file/directory/temp'; $d6_file_directory_path = '/drupal-6/file/directory/path'; diff --git a/modules/simpletest/tests/url_alter_test.info b/modules/simpletest/tests/url_alter_test.info index af00b0f..23676af 100644 --- a/modules/simpletest/tests/url_alter_test.info +++ b/modules/simpletest/tests/url_alter_test.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/simpletest/tests/xmlrpc.test b/modules/simpletest/tests/xmlrpc.test index 1a9ef23..bb74f05 100644 --- a/modules/simpletest/tests/xmlrpc.test +++ b/modules/simpletest/tests/xmlrpc.test @@ -246,4 +246,38 @@ class XMLRPCMessagesTestCase extends DrupalWebTestCase { $this->assertEqual($removed, 'system.methodSignature', 'Hiding builting system.methodSignature with hook_xmlrpc_alter works'); } + /** + * Test limits on system.multicall that can prevent brute-force attacks. + */ + function testMulticallLimit() { + $url = url(NULL, array('absolute' => TRUE)) . 'xmlrpc.php'; + $multicall_args = array(); + $num_method_calls = 10; + for ($i = 0; $i < $num_method_calls; $i++) { + $struct = array('i' => $i); + $multicall_args[] = array('methodName' => 'validator1.echoStructTest', 'params' => array($struct)); + } + // Test limits of 1, 5, 9, 13. + for ($limit = 1; $limit < $num_method_calls + 4; $limit += 4) { + variable_set('xmlrpc_multicall_duplicate_method_limit', $limit); + $results = xmlrpc($url, array('system.multicall' => array($multicall_args))); + $this->assertEqual($num_method_calls, count($results)); + for ($i = 0; $i < min($limit, $num_method_calls); $i++) { + $x = array_shift($results); + $this->assertTrue(empty($x->is_error), "Result $i is not an error"); + $this->assertEqual($multicall_args[$i]['params'][0], $x); + } + for (; $i < $num_method_calls; $i++) { + $x = array_shift($results); + $this->assertFalse(empty($x->is_error), "Result $i is an error"); + $this->assertEqual(-156579, $x->code); + } + } + variable_set('xmlrpc_multicall_duplicate_method_limit', -1); + $results = xmlrpc($url, array('system.multicall' => array($multicall_args))); + $this->assertEqual($num_method_calls, count($results)); + foreach ($results as $i => $x) { + $this->assertTrue(empty($x->is_error), "Result $i is not an error"); + } + } } diff --git a/modules/simpletest/tests/xmlrpc_test.info b/modules/simpletest/tests/xmlrpc_test.info index 1113983..ab787cc 100644 --- a/modules/simpletest/tests/xmlrpc_test.info +++ b/modules/simpletest/tests/xmlrpc_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/statistics/statistics.admin.inc b/modules/statistics/statistics.admin.inc index e83a115..f061081 100644 --- a/modules/statistics/statistics.admin.inc +++ b/modules/statistics/statistics.admin.inc @@ -59,7 +59,7 @@ function statistics_recent_hits() { * statistics settings form, but is dependent on cron running. * * @return - * A render array containing information about the the top pages. + * A render array containing information about the top pages. */ function statistics_top_pages() { $header = array( diff --git a/modules/statistics/statistics.info b/modules/statistics/statistics.info index 6c818c3..26c1794 100644 --- a/modules/statistics/statistics.info +++ b/modules/statistics/statistics.info @@ -6,8 +6,8 @@ core = 7.x files[] = statistics.test configure = admin/config/system/statistics -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/statistics/statistics.module b/modules/statistics/statistics.module index f665a14..356a0e2 100644 --- a/modules/statistics/statistics.module +++ b/modules/statistics/statistics.module @@ -118,10 +118,9 @@ function statistics_node_view($node, $view_mode) { // Attach Ajax node count statistics if configured. if (variable_get('statistics_count_content_views', 0) && variable_get('statistics_count_content_views_ajax', 0)) { if (!empty($node->nid) && $view_mode == 'full' && node_is_page($node) && empty($node->in_preview)) { - $node->content['#attached']['js'] = array( - drupal_get_path('module', 'statistics') . '/statistics.js' => array( - 'scope' => 'footer' - ), + $statistics = drupal_get_path('module', 'statistics') . '/statistics.js'; + $node->content['#attached']['js'][$statistics] = array( + 'scope' => 'footer', ); $settings = array('data' => array('nid' => $node->nid), 'url' => url(drupal_get_path('module', 'statistics') . '/statistics.php')); $node->content['#attached']['js'][] = array( @@ -246,7 +245,7 @@ function statistics_user_delete($account) { * Implements hook_cron(). */ function statistics_cron() { - $statistics_timestamp = variable_get('statistics_day_timestamp', ''); + $statistics_timestamp = variable_get('statistics_day_timestamp', 0); if ((REQUEST_TIME - $statistics_timestamp) >= 86400) { // Reset day counts. diff --git a/modules/statistics/statistics.php b/modules/statistics/statistics.php index f00e039..48340c8 100644 --- a/modules/statistics/statistics.php +++ b/modules/statistics/statistics.php @@ -15,17 +15,19 @@ chdir(DRUPAL_ROOT); include_once DRUPAL_ROOT . '/includes/bootstrap.inc'; drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES); if (variable_get('statistics_count_content_views', 0) && variable_get('statistics_count_content_views_ajax', 0)) { - $nid = $_POST['nid']; - if (is_numeric($nid)) { - db_merge('node_counter') - ->key(array('nid' => $nid)) - ->fields(array( - 'daycount' => 1, - 'totalcount' => 1, - 'timestamp' => REQUEST_TIME, - )) - ->expression('daycount', 'daycount + 1') - ->expression('totalcount', 'totalcount + 1') - ->execute(); + if (isset($_POST['nid'])) { + $nid = $_POST['nid']; + if (is_numeric($nid)) { + db_merge('node_counter') + ->key(array('nid' => $nid)) + ->fields(array( + 'daycount' => 1, + 'totalcount' => 1, + 'timestamp' => REQUEST_TIME, + )) + ->expression('daycount', 'daycount + 1') + ->expression('totalcount', 'totalcount + 1') + ->execute(); + } } } diff --git a/modules/statistics/statistics.test b/modules/statistics/statistics.test index 0498bb7..50accd7 100644 --- a/modules/statistics/statistics.test +++ b/modules/statistics/statistics.test @@ -35,7 +35,7 @@ class StatisticsTestCase extends DrupalWebTestCase { 'title' => 'test', 'path' => 'node/1', 'url' => 'http://example.com', - 'hostname' => '192.168.1.1', + 'hostname' => '1.2.3.3', 'uid' => 0, 'sid' => 10, 'timer' => 10, @@ -268,7 +268,7 @@ class StatisticsBlockVisitorsTestCase extends StatisticsTestCase { */ function testIPAddressBlocking() { // IP address for testing. - $test_ip_address = '192.168.1.1'; + $test_ip_address = '1.2.3.3'; // Verify the IP address from accesslog appears on the top visitors page // and that a 'block IP address' link is displayed. @@ -414,7 +414,7 @@ class StatisticsAdminTestCase extends DrupalWebTestCase { $timestamp = time(); $this->drupalPost(NULL, NULL, t('Cancel account')); // Confirm account cancellation request. - $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login)); + $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid)); $this->assertFalse(user_load($account->uid, TRUE), 'User is not found in the database.'); $this->drupalGet('admin/reports/visitors'); diff --git a/modules/syslog/syslog.info b/modules/syslog/syslog.info index b12b127..3f8b6ca 100644 --- a/modules/syslog/syslog.info +++ b/modules/syslog/syslog.info @@ -6,8 +6,8 @@ core = 7.x files[] = syslog.test configure = admin/config/development/logging -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/system/image.gd.inc b/modules/system/image.gd.inc index d9035e4..3d0797e 100644 --- a/modules/system/image.gd.inc +++ b/modules/system/image.gd.inc @@ -116,38 +116,62 @@ function image_gd_rotate(stdClass $image, $degrees, $background = NULL) { return FALSE; } - $width = $image->info['width']; - $height = $image->info['height']; + // PHP 5.5 GD bug: https://bugs.php.net/bug.php?id=65148: To prevent buggy + // behavior on negative multiples of 90 degrees we convert any negative + // angle to a positive one between 0 and 360 degrees. + $degrees -= floor($degrees / 360) * 360; - // Convert the hexadecimal background value to a color index value. + // Convert the hexadecimal background value to a RGBA array. if (isset($background)) { - $rgb = array(); - for ($i = 16; $i >= 0; $i -= 8) { - $rgb[] = (($background >> $i) & 0xFF); - } - $background = imagecolorallocatealpha($image->resource, $rgb[0], $rgb[1], $rgb[2], 0); + $background = array( + 'red' => $background >> 16 & 0xFF, + 'green' => $background >> 8 & 0xFF, + 'blue' => $background & 0xFF, + 'alpha' => 0, + ); } - // Set the background color as transparent if $background is NULL. else { - // Get the current transparent color. - $background = imagecolortransparent($image->resource); - - // If no transparent colors, use white. - if ($background == 0) { - $background = imagecolorallocatealpha($image->resource, 255, 255, 255, 0); - } + // Background color is not specified: use transparent white as background. + $background = array( + 'red' => 255, + 'green' => 255, + 'blue' => 255, + 'alpha' => 127 + ); } + // Store the color index for the background as that is what GD uses. + $background_idx = imagecolorallocatealpha($image->resource, $background['red'], $background['green'], $background['blue'], $background['alpha']); + // Images are assigned a new color palette when rotating, removing any // transparency flags. For GIF images, keep a record of the transparent color. if ($image->info['extension'] == 'gif') { - $transparent_index = imagecolortransparent($image->resource); - if ($transparent_index != 0) { - $transparent_gif_color = imagecolorsforindex($image->resource, $transparent_index); + // GIF does not work with a transparency channel, but can define 1 color + // in its palette to act as transparent. + + // Get the current transparent color, if any. + $gif_transparent_id = imagecolortransparent($image->resource); + if ($gif_transparent_id !== -1) { + // The gif already has a transparent color set: remember it to set it on + // the rotated image as well. + $transparent_gif_color = imagecolorsforindex($image->resource, $gif_transparent_id); + + if ($background['alpha'] >= 127) { + // We want a transparent background: use the color already set to act + // as transparent, as background. + $background_idx = $gif_transparent_id; + } + } + else { + // The gif does not currently have a transparent color set. + if ($background['alpha'] >= 127) { + // But as the background is transparent, it should get one. + $transparent_gif_color = $background; + } } } - $image->resource = imagerotate($image->resource, 360 - $degrees, $background); + $image->resource = imagerotate($image->resource, 360 - $degrees, $background_idx); // GIFs need to reassign the transparent color after performing the rotate. if (isset($transparent_gif_color)) { @@ -229,7 +253,24 @@ function image_gd_desaturate(stdClass $image) { function image_gd_load(stdClass $image) { $extension = str_replace('jpg', 'jpeg', $image->info['extension']); $function = 'imagecreatefrom' . $extension; - return (function_exists($function) && $image->resource = $function($image->source)); + if (function_exists($function) && $image->resource = $function($image->source)) { + if (imageistruecolor($image->resource)) { + return TRUE; + } + else { + // Convert indexed images to truecolor, copying the image to a new + // truecolor resource, so that filters work correctly and don't result + // in unnecessary dither. + $resource = image_gd_create_tmp($image, $image->info['width'], $image->info['height']); + if ($resource) { + imagecopy($resource, $image->resource, 0, 0, 0, 0, imagesx($resource), imagesy($resource)); + imagedestroy($image->resource); + $image->resource = $resource; + } + } + return (bool) $image->resource; + } + return FALSE; } /** @@ -297,17 +338,31 @@ function image_gd_create_tmp(stdClass $image, $width, $height) { $res = imagecreatetruecolor($width, $height); if ($image->info['extension'] == 'gif') { - // Grab transparent color index from image resource. + // Find out if a transparent color is set, will return -1 if no + // transparent color has been defined in the image. $transparent = imagecolortransparent($image->resource); if ($transparent >= 0) { - // The original must have a transparent color, allocate to the new image. - $transparent_color = imagecolorsforindex($image->resource, $transparent); - $transparent = imagecolorallocate($res, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']); + // Find out the number of colors in the image palette. It will be 0 for + // truecolor images. + $palette_size = imagecolorstotal($image->resource); + if ($palette_size == 0 || $transparent < $palette_size) { + // Set the transparent color in the new resource, either if it is a + // truecolor image or if the transparent color is part of the palette. + // Since the index of the transparency color is a property of the + // image rather than of the palette, it is possible that an image + // could be created with this index set outside the palette size (see + // http://stackoverflow.com/a/3898007). + $transparent_color = imagecolorsforindex($image->resource, $transparent); + $transparent = imagecolorallocate($res, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']); - // Flood with our new transparent color. - imagefill($res, 0, 0, $transparent); - imagecolortransparent($res, $transparent); + // Flood with our new transparent color. + imagefill($res, 0, 0, $transparent); + imagecolortransparent($res, $transparent); + } + else { + imagefill($res, 0, 0, imagecolorallocate($res, 255, 255, 255)); + } } } elseif ($image->info['extension'] == 'png') { diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc index 22c202c..cdcc78f 100644 --- a/modules/system/system.admin.inc +++ b/modules/system/system.admin.inc @@ -1856,7 +1856,7 @@ function system_image_toolkit_settings() { if (count($toolkits_available) == 0) { variable_del('image_toolkit'); $form['image_toolkit_help'] = array( - '#markup' => t("No image toolkits were detected. Drupal includes support for PHP's built-in image processing functions but they were not detected on this system. You should consult your system administrator to have them enabled, or try using a third party toolkit.", array('gd-link' => url('http://php.net/gd'))), + '#markup' => t("No image toolkits were detected. Drupal includes support for PHP's built-in image processing functions but they were not detected on this system. You should consult your system administrator to have them enabled, or try using a third party toolkit.", array('!gd-link' => url('http://php.net/gd'))), ); return $form; } @@ -2202,6 +2202,11 @@ function system_add_date_format_type_form_submit($form, &$form_state) { * Return the date for a given format string via Ajax. */ function system_date_time_lookup() { + // This callback is protected with a CSRF token because user input from the + // query string is reflected in the output. + if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], 'admin/config/regional/date-time/formats/lookup')) { + return MENU_ACCESS_DENIED; + } $result = format_date(REQUEST_TIME, 'custom', $_GET['format']); drupal_json_output($result); } @@ -2592,6 +2597,8 @@ function theme_status_report($variables) { if (empty($requirement['#type'])) { $severity = $severities[isset($requirement['severity']) ? (int) $requirement['severity'] : REQUIREMENT_OK]; $severity['icon'] = '
          ' . $severity['title'] . '
          '; + // The requirement's 'value' key is optional, provide a default value. + $requirement['value'] = isset($requirement['value']) ? $requirement['value'] : ''; // Output table row(s) if (!empty($requirement['description'])) { @@ -2646,8 +2653,8 @@ function theme_system_modules_fieldset($variables) { } $row[] = array('data' => $description, 'class' => array('description')); // Display links (such as help or permissions) in their own columns. - foreach (array('help', 'permissions', 'configure') as $key) { - $row[] = array('data' => drupal_render($module['links'][$key]), 'class' => array($key)); + foreach (array('help', 'permissions', 'configure') as $link_type) { + $row[] = array('data' => drupal_render($module['links'][$link_type]), 'class' => array($link_type)); } $rows[] = $row; } @@ -2875,13 +2882,14 @@ function system_date_time_formats() { * Allow users to add additional date formats. */ function system_configure_date_formats_form($form, &$form_state, $dfid = 0) { + $ajax_path = 'admin/config/regional/date-time/formats/lookup'; $js_settings = array( 'type' => 'setting', 'data' => array( 'dateTime' => array( 'date-format' => array( 'text' => t('Displayed as'), - 'lookup' => url('admin/config/regional/date-time/formats/lookup'), + 'lookup' => url($ajax_path, array('query' => array('token' => drupal_get_token($ajax_path)))), ), ), ), diff --git a/modules/system/system.api.php b/modules/system/system.api.php index b60f50d..f1855b9 100644 --- a/modules/system/system.api.php +++ b/modules/system/system.api.php @@ -113,21 +113,21 @@ function hook_hook_info_alter(&$hooks) { * translation handlers. Array keys are the module names, array values * can be any data structure the module uses to provide field translation. * Any empty value disallows the module to appear as a translation handler. - * - entity keys: An array describing how the Field API can extract the - * information it needs from the objects of the type. Elements: + * - entity keys: (optional) An array describing how the Field API can extract + * the information it needs from the objects of the type. Elements: * - id: The name of the property that contains the primary id of the * entity. Every entity object passed to the Field API must have this * property and its value must be numeric. * - revision: The name of the property that contains the revision id of * the entity. The Field API assumes that all revision ids are unique * across all entities of a type. This entry can be omitted if the - * entities of this type are not versionable. + * entities of this type are not versionable. Defaults to an empty string. * - bundle: The name of the property that contains the bundle name for the * entity. The bundle name defines which set of fields are attached to * the entity (e.g. what nodes call "content type"). This entry can be * omitted if this entity type exposes a single bundle (all entities have * the same collection of fields). The name of this single bundle will be - * the same as the entity type. + * the same as the entity type. Defaults to an empty string. * - label: The name of the property that contains the entity label. For * example, if the entity's label is located in $entity->subject, then * 'subject' should be specified here. If complex logic is required to @@ -606,8 +606,8 @@ function hook_cron() { * @return * An associative array where the key is the queue name and the value is * again an associative array. Possible keys are: - * - 'worker callback': The name of the function to call. It will be called - * with one argument, the item created via DrupalQueue::createItem(). + * - 'worker callback': The name of an implementation of + * callback_queue_worker(). * - 'time': (optional) How much time Drupal should spend on calling this * worker in seconds. Defaults to 15. * - 'skip on cron': (optional) Set to TRUE to avoid being processed during @@ -1797,6 +1797,8 @@ function hook_form_BASE_FORM_ID_alter(&$form, &$form_state, $form_id) { * the $form_id input matched your module's format for dynamically-generated * form IDs, and if so, act appropriately. * + * Third, forms defined in classes can be defined this way. + * * @param $form_id * The unique string identifying the desired form. * @param $args @@ -1807,19 +1809,22 @@ function hook_form_BASE_FORM_ID_alter(&$form, &$form_state, $form_id) { * @return * An associative array whose keys define form_ids and whose values are an * associative array defining the following keys: - * - callback: The name of the form builder function to invoke. This will be - * used for the base form ID, for example, to target a base form using - * hook_form_BASE_FORM_ID_alter(). + * - callback: The callable returning the form array. If it is the name of + * the form builder function then this will be used for the base + * form ID, for example, to target a base form using + * hook_form_BASE_FORM_ID_alter(). Otherwise use the base_form_id key to + * define the base form ID. * - callback arguments: (optional) Additional arguments to pass to the * function defined in 'callback', which are prepended to $args. - * - wrapper_callback: (optional) The name of a form builder function to - * invoke before the form builder defined in 'callback' is invoked. This - * wrapper callback may prepopulate the $form array with form elements, - * which will then be already contained in the $form that is passed on to - * the form builder defined in 'callback'. For example, a wrapper callback - * could setup wizard-alike form buttons that are the same for a variety of - * forms that belong to the wizard, which all share the same wrapper - * callback. + * - base_form_id: The base form ID can be specified explicitly. This is + * required when callback is not the name of a function. + * - wrapper_callback: (optional) Any callable to invoke before the form + * builder defined in 'callback' is invoked. This wrapper callback may + * prepopulate the $form array with form elements, which will then be + * already contained in the $form that is passed on to the form builder + * defined in 'callback'. For example, a wrapper callback could setup + * wizard-like form buttons that are the same for a variety of forms that + * belong to the wizard, which all share the same wrapper callback. */ function hook_forms($form_id, $args) { // Simply reroute the (non-existing) $form_id 'mymodule_first_form' to @@ -1843,6 +1848,15 @@ function hook_forms($form_id, $args) { 'wrapper_callback' => 'mymodule_main_form_wrapper', ); + // Build a form with a static class callback. + $forms['mymodule_class_generated_form'] = array( + // This will call: MyClass::generateMainForm(). + 'callback' => array('MyClass', 'generateMainForm'), + // The base_form_id is required when the callback is a static function in + // a class. This can also be used to keep newer code backwards compatible. + 'base_form_id' => 'mymodule_main_form', + ); + return $forms; } @@ -1890,9 +1904,8 @@ function hook_init() { /** * Define image toolkits provided by this module. * - * The file which includes each toolkit's functions must be declared as part of - * the files array in the module .info file so that the registry will find and - * parse it. + * The file which includes each toolkit's functions must be included in this + * hook. * * The toolkit's functions must be named image_toolkitname_operation(). * where the operation may be: @@ -2037,6 +2050,22 @@ function hook_system_theme_info() { return $themes; } +/** + * Return additional theme engines provided by modules. + * + * This hook is invoked from _system_rebuild_theme_data() and allows modules to + * register additional theme engines outside of the regular 'themes/engines' + * directories of a Drupal installation. + * + * @return + * An associative array. Each key is the system name of a theme engine and + * each value is the corresponding path to the theme engine's .engine file. + */ +function hook_system_theme_engine_info() { + $theme_engines['izumi'] = drupal_get_path('module', 'mymodule') . '/izumi/izumi.engine'; + return $theme_engines; +} + /** * Alter the information parsed from module and theme .info files * @@ -2633,6 +2662,8 @@ function hook_flush_caches() { * module_enable() for a detailed description of the order in which install and * enable hooks are invoked. * + * This hook should be implemented in a .module file, not in an .install file. + * * @param $modules * An array of the modules that were installed. * @@ -3174,7 +3205,9 @@ function hook_requirements($phase) { * creation and alteration of the supported database engines. * * See the Schema API Handbook at http://drupal.org/node/146843 for details on - * schema definition structures. + * schema definition structures. Note that foreign key definitions are for + * documentation purposes only; foreign keys are not created in the database, + * nor are they enforced by Drupal. * * @return array * A schema definition structure array. For each element of the @@ -3226,6 +3259,8 @@ function hook_schema() { 'nid_vid' => array('nid', 'vid'), 'vid' => array('vid'), ), + // For documentation purposes only; foreign keys are not created in the + // database. 'foreign keys' => array( 'node_revision' => array( 'table' => 'node_revision', @@ -3694,8 +3729,9 @@ function hook_registry_files_alter(&$files, $modules) { * * Any tasks you define here will be run, in order, after the installer has * finished the site configuration step but before it has moved on to the - * final import of languages and the end of the installation. You can have any - * number of custom tasks to perform during this phase. + * final import of languages and the end of the installation. This is invoked + * by install_tasks(). You can have any number of custom tasks to perform + * during this phase. * * Each task you define here corresponds to a callback function which you must * separately define and which is called when your task is run. This function @@ -3788,6 +3824,8 @@ function hook_registry_files_alter(&$files, $modules) { * * @see install_state_defaults() * @see batch_set() + * @see hook_install_tasks_alter() + * @see install_tasks() */ function hook_install_tasks(&$install_state) { // Here, we define a variable to allow tasks to indicate that a particular, @@ -3890,6 +3928,8 @@ function hook_html_head_alter(&$head_elements) { /** * Alter the full list of installation tasks. * + * This hook is invoked on the install profile in install_tasks(). + * * @param $tasks * An array of all available installation tasks, including those provided by * Drupal core. You can modify this array to change or replace any part of @@ -3897,6 +3937,9 @@ function hook_html_head_alter(&$head_elements) { * is selected. * @param $install_state * An array of information about the current installation state. + * + * @see hook_install_tasks() + * @see install_tasks() */ function hook_install_tasks_alter(&$tasks, $install_state) { // Replace the "Choose language" installation task provided by Drupal core @@ -4783,6 +4826,28 @@ function hook_filetransfer_info_alter(&$filetransfer_info) { * @{ */ +/** + * Work on a single queue item. + * + * Callback for hook_cron_queue_info(). + * + * @param $queue_item_data + * The data that was passed to DrupalQueueInterface::createItem() when the + * item was queued. + * + * @throws Exception + * The worker callback may throw an exception to indicate there was a problem. + * The cron process will log the exception, and leave the item in the queue to + * be processed again later. + * + * @see drupal_cron_run() + */ +function callback_queue_worker($queue_item_data) { + $node = node_load($queue_item_data); + $node->title = 'Updated title'; + node_save($node); +} + /** * Return the URI for an entity. * diff --git a/modules/system/system.info b/modules/system/system.info index d8ee86e..da08a99 100644 --- a/modules/system/system.info +++ b/modules/system/system.info @@ -12,8 +12,8 @@ files[] = system.test required = TRUE configure = admin/config/system -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/system/system.install b/modules/system/system.install index 43c7383..d5e6743 100644 --- a/modules/system/system.install +++ b/modules/system/system.install @@ -160,7 +160,7 @@ function system_requirements($phase) { if (empty($drivers)) { $database_ok = FALSE; $pdo_message = $t('Your web server does not appear to support any common PDO database extensions. Check with your hosting provider to see if they support PDO (PHP Data Objects) and offer any databases that Drupal supports.', array( - '@drupal-databases' => 'http://drupal.org/node/270#database', + '@drupal-databases' => 'https://www.drupal.org/requirements/database', )); } // Make sure the native PDO extension is available, not the older PEAR @@ -196,6 +196,12 @@ function system_requirements($phase) { ); } + // Test database-specific multi-byte UTF-8 related requirements. + $charset_requirements = _system_check_db_utf8mb4_requirements($phase); + if (!empty($charset_requirements)) { + $requirements['database_charset'] = $charset_requirements; + } + // Test PHP memory_limit $memory_limit = ini_get('memory_limit'); $requirements['php_memory_limit'] = array( @@ -517,6 +523,75 @@ function system_requirements($phase) { return $requirements; } +/** + * Checks whether the requirements for multi-byte UTF-8 support are met. + * + * @param string $phase + * The hook_requirements() stage. + * + * @return array + * A requirements array with the result of the charset check. + */ +function _system_check_db_utf8mb4_requirements($phase) { + global $install_state; + // In the requirements check of the installer, skip the utf8mb4 check unless + // the database connection info has been preconfigured by hand with valid + // information before running the installer, as otherwise we cannot get a + // valid database connection object. + if (isset($install_state['settings_verified']) && !$install_state['settings_verified']) { + return array(); + } + + $connection = Database::getConnection(); + $t = get_t(); + $requirements['title'] = $t('Database 4 byte UTF-8 support'); + + $utf8mb4_configurable = $connection->utf8mb4IsConfigurable(); + $utf8mb4_active = $connection->utf8mb4IsActive(); + $utf8mb4_supported = $connection->utf8mb4IsSupported(); + $driver = $connection->driver(); + $documentation_url = 'https://www.drupal.org/node/2754539'; + + if ($utf8mb4_active) { + if ($utf8mb4_supported) { + if ($phase != 'install' && $utf8mb4_configurable && !variable_get('drupal_all_databases_are_utf8mb4', FALSE)) { + // Supported, active, and configurable, but not all database tables + // have been converted yet. + $requirements['value'] = $t('Enabled, but database tables need conversion'); + $requirements['description'] = $t('Please convert all database tables to utf8mb4 prior to enabling it in settings.php. See the documentation on adding 4 byte UTF-8 support for more information.', array('@url' => $documentation_url)); + $requirements['severity'] = REQUIREMENT_ERROR; + } + else { + // Supported, active. + $requirements['value'] = $t('Enabled'); + $requirements['description'] = $t('4 byte UTF-8 for @driver is enabled.', array('@driver' => $driver)); + $requirements['severity'] = REQUIREMENT_OK; + } + } + else { + // Not supported, active. + $requirements['value'] = $t('Not supported'); + $requirements['description'] = $t('4 byte UTF-8 for @driver is activated, but not supported on your system. Please turn this off in settings.php, or ensure that all database-related requirements are met. See the documentation on adding 4 byte UTF-8 support for more information.', array('@driver' => $driver, '@url' => $documentation_url)); + $requirements['severity'] = REQUIREMENT_ERROR; + } + } + else { + if ($utf8mb4_supported) { + // Supported, not active. + $requirements['value'] = $t('Not enabled'); + $requirements['description'] = $t('4 byte UTF-8 for @driver is not activated, but it is supported on your system. It is recommended that you enable this to allow 4-byte UTF-8 input such as emojis, Asian symbols and mathematical symbols to be stored correctly. See the documentation on adding 4 byte UTF-8 support for more information.', array('@driver' => $driver, '@url' => $documentation_url)); + $requirements['severity'] = REQUIREMENT_INFO; + } + else { + // Not supported, not active. + $requirements['value'] = $t('Disabled'); + $requirements['description'] = $t('4 byte UTF-8 for @driver is disabled. See the documentation on adding 4 byte UTF-8 support for more information.', array('@driver' => $driver, '@url' => $documentation_url)); + $requirements['severity'] = REQUIREMENT_INFO; + } + } + return $requirements; +} + /** * Implements hook_install(). */ @@ -532,6 +607,9 @@ function system_install() { module_list(TRUE); module_implements('', FALSE, TRUE); + // Ensure the schema versions are not based on a previous module list. + drupal_static_reset('drupal_get_schema_versions'); + // Load system theme data appropriately. system_rebuild_theme_data(); @@ -800,6 +878,7 @@ function system_schema() { 'type' => 'varchar', 'length' => 100, 'not null' => TRUE, + 'binary' => TRUE, ), 'type' => array( 'description' => 'The date format type, e.g. medium.', @@ -2803,6 +2882,16 @@ function system_update_7061(&$sandbox) { ->from($query) ->execute(); + // Retrieve a list of duplicate files with the same filepath. Only the + // most-recently uploaded of these will be moved to the new {file_managed} + // table (and all references will be updated to point to it), since + // duplicate file URIs are not allowed in Drupal 7. + // Since the Drupal 6 to 7 upgrade path leaves the {files} table behind + // after it's done, custom or contributed modules which need to migrate + // file references of their own can use a similar query to determine the + // file IDs that duplicate filepaths were mapped to. + $sandbox['duplicate_filepath_fids_to_use'] = db_query("SELECT filepath, MAX(fid) FROM {files} GROUP BY filepath HAVING COUNT(*) > 1")->fetchAllKeyed(); + // Initialize batch update information. $sandbox['progress'] = 0; $sandbox['last_vid_processed'] = -1; @@ -2832,6 +2921,16 @@ function system_update_7061(&$sandbox) { continue; } + // If this file has a duplicate filepath, replace it with the + // most-recently uploaded file that has the same filepath. + if (isset($sandbox['duplicate_filepath_fids_to_use'][$file['filepath']]) && $record->fid != $sandbox['duplicate_filepath_fids_to_use'][$file['filepath']]) { + $file = db_select('files', 'f') + ->fields('f', array('fid', 'uid', 'filename', 'filepath', 'filemime', 'filesize', 'status', 'timestamp')) + ->condition('f.fid', $sandbox['duplicate_filepath_fids_to_use'][$file['filepath']]) + ->execute() + ->fetchAssoc(); + } + // Add in the file information from the upload table. $file['description'] = $record->description; $file['display'] = $record->list; @@ -2854,7 +2953,14 @@ function system_update_7061(&$sandbox) { // We will convert filepaths to URI using the default scheme // and stripping off the existing file directory path. $file['uri'] = $scheme . preg_replace('!^' . preg_quote($basename) . '!', '', $file['filepath']); - $file['uri'] = file_stream_wrapper_uri_normalize($file['uri']); + // Normalize the URI but don't call file_stream_wrapper_uri_normalize() + // directly, since that is a higher-level API function which invokes + // hooks while validating the scheme, and those will not work during + // the upgrade. Instead, use a simpler version that just assumes the + // scheme from above is already valid. + if (($file_uri_scheme = file_uri_scheme($file['uri'])) && ($file_uri_target = file_uri_target($file['uri']))) { + $file['uri'] = $file_uri_scheme . '://' . $file_uri_target; + } unset($file['filepath']); // Insert into the file_managed table. // Each fid should only be stored once in file_managed. @@ -3150,6 +3256,35 @@ function system_update_7079() { db_change_field('file_managed', 'filesize', 'filesize', $spec); } +/** + * Convert the 'format' column in {date_format_locale} to case sensitive varchar. + */ +function system_update_7080() { + $spec = array( + 'description' => 'The date format string.', + 'type' => 'varchar', + 'length' => 100, + 'not null' => TRUE, + 'binary' => TRUE, + ); + db_change_field('date_format_locale', 'format', 'format', $spec); +} + +/** + * Remove the Drupal 6 default install profile if it is still in the database. + */ +function system_update_7081() { + // Sites which used the default install profile in Drupal 6 and then updated + // to Drupal 7.44 or earlier will still have a record of this install profile + // in the database that needs to be deleted. + db_delete('system') + ->condition('filename', 'profiles/default/default.profile') + ->condition('type', 'module') + ->condition('status', 0) + ->condition('schema_version', 0) + ->execute(); +} + /** * @} End of "defgroup updates-7.x-extra". * The next series of updates should start at 8000. diff --git a/modules/system/system.js b/modules/system/system.js index 910fb5d..c0e76d3 100644 --- a/modules/system/system.js +++ b/modules/system/system.js @@ -105,7 +105,7 @@ Drupal.behaviors.dateTime = { // Attach keyup handler to custom format inputs. $('input' + source, context).once('date-time').keyup(function () { var input = $(this); - var url = fieldSettings.lookup + (/\?q=/.test(fieldSettings.lookup) ? '&format=' : '?format=') + encodeURIComponent(input.val()); + var url = fieldSettings.lookup + (/\?/.test(fieldSettings.lookup) ? '&format=' : '?format=') + encodeURIComponent(input.val()); $.getJSON(url, function (data) { $(suffix).empty().append(' ' + fieldSettings.text + ': ' + data + ''); }); diff --git a/modules/system/system.module b/modules/system/system.module index a274935..53844d8 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -359,7 +359,7 @@ function system_element_info() { '#size' => 60, '#maxlength' => 128, '#autocomplete_path' => FALSE, - '#process' => array('ajax_process_form'), + '#process' => array('form_process_autocomplete', 'ajax_process_form'), '#theme' => 'textfield', '#theme_wrappers' => array('form_element'), ); @@ -374,6 +374,9 @@ function system_element_info() { '#element_validate' => array('form_validate_machine_name'), '#theme' => 'textfield', '#theme_wrappers' => array('form_element'), + // Use the same value callback as for textfields; this ensures that we only + // get string values. + '#value_callback' => 'form_type_textfield_value', ); $types['password'] = array( '#input' => TRUE, @@ -382,6 +385,9 @@ function system_element_info() { '#process' => array('ajax_process_form'), '#theme' => 'password', '#theme_wrappers' => array('form_element'), + // Use the same value callback as for textfields; this ensures that we only + // get string values. + '#value_callback' => 'form_type_textfield_value', ); $types['password_confirm'] = array( '#input' => TRUE, @@ -2024,7 +2030,6 @@ function system_user_timezone(&$form, &$form_state) { '#description' => t('Select the desired local time and time zone. Dates and times throughout this site will be displayed using this time zone.'), ); if (!isset($account->timezone) && $account->uid == $user->uid && empty($form_state['input']['timezone'])) { - $form['timezone']['#description'] = t('Your time zone setting will be automatically detected if possible. Confirm the selection and click save.'); $form['timezone']['timezone']['#attributes'] = array('class' => array('timezone-detect')); drupal_add_js('misc/timezone.js'); } @@ -2406,6 +2411,10 @@ function _system_rebuild_module_data() { // Merge in defaults and save. $modules[$key]->info = $module->info + $defaults; + // The "name" key is required, but to avoid a fatal error in the menu system + // we set a reasonable default if it is not provided. + $modules[$key]->info += array('name' => $key); + // Prefix stylesheets and scripts with module path. $path = dirname($module->uri); if (isset($module->info['stylesheets'])) { @@ -2512,6 +2521,16 @@ function _system_rebuild_theme_data() { // Find theme engines $engines = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.engine$/', 'themes/engines'); + // Allow modules to add further theme engines. + if ($module_engines = module_invoke_all('system_theme_engine_info')) { + foreach ($module_engines as $name => $theme_engine_path) { + $engines[$name] = (object) array( + 'uri' => $theme_engine_path, + 'filename' => basename($theme_engine_path), + 'name' => $name, + ); + } + } // Set defaults for theme info. $defaults = array( @@ -2541,6 +2560,10 @@ function _system_rebuild_theme_data() { $themes[$key]->filename = $theme->uri; $themes[$key]->info = drupal_parse_info_file($theme->uri) + $defaults; + // The "name" key is required, but to avoid a fatal error in the menu system + // we set a reasonable default if it is not provided. + $themes[$key]->info += array('name' => $key); + // Add the info file modification time, so it becomes available for // contributed modules to use for ordering theme lists. $themes[$key]->info['mtime'] = filemtime($theme->uri); @@ -2692,10 +2715,17 @@ function system_find_base_themes($themes, $key, $used_keys = array()) { * @param $show * Possible values: REGIONS_ALL or REGIONS_VISIBLE. Visible excludes hidden * regions. - * @return - * An array of regions in the form $region['name'] = 'description'. + * @param bool $labels + * (optional) Boolean to specify whether the human readable machine names + * should be returned or not. Defaults to TRUE, but calling code can set + * this to FALSE for better performance, if it only needs machine names. + * + * @return array + * An associative array of regions in the form $region['name'] = 'description' + * if $labels is set to TRUE, or $region['name'] = 'name', if $labels is set + * to FALSE. */ -function system_region_list($theme_key, $show = REGIONS_ALL) { +function system_region_list($theme_key, $show = REGIONS_ALL, $labels = TRUE) { $themes = list_themes(); if (!isset($themes[$theme_key])) { return array(); @@ -2706,10 +2736,14 @@ function system_region_list($theme_key, $show = REGIONS_ALL) { // If requested, suppress hidden regions. See block_admin_display_form(). foreach ($info['regions'] as $name => $label) { if ($show == REGIONS_ALL || !isset($info['regions_hidden']) || !in_array($name, $info['regions_hidden'])) { - $list[$name] = t($label); + if ($labels) { + $list[$name] = t($label); + } + else { + $list[$name] = $name; + } } } - return $list; } @@ -2730,12 +2764,13 @@ function system_system_info_alter(&$info, $file, $type) { * * @param $theme * The name of a theme. + * * @return * A string that is the region name. */ function system_default_region($theme) { - $regions = array_keys(system_region_list($theme, REGIONS_VISIBLE)); - return isset($regions[0]) ? $regions[0] : ''; + $regions = system_region_list($theme, REGIONS_VISIBLE, FALSE); + return $regions ? reset($regions) : ''; } /** @@ -2802,7 +2837,7 @@ function system_settings_form_submit($form, &$form_state) { function _system_sort_requirements($a, $b) { if (!isset($a['weight'])) { if (!isset($b['weight'])) { - return strcmp($a['title'], $b['title']); + return strcasecmp($a['title'], $b['title']); } return -$b['weight']; } @@ -2858,7 +2893,7 @@ function confirm_form($form, $question, $path, $description = NULL, $yes = NULL, // Prepare cancel link. if (isset($_GET['destination'])) { - $options = drupal_parse_url(urldecode($_GET['destination'])); + $options = drupal_parse_url($_GET['destination']); } elseif (is_array($path)) { $options = $path; @@ -3043,8 +3078,20 @@ function system_cron() { } } - $core = array('cache', 'cache_path', 'cache_filter', 'cache_page', 'cache_form', 'cache_menu'); - $cache_tables = array_merge(module_invoke_all('flush_caches'), $core); + // Delete expired cache entries. + // Avoid invoking hook_flush_cashes() on every cron run because some modules + // use this hook to perform expensive rebuilding operations (which are only + // designed to happen on full cache clears), rather than just returning a + // list of cache tables to be cleared. + $cache_object = cache_get('system_cache_tables'); + if (empty($cache_object)) { + $core = array('cache', 'cache_path', 'cache_filter', 'cache_page', 'cache_form', 'cache_menu'); + $cache_tables = array_merge(module_invoke_all('flush_caches'), $core); + cache_set('system_cache_tables', $cache_tables); + } + else { + $cache_tables = $cache_object->data; + } foreach ($cache_tables as $table) { cache_clear_all(NULL, $table); } @@ -3292,7 +3339,7 @@ function system_goto_action_form($context) { $form['url'] = array( '#type' => 'textfield', '#title' => t('URL'), - '#description' => t('The URL to which the user should be redirected. This can be an internal URL like node/1234 or an external URL like http://drupal.org.'), + '#description' => t('The URL to which the user should be redirected. This can be an internal path like node/1234 or an external URL like http://example.com.'), '#default_value' => isset($context['url']) ? $context['url'] : '', '#required' => TRUE, ); @@ -3329,7 +3376,8 @@ function system_goto_action($entity, $context) { */ function system_block_ip_action() { $ip = ip_address(); - db_insert('blocked_ips') + db_merge('blocked_ips') + ->key(array('ip' => $ip)) ->fields(array('ip' => $ip)) ->execute(); watchdog('action', 'Banned IP address %ip', array('%ip' => $ip)); @@ -3491,8 +3539,7 @@ function system_retrieve_file($url, $destination = NULL, $managed = FALSE, $repl function system_page_alter(&$page) { // Find all non-empty page regions, and add a theme wrapper function that // allows them to be consistently themed. - $regions = system_region_list($GLOBALS['theme']); - foreach (array_keys($regions) as $region) { + foreach (system_region_list($GLOBALS['theme'], REGIONS_ALL, FALSE) as $region) { if (!empty($page[$region])) { $page[$region]['#theme_wrappers'][] = 'region'; $page[$region]['#region'] = $region; diff --git a/modules/system/system.queue.inc b/modules/system/system.queue.inc index 901c4d6..c17084d 100644 --- a/modules/system/system.queue.inc +++ b/modules/system/system.queue.inc @@ -231,7 +231,7 @@ class SystemQueue implements DrupalReliableQueueInterface { // until an item is successfully claimed or we are reasonably sure there // are no unclaimed items left. while (TRUE) { - $item = db_query_range('SELECT data, item_id FROM {queue} q WHERE expire = 0 AND name = :name ORDER BY created ASC', 0, 1, array(':name' => $this->name))->fetchObject(); + $item = db_query_range('SELECT data, item_id FROM {queue} q WHERE expire = 0 AND name = :name ORDER BY created, item_id ASC', 0, 1, array(':name' => $this->name))->fetchObject(); if ($item) { // Try to update the item. Only one thread can succeed in UPDATEing the // same row. We cannot rely on REQUEST_TIME because items might be @@ -326,6 +326,7 @@ class MemoryQueue implements DrupalQueueInterface { $item->created = time(); $item->expire = 0; $this->queue[$item->item_id] = $item; + return TRUE; } public function numberOfItems() { diff --git a/modules/system/system.tar.inc b/modules/system/system.tar.inc index 32bf7f0..86e4e3d 100644 --- a/modules/system/system.tar.inc +++ b/modules/system/system.tar.inc @@ -30,81 +30,148 @@ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * - * @category File_Formats - * @package Archive_Tar - * @author Vincent Blavet - * @copyright 1997-2008 The Authors - * @license http://www.opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: Id: Tar.php,v 1.43 2008/10/30 17:58:42 dufuz Exp - * @link http://pear.php.net/package/Archive_Tar + * @category File_Formats + * @package Archive_Tar + * @author Vincent Blavet + * @copyright 1997-2010 The Authors + * @license http://www.opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/Archive_Tar */ -//require_once 'PEAR.php'; -// -// -define ('ARCHIVE_TAR_ATT_SEPARATOR', 90001); -define ('ARCHIVE_TAR_END_BLOCK', pack("a512", '')); + /** + * Note on Drupal 8 porting. + * This file origin is Tar.php, release 1.4.0 (stable) with some code + * from PEAR.php, release 1.9.5 (stable) both at http://pear.php.net. + * To simplify future porting from pear of this file, you should not + * do cosmetic or other non significant changes to this file. + * The following changes have been done: + * Added namespace Drupal\Core\Archiver. + * Removed require_once 'PEAR.php'. + * Added defintion of OS_WINDOWS taken from PEAR.php. + * Renamed class to ArchiveTar. + * Removed extends PEAR from class. + * Removed call parent:: __construct(). + * Changed PEAR::loadExtension($extname) to this->loadExtension($extname). + * Added function loadExtension() taken from PEAR.php. + * Changed all calls of unlink() to drupal_unlink(). + * Changed $this->error_object = &$this->raiseError($p_message) + * to throw new \Exception($p_message). + */ + + /** + * Note on Drupal 7 backporting from Drupal 8. + * File origin is core/lib/Drupal/Core/Archiver/ArchiveTar.php from Drupal 8. + * The following changes have been done: + * Removed namespace Drupal\Core\Archiver. + * Renamed class to Archive_Tar. + * Changed \Exception to Exception. + */ + + +// Drupal removal require_once 'PEAR.php'. + +// Drupal addition OS_WINDOWS as defined in PEAR.php. +if (substr(PHP_OS, 0, 3) == 'WIN') { + define('OS_WINDOWS', true); +} else { + define('OS_WINDOWS', false); +} + +define('ARCHIVE_TAR_ATT_SEPARATOR', 90001); +define('ARCHIVE_TAR_END_BLOCK', pack("a512", '')); + +if (!function_exists('gzopen') && function_exists('gzopen64')) { + function gzopen($filename, $mode, $use_include_path = 0) + { + return gzopen64($filename, $mode, $use_include_path); + } +} + +if (!function_exists('gztell') && function_exists('gztell64')) { + function gztell($zp) + { + return gztell64($zp); + } +} + +if (!function_exists('gzseek') && function_exists('gzseek64')) { + function gzseek($zp, $offset, $whence = SEEK_SET) + { + return gzseek64($zp, $offset, $whence); + } +} /** -* Creates a (compressed) Tar archive -* -* @author Vincent Blavet -* @version Revision: 1.43 -* @license http://www.opensource.org/licenses/bsd-license.php New BSD License -* @package Archive_Tar -*/ -class Archive_Tar // extends PEAR + * Creates a (compressed) Tar archive + * + * @package Archive_Tar + * @author Vincent Blavet + * @license http://www.opensource.org/licenses/bsd-license.php New BSD License + * @version $Revision$ + */ +// Drupal change class Archive_Tar extends PEAR. +class Archive_Tar { /** - * @var string Name of the Tar - */ - var $_tarname=''; + * @var string Name of the Tar + */ + public $_tarname = ''; /** - * @var boolean if true, the Tar file will be gzipped - */ - var $_compress=false; + * @var boolean if true, the Tar file will be gzipped + */ + public $_compress = false; /** - * @var string Type of compression : 'none', 'gz' or 'bz2' - */ - var $_compress_type='none'; + * @var string Type of compression : 'none', 'gz', 'bz2' or 'lzma2' + */ + public $_compress_type = 'none'; /** - * @var string Explode separator - */ - var $_separator=' '; + * @var string Explode separator + */ + public $_separator = ' '; /** - * @var file descriptor - */ - var $_file=0; + * @var file descriptor + */ + public $_file = 0; /** - * @var string Local Tar name of a remote Tar (http:// or ftp://) - */ - var $_temp_tarname=''; + * @var string Local Tar name of a remote Tar (http:// or ftp://) + */ + public $_temp_tarname = ''; - // {{{ constructor /** - * Archive_Tar Class constructor. This flavour of the constructor only - * declare a new Archive_Tar object, identifying it by the name of the - * tar file. - * If the compress argument is set the tar will be read or created as a - * gzip or bz2 compressed TAR file. - * - * @param string $p_tarname The name of the tar archive to create - * @param string $p_compress can be null, 'gz' or 'bz2'. This - * parameter indicates if gzip or bz2 compression - * is required. For compatibility reason the - * boolean value 'true' means 'gz'. - * @access public - */ -// function Archive_Tar($p_tarname, $p_compress = null) - function __construct($p_tarname, $p_compress = null) + * @var string regular expression for ignoring files or directories + */ + public $_ignore_regexp = ''; + + /** + * @var object PEAR_Error object + */ + public $error_object = null; + + /** + * Archive_Tar Class constructor. This flavour of the constructor only + * declare a new Archive_Tar object, identifying it by the name of the + * tar file. + * If the compress argument is set the tar will be read or created as a + * gzip or bz2 compressed TAR file. + * + * @param string $p_tarname The name of the tar archive to create + * @param string $p_compress can be null, 'gz', 'bz2' or 'lzma2'. This + * parameter indicates if gzip, bz2 or lzma2 compression + * is required. For compatibility reason the + * boolean value 'true' means 'gz'. + * + * @return bool + */ + public function __construct($p_tarname, $p_compress = null) { -// $this->PEAR(); + // Drupal removal parent::__construct(). + $this->_compress = false; $this->_compress_type = 'none'; if (($p_compress === null) || ($p_compress == '')) { @@ -116,10 +183,13 @@ class Archive_Tar // extends PEAR if ($data == "\37\213") { $this->_compress = true; $this->_compress_type = 'gz'; - // No sure it's enought for a magic code .... + // No sure it's enought for a magic code .... } elseif ($data == "BZ") { $this->_compress = true; $this->_compress_type = 'bz2'; + } elseif (file_get_contents($p_tarname, false, null, 1, 4) == '7zXZ') { + $this->_compress = true; + $this->_compress_type = 'lzma2'; } } } else { @@ -129,151 +199,177 @@ class Archive_Tar // extends PEAR $this->_compress = true; $this->_compress_type = 'gz'; } elseif ((substr($p_tarname, -3) == 'bz2') || - (substr($p_tarname, -2) == 'bz')) { + (substr($p_tarname, -2) == 'bz') + ) { $this->_compress = true; $this->_compress_type = 'bz2'; + } else { + if (substr($p_tarname, -2) == 'xz') { + $this->_compress = true; + $this->_compress_type = 'lzma2'; + } } } } else { if (($p_compress === true) || ($p_compress == 'gz')) { $this->_compress = true; $this->_compress_type = 'gz'; - } else if ($p_compress == 'bz2') { - $this->_compress = true; - $this->_compress_type = 'bz2'; } else { - die("Unsupported compression type '$p_compress'\n". - "Supported types are 'gz' and 'bz2'.\n"); - return false; + if ($p_compress == 'bz2') { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } else { + if ($p_compress == 'lzma2') { + $this->_compress = true; + $this->_compress_type = 'lzma2'; + } else { + $this->_error( + "Unsupported compression type '$p_compress'\n" . + "Supported types are 'gz', 'bz2' and 'lzma2'.\n" + ); + return false; + } + } } } $this->_tarname = $p_tarname; - if ($this->_compress) { // assert zlib or bz2 extension support - if ($this->_compress_type == 'gz') + if ($this->_compress) { // assert zlib or bz2 or xz extension support + if ($this->_compress_type == 'gz') { $extname = 'zlib'; - else if ($this->_compress_type == 'bz2') - $extname = 'bz2'; + } else { + if ($this->_compress_type == 'bz2') { + $extname = 'bz2'; + } else { + if ($this->_compress_type == 'lzma2') { + $extname = 'xz'; + } + } + } if (!extension_loaded($extname)) { -// PEAR::loadExtension($extname); + // Drupal change PEAR::loadExtension($extname). $this->loadExtension($extname); } if (!extension_loaded($extname)) { - die("The extension '$extname' couldn't be found.\n". - "Please make sure your version of PHP was built ". - "with '$extname' support.\n"); + $this->_error( + "The extension '$extname' couldn't be found.\n" . + "Please make sure your version of PHP was built " . + "with '$extname' support.\n" + ); return false; } } } - // }}} + public function __destruct() + { + $this->_close(); + // ----- Look for a local copy to delete + if ($this->_temp_tarname != '') { + @drupal_unlink($this->_temp_tarname); + } + } + + // Drupal addition from PEAR.php. /** * OS independent PHP extension load. Remember to take care * on the correct extension name for case sensitive OSes. - * The function is the copy of PEAR::loadExtension(). * * @param string $ext The extension name * @return bool Success or not on the dl() call */ function loadExtension($ext) { - if (!extension_loaded($ext)) { - // if either returns true dl() will produce a FATAL error, stop that - if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) { - return false; - } - - if (OS_WINDOWS) { - $suffix = '.dll'; - } elseif (PHP_OS == 'HP-UX') { - $suffix = '.sl'; - } elseif (PHP_OS == 'AIX') { - $suffix = '.a'; - } elseif (PHP_OS == 'OSX') { - $suffix = '.bundle'; - } else { - $suffix = '.so'; - } - - return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); + if (extension_loaded($ext)) { + return true; } - return true; + // if either returns true dl() will produce a FATAL error, stop that + if ( + function_exists('dl') === false || + ini_get('enable_dl') != 1 || + ini_get('safe_mode') == 1 + ) { + return false; + } + + if (OS_WINDOWS) { + $suffix = '.dll'; + } elseif (PHP_OS == 'HP-UX') { + $suffix = '.sl'; + } elseif (PHP_OS == 'AIX') { + $suffix = '.a'; + } elseif (PHP_OS == 'OSX') { + $suffix = '.bundle'; + } else { + $suffix = '.so'; + } + + return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); } - // {{{ destructor -// function _Archive_Tar() - function __destruct() - { - $this->_close(); - // ----- Look for a local copy to delete - if ($this->_temp_tarname != '') - @drupal_unlink($this->_temp_tarname); -// $this->_PEAR(); - } - // }}} - - // {{{ create() /** - * This method creates the archive file and add the files / directories - * that are listed in $p_filelist. - * If a file with the same name exist and is writable, it is replaced - * by the new tar. - * The method return false and a PEAR error text. - * The $p_filelist parameter can be an array of string, each string - * representing a filename or a directory name with their path if - * needed. It can also be a single string with names separated by a - * single blank. - * For each directory added in the archive, the files and - * sub-directories are also added. - * See also createModify() method for more details. - * - * @param array $p_filelist An array of filenames and directory names, or a - * single string with names separated by a single - * blank space. - * @return true on success, false on error. - * @see createModify() - * @access public - */ - function create($p_filelist) + * This method creates the archive file and add the files / directories + * that are listed in $p_filelist. + * If a file with the same name exist and is writable, it is replaced + * by the new tar. + * The method return false and a PEAR error text. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * For each directory added in the archive, the files and + * sub-directories are also added. + * See also createModify() method for more details. + * + * @param array $p_filelist An array of filenames and directory names, or a + * single string with names separated by a single + * blank space. + * + * @return true on success, false on error. + * @see createModify() + */ + public function create($p_filelist) { return $this->createModify($p_filelist, '', ''); } - // }}} - // {{{ add() /** - * This method add the files / directories that are listed in $p_filelist in - * the archive. If the archive does not exist it is created. - * The method return false and a PEAR error text. - * The files and directories listed are only added at the end of the archive, - * even if a file with the same name is already archived. - * See also createModify() method for more details. - * - * @param array $p_filelist An array of filenames and directory names, or a - * single string with names separated by a single - * blank space. - * @return true on success, false on error. - * @see createModify() - * @access public - */ - function add($p_filelist) + * This method add the files / directories that are listed in $p_filelist in + * the archive. If the archive does not exist it is created. + * The method return false and a PEAR error text. + * The files and directories listed are only added at the end of the archive, + * even if a file with the same name is already archived. + * See also createModify() method for more details. + * + * @param array $p_filelist An array of filenames and directory names, or a + * single string with names separated by a single + * blank space. + * + * @return true on success, false on error. + * @see createModify() + * @access public + */ + public function add($p_filelist) { return $this->addModify($p_filelist, '', ''); } - // }}} - // {{{ extract() - function extract($p_path='') + /** + * @param string $p_path + * @param bool $p_preserve + * @return bool + */ + public function extract($p_path = '', $p_preserve = false) { - return $this->extractModify($p_path, ''); + return $this->extractModify($p_path, '', $p_preserve); } - // }}} - // {{{ listContent() - function listContent() + /** + * @return array|int + */ + public function listContent() { $v_list_detail = array(); @@ -287,57 +383,56 @@ class Archive_Tar // extends PEAR return $v_list_detail; } - // }}} - // {{{ createModify() /** - * This method creates the archive file and add the files / directories - * that are listed in $p_filelist. - * If the file already exists and is writable, it is replaced by the - * new tar. It is a create and not an add. If the file exists and is - * read-only or is a directory it is not replaced. The method return - * false and a PEAR error text. - * The $p_filelist parameter can be an array of string, each string - * representing a filename or a directory name with their path if - * needed. It can also be a single string with names separated by a - * single blank. - * The path indicated in $p_remove_dir will be removed from the - * memorized path of each file / directory listed when this path - * exists. By default nothing is removed (empty path '') - * The path indicated in $p_add_dir will be added at the beginning of - * the memorized path of each file / directory listed. However it can - * be set to empty ''. The adding of a path is done after the removing - * of path. - * The path add/remove ability enables the user to prepare an archive - * for extraction in a different path than the origin files are. - * See also addModify() method for file adding properties. - * - * @param array $p_filelist An array of filenames and directory names, - * or a single string with names separated by - * a single blank space. - * @param string $p_add_dir A string which contains a path to be added - * to the memorized path of each element in - * the list. - * @param string $p_remove_dir A string which contains a path to be - * removed from the memorized path of each - * element in the list, when relevant. - * @return boolean true on success, false on error. - * @access public - * @see addModify() - */ - function createModify($p_filelist, $p_add_dir, $p_remove_dir='') + * This method creates the archive file and add the files / directories + * that are listed in $p_filelist. + * If the file already exists and is writable, it is replaced by the + * new tar. It is a create and not an add. If the file exists and is + * read-only or is a directory it is not replaced. The method return + * false and a PEAR error text. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * The path indicated in $p_remove_dir will be removed from the + * memorized path of each file / directory listed when this path + * exists. By default nothing is removed (empty path '') + * The path indicated in $p_add_dir will be added at the beginning of + * the memorized path of each file / directory listed. However it can + * be set to empty ''. The adding of a path is done after the removing + * of path. + * The path add/remove ability enables the user to prepare an archive + * for extraction in a different path than the origin files are. + * See also addModify() method for file adding properties. + * + * @param array $p_filelist An array of filenames and directory names, + * or a single string with names separated by + * a single blank space. + * @param string $p_add_dir A string which contains a path to be added + * to the memorized path of each element in + * the list. + * @param string $p_remove_dir A string which contains a path to be + * removed from the memorized path of each + * element in the list, when relevant. + * + * @return boolean true on success, false on error. + * @see addModify() + */ + public function createModify($p_filelist, $p_add_dir, $p_remove_dir = '') { $v_result = true; - if (!$this->_openWrite()) + if (!$this->_openWrite()) { return false; + } if ($p_filelist != '') { - if (is_array($p_filelist)) + if (is_array($p_filelist)) { $v_list = $p_filelist; - elseif (is_string($p_filelist)) + } elseif (is_string($p_filelist)) { $v_list = explode($this->_separator, $p_filelist); - else { + } else { $this->_cleanFile(); $this->_error('Invalid file list'); return false; @@ -349,67 +444,69 @@ class Archive_Tar // extends PEAR if ($v_result) { $this->_writeFooter(); $this->_close(); - } else + } else { $this->_cleanFile(); + } return $v_result; } - // }}} - // {{{ addModify() /** - * This method add the files / directories listed in $p_filelist at the - * end of the existing archive. If the archive does not yet exists it - * is created. - * The $p_filelist parameter can be an array of string, each string - * representing a filename or a directory name with their path if - * needed. It can also be a single string with names separated by a - * single blank. - * The path indicated in $p_remove_dir will be removed from the - * memorized path of each file / directory listed when this path - * exists. By default nothing is removed (empty path '') - * The path indicated in $p_add_dir will be added at the beginning of - * the memorized path of each file / directory listed. However it can - * be set to empty ''. The adding of a path is done after the removing - * of path. - * The path add/remove ability enables the user to prepare an archive - * for extraction in a different path than the origin files are. - * If a file/dir is already in the archive it will only be added at the - * end of the archive. There is no update of the existing archived - * file/dir. However while extracting the archive, the last file will - * replace the first one. This results in a none optimization of the - * archive size. - * If a file/dir does not exist the file/dir is ignored. However an - * error text is send to PEAR error. - * If a file/dir is not readable the file/dir is ignored. However an - * error text is send to PEAR error. - * - * @param array $p_filelist An array of filenames and directory - * names, or a single string with names - * separated by a single blank space. - * @param string $p_add_dir A string which contains a path to be - * added to the memorized path of each - * element in the list. - * @param string $p_remove_dir A string which contains a path to be - * removed from the memorized path of - * each element in the list, when - * relevant. - * @return true on success, false on error. - * @access public - */ - function addModify($p_filelist, $p_add_dir, $p_remove_dir='') + * This method add the files / directories listed in $p_filelist at the + * end of the existing archive. If the archive does not yet exists it + * is created. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * The path indicated in $p_remove_dir will be removed from the + * memorized path of each file / directory listed when this path + * exists. By default nothing is removed (empty path '') + * The path indicated in $p_add_dir will be added at the beginning of + * the memorized path of each file / directory listed. However it can + * be set to empty ''. The adding of a path is done after the removing + * of path. + * The path add/remove ability enables the user to prepare an archive + * for extraction in a different path than the origin files are. + * If a file/dir is already in the archive it will only be added at the + * end of the archive. There is no update of the existing archived + * file/dir. However while extracting the archive, the last file will + * replace the first one. This results in a none optimization of the + * archive size. + * If a file/dir does not exist the file/dir is ignored. However an + * error text is send to PEAR error. + * If a file/dir is not readable the file/dir is ignored. However an + * error text is send to PEAR error. + * + * @param array $p_filelist An array of filenames and directory + * names, or a single string with names + * separated by a single blank space. + * @param string $p_add_dir A string which contains a path to be + * added to the memorized path of each + * element in the list. + * @param string $p_remove_dir A string which contains a path to be + * removed from the memorized path of + * each element in the list, when + * relevant. + * + * @return true on success, false on error. + */ + public function addModify($p_filelist, $p_add_dir, $p_remove_dir = '') { $v_result = true; - if (!$this->_isArchive()) - $v_result = $this->createModify($p_filelist, $p_add_dir, - $p_remove_dir); - else { - if (is_array($p_filelist)) + if (!$this->_isArchive()) { + $v_result = $this->createModify( + $p_filelist, + $p_add_dir, + $p_remove_dir + ); + } else { + if (is_array($p_filelist)) { $v_list = $p_filelist; - elseif (is_string($p_filelist)) + } elseif (is_string($p_filelist)) { $v_list = explode($this->_separator, $p_filelist); - else { + } else { $this->_error('Invalid file list'); return false; } @@ -419,24 +516,41 @@ class Archive_Tar // extends PEAR return $v_result; } - // }}} - // {{{ addString() /** - * This method add a single string as a file at the - * end of the existing archive. If the archive does not yet exists it - * is created. - * - * @param string $p_filename A string which contains the full - * filename path that will be associated - * with the string. - * @param string $p_string The content of the file added in - * the archive. - * @return true on success, false on error. - * @access public - */ - function addString($p_filename, $p_string) + * This method add a single string as a file at the + * end of the existing archive. If the archive does not yet exists it + * is created. + * + * @param string $p_filename A string which contains the full + * filename path that will be associated + * with the string. + * @param string $p_string The content of the file added in + * the archive. + * @param bool|int $p_datetime A custom date/time (unix timestamp) + * for the file (optional). + * @param array $p_params An array of optional params: + * stamp => the datetime (replaces + * datetime above if it exists) + * mode => the permissions on the + * file (600 by default) + * type => is this a link? See the + * tar specification for details. + * (default = regular file) + * uid => the user ID of the file + * (default = 0 = root) + * gid => the group ID of the file + * (default = 0 = root) + * + * @return true on success, false on error. + */ + public function addString($p_filename, $p_string, $p_datetime = false, $p_params = array()) { + $p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time()); + $p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600; + $p_type = @$p_params["type"] ? $p_params["type"] : ""; + $p_uid = @$p_params["uid"] ? $p_params["uid"] : ""; + $p_gid = @$p_params["gid"] ? $p_params["gid"] : ""; $v_result = true; if (!$this->_isArchive()) { @@ -446,11 +560,12 @@ class Archive_Tar // extends PEAR $this->_close(); } - if (!$this->_openAppend()) + if (!$this->_openAppend()) { return false; + } // Need to check the get back to the temporary file ? .... - $v_result = $this->_addString($p_filename, $p_string); + $v_result = $this->_addString($p_filename, $p_string, $p_datetime, $p_params); $this->_writeFooter(); @@ -458,131 +573,138 @@ class Archive_Tar // extends PEAR return $v_result; } - // }}} - // {{{ extractModify() /** - * This method extract all the content of the archive in the directory - * indicated by $p_path. When relevant the memorized path of the - * files/dir can be modified by removing the $p_remove_path path at the - * beginning of the file/dir path. - * While extracting a file, if the directory path does not exists it is - * created. - * While extracting a file, if the file already exists it is replaced - * without looking for last modification date. - * While extracting a file, if the file already exists and is write - * protected, the extraction is aborted. - * While extracting a file, if a directory with the same name already - * exists, the extraction is aborted. - * While extracting a directory, if a file with the same name already - * exists, the extraction is aborted. - * While extracting a file/directory if the destination directory exist - * and is write protected, or does not exist but can not be created, - * the extraction is aborted. - * If after extraction an extracted file does not show the correct - * stored file size, the extraction is aborted. - * When the extraction is aborted, a PEAR error text is set and false - * is returned. However the result can be a partial extraction that may - * need to be manually cleaned. - * - * @param string $p_path The path of the directory where the - * files/dir need to by extracted. - * @param string $p_remove_path Part of the memorized path that can be - * removed if present at the beginning of - * the file/dir path. - * @return boolean true on success, false on error. - * @access public - * @see extractList() - */ - function extractModify($p_path, $p_remove_path) + * This method extract all the content of the archive in the directory + * indicated by $p_path. When relevant the memorized path of the + * files/dir can be modified by removing the $p_remove_path path at the + * beginning of the file/dir path. + * While extracting a file, if the directory path does not exists it is + * created. + * While extracting a file, if the file already exists it is replaced + * without looking for last modification date. + * While extracting a file, if the file already exists and is write + * protected, the extraction is aborted. + * While extracting a file, if a directory with the same name already + * exists, the extraction is aborted. + * While extracting a directory, if a file with the same name already + * exists, the extraction is aborted. + * While extracting a file/directory if the destination directory exist + * and is write protected, or does not exist but can not be created, + * the extraction is aborted. + * If after extraction an extracted file does not show the correct + * stored file size, the extraction is aborted. + * When the extraction is aborted, a PEAR error text is set and false + * is returned. However the result can be a partial extraction that may + * need to be manually cleaned. + * + * @param string $p_path The path of the directory where the + * files/dir need to by extracted. + * @param string $p_remove_path Part of the memorized path that can be + * removed if present at the beginning of + * the file/dir path. + * @param boolean $p_preserve Preserve user/group ownership of files + * + * @return boolean true on success, false on error. + * @see extractList() + */ + public function extractModify($p_path, $p_remove_path, $p_preserve = false) { $v_result = true; $v_list_detail = array(); if ($v_result = $this->_openRead()) { - $v_result = $this->_extractList($p_path, $v_list_detail, - "complete", 0, $p_remove_path); + $v_result = $this->_extractList( + $p_path, + $v_list_detail, + "complete", + 0, + $p_remove_path, + $p_preserve + ); $this->_close(); } return $v_result; } - // }}} - // {{{ extractInString() /** - * This method extract from the archive one file identified by $p_filename. - * The return value is a string with the file content, or NULL on error. - * @param string $p_filename The path of the file to extract in a string. - * @return a string with the file content or NULL. - * @access public - */ - function extractInString($p_filename) + * This method extract from the archive one file identified by $p_filename. + * The return value is a string with the file content, or NULL on error. + * + * @param string $p_filename The path of the file to extract in a string. + * + * @return a string with the file content or NULL. + */ + public function extractInString($p_filename) { if ($this->_openRead()) { $v_result = $this->_extractInString($p_filename); $this->_close(); } else { - $v_result = NULL; + $v_result = null; } return $v_result; } - // }}} - // {{{ extractList() /** - * This method extract from the archive only the files indicated in the - * $p_filelist. These files are extracted in the current directory or - * in the directory indicated by the optional $p_path parameter. - * If indicated the $p_remove_path can be used in the same way as it is - * used in extractModify() method. - * @param array $p_filelist An array of filenames and directory names, - * or a single string with names separated - * by a single blank space. - * @param string $p_path The path of the directory where the - * files/dir need to by extracted. - * @param string $p_remove_path Part of the memorized path that can be - * removed if present at the beginning of - * the file/dir path. - * @return true on success, false on error. - * @access public - * @see extractModify() - */ - function extractList($p_filelist, $p_path='', $p_remove_path='') + * This method extract from the archive only the files indicated in the + * $p_filelist. These files are extracted in the current directory or + * in the directory indicated by the optional $p_path parameter. + * If indicated the $p_remove_path can be used in the same way as it is + * used in extractModify() method. + * + * @param array $p_filelist An array of filenames and directory names, + * or a single string with names separated + * by a single blank space. + * @param string $p_path The path of the directory where the + * files/dir need to by extracted. + * @param string $p_remove_path Part of the memorized path that can be + * removed if present at the beginning of + * the file/dir path. + * @param boolean $p_preserve Preserve user/group ownership of files + * + * @return true on success, false on error. + * @see extractModify() + */ + public function extractList($p_filelist, $p_path = '', $p_remove_path = '', $p_preserve = false) { $v_result = true; $v_list_detail = array(); - if (is_array($p_filelist)) + if (is_array($p_filelist)) { $v_list = $p_filelist; - elseif (is_string($p_filelist)) + } elseif (is_string($p_filelist)) { $v_list = explode($this->_separator, $p_filelist); - else { + } else { $this->_error('Invalid string list'); return false; } if ($v_result = $this->_openRead()) { - $v_result = $this->_extractList($p_path, $v_list_detail, "partial", - $v_list, $p_remove_path); + $v_result = $this->_extractList( + $p_path, + $v_list_detail, + "partial", + $v_list, + $p_remove_path, + $p_preserve + ); $this->_close(); } return $v_result; } - // }}} - // {{{ setAttribute() /** - * This method set specific attributes of the archive. It uses a variable - * list of parameters, in the format attribute code + attribute values : - * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ','); - * @param mixed $argv variable list of attributes and values - * @return true on success, false on error. - * @access public - */ - function setAttribute() + * This method set specific attributes of the archive. It uses a variable + * list of parameters, in the format attribute code + attribute values : + * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ','); + * + * @return true on success, false on error. + */ + public function setAttribute() { $v_result = true; @@ -592,30 +714,32 @@ class Archive_Tar // extends PEAR } // ----- Get the arguments - $v_att_list = &func_get_args(); + $v_att_list = & func_get_args(); // ----- Read the attributes - $i=0; - while ($i<$v_size) { + $i = 0; + while ($i < $v_size) { // ----- Look for next option switch ($v_att_list[$i]) { // ----- Look for options that request a string value case ARCHIVE_TAR_ATT_SEPARATOR : // ----- Check the number of parameters - if (($i+1) >= $v_size) { - $this->_error('Invalid number of parameters for ' - .'attribute ARCHIVE_TAR_ATT_SEPARATOR'); + if (($i + 1) >= $v_size) { + $this->_error( + 'Invalid number of parameters for ' + . 'attribute ARCHIVE_TAR_ATT_SEPARATOR' + ); return false; } // ----- Get the value - $this->_separator = $v_att_list[$i+1]; + $this->_separator = $v_att_list[$i + 1]; $i++; - break; + break; default : - $this->_error('Unknow attribute code '.$v_att_list[$i].''); + $this->_error('Unknown attribute code ' . $v_att_list[$i] . ''); return false; } @@ -625,151 +749,248 @@ class Archive_Tar // extends PEAR return $v_result; } - // }}} - // {{{ _error() - function _error($p_message) + /** + * This method sets the regular expression for ignoring files and directories + * at import, for example: + * $arch->setIgnoreRegexp("#CVS|\.svn#"); + * + * @param string $regexp regular expression defining which files or directories to ignore + */ + public function setIgnoreRegexp($regexp) { - // ----- To be completed -// $this->raiseError($p_message); + $this->_ignore_regexp = $regexp; + } + + /** + * This method sets the regular expression for ignoring all files and directories + * matching the filenames in the array list at import, for example: + * $arch->setIgnoreList(array('CVS', '.svn', 'bin/tool')); + * + * @param array $list a list of file or directory names to ignore + * + * @access public + */ + public function setIgnoreList($list) + { + $regexp = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list); + $regexp = '#/' . join('$|/', $list) . '#'; + $this->setIgnoreRegexp($regexp); + } + + /** + * @param string $p_message + */ + public function _error($p_message) + { + // Drupal change $this->error_object = $this->raiseError($p_message). throw new Exception($p_message); } - // }}} - // {{{ _warning() - function _warning($p_message) + /** + * @param string $p_message + */ + public function _warning($p_message) { - // ----- To be completed -// $this->raiseError($p_message); + // Drupal change $this->error_object = $this->raiseError($p_message). throw new Exception($p_message); } - // }}} - // {{{ _isArchive() - function _isArchive($p_filename=NULL) + /** + * @param string $p_filename + * @return bool + */ + public function _isArchive($p_filename = null) { - if ($p_filename == NULL) { + if ($p_filename == null) { $p_filename = $this->_tarname; } clearstatcache(); return @is_file($p_filename) && !@is_link($p_filename); } - // }}} - // {{{ _openWrite() - function _openWrite() + /** + * @return bool + */ + public function _openWrite() { - if ($this->_compress_type == 'gz') + if ($this->_compress_type == 'gz' && function_exists('gzopen')) { $this->_file = @gzopen($this->_tarname, "wb9"); - else if ($this->_compress_type == 'bz2') - $this->_file = @bzopen($this->_tarname, "w"); - else if ($this->_compress_type == 'none') - $this->_file = @fopen($this->_tarname, "wb"); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); + } else { + if ($this->_compress_type == 'bz2' && function_exists('bzopen')) { + $this->_file = @bzopen($this->_tarname, "w"); + } else { + if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) { + $this->_file = @xzopen($this->_tarname, 'w'); + } else { + if ($this->_compress_type == 'none') { + $this->_file = @fopen($this->_tarname, "wb"); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + return false; + } + } + } + } if ($this->_file == 0) { - $this->_error('Unable to open in write mode \'' - .$this->_tarname.'\''); + $this->_error( + 'Unable to open in write mode \'' + . $this->_tarname . '\'' + ); return false; } return true; } - // }}} - // {{{ _openRead() - function _openRead() + /** + * @return bool + */ + public function _openRead() { if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') { - // ----- Look if a local copy need to be done - if ($this->_temp_tarname == '') { - $this->_temp_tarname = uniqid('tar').'.tmp'; - if (!$v_file_from = @fopen($this->_tarname, 'rb')) { - $this->_error('Unable to open in read mode \'' - .$this->_tarname.'\''); - $this->_temp_tarname = ''; - return false; - } - if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) { - $this->_error('Unable to open in write mode \'' - .$this->_temp_tarname.'\''); - $this->_temp_tarname = ''; - return false; - } - while ($v_data = @fread($v_file_from, 1024)) - @fwrite($v_file_to, $v_data); - @fclose($v_file_from); - @fclose($v_file_to); - } + // ----- Look if a local copy need to be done + if ($this->_temp_tarname == '') { + $this->_temp_tarname = uniqid('tar') . '.tmp'; + if (!$v_file_from = @fopen($this->_tarname, 'rb')) { + $this->_error( + 'Unable to open in read mode \'' + . $this->_tarname . '\'' + ); + $this->_temp_tarname = ''; + return false; + } + if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) { + $this->_error( + 'Unable to open in write mode \'' + . $this->_temp_tarname . '\'' + ); + $this->_temp_tarname = ''; + return false; + } + while ($v_data = @fread($v_file_from, 1024)) { + @fwrite($v_file_to, $v_data); + } + @fclose($v_file_from); + @fclose($v_file_to); + } - // ----- File to open if the local copy - $v_filename = $this->_temp_tarname; + // ----- File to open if the local copy + $v_filename = $this->_temp_tarname; + } else { + // ----- File to open if the normal Tar file - } else - // ----- File to open if the normal Tar file - $v_filename = $this->_tarname; + $v_filename = $this->_tarname; + } - if ($this->_compress_type == 'gz') + if ($this->_compress_type == 'gz' && function_exists('gzopen')) { $this->_file = @gzopen($v_filename, "rb"); - else if ($this->_compress_type == 'bz2') - $this->_file = @bzopen($v_filename, "r"); - else if ($this->_compress_type == 'none') - $this->_file = @fopen($v_filename, "rb"); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); + } else { + if ($this->_compress_type == 'bz2' && function_exists('bzopen')) { + $this->_file = @bzopen($v_filename, "r"); + } else { + if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) { + $this->_file = @xzopen($v_filename, "r"); + } else { + if ($this->_compress_type == 'none') { + $this->_file = @fopen($v_filename, "rb"); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + return false; + } + } + } + } if ($this->_file == 0) { - $this->_error('Unable to open in read mode \''.$v_filename.'\''); + $this->_error('Unable to open in read mode \'' . $v_filename . '\''); return false; } return true; } - // }}} - // {{{ _openReadWrite() - function _openReadWrite() + /** + * @return bool + */ + public function _openReadWrite() { - if ($this->_compress_type == 'gz') + if ($this->_compress_type == 'gz') { $this->_file = @gzopen($this->_tarname, "r+b"); - else if ($this->_compress_type == 'bz2') { - $this->_error('Unable to open bz2 in read/write mode \'' - .$this->_tarname.'\' (limitation of bz2 extension)'); - return false; - } else if ($this->_compress_type == 'none') - $this->_file = @fopen($this->_tarname, "r+b"); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); + } else { + if ($this->_compress_type == 'bz2') { + $this->_error( + 'Unable to open bz2 in read/write mode \'' + . $this->_tarname . '\' (limitation of bz2 extension)' + ); + return false; + } else { + if ($this->_compress_type == 'lzma2') { + $this->_error( + 'Unable to open lzma2 in read/write mode \'' + . $this->_tarname . '\' (limitation of lzma2 extension)' + ); + return false; + } else { + if ($this->_compress_type == 'none') { + $this->_file = @fopen($this->_tarname, "r+b"); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + return false; + } + } + } + } if ($this->_file == 0) { - $this->_error('Unable to open in read/write mode \'' - .$this->_tarname.'\''); + $this->_error( + 'Unable to open in read/write mode \'' + . $this->_tarname . '\'' + ); return false; } return true; } - // }}} - // {{{ _close() - function _close() + /** + * @return bool + */ + public function _close() { //if (isset($this->_file)) { if (is_resource($this->_file)) { - if ($this->_compress_type == 'gz') + if ($this->_compress_type == 'gz') { @gzclose($this->_file); - else if ($this->_compress_type == 'bz2') - @bzclose($this->_file); - else if ($this->_compress_type == 'none') - @fclose($this->_file); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); + } else { + if ($this->_compress_type == 'bz2') { + @bzclose($this->_file); + } else { + if ($this->_compress_type == 'lzma2') { + @xzclose($this->_file); + } else { + if ($this->_compress_type == 'none') { + @fclose($this->_file); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + } + } + } + } $this->_file = 0; } @@ -783,10 +1004,11 @@ class Archive_Tar // extends PEAR return true; } - // }}} - // {{{ _cleanFile() - function _cleanFile() + /** + * @return bool + */ + public function _cleanFile() { $this->_close(); @@ -803,296 +1025,419 @@ class Archive_Tar // extends PEAR return true; } - // }}} - // {{{ _writeBlock() - function _writeBlock($p_binary_data, $p_len=null) + /** + * @param mixed $p_binary_data + * @param integer $p_len + * @return bool + */ + public function _writeBlock($p_binary_data, $p_len = null) { - if (is_resource($this->_file)) { - if ($p_len === null) { - if ($this->_compress_type == 'gz') - @gzputs($this->_file, $p_binary_data); - else if ($this->_compress_type == 'bz2') - @bzwrite($this->_file, $p_binary_data); - else if ($this->_compress_type == 'none') - @fputs($this->_file, $p_binary_data); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); - } else { - if ($this->_compress_type == 'gz') - @gzputs($this->_file, $p_binary_data, $p_len); - else if ($this->_compress_type == 'bz2') - @bzwrite($this->_file, $p_binary_data, $p_len); - else if ($this->_compress_type == 'none') - @fputs($this->_file, $p_binary_data, $p_len); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); - - } - } - return true; + if (is_resource($this->_file)) { + if ($p_len === null) { + if ($this->_compress_type == 'gz') { + @gzputs($this->_file, $p_binary_data); + } else { + if ($this->_compress_type == 'bz2') { + @bzwrite($this->_file, $p_binary_data); + } else { + if ($this->_compress_type == 'lzma2') { + @xzwrite($this->_file, $p_binary_data); + } else { + if ($this->_compress_type == 'none') { + @fputs($this->_file, $p_binary_data); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + } + } + } + } + } else { + if ($this->_compress_type == 'gz') { + @gzputs($this->_file, $p_binary_data, $p_len); + } else { + if ($this->_compress_type == 'bz2') { + @bzwrite($this->_file, $p_binary_data, $p_len); + } else { + if ($this->_compress_type == 'lzma2') { + @xzwrite($this->_file, $p_binary_data, $p_len); + } else { + if ($this->_compress_type == 'none') { + @fputs($this->_file, $p_binary_data, $p_len); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + } + } + } + } + } + } + return true; } - // }}} - // {{{ _readBlock() - function _readBlock() + /** + * @return null|string + */ + public function _readBlock() { - $v_block = null; - if (is_resource($this->_file)) { - if ($this->_compress_type == 'gz') - $v_block = @gzread($this->_file, 512); - else if ($this->_compress_type == 'bz2') - $v_block = @bzread($this->_file, 512); - else if ($this->_compress_type == 'none') - $v_block = @fread($this->_file, 512); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); - } - return $v_block; + $v_block = null; + if (is_resource($this->_file)) { + if ($this->_compress_type == 'gz') { + $v_block = @gzread($this->_file, 512); + } else { + if ($this->_compress_type == 'bz2') { + $v_block = @bzread($this->_file, 512); + } else { + if ($this->_compress_type == 'lzma2') { + $v_block = @xzread($this->_file, 512); + } else { + if ($this->_compress_type == 'none') { + $v_block = @fread($this->_file, 512); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + } + } + } + } + } + return $v_block; } - // }}} - // {{{ _jumpBlock() - function _jumpBlock($p_len=null) + /** + * @param null $p_len + * @return bool + */ + public function _jumpBlock($p_len = null) { - if (is_resource($this->_file)) { - if ($p_len === null) - $p_len = 1; + if (is_resource($this->_file)) { + if ($p_len === null) { + $p_len = 1; + } - if ($this->_compress_type == 'gz') { - @gzseek($this->_file, gztell($this->_file)+($p_len*512)); - } - else if ($this->_compress_type == 'bz2') { - // ----- Replace missing bztell() and bzseek() - for ($i=0; $i<$p_len; $i++) - $this->_readBlock(); - } else if ($this->_compress_type == 'none') - @fseek($this->_file, ftell($this->_file)+($p_len*512)); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); - - } - return true; + if ($this->_compress_type == 'gz') { + @gzseek($this->_file, gztell($this->_file) + ($p_len * 512)); + } else { + if ($this->_compress_type == 'bz2') { + // ----- Replace missing bztell() and bzseek() + for ($i = 0; $i < $p_len; $i++) { + $this->_readBlock(); + } + } else { + if ($this->_compress_type == 'lzma2') { + // ----- Replace missing xztell() and xzseek() + for ($i = 0; $i < $p_len; $i++) { + $this->_readBlock(); + } + } else { + if ($this->_compress_type == 'none') { + @fseek($this->_file, $p_len * 512, SEEK_CUR); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + } + } + } + } + } + return true; } - // }}} - // {{{ _writeFooter() - function _writeFooter() + /** + * @return bool + */ + public function _writeFooter() { - if (is_resource($this->_file)) { - // ----- Write the last 0 filled block for end of archive - $v_binary_data = pack('a1024', ''); - $this->_writeBlock($v_binary_data); - } - return true; + if (is_resource($this->_file)) { + // ----- Write the last 0 filled block for end of archive + $v_binary_data = pack('a1024', ''); + $this->_writeBlock($v_binary_data); + } + return true; } - // }}} - // {{{ _addList() - function _addList($p_list, $p_add_dir, $p_remove_dir) + /** + * @param array $p_list + * @param string $p_add_dir + * @param string $p_remove_dir + * @return bool + */ + public function _addList($p_list, $p_add_dir, $p_remove_dir) { - $v_result=true; - $v_header = array(); + $v_result = true; + $v_header = array(); - // ----- Remove potential windows directory separator - $p_add_dir = $this->_translateWinPath($p_add_dir); - $p_remove_dir = $this->_translateWinPath($p_remove_dir, false); + // ----- Remove potential windows directory separator + $p_add_dir = $this->_translateWinPath($p_add_dir); + $p_remove_dir = $this->_translateWinPath($p_remove_dir, false); - if (!$this->_file) { - $this->_error('Invalid file descriptor'); - return false; - } - - if (sizeof($p_list) == 0) - return true; - - foreach ($p_list as $v_filename) { - if (!$v_result) { - break; - } - - // ----- Skip the current tar name - if ($v_filename == $this->_tarname) - continue; - - if ($v_filename == '') - continue; - - if (!file_exists($v_filename)) { - $this->_warning("File '$v_filename' does not exist"); - continue; + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; } - // ----- Add the file or directory header - if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) - return false; + if (sizeof($p_list) == 0) { + return true; + } - if (@is_dir($v_filename) && !@is_link($v_filename)) { - if (!($p_hdir = opendir($v_filename))) { - $this->_warning("Directory '$v_filename' can not be read"); + foreach ($p_list as $v_filename) { + if (!$v_result) { + break; + } + + // ----- Skip the current tar name + if ($v_filename == $this->_tarname) { continue; } - while (false !== ($p_hitem = readdir($p_hdir))) { - if (($p_hitem != '.') && ($p_hitem != '..')) { - if ($v_filename != ".") - $p_temp_list[0] = $v_filename.'/'.$p_hitem; - else - $p_temp_list[0] = $p_hitem; - $v_result = $this->_addList($p_temp_list, - $p_add_dir, - $p_remove_dir); + if ($v_filename == '') { + continue; + } + + // ----- ignore files and directories matching the ignore regular expression + if ($this->_ignore_regexp && preg_match($this->_ignore_regexp, '/' . $v_filename)) { + $this->_warning("File '$v_filename' ignored"); + continue; + } + + if (!file_exists($v_filename) && !is_link($v_filename)) { + $this->_warning("File '$v_filename' does not exist"); + continue; + } + + // ----- Add the file or directory header + if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) { + return false; + } + + if (@is_dir($v_filename) && !@is_link($v_filename)) { + if (!($p_hdir = opendir($v_filename))) { + $this->_warning("Directory '$v_filename' can not be read"); + continue; + } + while (false !== ($p_hitem = readdir($p_hdir))) { + if (($p_hitem != '.') && ($p_hitem != '..')) { + if ($v_filename != ".") { + $p_temp_list[0] = $v_filename . '/' . $p_hitem; + } else { + $p_temp_list[0] = $p_hitem; + } + + $v_result = $this->_addList( + $p_temp_list, + $p_add_dir, + $p_remove_dir + ); + } + } + + unset($p_temp_list); + unset($p_hdir); + unset($p_hitem); + } + } + + return $v_result; + } + + /** + * @param string $p_filename + * @param mixed $p_header + * @param string $p_add_dir + * @param string $p_remove_dir + * @param null $v_stored_filename + * @return bool + */ + public function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir, $v_stored_filename = null) + { + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if ($p_filename == '') { + $this->_error('Invalid file name'); + return false; + } + + if (is_null($v_stored_filename)) { + // ----- Calculate the stored filename + $p_filename = $this->_translateWinPath($p_filename, false); + $v_stored_filename = $p_filename; + + if (strcmp($p_filename, $p_remove_dir) == 0) { + return true; + } + + if ($p_remove_dir != '') { + if (substr($p_remove_dir, -1) != '/') { + $p_remove_dir .= '/'; + } + + if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) { + $v_stored_filename = substr($p_filename, strlen($p_remove_dir)); } } - unset($p_temp_list); - unset($p_hdir); - unset($p_hitem); + $v_stored_filename = $this->_translateWinPath($v_stored_filename); + if ($p_add_dir != '') { + if (substr($p_add_dir, -1) == '/') { + $v_stored_filename = $p_add_dir . $v_stored_filename; + } else { + $v_stored_filename = $p_add_dir . '/' . $v_stored_filename; + } + } + + $v_stored_filename = $this->_pathReduction($v_stored_filename); } - } - return $v_result; + if ($this->_isArchive($p_filename)) { + if (($v_file = @fopen($p_filename, "rb")) == 0) { + $this->_warning( + "Unable to open file '" . $p_filename + . "' in binary read mode" + ); + return true; + } + + if (!$this->_writeHeader($p_filename, $v_stored_filename)) { + return false; + } + + while (($v_buffer = fread($v_file, 512)) != '') { + $v_binary_data = pack("a512", "$v_buffer"); + $this->_writeBlock($v_binary_data); + } + + fclose($v_file); + } else { + // ----- Only header for dir + if (!$this->_writeHeader($p_filename, $v_stored_filename)) { + return false; + } + } + + return true; } - // }}} - // {{{ _addFile() - function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir) + /** + * @param string $p_filename + * @param string $p_string + * @param bool $p_datetime + * @param array $p_params + * @return bool + */ + public function _addString($p_filename, $p_string, $p_datetime = false, $p_params = array()) { - if (!$this->_file) { - $this->_error('Invalid file descriptor'); - return false; - } - - if ($p_filename == '') { - $this->_error('Invalid file name'); - return false; - } - - // ----- Calculate the stored filename - $p_filename = $this->_translateWinPath($p_filename, false);; - $v_stored_filename = $p_filename; - if (strcmp($p_filename, $p_remove_dir) == 0) { - return true; - } - if ($p_remove_dir != '') { - if (substr($p_remove_dir, -1) != '/') - $p_remove_dir .= '/'; - - if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) - $v_stored_filename = substr($p_filename, strlen($p_remove_dir)); - } - $v_stored_filename = $this->_translateWinPath($v_stored_filename); - if ($p_add_dir != '') { - if (substr($p_add_dir, -1) == '/') - $v_stored_filename = $p_add_dir.$v_stored_filename; - else - $v_stored_filename = $p_add_dir.'/'.$v_stored_filename; - } - - $v_stored_filename = $this->_pathReduction($v_stored_filename); - - if ($this->_isArchive($p_filename)) { - if (($v_file = @fopen($p_filename, "rb")) == 0) { - $this->_warning("Unable to open file '".$p_filename - ."' in binary read mode"); - return true; - } - - if (!$this->_writeHeader($p_filename, $v_stored_filename)) - return false; - - while (($v_buffer = fread($v_file, 512)) != '') { - $v_binary_data = pack("a512", "$v_buffer"); - $this->_writeBlock($v_binary_data); - } - - fclose($v_file); - - } else { - // ----- Only header for dir - if (!$this->_writeHeader($p_filename, $v_stored_filename)) - return false; - } - - return true; - } - // }}} - - // {{{ _addString() - function _addString($p_filename, $p_string) - { - if (!$this->_file) { - $this->_error('Invalid file descriptor'); - return false; - } - - if ($p_filename == '') { - $this->_error('Invalid file name'); - return false; - } - - // ----- Calculate the stored filename - $p_filename = $this->_translateWinPath($p_filename, false);; - - if (!$this->_writeHeaderBlock($p_filename, strlen($p_string), - time(), 384, "", 0, 0)) - return false; - - $i=0; - while (($v_buffer = substr($p_string, (($i++)*512), 512)) != '') { - $v_binary_data = pack("a512", $v_buffer); - $this->_writeBlock($v_binary_data); - } - - return true; - } - // }}} - - // {{{ _writeHeader() - function _writeHeader($p_filename, $p_stored_filename) - { - if ($p_stored_filename == '') - $p_stored_filename = $p_filename; - $v_reduce_filename = $this->_pathReduction($p_stored_filename); - - if (strlen($v_reduce_filename) > 99) { - if (!$this->_writeLongHeader($v_reduce_filename)) + $p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time()); + $p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600; + $p_type = @$p_params["type"] ? $p_params["type"] : ""; + $p_uid = @$p_params["uid"] ? $p_params["uid"] : 0; + $p_gid = @$p_params["gid"] ? $p_params["gid"] : 0; + if (!$this->_file) { + $this->_error('Invalid file descriptor'); return false; } - $v_info = lstat($p_filename); - $v_uid = sprintf("%6s ", DecOct($v_info[4])); - $v_gid = sprintf("%6s ", DecOct($v_info[5])); - $v_perms = sprintf("%6s ", DecOct($v_info['mode'])); + if ($p_filename == '') { + $this->_error('Invalid file name'); + return false; + } - $v_mtime = sprintf("%11s", DecOct($v_info['mode'])); + // ----- Calculate the stored filename + $p_filename = $this->_translateWinPath($p_filename, false); + + // ----- If datetime is not specified, set current time + if ($p_datetime === false) { + $p_datetime = time(); + } + + if (!$this->_writeHeaderBlock( + $p_filename, + strlen($p_string), + $p_stamp, + $p_mode, + $p_type, + $p_uid, + $p_gid + ) + ) { + return false; + } + + $i = 0; + while (($v_buffer = substr($p_string, (($i++) * 512), 512)) != '') { + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + return true; + } + + /** + * @param string $p_filename + * @param string $p_stored_filename + * @return bool + */ + public function _writeHeader($p_filename, $p_stored_filename) + { + if ($p_stored_filename == '') { + $p_stored_filename = $p_filename; + } + $v_reduce_filename = $this->_pathReduction($p_stored_filename); + + if (strlen($v_reduce_filename) > 99) { + if (!$this->_writeLongHeader($v_reduce_filename)) { + return false; + } + } + + $v_info = lstat($p_filename); + $v_uid = sprintf("%07s", DecOct($v_info[4])); + $v_gid = sprintf("%07s", DecOct($v_info[5])); + $v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777)); + + $v_mtime = sprintf("%011s", DecOct($v_info['mtime'])); $v_linkname = ''; if (@is_link($p_filename)) { - $v_typeflag = '2'; - $v_linkname = readlink($p_filename); - $v_size = sprintf("%11s ", DecOct(0)); + $v_typeflag = '2'; + $v_linkname = readlink($p_filename); + $v_size = sprintf("%011s", DecOct(0)); } elseif (@is_dir($p_filename)) { - $v_typeflag = "5"; - $v_size = sprintf("%11s ", DecOct(0)); + $v_typeflag = "5"; + $v_size = sprintf("%011s", DecOct(0)); } else { - $v_typeflag = ''; - clearstatcache(); - $v_size = sprintf("%11s ", DecOct($v_info['size'])); + $v_typeflag = '0'; + clearstatcache(); + $v_size = sprintf("%011s", DecOct($v_info['size'])); } - $v_magic = ''; + $v_magic = 'ustar '; - $v_version = ''; + $v_version = ' '; - $v_uname = ''; + if (function_exists('posix_getpwuid')) { + $userinfo = posix_getpwuid($v_info[4]); + $groupinfo = posix_getgrgid($v_info[5]); - $v_gname = ''; + $v_uname = $userinfo['name']; + $v_gname = $groupinfo['name']; + } else { + $v_uname = ''; + $v_gname = ''; + } $v_devmajor = ''; @@ -1100,31 +1445,49 @@ class Archive_Tar // extends PEAR $v_prefix = ''; - $v_binary_data_first = pack("a100a8a8a8a12A12", - $v_reduce_filename, $v_perms, $v_uid, - $v_gid, $v_size, $v_mtime); - $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", - $v_typeflag, $v_linkname, $v_magic, - $v_version, $v_uname, $v_gname, - $v_devmajor, $v_devminor, $v_prefix, ''); + $v_binary_data_first = pack( + "a100a8a8a8a12a12", + $v_reduce_filename, + $v_perms, + $v_uid, + $v_gid, + $v_size, + $v_mtime + ); + $v_binary_data_last = pack( + "a1a100a6a2a32a32a8a8a155a12", + $v_typeflag, + $v_linkname, + $v_magic, + $v_version, + $v_uname, + $v_gname, + $v_devmajor, + $v_devminor, + $v_prefix, + '' + ); // ----- Calculate the checksum $v_checksum = 0; // ..... First part of the header - for ($i=0; $i<148; $i++) - $v_checksum += ord(substr($v_binary_data_first,$i,1)); + for ($i = 0; $i < 148; $i++) { + $v_checksum += ord(substr($v_binary_data_first, $i, 1)); + } // ..... Ignore the checksum value and replace it by ' ' (space) - for ($i=148; $i<156; $i++) + for ($i = 148; $i < 156; $i++) { $v_checksum += ord(' '); + } // ..... Last part of the header - for ($i=156, $j=0; $i<512; $i++, $j++) - $v_checksum += ord(substr($v_binary_data_last,$j,1)); + for ($i = 156, $j = 0; $i < 512; $i++, $j++) { + $v_checksum += ord(substr($v_binary_data_last, $j, 1)); + } // ----- Write the first 148 bytes of the header in the archive $this->_writeBlock($v_binary_data_first, 148); // ----- Write the calculated checksum - $v_checksum = sprintf("%6s ", DecOct($v_checksum)); + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); $v_binary_data = pack("a8", $v_checksum); $this->_writeBlock($v_binary_data, 8); @@ -1133,40 +1496,62 @@ class Archive_Tar // extends PEAR return true; } - // }}} - // {{{ _writeHeaderBlock() - function _writeHeaderBlock($p_filename, $p_size, $p_mtime=0, $p_perms=0, - $p_type='', $p_uid=0, $p_gid=0) - { + /** + * @param string $p_filename + * @param int $p_size + * @param int $p_mtime + * @param int $p_perms + * @param string $p_type + * @param int $p_uid + * @param int $p_gid + * @return bool + */ + public function _writeHeaderBlock( + $p_filename, + $p_size, + $p_mtime = 0, + $p_perms = 0, + $p_type = '', + $p_uid = 0, + $p_gid = 0 + ) { $p_filename = $this->_pathReduction($p_filename); if (strlen($p_filename) > 99) { - if (!$this->_writeLongHeader($p_filename)) - return false; + if (!$this->_writeLongHeader($p_filename)) { + return false; + } } if ($p_type == "5") { - $v_size = sprintf("%11s ", DecOct(0)); + $v_size = sprintf("%011s", DecOct(0)); } else { - $v_size = sprintf("%11s ", DecOct($p_size)); + $v_size = sprintf("%011s", DecOct($p_size)); } - $v_uid = sprintf("%6s ", DecOct($p_uid)); - $v_gid = sprintf("%6s ", DecOct($p_gid)); - $v_perms = sprintf("%6s ", DecOct($p_perms)); + $v_uid = sprintf("%07s", DecOct($p_uid)); + $v_gid = sprintf("%07s", DecOct($p_gid)); + $v_perms = sprintf("%07s", DecOct($p_perms & 000777)); $v_mtime = sprintf("%11s", DecOct($p_mtime)); $v_linkname = ''; - $v_magic = ''; + $v_magic = 'ustar '; - $v_version = ''; + $v_version = ' '; - $v_uname = ''; + if (function_exists('posix_getpwuid')) { + $userinfo = posix_getpwuid($p_uid); + $groupinfo = posix_getgrgid($p_gid); - $v_gname = ''; + $v_uname = $userinfo['name']; + $v_gname = $groupinfo['name']; + } else { + $v_uname = ''; + $v_gname = ''; + } $v_devmajor = ''; @@ -1174,31 +1559,49 @@ class Archive_Tar // extends PEAR $v_prefix = ''; - $v_binary_data_first = pack("a100a8a8a8a12A12", - $p_filename, $v_perms, $v_uid, $v_gid, - $v_size, $v_mtime); - $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", - $p_type, $v_linkname, $v_magic, - $v_version, $v_uname, $v_gname, - $v_devmajor, $v_devminor, $v_prefix, ''); + $v_binary_data_first = pack( + "a100a8a8a8a12A12", + $p_filename, + $v_perms, + $v_uid, + $v_gid, + $v_size, + $v_mtime + ); + $v_binary_data_last = pack( + "a1a100a6a2a32a32a8a8a155a12", + $p_type, + $v_linkname, + $v_magic, + $v_version, + $v_uname, + $v_gname, + $v_devmajor, + $v_devminor, + $v_prefix, + '' + ); // ----- Calculate the checksum $v_checksum = 0; // ..... First part of the header - for ($i=0; $i<148; $i++) - $v_checksum += ord(substr($v_binary_data_first,$i,1)); + for ($i = 0; $i < 148; $i++) { + $v_checksum += ord(substr($v_binary_data_first, $i, 1)); + } // ..... Ignore the checksum value and replace it by ' ' (space) - for ($i=148; $i<156; $i++) + for ($i = 148; $i < 156; $i++) { $v_checksum += ord(' '); + } // ..... Last part of the header - for ($i=156, $j=0; $i<512; $i++, $j++) - $v_checksum += ord(substr($v_binary_data_last,$j,1)); + for ($i = 156, $j = 0; $i < 512; $i++, $j++) { + $v_checksum += ord(substr($v_binary_data_last, $j, 1)); + } // ----- Write the first 148 bytes of the header in the archive $this->_writeBlock($v_binary_data_first, 148); // ----- Write the calculated checksum - $v_checksum = sprintf("%6s ", DecOct($v_checksum)); + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); $v_binary_data = pack("a8", $v_checksum); $this->_writeBlock($v_binary_data, 8); @@ -1207,10 +1610,12 @@ class Archive_Tar // extends PEAR return true; } - // }}} - // {{{ _writeLongHeader() - function _writeLongHeader($p_filename) + /** + * @param string $p_filename + * @return bool + */ + public function _writeLongHeader($p_filename) { $v_size = sprintf("%11s ", DecOct(strlen($p_filename))); @@ -1232,30 +1637,49 @@ class Archive_Tar // extends PEAR $v_prefix = ''; - $v_binary_data_first = pack("a100a8a8a8a12A12", - '././@LongLink', 0, 0, 0, $v_size, 0); - $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", - $v_typeflag, $v_linkname, $v_magic, - $v_version, $v_uname, $v_gname, - $v_devmajor, $v_devminor, $v_prefix, ''); + $v_binary_data_first = pack( + "a100a8a8a8a12a12", + '././@LongLink', + 0, + 0, + 0, + $v_size, + 0 + ); + $v_binary_data_last = pack( + "a1a100a6a2a32a32a8a8a155a12", + $v_typeflag, + $v_linkname, + $v_magic, + $v_version, + $v_uname, + $v_gname, + $v_devmajor, + $v_devminor, + $v_prefix, + '' + ); // ----- Calculate the checksum $v_checksum = 0; // ..... First part of the header - for ($i=0; $i<148; $i++) - $v_checksum += ord(substr($v_binary_data_first,$i,1)); + for ($i = 0; $i < 148; $i++) { + $v_checksum += ord(substr($v_binary_data_first, $i, 1)); + } // ..... Ignore the checksum value and replace it by ' ' (space) - for ($i=148; $i<156; $i++) + for ($i = 148; $i < 156; $i++) { $v_checksum += ord(' '); + } // ..... Last part of the header - for ($i=156, $j=0; $i<512; $i++, $j++) - $v_checksum += ord(substr($v_binary_data_last,$j,1)); + for ($i = 156, $j = 0; $i < 512; $i++, $j++) { + $v_checksum += ord(substr($v_binary_data_last, $j, 1)); + } // ----- Write the first 148 bytes of the header in the archive $this->_writeBlock($v_binary_data_first, 148); // ----- Write the calculated checksum - $v_checksum = sprintf("%6s ", DecOct($v_checksum)); + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); $v_binary_data = pack("a8", $v_checksum); $this->_writeBlock($v_binary_data, 8); @@ -1263,27 +1687,30 @@ class Archive_Tar // extends PEAR $this->_writeBlock($v_binary_data_last, 356); // ----- Write the filename as content of the block - $i=0; - while (($v_buffer = substr($p_filename, (($i++)*512), 512)) != '') { + $i = 0; + while (($v_buffer = substr($p_filename, (($i++) * 512), 512)) != '') { $v_binary_data = pack("a512", "$v_buffer"); $this->_writeBlock($v_binary_data); } return true; } - // }}} - // {{{ _readHeader() - function _readHeader($v_binary_data, &$v_header) + /** + * @param mixed $v_binary_data + * @param mixed $v_header + * @return bool + */ + public function _readHeader($v_binary_data, &$v_header) { - if (strlen($v_binary_data)==0) { + if (strlen($v_binary_data) == 0) { $v_header['filename'] = ''; return true; } if (strlen($v_binary_data) != 512) { $v_header['filename'] = ''; - $this->_error('Invalid block size : '.strlen($v_binary_data)); + $this->_error('Invalid block size : ' . strlen($v_binary_data)); return false; } @@ -1293,19 +1720,32 @@ class Archive_Tar // extends PEAR // ----- Calculate the checksum $v_checksum = 0; // ..... First part of the header - for ($i=0; $i<148; $i++) - $v_checksum+=ord(substr($v_binary_data,$i,1)); + for ($i = 0; $i < 148; $i++) { + $v_checksum += ord(substr($v_binary_data, $i, 1)); + } // ..... Ignore the checksum value and replace it by ' ' (space) - for ($i=148; $i<156; $i++) + for ($i = 148; $i < 156; $i++) { $v_checksum += ord(' '); + } // ..... Last part of the header - for ($i=156; $i<512; $i++) - $v_checksum+=ord(substr($v_binary_data,$i,1)); + for ($i = 156; $i < 512; $i++) { + $v_checksum += ord(substr($v_binary_data, $i, 1)); + } - $v_data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" - ."a8checksum/a1typeflag/a100link/a6magic/a2version/" - ."a32uname/a32gname/a8devmajor/a8devminor", - $v_binary_data); + if (version_compare(PHP_VERSION, "5.5.0-dev") < 0) { + $fmt = "a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" . + "a8checksum/a1typeflag/a100link/a6magic/a2version/" . + "a32uname/a32gname/a8devmajor/a8devminor/a131prefix"; + } else { + $fmt = "Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/" . + "Z8checksum/Z1typeflag/Z100link/Z6magic/Z2version/" . + "Z32uname/Z32gname/Z8devmajor/Z8devminor/Z131prefix"; + } + $v_data = unpack($fmt, $v_binary_data); + + if (strlen($v_data["prefix"]) > 0) { + $v_data["filename"] = "$v_data[prefix]/$v_data[filename]"; + } // ----- Extract the checksum $v_header['checksum'] = OctDec(trim($v_data['checksum'])); @@ -1313,20 +1753,25 @@ class Archive_Tar // extends PEAR $v_header['filename'] = ''; // ----- Look for last block (empty block) - if (($v_checksum == 256) && ($v_header['checksum'] == 0)) + if (($v_checksum == 256) && ($v_header['checksum'] == 0)) { return true; + } - $this->_error('Invalid checksum for file "'.$v_data['filename'] - .'" : '.$v_checksum.' calculated, ' - .$v_header['checksum'].' expected'); + $this->_error( + 'Invalid checksum for file "' . $v_data['filename'] + . '" : ' . $v_checksum . ' calculated, ' + . $v_header['checksum'] . ' expected' + ); return false; } // ----- Extract the properties - $v_header['filename'] = trim($v_data['filename']); + $v_header['filename'] = rtrim($v_data['filename'], "\0"); if ($this->_maliciousFilename($v_header['filename'])) { - $this->_error('Malicious .tar detected, file "' . $v_header['filename'] . - '" will not install in desired directory tree'); + $this->_error( + 'Malicious .tar detected, file "' . $v_header['filename'] . + '" will not install in desired directory tree' + ); return false; } $v_header['mode'] = OctDec(trim($v_data['mode'])); @@ -1335,11 +1780,11 @@ class Archive_Tar // extends PEAR $v_header['size'] = OctDec(trim($v_data['size'])); $v_header['mtime'] = OctDec(trim($v_data['mtime'])); if (($v_header['typeflag'] = $v_data['typeflag']) == "5") { - $v_header['size'] = 0; + $v_header['size'] = 0; } $v_header['link'] = trim($v_data['link']); /* ----- All these fields are removed form the header because - they do not carry interesting info + they do not carry interesting info $v_header[magic] = trim($v_data[magic]); $v_header[version] = trim($v_data[version]); $v_header[uname] = trim($v_data[uname]); @@ -1350,17 +1795,15 @@ class Archive_Tar // extends PEAR return true; } - // }}} - // {{{ _maliciousFilename() /** * Detect and report a malicious file name * * @param string $file + * * @return bool - * @access private */ - function _maliciousFilename($file) + private function _maliciousFilename($file) { if (strpos($file, '/../') !== false) { return true; @@ -1370,386 +1813,507 @@ class Archive_Tar // extends PEAR } return false; } - // }}} - // {{{ _readLongHeader() - function _readLongHeader(&$v_header) - { - $v_filename = ''; - $n = floor($v_header['size']/512); - for ($i=0; $i<$n; $i++) { - $v_content = $this->_readBlock(); - $v_filename .= $v_content; - } - if (($v_header['size'] % 512) != 0) { - $v_content = $this->_readBlock(); - $v_filename .= $v_content; - } - - // ----- Read the next header - $v_binary_data = $this->_readBlock(); - - if (!$this->_readHeader($v_binary_data, $v_header)) - return false; - - $v_filename = trim($v_filename); - $v_header['filename'] = $v_filename; - if ($this->_maliciousFilename($v_filename)) { - $this->_error('Malicious .tar detected, file "' . $v_filename . - '" will not install in desired directory tree'); - return false; - } - - return true; - } - // }}} - - // {{{ _extractInString() /** - * This method extract from the archive one file identified by $p_filename. - * The return value is a string with the file content, or NULL on error. - * @param string $p_filename The path of the file to extract in a string. - * @return a string with the file content or NULL. - * @access private - */ - function _extractInString($p_filename) + * @param $v_header + * @return bool + */ + public function _readLongHeader(&$v_header) { - $v_result_str = ""; - - While (strlen($v_binary_data = $this->_readBlock()) != 0) - { - if (!$this->_readHeader($v_binary_data, $v_header)) - return NULL; - - if ($v_header['filename'] == '') - continue; - - // ----- Look for long filename - if ($v_header['typeflag'] == 'L') { - if (!$this->_readLongHeader($v_header)) - return NULL; - } - - if ($v_header['filename'] == $p_filename) { - if ($v_header['typeflag'] == "5") { - $this->_error('Unable to extract in string a directory ' - .'entry {'.$v_header['filename'].'}'); - return NULL; - } else { - $n = floor($v_header['size']/512); - for ($i=0; $i<$n; $i++) { - $v_result_str .= $this->_readBlock(); - } - if (($v_header['size'] % 512) != 0) { - $v_content = $this->_readBlock(); - $v_result_str .= substr($v_content, 0, - ($v_header['size'] % 512)); - } - return $v_result_str; - } - } else { - $this->_jumpBlock(ceil(($v_header['size']/512))); - } + $v_filename = ''; + $v_filesize = $v_header['size']; + $n = floor($v_header['size'] / 512); + for ($i = 0; $i < $n; $i++) { + $v_content = $this->_readBlock(); + $v_filename .= $v_content; + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + $v_filename .= $v_content; } - return NULL; - } - // }}} + // ----- Read the next header + $v_binary_data = $this->_readBlock(); - // {{{ _extractList() - function _extractList($p_path, &$p_list_detail, $p_mode, - $p_file_list, $p_remove_path) - { - $v_result=true; - $v_nb = 0; - $v_extract_all = true; - $v_listing = false; - - $p_path = $this->_translateWinPath($p_path, false); - if ($p_path == '' || (substr($p_path, 0, 1) != '/' - && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))) { - $p_path = "./".$p_path; - } - $p_remove_path = $this->_translateWinPath($p_remove_path); - - // ----- Look for path to remove format (should end by /) - if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) - $p_remove_path .= '/'; - $p_remove_path_size = strlen($p_remove_path); - - switch ($p_mode) { - case "complete" : - $v_extract_all = TRUE; - $v_listing = FALSE; - break; - case "partial" : - $v_extract_all = FALSE; - $v_listing = FALSE; - break; - case "list" : - $v_extract_all = FALSE; - $v_listing = TRUE; - break; - default : - $this->_error('Invalid extract mode ('.$p_mode.')'); - return false; - } - - clearstatcache(); - - while (strlen($v_binary_data = $this->_readBlock()) != 0) - { - $v_extract_file = FALSE; - $v_extraction_stopped = 0; - - if (!$this->_readHeader($v_binary_data, $v_header)) - return false; - - if ($v_header['filename'] == '') { - continue; - } - - // ----- Look for long filename - if ($v_header['typeflag'] == 'L') { - if (!$this->_readLongHeader($v_header)) - return false; - } - - if ((!$v_extract_all) && (is_array($p_file_list))) { - // ----- By default no unzip if the file is not found - $v_extract_file = false; - - for ($i=0; $i strlen($p_file_list[$i])) - && (substr($v_header['filename'], 0, strlen($p_file_list[$i])) - == $p_file_list[$i])) { - $v_extract_file = TRUE; - break; - } - } - - // ----- It is a file, so compare the file names - elseif ($p_file_list[$i] == $v_header['filename']) { - $v_extract_file = TRUE; - break; - } - } - } else { - $v_extract_file = TRUE; - } - - // ----- Look if this file need to be extracted - if (($v_extract_file) && (!$v_listing)) - { - if (($p_remove_path != '') - && (substr($v_header['filename'], 0, $p_remove_path_size) - == $p_remove_path)) - $v_header['filename'] = substr($v_header['filename'], - $p_remove_path_size); - if (($p_path != './') && ($p_path != '/')) { - while (substr($p_path, -1) == '/') - $p_path = substr($p_path, 0, strlen($p_path)-1); - - if (substr($v_header['filename'], 0, 1) == '/') - $v_header['filename'] = $p_path.$v_header['filename']; - else - $v_header['filename'] = $p_path.'/'.$v_header['filename']; - } - if (file_exists($v_header['filename'])) { - if ( (@is_dir($v_header['filename'])) - && ($v_header['typeflag'] == '')) { - $this->_error('File '.$v_header['filename'] - .' already exists as a directory'); - return false; - } - if ( ($this->_isArchive($v_header['filename'])) - && ($v_header['typeflag'] == "5")) { - $this->_error('Directory '.$v_header['filename'] - .' already exists as a file'); - return false; - } - if (!is_writeable($v_header['filename'])) { - $this->_error('File '.$v_header['filename'] - .' already exists and is write protected'); - return false; - } - if (filemtime($v_header['filename']) > $v_header['mtime']) { - // To be completed : An error or silent no replace ? - } - } - - // ----- Check the directory availability and create it if necessary - elseif (($v_result - = $this->_dirCheck(($v_header['typeflag'] == "5" - ?$v_header['filename'] - :dirname($v_header['filename'])))) != 1) { - $this->_error('Unable to create path for '.$v_header['filename']); + if (!$this->_readHeader($v_binary_data, $v_header)) { return false; } - if ($v_extract_file) { - if ($v_header['typeflag'] == "5") { - if (!@file_exists($v_header['filename'])) { - // Drupal integration. - // Changed the code to use drupal_mkdir() instead of mkdir(). - if (!@drupal_mkdir($v_header['filename'], 0777)) { - $this->_error('Unable to create directory {' - .$v_header['filename'].'}'); - return false; - } - } - } elseif ($v_header['typeflag'] == "2") { - if (@file_exists($v_header['filename'])) { - @drupal_unlink($v_header['filename']); - } - if (!@symlink($v_header['link'], $v_header['filename'])) { - $this->_error('Unable to extract symbolic link {' - .$v_header['filename'].'}'); - return false; - } - } else { - if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) { - $this->_error('Error while opening {'.$v_header['filename'] - .'} in write binary mode'); - return false; - } else { - $n = floor($v_header['size']/512); - for ($i=0; $i<$n; $i++) { - $v_content = $this->_readBlock(); - fwrite($v_dest_file, $v_content, 512); - } - if (($v_header['size'] % 512) != 0) { - $v_content = $this->_readBlock(); - fwrite($v_dest_file, $v_content, ($v_header['size'] % 512)); - } - - @fclose($v_dest_file); - - // ----- Change the file mode, mtime - @touch($v_header['filename'], $v_header['mtime']); - if ($v_header['mode'] & 0111) { - // make file executable, obey umask - $mode = fileperms($v_header['filename']) | (~umask() & 0111); - @chmod($v_header['filename'], $mode); - } - } - - // ----- Check the file size - clearstatcache(); - if (filesize($v_header['filename']) != $v_header['size']) { - $this->_error('Extracted file '.$v_header['filename'] - .' does not have the correct file size \'' - .filesize($v_header['filename']) - .'\' ('.$v_header['size'] - .' expected). Archive may be corrupted.'); - return false; - } - } - } else { - $this->_jumpBlock(ceil(($v_header['size']/512))); + $v_filename = rtrim(substr($v_filename, 0, $v_filesize), "\0"); + $v_header['filename'] = $v_filename; + if ($this->_maliciousFilename($v_filename)) { + $this->_error( + 'Malicious .tar detected, file "' . $v_filename . + '" will not install in desired directory tree' + ); + return false; } - } else { - $this->_jumpBlock(ceil(($v_header['size']/512))); - } - - /* TBC : Seems to be unused ... - if ($this->_compress) - $v_end_of_file = @gzeof($this->_file); - else - $v_end_of_file = @feof($this->_file); - */ - - if ($v_listing || $v_extract_file || $v_extraction_stopped) { - // ----- Log extracted files - if (($v_file_dir = dirname($v_header['filename'])) - == $v_header['filename']) - $v_file_dir = ''; - if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) - $v_file_dir = '/'; - - $p_list_detail[$v_nb++] = $v_header; - if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) { - return true; - } - } - } return true; } - // }}} - // {{{ _openAppend() - function _openAppend() + /** + * This method extract from the archive one file identified by $p_filename. + * The return value is a string with the file content, or null on error. + * + * @param string $p_filename The path of the file to extract in a string. + * + * @return a string with the file content or null. + */ + private function _extractInString($p_filename) { - if (filesize($this->_tarname) == 0) - return $this->_openWrite(); + $v_result_str = ""; + + while (strlen($v_binary_data = $this->_readBlock()) != 0) { + if (!$this->_readHeader($v_binary_data, $v_header)) { + return null; + } + + if ($v_header['filename'] == '') { + continue; + } + + // ----- Look for long filename + if ($v_header['typeflag'] == 'L') { + if (!$this->_readLongHeader($v_header)) { + return null; + } + } + + if ($v_header['filename'] == $p_filename) { + if ($v_header['typeflag'] == "5") { + $this->_error( + 'Unable to extract in string a directory ' + . 'entry {' . $v_header['filename'] . '}' + ); + return null; + } else { + $n = floor($v_header['size'] / 512); + for ($i = 0; $i < $n; $i++) { + $v_result_str .= $this->_readBlock(); + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + $v_result_str .= substr( + $v_content, + 0, + ($v_header['size'] % 512) + ); + } + return $v_result_str; + } + } else { + $this->_jumpBlock(ceil(($v_header['size'] / 512))); + } + } + + return null; + } + + /** + * @param string $p_path + * @param string $p_list_detail + * @param string $p_mode + * @param string $p_file_list + * @param string $p_remove_path + * @param bool $p_preserve + * @return bool + */ + public function _extractList( + $p_path, + &$p_list_detail, + $p_mode, + $p_file_list, + $p_remove_path, + $p_preserve = false + ) { + $v_result = true; + $v_nb = 0; + $v_extract_all = true; + $v_listing = false; + + $p_path = $this->_translateWinPath($p_path, false); + if ($p_path == '' || (substr($p_path, 0, 1) != '/' + && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':')) + ) { + $p_path = "./" . $p_path; + } + $p_remove_path = $this->_translateWinPath($p_remove_path); + + // ----- Look for path to remove format (should end by /) + if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) { + $p_remove_path .= '/'; + } + $p_remove_path_size = strlen($p_remove_path); + + switch ($p_mode) { + case "complete" : + $v_extract_all = true; + $v_listing = false; + break; + case "partial" : + $v_extract_all = false; + $v_listing = false; + break; + case "list" : + $v_extract_all = false; + $v_listing = true; + break; + default : + $this->_error('Invalid extract mode (' . $p_mode . ')'); + return false; + } + + clearstatcache(); + + while (strlen($v_binary_data = $this->_readBlock()) != 0) { + $v_extract_file = false; + $v_extraction_stopped = 0; + + if (!$this->_readHeader($v_binary_data, $v_header)) { + return false; + } + + if ($v_header['filename'] == '') { + continue; + } + + // ----- Look for long filename + if ($v_header['typeflag'] == 'L') { + if (!$this->_readLongHeader($v_header)) { + return false; + } + } + + // ignore extended / pax headers + if ($v_header['typeflag'] == 'x' || $v_header['typeflag'] == 'g') { + $this->_jumpBlock(ceil(($v_header['size'] / 512))); + continue; + } + + if ((!$v_extract_all) && (is_array($p_file_list))) { + // ----- By default no unzip if the file is not found + $v_extract_file = false; + + for ($i = 0; $i < sizeof($p_file_list); $i++) { + // ----- Look if it is a directory + if (substr($p_file_list[$i], -1) == '/') { + // ----- Look if the directory is in the filename path + if ((strlen($v_header['filename']) > strlen($p_file_list[$i])) + && (substr($v_header['filename'], 0, strlen($p_file_list[$i])) + == $p_file_list[$i]) + ) { + $v_extract_file = true; + break; + } + } // ----- It is a file, so compare the file names + elseif ($p_file_list[$i] == $v_header['filename']) { + $v_extract_file = true; + break; + } + } + } else { + $v_extract_file = true; + } + + // ----- Look if this file need to be extracted + if (($v_extract_file) && (!$v_listing)) { + if (($p_remove_path != '') + && (substr($v_header['filename'] . '/', 0, $p_remove_path_size) + == $p_remove_path) + ) { + $v_header['filename'] = substr( + $v_header['filename'], + $p_remove_path_size + ); + if ($v_header['filename'] == '') { + continue; + } + } + if (($p_path != './') && ($p_path != '/')) { + while (substr($p_path, -1) == '/') { + $p_path = substr($p_path, 0, strlen($p_path) - 1); + } + + if (substr($v_header['filename'], 0, 1) == '/') { + $v_header['filename'] = $p_path . $v_header['filename']; + } else { + $v_header['filename'] = $p_path . '/' . $v_header['filename']; + } + } + if (file_exists($v_header['filename'])) { + if ((@is_dir($v_header['filename'])) + && ($v_header['typeflag'] == '') + ) { + $this->_error( + 'File ' . $v_header['filename'] + . ' already exists as a directory' + ); + return false; + } + if (($this->_isArchive($v_header['filename'])) + && ($v_header['typeflag'] == "5") + ) { + $this->_error( + 'Directory ' . $v_header['filename'] + . ' already exists as a file' + ); + return false; + } + if (!is_writeable($v_header['filename'])) { + $this->_error( + 'File ' . $v_header['filename'] + . ' already exists and is write protected' + ); + return false; + } + if (filemtime($v_header['filename']) > $v_header['mtime']) { + // To be completed : An error or silent no replace ? + } + } // ----- Check the directory availability and create it if necessary + elseif (($v_result + = $this->_dirCheck( + ($v_header['typeflag'] == "5" + ? $v_header['filename'] + : dirname($v_header['filename'])) + )) != 1 + ) { + $this->_error('Unable to create path for ' . $v_header['filename']); + return false; + } + + if ($v_extract_file) { + if ($v_header['typeflag'] == "5") { + if (!@file_exists($v_header['filename'])) { + if (!@mkdir($v_header['filename'], 0777)) { + $this->_error( + 'Unable to create directory {' + . $v_header['filename'] . '}' + ); + return false; + } + } + } elseif ($v_header['typeflag'] == "2") { + if (@file_exists($v_header['filename'])) { + @drupal_unlink($v_header['filename']); + } + if (!@symlink($v_header['link'], $v_header['filename'])) { + $this->_error( + 'Unable to extract symbolic link {' + . $v_header['filename'] . '}' + ); + return false; + } + } else { + if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) { + $this->_error( + 'Error while opening {' . $v_header['filename'] + . '} in write binary mode' + ); + return false; + } else { + $n = floor($v_header['size'] / 512); + for ($i = 0; $i < $n; $i++) { + $v_content = $this->_readBlock(); + fwrite($v_dest_file, $v_content, 512); + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + fwrite($v_dest_file, $v_content, ($v_header['size'] % 512)); + } + + @fclose($v_dest_file); + + if ($p_preserve) { + @chown($v_header['filename'], $v_header['uid']); + @chgrp($v_header['filename'], $v_header['gid']); + } + + // ----- Change the file mode, mtime + @touch($v_header['filename'], $v_header['mtime']); + if ($v_header['mode'] & 0111) { + // make file executable, obey umask + $mode = fileperms($v_header['filename']) | (~umask() & 0111); + @chmod($v_header['filename'], $mode); + } + } + + // ----- Check the file size + clearstatcache(); + if (!is_file($v_header['filename'])) { + $this->_error( + 'Extracted file ' . $v_header['filename'] + . 'does not exist. Archive may be corrupted.' + ); + return false; + } + + $filesize = filesize($v_header['filename']); + if ($filesize != $v_header['size']) { + $this->_error( + 'Extracted file ' . $v_header['filename'] + . ' does not have the correct file size \'' + . $filesize + . '\' (' . $v_header['size'] + . ' expected). Archive may be corrupted.' + ); + return false; + } + } + } else { + $this->_jumpBlock(ceil(($v_header['size'] / 512))); + } + } else { + $this->_jumpBlock(ceil(($v_header['size'] / 512))); + } + + /* TBC : Seems to be unused ... + if ($this->_compress) + $v_end_of_file = @gzeof($this->_file); + else + $v_end_of_file = @feof($this->_file); + */ + + if ($v_listing || $v_extract_file || $v_extraction_stopped) { + // ----- Log extracted files + if (($v_file_dir = dirname($v_header['filename'])) + == $v_header['filename'] + ) { + $v_file_dir = ''; + } + if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) { + $v_file_dir = '/'; + } + + $p_list_detail[$v_nb++] = $v_header; + if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) { + return true; + } + } + } + + return true; + } + + /** + * @return bool + */ + public function _openAppend() + { + if (filesize($this->_tarname) == 0) { + return $this->_openWrite(); + } if ($this->_compress) { $this->_close(); - if (!@rename($this->_tarname, $this->_tarname.".tmp")) { - $this->_error('Error while renaming \''.$this->_tarname - .'\' to temporary file \''.$this->_tarname - .'.tmp\''); - return false; - } - - if ($this->_compress_type == 'gz') - $v_temp_tar = @gzopen($this->_tarname.".tmp", "rb"); - elseif ($this->_compress_type == 'bz2') - $v_temp_tar = @bzopen($this->_tarname.".tmp", "r"); - - if ($v_temp_tar == 0) { - $this->_error('Unable to open file \''.$this->_tarname - .'.tmp\' in binary read mode'); - @rename($this->_tarname.".tmp", $this->_tarname); - return false; - } - - if (!$this->_openWrite()) { - @rename($this->_tarname.".tmp", $this->_tarname); + if (!@rename($this->_tarname, $this->_tarname . ".tmp")) { + $this->_error( + 'Error while renaming \'' . $this->_tarname + . '\' to temporary file \'' . $this->_tarname + . '.tmp\'' + ); return false; } if ($this->_compress_type == 'gz') { + $v_temp_tar = @gzopen($this->_tarname . ".tmp", "rb"); + } elseif ($this->_compress_type == 'bz2') { + $v_temp_tar = @bzopen($this->_tarname . ".tmp", "r"); + } elseif ($this->_compress_type == 'lzma2') { + $v_temp_tar = @xzopen($this->_tarname . ".tmp", "r"); + } + + + if ($v_temp_tar == 0) { + $this->_error( + 'Unable to open file \'' . $this->_tarname + . '.tmp\' in binary read mode' + ); + @rename($this->_tarname . ".tmp", $this->_tarname); + return false; + } + + if (!$this->_openWrite()) { + @rename($this->_tarname . ".tmp", $this->_tarname); + return false; + } + + if ($this->_compress_type == 'gz') { + $end_blocks = 0; + while (!@gzeof($v_temp_tar)) { $v_buffer = @gzread($v_temp_tar, 512); - if ($v_buffer == ARCHIVE_TAR_END_BLOCK) { + if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) { + $end_blocks++; // do not copy end blocks, we will re-make them // after appending continue; + } elseif ($end_blocks > 0) { + for ($i = 0; $i < $end_blocks; $i++) { + $this->_writeBlock(ARCHIVE_TAR_END_BLOCK); + } + $end_blocks = 0; } $v_binary_data = pack("a512", $v_buffer); $this->_writeBlock($v_binary_data); } @gzclose($v_temp_tar); - } - elseif ($this->_compress_type == 'bz2') { + } elseif ($this->_compress_type == 'bz2') { + $end_blocks = 0; + while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) { - if ($v_buffer == ARCHIVE_TAR_END_BLOCK) { + if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) { + $end_blocks++; + // do not copy end blocks, we will re-make them + // after appending continue; + } elseif ($end_blocks > 0) { + for ($i = 0; $i < $end_blocks; $i++) { + $this->_writeBlock(ARCHIVE_TAR_END_BLOCK); + } + $end_blocks = 0; } $v_binary_data = pack("a512", $v_buffer); $this->_writeBlock($v_binary_data); } @bzclose($v_temp_tar); + } elseif ($this->_compress_type == 'lzma2') { + $end_blocks = 0; + + while (strlen($v_buffer = @xzread($v_temp_tar, 512)) > 0) { + if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) { + $end_blocks++; + // do not copy end blocks, we will re-make them + // after appending + continue; + } elseif ($end_blocks > 0) { + for ($i = 0; $i < $end_blocks; $i++) { + $this->_writeBlock(ARCHIVE_TAR_END_BLOCK); + } + $end_blocks = 0; + } + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + @xzclose($v_temp_tar); } - if (!@drupal_unlink($this->_tarname.".tmp")) { - $this->_error('Error while deleting temporary file \'' - .$this->_tarname.'.tmp\''); + if (!@drupal_unlink($this->_tarname . ".tmp")) { + $this->_error( + 'Error while deleting temporary file \'' + . $this->_tarname . '.tmp\'' + ); } - } else { // ----- For not compressed tar, just add files before the last - // one or two 512 bytes block - if (!$this->_openReadWrite()) - return false; + // one or two 512 bytes block + if (!$this->_openReadWrite()) { + return false; + } clearstatcache(); $v_size = filesize($this->_tarname); @@ -1760,32 +2324,34 @@ class Archive_Tar // extends PEAR fseek($this->_file, $v_size - 1024); if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { fseek($this->_file, $v_size - 1024); - } - elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { + } elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { fseek($this->_file, $v_size - 512); } } return true; } - // }}} - // {{{ _append() - function _append($p_filelist, $p_add_dir='', $p_remove_dir='') + /** + * @param $p_filelist + * @param string $p_add_dir + * @param string $p_remove_dir + * @return bool + */ + public function _append($p_filelist, $p_add_dir = '', $p_remove_dir = '') { - if (!$this->_openAppend()) + if (!$this->_openAppend()) { return false; + } - if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) - $this->_writeFooter(); + if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) { + $this->_writeFooter(); + } $this->_close(); return true; } - // }}} - - // {{{ _dirCheck() /** * Check if a directory exists and create it (including parent @@ -1793,24 +2359,25 @@ class Archive_Tar // extends PEAR * * @param string $p_dir directory to check * - * @return bool TRUE if the directory exists or was created + * @return bool true if the directory exists or was created */ - function _dirCheck($p_dir) + public function _dirCheck($p_dir) { clearstatcache(); - if ((@is_dir($p_dir)) || ($p_dir == '')) + if ((@is_dir($p_dir)) || ($p_dir == '')) { return true; + } $p_parent_dir = dirname($p_dir); if (($p_parent_dir != $p_dir) && ($p_parent_dir != '') && - (!$this->_dirCheck($p_parent_dir))) - return false; + (!$this->_dirCheck($p_parent_dir)) + ) { + return false; + } - // Drupal integration. - // Changed the code to use drupal_mkdir() instead of mkdir(). - if (!@drupal_mkdir($p_dir, 0777)) { + if (!@mkdir($p_dir, 0777)) { $this->_error("Unable to create directory '$p_dir'"); return false; } @@ -1818,10 +2385,6 @@ class Archive_Tar // extends PEAR return true; } - // }}} - - // {{{ _pathReduction() - /** * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar", * rand emove double slashes. @@ -1829,11 +2392,8 @@ class Archive_Tar // extends PEAR * @param string $p_dir path to reduce * * @return string reduced path - * - * @access private - * */ - function _pathReduction($p_dir) + private function _pathReduction($p_dir) { $v_result = ''; @@ -1843,50 +2403,57 @@ class Archive_Tar // extends PEAR $v_list = explode('/', $p_dir); // ----- Study directories from last to first - for ($i=sizeof($v_list)-1; $i>=0; $i--) { + for ($i = sizeof($v_list) - 1; $i >= 0; $i--) { // ----- Look for current path if ($v_list[$i] == ".") { // ----- Ignore this directory // Should be the first $i=0, but no check is done - } - else if ($v_list[$i] == "..") { - // ----- Ignore it and ignore the $i-1 - $i--; - } - else if ( ($v_list[$i] == '') - && ($i!=(sizeof($v_list)-1)) - && ($i!=0)) { - // ----- Ignore only the double '//' in path, - // but not the first and last / } else { - $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?'/' - .$v_result:''); + if ($v_list[$i] == "..") { + // ----- Ignore it and ignore the $i-1 + $i--; + } else { + if (($v_list[$i] == '') + && ($i != (sizeof($v_list) - 1)) + && ($i != 0) + ) { + // ----- Ignore only the double '//' in path, + // but not the first and last / + } else { + $v_result = $v_list[$i] . ($i != (sizeof($v_list) - 1) ? '/' + . $v_result : ''); + } + } } } } - $v_result = strtr($v_result, '\\', '/'); + + if (defined('OS_WINDOWS') && OS_WINDOWS) { + $v_result = strtr($v_result, '\\', '/'); + } + return $v_result; } - // }}} - - // {{{ _translateWinPath() - function _translateWinPath($p_path, $p_remove_disk_letter=true) + /** + * @param $p_path + * @param bool $p_remove_disk_letter + * @return string + */ + public function _translateWinPath($p_path, $p_remove_disk_letter = true) { - if (defined('OS_WINDOWS') && OS_WINDOWS) { - // ----- Look for potential disk letter - if ( ($p_remove_disk_letter) - && (($v_position = strpos($p_path, ':')) != false)) { - $p_path = substr($p_path, $v_position+1); - } - // ----- Change potential windows directory separator - if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) { - $p_path = strtr($p_path, '\\', '/'); - } - } - return $p_path; + if (defined('OS_WINDOWS') && OS_WINDOWS) { + // ----- Look for potential disk letter + if (($p_remove_disk_letter) + && (($v_position = strpos($p_path, ':')) != false) + ) { + $p_path = substr($p_path, $v_position + 1); + } + // ----- Change potential windows directory separator + if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\')) { + $p_path = strtr($p_path, '\\', '/'); + } + } + return $p_path; } - // }}} - } -?> diff --git a/modules/system/system.test b/modules/system/system.test index aefbfbc..9eaf562 100644 --- a/modules/system/system.test +++ b/modules/system/system.test @@ -389,6 +389,18 @@ class ModuleDependencyTestCase extends ModuleTestCase { ); } + /** + * Checks functionality of project namespaces for dependencies. + */ + function testProjectNamespaceForDependencies() { + // Enable module with project namespace to ensure nothing breaks. + $edit = array( + 'modules[Testing][system_project_namespace_test][enable]' => TRUE, + ); + $this->drupalPost('admin/modules', $edit, t('Save configuration')); + $this->assertModules(array('system_project_namespace_test'), TRUE); + } + /** * Attempt to enable translation module without locale enabled. */ @@ -714,7 +726,7 @@ class IPAddressBlockingTestCase extends DrupalWebTestCase { // Block a valid IP address. $edit = array(); - $edit['ip'] = '192.168.1.1'; + $edit['ip'] = '1.2.3.3'; $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add')); $ip = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $edit['ip']))->fetchField(); $this->assertTrue($ip, t('IP address found in database.')); @@ -722,7 +734,7 @@ class IPAddressBlockingTestCase extends DrupalWebTestCase { // Try to block an IP address that's already blocked. $edit = array(); - $edit['ip'] = '192.168.1.1'; + $edit['ip'] = '1.2.3.3'; $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add')); $this->assertText(t('This IP address is already blocked.')); @@ -758,6 +770,25 @@ class IPAddressBlockingTestCase extends DrupalWebTestCase { // $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Save')); // $this->assertText(t('You may not block your own IP address.')); } + + /** + * Test duplicate IP addresses are not present in the 'blocked_ips' table. + */ + function testDuplicateIpAddress() { + drupal_static_reset('ip_address'); + $submit_ip = $_SERVER['REMOTE_ADDR'] = '192.168.1.1'; + system_block_ip_action(); + system_block_ip_action(); + $ip_count = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $submit_ip))->rowCount(); + $this->assertEqual('1', $ip_count); + drupal_static_reset('ip_address'); + $submit_ip = $_SERVER['REMOTE_ADDR'] = ' '; + system_block_ip_action(); + system_block_ip_action(); + system_block_ip_action(); + $ip_count = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $submit_ip))->rowCount(); + $this->assertEqual('1', $ip_count); + } } class CronRunTestCase extends DrupalWebTestCase { @@ -893,6 +924,29 @@ class CronRunTestCase extends DrupalWebTestCase { $result = variable_get('common_test_cron'); $this->assertEqual($result, 'success', 'Cron correctly handles exceptions thrown during hook_cron() invocations.'); } + + /** + * Tests that hook_flush_caches() is not invoked on every single cron run. + * + * @see system_cron() + */ + public function testCronCacheExpiration() { + module_enable(array('system_cron_test')); + variable_del('system_cron_test_flush_caches'); + + // Invoke cron the first time: hook_flush_caches() should be called and then + // get cached. + drupal_cron_run(); + $this->assertEqual(variable_get('system_cron_test_flush_caches'), 1, 'hook_flush_caches() was invoked the first time.'); + $cache = cache_get('system_cache_tables'); + $this->assertEqual(empty($cache), FALSE, 'Cache is filled with cache table data.'); + + // Run cron again and ensure that hook_flush_caches() is not called. + variable_del('system_cron_test_flush_caches'); + drupal_cron_run(); + $this->assertNull(variable_get('system_cron_test_flush_caches'), 'hook_flush_caches() was not invoked the second time.'); + } + } /** @@ -911,7 +965,7 @@ class CronQueueTestCase extends DrupalWebTestCase { } function setUp() { - parent::setUp(array('common_test', 'common_test_cron_helper')); + parent::setUp(array('common_test', 'common_test_cron_helper', 'cron_queue_test')); } /** @@ -931,6 +985,23 @@ class CronQueueTestCase extends DrupalWebTestCase { $this->assertEqual($queue->numberOfItems(), 1, 'Failing item still in the queue after throwing an exception.'); } + /** + * Tests worker defined as a class method callable. + */ + function testCallable() { + $queue = DrupalQueue::get('cron_queue_test_callback'); + + // Enqueue an item for processing. + $queue->createItem(array($this->randomName() => $this->randomName())); + + // Run cron; the worker should perform the task and delete the item from the + // queue. + $this->cronRun(); + + // The queue should be empty. + $this->assertEqual($queue->numberOfItems(), 0); + } + } class AdminMetaTagTestCase extends DrupalWebTestCase { @@ -1068,6 +1139,11 @@ class PageNotFoundTestCase extends DrupalWebTestCase { ); $node = $this->drupalCreateNode($edit); + // As node IDs must be integers, make sure requests for non-integer IDs + // return a page not found error. + $this->drupalGet('node/invalid'); + $this->assertResponse(404); + // Use a custom 404 page. $this->drupalPost('admin/config/system/site-information', array('site_404' => 'node/' . $node->nid), t('Save configuration')); @@ -1293,7 +1369,23 @@ class DateTimeFunctionalTest extends DrupalWebTestCase { $this->assertEqual($this->getUrl(), url('admin/config/regional/date-time/formats', array('absolute' => TRUE)), 'Correct page redirection.'); $this->assertText(t('Custom date format updated.'), 'Custom date format successfully updated.'); + // Check that ajax callback is protected by CSRF token. + $this->drupalGet('admin/config/regional/date-time/formats/lookup', array('query' => array('format' => 'Y m d'))); + $this->assertResponse(403, 'Access denied with no token'); + $this->drupalGet('admin/config/regional/date-time/formats/lookup', array('query' => array('token' => 'invalid', 'format' => 'Y m d'))); + $this->assertResponse(403, 'Access denied with invalid token'); + $this->drupalGet('admin/config/regional/date-time/formats'); + $this->clickLink(t('edit')); + $settings = $this->drupalGetSettings(); + $lookup_url = $settings['dateTime']['date-format']['lookup']; + preg_match('/token=([^&]+)/', $lookup_url, $matches); + $this->assertFalse(empty($matches[1]), 'Found token value'); + $this->drupalGet('admin/config/regional/date-time/formats/lookup', array('query' => array('token' => $matches[1], 'format' => 'Y m d'))); + $this->assertResponse(200, 'Access allowed with valid token'); + $this->assertText(format_date(time(), 'custom', 'Y m d')); + // Delete custom date format. + $this->drupalGet('admin/config/regional/date-time/formats'); $this->clickLink(t('delete')); $this->drupalPost($this->getUrl(), array(), t('Remove')); $this->assertEqual($this->getUrl(), url('admin/config/regional/date-time/formats', array('absolute' => TRUE)), 'Correct page redirection.'); @@ -1369,6 +1461,60 @@ class DateTimeFunctionalTest extends DrupalWebTestCase { } } +/** + * Tests date format configuration. + */ +class DateFormatTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Date format', + 'description' => 'Test date format configuration and defaults.', + 'group' => 'System', + ); + } + + function setUp() { + parent::setUp(); + + // Create admin user and log in admin user. + $this->admin_user = $this->drupalCreateUser(array('administer site configuration')); + $this->drupalLogin($this->admin_user); + } + + /** + * Test the default date type formats are consistent. + */ + function testDefaultDateFormats() { + // These are the default format values from format_date(). + $default_formats = array( + 'short' => 'm/d/Y - H:i', + 'medium' => 'D, m/d/Y - H:i', + 'long' => 'l, F j, Y - H:i', + ); + + // Clear the date format variables. + variable_del('date_format_short'); + variable_del('date_format_medium'); + variable_del('date_format_long'); + + $this->drupalGet('admin/config/regional/date-time'); + + foreach ($default_formats as $format_name => $format_value) { + $id = 'edit-date-format-' . $format_name; + // Check that the configuration fields match the default format. + $this->assertOptionSelected( + $id, + $format_value, + format_string('The @type format type matches the expected format @format.', + array( + '@type' => $format_name, + '@format' => $format_value, + ) + )); + } + } +} + class PageTitleFiltering extends DrupalWebTestCase { protected $content_user; protected $saved_title; @@ -2281,6 +2427,20 @@ class UpdateScriptFunctionalTest extends DrupalWebTestCase { $this->update_user = $this->drupalCreateUser(array('administer software updates')); } + /** + * Tests that there are no pending updates for the first test method. + */ + function testNoPendingUpdates() { + // Ensure that for the first test method in a class, there are no pending + // updates. This tests a drupal_get_schema_versions() bug that previously + // led to the wrong schema version being recorded for the initial install + // of a child site during automated testing. + $this->drupalLogin($this->update_user); + $this->drupalGet($this->update_url, array('external' => TRUE)); + $this->drupalPost(NULL, array(), t('Continue')); + $this->assertText(t('No pending updates.'), 'End of update process was reached.'); + } + /** * Tests access to the update script. */ @@ -2362,6 +2522,12 @@ class UpdateScriptFunctionalTest extends DrupalWebTestCase { $this->assertText('This is a requirements error provided by the update_script_test module.'); $this->clickLink('try again'); $this->assertText('This is a requirements error provided by the update_script_test module.'); + + // Check if the optional 'value' key displays without a notice. + variable_set('update_script_test_requirement_type', REQUIREMENT_INFO); + $this->drupalGet($this->update_url, array('external' => TRUE)); + $this->assertText('This is a requirements info provided by the update_script_test module.'); + $this->assertNoText('Notice: Undefined index: value in theme_status_report()'); } /** @@ -2825,3 +2991,75 @@ class SystemValidTokenTest extends DrupalUnitTestCase { return TRUE; } } + +/** + * Tests drupal_set_message() and related functions. + */ +class DrupalSetMessageTest extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Messages', + 'description' => 'Tests that messages can be displayed using drupal_set_message().', + 'group' => 'System', + ); + } + + function setUp() { + parent::setUp('system_test'); + } + + /** + * Tests setting messages and removing one before it is displayed. + */ + function testSetRemoveMessages() { + // The page at system-test/drupal-set-message sets two messages and then + // removes the first before it is displayed. + $this->drupalGet('system-test/drupal-set-message'); + $this->assertNoText('First message (removed).'); + $this->assertText('Second message (not removed).'); + } +} + +/** + * Tests confirm form destinations. + */ +class ConfirmFormTest extends DrupalWebTestCase { + protected $admin_user; + + public static function getInfo() { + return array( + 'name' => 'Confirm form', + 'description' => 'Tests that the confirm form does not use external destinations.', + 'group' => 'System', + ); + } + + function setUp() { + parent::setUp(); + + $this->admin_user = $this->drupalCreateUser(array('administer users')); + $this->drupalLogin($this->admin_user); + } + + /** + * Tests that the confirm form does not use external destinations. + */ + function testConfirmForm() { + $this->drupalGet('user/1/cancel'); + $this->assertCancelLinkUrl(url('user/1')); + $this->drupalGet('user/1/cancel', array('query' => array('destination' => 'node'))); + $this->assertCancelLinkUrl(url('node')); + $this->drupalGet('user/1/cancel', array('query' => array('destination' => 'http://example.com'))); + $this->assertCancelLinkUrl(url('user/1')); + } + + /** + * Asserts that a cancel link is present pointing to the provided URL. + */ + function assertCancelLinkUrl($url, $message = '', $group = 'Other') { + $links = $this->xpath('//a[normalize-space(text())=:label and @href=:url]', array(':label' => t('Cancel'), ':url' => $url)); + $message = ($message ? $message : format_string('Cancel link with url %url found.', array('%url' => $url))); + return $this->assertTrue(isset($links[0]), $message, $group); + } +} diff --git a/modules/system/system.updater.inc b/modules/system/system.updater.inc index a14d788..2a32c4b 100644 --- a/modules/system/system.updater.inc +++ b/modules/system/system.updater.inc @@ -24,7 +24,7 @@ class ModuleUpdater extends Updater implements DrupalUpdaterInterface { * found on your system, and if there was a copy in sites/all, we'd see it. */ public function getInstallDirectory() { - if ($relative_path = drupal_get_path('module', $this->name)) { + if ($this->isInstalled() && ($relative_path = drupal_get_path('module', $this->name))) { $relative_path = dirname($relative_path); } else { @@ -34,7 +34,7 @@ class ModuleUpdater extends Updater implements DrupalUpdaterInterface { } public function isInstalled() { - return (bool) drupal_get_path('module', $this->name); + return (bool) drupal_get_filename('module', $this->name, NULL, FALSE); } public static function canUpdateDirectory($directory) { @@ -109,7 +109,7 @@ class ThemeUpdater extends Updater implements DrupalUpdaterInterface { * found on your system, and if there was a copy in sites/all, we'd see it. */ public function getInstallDirectory() { - if ($relative_path = drupal_get_path('theme', $this->name)) { + if ($this->isInstalled() && ($relative_path = drupal_get_path('theme', $this->name))) { $relative_path = dirname($relative_path); } else { @@ -119,7 +119,7 @@ class ThemeUpdater extends Updater implements DrupalUpdaterInterface { } public function isInstalled() { - return (bool) drupal_get_path('theme', $this->name); + return (bool) drupal_get_filename('theme', $this->name, NULL, FALSE); } static function canUpdateDirectory($directory) { diff --git a/modules/system/tests/cron_queue_test.info b/modules/system/tests/cron_queue_test.info index 3e26e25..86d9e08 100644 --- a/modules/system/tests/cron_queue_test.info +++ b/modules/system/tests/cron_queue_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/system/tests/cron_queue_test.module b/modules/system/tests/cron_queue_test.module index e95c6b6..0df6396 100644 --- a/modules/system/tests/cron_queue_test.module +++ b/modules/system/tests/cron_queue_test.module @@ -7,9 +7,21 @@ function cron_queue_test_cron_queue_info() { $queues['cron_queue_test_exception'] = array( 'worker callback' => 'cron_queue_test_exception', ); + $queues['cron_queue_test_callback'] = array( + 'worker callback' => array('CronQueueTestCallbackClass', 'foo'), + ); + return $queues; } function cron_queue_test_exception($item) { throw new Exception('That is not supposed to happen.'); } + +class CronQueueTestCallbackClass { + + static public function foo() { + // Do nothing. + } + +} diff --git a/modules/system/tests/system_cron_test.info b/modules/system/tests/system_cron_test.info new file mode 100644 index 0000000..662c7c4 --- /dev/null +++ b/modules/system/tests/system_cron_test.info @@ -0,0 +1,12 @@ +name = System Cron Test +description = 'Support module for testing the system_cron().' +package = Testing +version = VERSION +core = 7.x +hidden = TRUE + +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" +project = "drupal" +datestamp = "1522264019" + diff --git a/modules/system/tests/system_cron_test.module b/modules/system/tests/system_cron_test.module new file mode 100644 index 0000000..9ef80e2 --- /dev/null +++ b/modules/system/tests/system_cron_test.module @@ -0,0 +1,15 @@ + $bundle->type, 'settings' => array(), 'description' => 'Debris left over after upgrade from Drupal 6', + 'required' => FALSE, 'widget' => array( 'type' => 'taxonomy_autocomplete', 'module' => 'taxonomy', @@ -557,7 +558,7 @@ function taxonomy_update_7005(&$sandbox) { // of term references stored so far for the current revision, which // provides the delta value for each term reference data insert. The // deltas are reset for each new revision. - + $conditions = array( 'type' => 'taxonomy_term_reference', 'deleted' => 0, diff --git a/modules/taxonomy/taxonomy.module b/modules/taxonomy/taxonomy.module index e147c1c..981649d 100644 --- a/modules/taxonomy/taxonomy.module +++ b/modules/taxonomy/taxonomy.module @@ -25,7 +25,7 @@ function taxonomy_help($path, $arg) { $output .= '

          ' . t('Uses') . '

          '; $output .= '
          '; $output .= '
          ' . t('Creating vocabularies') . '
          '; - $output .= '
          ' . t('Users with sufficient permissions can create vocabularies and terms through the Taxonomy page. The page listing the terms provides a drag-and-drop interface for controlling the order of the terms and sub-terms within a vocabulary, in a hierarchical fashion. A controlled vocabulary classifying music by genre with terms and sub-terms could look as follows:', array('@taxo' => url('admin/structure/taxonomy'), '@perm' => url('admin/people/permissions', array('fragment'=>'module-taxonomy')))); + $output .= '
          ' . t('Users with sufficient permissions can create vocabularies and terms through the Taxonomy page. The page listing the terms provides a drag-and-drop interface for controlling the order of the terms and sub-terms within a vocabulary, in a hierarchical fashion. A controlled vocabulary classifying music by genre with terms and sub-terms could look as follows:', array('@taxo' => url('admin/structure/taxonomy'), '@perm' => url('admin/people/permissions', array('fragment' => 'module-taxonomy')))); $output .= '
          • ' . t('vocabulary: Music') . '
          • '; $output .= '
            • ' . t('term: Jazz') . '
            • '; $output .= '
              • ' . t('sub-term: Swing') . '
              • '; @@ -1023,7 +1023,7 @@ function taxonomy_get_parents($tid) { $query->join('taxonomy_term_hierarchy', 'h', 'h.parent = t.tid'); $query->addField('t', 'tid'); $query->condition('h.tid', $tid); - $query->addTag('term_access'); + $query->addTag('taxonomy_term_access'); $query->orderBy('t.weight'); $query->orderBy('t.name'); $tids = $query->execute()->fetchCol(); @@ -1081,7 +1081,7 @@ function taxonomy_get_children($tid, $vid = 0) { if ($vid) { $query->condition('t.vid', $vid); } - $query->addTag('term_access'); + $query->addTag('taxonomy_term_access'); $query->orderBy('t.weight'); $query->orderBy('t.name'); $tids = $query->execute()->fetchCol(); @@ -1129,7 +1129,7 @@ function taxonomy_get_tree($vid, $parent = 0, $max_depth = NULL, $load_entities $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid'); $result = $query ->addTag('translatable') - ->addTag('term_access') + ->addTag('taxonomy_term_access') ->fields('t') ->fields('h', array('parent')) ->condition('t.vid', $vid) @@ -1249,7 +1249,7 @@ class TaxonomyTermController extends DrupalDefaultEntityController { protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) { $query = parent::buildQuery($ids, $conditions, $revision_id); $query->addTag('translatable'); - $query->addTag('term_access'); + $query->addTag('taxonomy_term_access'); // When name is passed as a condition use LIKE. if (isset($conditions['name'])) { $query_conditions = &$query->conditions(); diff --git a/modules/taxonomy/taxonomy.pages.inc b/modules/taxonomy/taxonomy.pages.inc index 975ff12..38b24b3 100644 --- a/modules/taxonomy/taxonomy.pages.inc +++ b/modules/taxonomy/taxonomy.pages.inc @@ -150,7 +150,7 @@ function taxonomy_autocomplete($field_name = '', $tags_typed = '') { $query = db_select('taxonomy_term_data', 't'); $query->addTag('translatable'); - $query->addTag('term_access'); + $query->addTag('taxonomy_term_access'); // Do not select already entered terms. if (!empty($tags_typed)) { diff --git a/modules/taxonomy/taxonomy.test b/modules/taxonomy/taxonomy.test index fdf354b..a4b7ee8 100644 --- a/modules/taxonomy/taxonomy.test +++ b/modules/taxonomy/taxonomy.test @@ -1025,7 +1025,7 @@ class TaxonomyRSSTestCase extends TaxonomyWebTestCase { function setUp() { parent::setUp('taxonomy'); - $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access', 'administer content types')); + $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access', 'administer content types', 'administer fields')); $this->drupalLogin($this->admin_user); $this->vocabulary = $this->createVocabulary(); @@ -1983,3 +1983,113 @@ class TaxonomyEFQTestCase extends TaxonomyWebTestCase { } } + +/** + * Tests that appropriate query tags are added. + */ +class TaxonomyQueryAlterTestCase extends TaxonomyWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Taxonomy query tags', + 'description' => 'Verifies that taxonomy_term_access tags are added to queries.', + 'group' => 'Taxonomy', + ); + } + + public function setUp() { + parent::setUp('taxonomy_test'); + } + + /** + * Tests that appropriate tags are added when querying the database. + */ + public function testTaxonomyQueryAlter() { + // Create a new vocabulary and add a few terms to it. + $vocabulary = $this->createVocabulary(); + $terms = array(); + for ($i = 0; $i < 5; $i++) { + $terms[$i] = $this->createTerm($vocabulary); + } + + // Set up hierarchy. Term 2 is a child of 1. + $terms[2]->parent = array($terms[1]->tid); + taxonomy_term_save($terms[2]); + + $this->setupQueryTagTestHooks(); + $loaded_term = taxonomy_term_load($terms[0]->tid); + $this->assertEqual($loaded_term->tid, $terms[0]->tid, 'First term was loaded'); + $this->assertQueryTagTestResult(1, 'taxonomy_term_load()'); + + $this->setupQueryTagTestHooks(); + $loaded_terms = taxonomy_get_tree($vocabulary->vid); + $this->assertEqual(count($loaded_terms), count($terms), 'All terms were loaded'); + $this->assertQueryTagTestResult(1, 'taxonomy_get_tree()'); + + $this->setupQueryTagTestHooks(); + $loaded_terms = taxonomy_get_parents($terms[2]->tid); + $this->assertEqual(count($loaded_terms), 1, 'All parent terms were loaded'); + $this->assertQueryTagTestResult(2, 'taxonomy_get_parents()'); + + $this->setupQueryTagTestHooks(); + $loaded_terms = taxonomy_get_children($terms[1]->tid); + $this->assertEqual(count($loaded_terms), 1, 'All child terms were loaded'); + $this->assertQueryTagTestResult(2, 'taxonomy_get_children()'); + + $this->setupQueryTagTestHooks(); + $query = db_select('taxonomy_term_data', 't'); + $query->addField('t', 'tid'); + $query->addTag('taxonomy_term_access'); + $tids = $query->execute()->fetchCol(); + $this->assertEqual(count($tids), count($terms), 'All term IDs were retrieved'); + $this->assertQueryTagTestResult(1, 'custom db_select() with taxonomy_term_access tag (preferred)'); + + $this->setupQueryTagTestHooks(); + $query = db_select('taxonomy_term_data', 't'); + $query->addField('t', 'tid'); + $query->addTag('term_access'); + $tids = $query->execute()->fetchCol(); + $this->assertEqual(count($tids), count($terms), 'All term IDs were retrieved'); + $this->assertQueryTagTestResult(1, 'custom db_select() with term_access tag (deprecated)'); + + $this->setupQueryTagTestHooks(); + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', 'taxonomy_term'); + $query->addTag('taxonomy_term_access'); + $result = $query->execute(); + $this->assertEqual(count($result['taxonomy_term']), count($terms), 'All term IDs were retrieved'); + $this->assertQueryTagTestResult(1, 'custom EntityFieldQuery with taxonomy_term_access tag (preferred)'); + + $this->setupQueryTagTestHooks(); + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', 'taxonomy_term'); + $query->addTag('term_access'); + $result = $query->execute(); + $this->assertEqual(count($result['taxonomy_term']), count($terms), 'All term IDs were retrieved'); + $this->assertQueryTagTestResult(1, 'custom EntityFieldQuery with term_access tag (deprecated)'); + } + + /** + * Sets up the hooks in the test module. + */ + protected function setupQueryTagTestHooks() { + taxonomy_terms_static_reset(); + variable_set('taxonomy_test_query_alter', 0); + variable_set('taxonomy_test_query_term_access_alter', 0); + variable_set('taxonomy_test_query_taxonomy_term_access_alter', 0); + } + + /** + * Verifies invocation of the hooks in the test module. + * + * @param int $expected_invocations + * The number of times the hooks are expected to have been invoked. + * @param string $method + * A string describing the invoked function which generated the query. + */ + protected function assertQueryTagTestResult($expected_invocations, $method) { + $this->assertIdentical($expected_invocations, variable_get('taxonomy_test_query_alter'), 'hook_query_alter() invoked when executing ' . $method); + $this->assertIdentical($expected_invocations, variable_get('taxonomy_test_query_term_access_alter'), 'Deprecated hook_query_term_access_alter() invoked when executing ' . $method); + $this->assertIdentical($expected_invocations, variable_get('taxonomy_test_query_taxonomy_term_access_alter'), 'Preferred hook_query_taxonomy_term_access_alter() invoked when executing ' . $method); + } + +} diff --git a/modules/toolbar/toolbar.info b/modules/toolbar/toolbar.info index a35b6ba..f025761 100644 --- a/modules/toolbar/toolbar.info +++ b/modules/toolbar/toolbar.info @@ -4,8 +4,8 @@ core = 7.x package = Core version = VERSION -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/tracker/tracker.info b/modules/tracker/tracker.info index d999477..d7cb61b 100644 --- a/modules/tracker/tracker.info +++ b/modules/tracker/tracker.info @@ -6,8 +6,8 @@ version = VERSION core = 7.x files[] = tracker.test -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/tracker/tracker.test b/modules/tracker/tracker.test index 8a48ea8..e472978 100644 --- a/modules/tracker/tracker.test +++ b/modules/tracker/tracker.test @@ -151,7 +151,6 @@ class TrackerTest extends DrupalWebTestCase { $node = $this->drupalCreateNode(array( 'comment' => 2, - 'title' => array(LANGUAGE_NONE => array(array('value' => $this->randomName(8)))), )); // Add a comment to the page. diff --git a/modules/translation/tests/translation_test.info b/modules/translation/tests/translation_test.info index aecb984..9b597a0 100644 --- a/modules/translation/tests/translation_test.info +++ b/modules/translation/tests/translation_test.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/translation/translation.info b/modules/translation/translation.info index 1ff8a13..3e6513f 100644 --- a/modules/translation/translation.info +++ b/modules/translation/translation.info @@ -6,8 +6,8 @@ version = VERSION core = 7.x files[] = translation.test -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/translation/translation.module b/modules/translation/translation.module index 53c4641..580d000 100644 --- a/modules/translation/translation.module +++ b/modules/translation/translation.module @@ -428,7 +428,7 @@ function translation_node_delete($node) { * A node object. */ function translation_remove_from_set($node) { - if (isset($node->tnid)) { + if (isset($node->tnid) && $node->tnid) { $query = db_update('node') ->fields(array( 'tnid' => 0, diff --git a/modules/trigger/tests/trigger_test.info b/modules/trigger/tests/trigger_test.info index f725f90..13e0698 100644 --- a/modules/trigger/tests/trigger_test.info +++ b/modules/trigger/tests/trigger_test.info @@ -4,8 +4,8 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/trigger/trigger.info b/modules/trigger/trigger.info index 30fc8bb..4c21ba8 100644 --- a/modules/trigger/trigger.info +++ b/modules/trigger/trigger.info @@ -6,8 +6,8 @@ core = 7.x files[] = trigger.test configure = admin/structure/trigger -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/trigger/trigger.test b/modules/trigger/trigger.test index 9e5f114..09169b7 100644 --- a/modules/trigger/trigger.test +++ b/modules/trigger/trigger.test @@ -85,7 +85,7 @@ class TriggerContentTestCase extends TriggerWebTestCase { $this->assertRaw(t('!post %title has been created.', array('!post' => 'Basic page', '%title' => $edit["title"])), 'Make sure the Basic page has actually been created'); // Action should have been fired. $loaded_node = $this->drupalGetNodeByTitle($edit["title"]); - $this->assertTrue($loaded_node->$info['property'] == $info['expected'], format_string('Make sure the @action action fired.', array('@action' => $info['name']))); + $this->assertTrue($loaded_node->{$info['property']} == $info['expected'], format_string('Make sure the @action action fired.', array('@action' => $info['name']))); // Leave action assigned for next test // There should be an error when the action is assigned to the trigger diff --git a/modules/update/tests/aaa_update_test.info b/modules/update/tests/aaa_update_test.info index 935852d..96ac28a 100644 --- a/modules/update/tests/aaa_update_test.info +++ b/modules/update/tests/aaa_update_test.info @@ -4,8 +4,8 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/update/tests/bbb_update_test.info b/modules/update/tests/bbb_update_test.info index 83830dc..954fb75 100644 --- a/modules/update/tests/bbb_update_test.info +++ b/modules/update/tests/bbb_update_test.info @@ -4,8 +4,8 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/update/tests/ccc_update_test.info b/modules/update/tests/ccc_update_test.info index 54499be..f3b7d04 100644 --- a/modules/update/tests/ccc_update_test.info +++ b/modules/update/tests/ccc_update_test.info @@ -4,8 +4,8 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info b/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info new file mode 100644 index 0000000..d21dd9c --- /dev/null +++ b/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info @@ -0,0 +1,10 @@ +name = Update test admin theme +description = Test theme which is used as admin theme. +core = 7.x +hidden = TRUE + +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" +project = "drupal" +datestamp = "1522264019" + diff --git a/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info b/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info index 904c0b1..07eb50f 100644 --- a/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info +++ b/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info @@ -3,8 +3,8 @@ description = Test theme which acts as a base theme for other test subthemes. core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info b/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info index a08108a..26b480c 100644 --- a/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info +++ b/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info @@ -4,8 +4,8 @@ core = 7.x base theme = update_test_basetheme hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/update/tests/update_test.info b/modules/update/tests/update_test.info index 0aa8d80..7b42bc2 100644 --- a/modules/update/tests/update_test.info +++ b/modules/update/tests/update_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/update/tests/update_test.module b/modules/update/tests/update_test.module index 6fe4bdd..594f80f 100644 --- a/modules/update/tests/update_test.module +++ b/modules/update/tests/update_test.module @@ -11,6 +11,7 @@ function update_test_system_theme_info() { $themes['update_test_basetheme'] = drupal_get_path('module', 'update_test') . '/themes/update_test_basetheme/update_test_basetheme.info'; $themes['update_test_subtheme'] = drupal_get_path('module', 'update_test') . '/themes/update_test_subtheme/update_test_subtheme.info'; + $themes['update_test_admintheme'] = drupal_get_path('module', 'update_test') . '/themes/update_test_admintheme/update_test_admintheme.info'; return $themes; } diff --git a/modules/update/update.authorize.inc b/modules/update/update.authorize.inc index 6ddd2c5..03d3704 100644 --- a/modules/update/update.authorize.inc +++ b/modules/update/update.authorize.inc @@ -97,7 +97,9 @@ function update_authorize_run_install($filetransfer, $project, $updater_name, $l } /** - * Batch callback: Copies project to its proper place when authorized to do so. + * Implements callback_batch_operation(). + * + * Copies project to its proper place when authorized to do so. * * @param string $project * The canonical short name of the project being installed. @@ -168,7 +170,9 @@ function update_authorize_batch_copy_project($project, $updater_name, $local_url } /** - * Batch callback: Performs actions when the authorized update batch is done. + * Implements callback_batch_finished(). + * + * Performs actions when the authorized update batch is done. * * This processes the results and stashes them into SESSION such that * authorize.php will render a report. Also responsible for putting the site @@ -235,7 +239,9 @@ function update_authorize_update_batch_finished($success, $results) { } /** - * Batch callback: Performs actions when the authorized install batch is done. + * Implements callback_batch_finished(). + * + * Performs actions when the authorized install batch is done. * * This processes the results and stashes them into SESSION such that * authorize.php will render a report. Also responsible for putting the site diff --git a/modules/update/update.compare.inc b/modules/update/update.compare.inc index 072a0da..e3e0de3 100644 --- a/modules/update/update.compare.inc +++ b/modules/update/update.compare.inc @@ -104,7 +104,13 @@ function update_get_projects() { * @see update_get_projects() */ function _update_process_info_list(&$projects, $list, $project_type, $status) { + $admin_theme = variable_get('admin_theme', 'seven'); foreach ($list as $file) { + // The admin theme is a special case. It should always be considered enabled + // for the purposes of update checking. + if ($file->name === $admin_theme) { + $file->status = TRUE; + } // A disabled base theme of an enabled sub-theme still has all of its code // run by the sub-theme, so we include it in our "enabled" projects list. if ($status && !$file->status && !empty($file->sub_themes)) { diff --git a/modules/update/update.fetch.inc b/modules/update/update.fetch.inc index 9dd2f0b..428cace 100644 --- a/modules/update/update.fetch.inc +++ b/modules/update/update.fetch.inc @@ -29,7 +29,9 @@ function update_manual_status() { } /** - * Batch callback: Processes a step in batch for fetching available update data. + * Implements callback_batch_operation(). + * + * Processes a step in batch for fetching available update data. * * @param $context * Reference to an array used for Batch API storage. @@ -77,7 +79,9 @@ function update_fetch_data_batch(&$context) { } /** - * Batch callback: Performs actions when all fetch tasks have been completed. + * Implements callback_batch_finished(). + * + * Performs actions when all fetch tasks have been completed. * * @param $success * TRUE if the batch operation was successful; FALSE if there were errors. diff --git a/modules/update/update.info b/modules/update/update.info index dbdd67d..4d9c369 100644 --- a/modules/update/update.info +++ b/modules/update/update.info @@ -6,8 +6,8 @@ core = 7.x files[] = update.test configure = admin/reports/updates/settings -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/update/update.manager.inc b/modules/update/update.manager.inc index 85b587d..c7c4e4a 100644 --- a/modules/update/update.manager.inc +++ b/modules/update/update.manager.inc @@ -59,7 +59,7 @@ * @see update_menu() * @ingroup forms */ -function update_manager_update_form($form, $form_state = array(), $context) { +function update_manager_update_form($form, $form_state, $context) { if (!_update_manager_check_backends($form, 'update')) { return $form; } @@ -335,6 +335,8 @@ function update_manager_update_form_submit($form, &$form_state) { } /** + * Implements callback_batch_finished(). + * * Batch callback: Performs actions when the download batch is completed. * * @param $success @@ -847,7 +849,9 @@ function update_manager_file_get($url) { } /** - * Batch callback: Downloads, unpacks, and verifies a project. + * Implements callback_batch_operation(). + * + * Downloads, unpacks, and verifies a project. * * This function assumes that the provided URL points to a file archive of some * sort. The URL can have any scheme that we have a file stream wrapper to diff --git a/modules/update/update.module b/modules/update/update.module index d1f0d85..a59c7d7 100644 --- a/modules/update/update.module +++ b/modules/update/update.module @@ -278,12 +278,15 @@ function update_theme() { ), 'update_report' => array( 'variables' => array('data' => NULL), + 'file' => 'update.report.inc', ), 'update_version' => array( 'variables' => array('version' => NULL, 'tag' => NULL, 'class' => array()), + 'file' => 'update.report.inc', ), 'update_status_label' => array( 'variables' => array('status' => NULL), + 'file' => 'update.report.inc', ), ); } diff --git a/modules/update/update.settings.inc b/modules/update/update.settings.inc index 5cd2414..75de6cd 100644 --- a/modules/update/update.settings.inc +++ b/modules/update/update.settings.inc @@ -26,7 +26,7 @@ function update_settings($form) { $form['update_check_disabled'] = array( '#type' => 'checkbox', - '#title' => t('Check for updates of disabled modules and themes'), + '#title' => t('Check for updates of disabled and uninstalled modules and themes'), '#default_value' => variable_get('update_check_disabled', FALSE), ); @@ -98,10 +98,11 @@ function update_settings_validate($form, &$form_state) { * Form submission handler for update_settings(). * * Also invalidates the cache of available updates if the "Check for updates of - * disabled modules and themes" setting is being changed. The available updates - * report needs to refetch available update data after this setting changes or - * it would show misleading things (e.g., listing the disabled projects on the - * site with the "No available releases found" warning). + * disabled and uninstalled modules and themes" setting is being changed. The + * available updates report needs to refetch available update data after this + * setting changes or it would show misleading things (e.g., listing the + * disabled projects on the site with the "No available releases found" + * warning). * * @see update_settings_validate() */ diff --git a/modules/update/update.test b/modules/update/update.test index 9e04cda..5ce5bb8 100644 --- a/modules/update/update.test +++ b/modules/update/update.test @@ -462,6 +462,55 @@ class UpdateTestContribCase extends UpdateTestHelper { $this->assertRaw(l(t('Update test base theme'), 'http://example.com/project/update_test_basetheme'), 'Link to the Update test base theme project appears.'); } + /** + * Tests that the admin theme is always notified about security updates. + */ + function testUpdateAdminThemeSecurityUpdate() { + // Disable the admin theme. + db_update('system') + ->fields(array('status' => 0)) + ->condition('type', 'theme') + ->condition('name', 'update_test_%', 'LIKE') + ->execute(); + + variable_set('admin_theme', 'update_test_admintheme'); + + // Define the initial state for core and the themes. + $system_info = array( + '#all' => array( + 'version' => '7.0', + ), + 'update_test_admintheme' => array( + 'project' => 'update_test_admintheme', + 'version' => '7.x-1.0', + 'hidden' => FALSE, + ), + 'update_test_basetheme' => array( + 'project' => 'update_test_basetheme', + 'version' => '7.x-1.1', + 'hidden' => FALSE, + ), + 'update_test_subtheme' => array( + 'project' => 'update_test_subtheme', + 'version' => '7.x-1.0', + 'hidden' => FALSE, + ), + ); + variable_set('update_test_system_info', $system_info); + variable_set('update_check_disabled', FALSE); + $xml_mapping = array( + // This is enough because we don't check the update status of the admin + // theme. We want to check that the admin theme is included in the list. + 'drupal' => '0', + ); + $this->refreshUpdateStatus($xml_mapping); + // The admin theme is displayed even if it's disabled. + $this->assertText('update_test_admintheme', "The admin theme is checked for update even if it's disabled"); + // The other disabled themes are not displayed. + $this->assertNoText('update_test_basetheme', 'Disabled theme is not checked for update in the list.'); + $this->assertNoText('update_test_subtheme', 'Disabled theme is not checked for update in the list.'); + } + /** * Tests that disabled themes are only shown when desired. */ @@ -800,4 +849,4 @@ class UpdateCoreUnitTestCase extends DrupalUnitTestCase { $this->assertEqual($url, $expected, "When ? is present, '$url' should be '$expected'."); } -} \ No newline at end of file +} diff --git a/modules/user/tests/user_form_test.info b/modules/user/tests/user_form_test.info index 5a30a0b..17ce34a 100644 --- a/modules/user/tests/user_form_test.info +++ b/modules/user/tests/user_form_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/user/tests/user_form_test.module b/modules/user/tests/user_form_test.module index 4e907f3..382bc57 100644 --- a/modules/user/tests/user_form_test.module +++ b/modules/user/tests/user_form_test.module @@ -62,3 +62,21 @@ function user_form_test_current_password($form, &$form_state, $account) { function user_form_test_current_password_submit($form, &$form_state) { drupal_set_message(t('The password has been validated and the form submitted successfully.')); } + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function user_form_test_form_user_profile_form_alter(&$form, &$form_state) { + if (variable_get('user_form_test_user_profile_form_rebuild', FALSE)) { + $form['#submit'][] = 'user_form_test_user_account_submit'; + } +} + +/** + * Submit function for user_profile_form(). + */ +function user_form_test_user_account_submit($form, &$form_state) { + // Rebuild the form instead of letting the process end. This allows us to + // test for bugs that can be triggered in contributed modules. + $form_state['rebuild'] = TRUE; +} diff --git a/modules/user/user-picture.tpl.php b/modules/user/user-picture.tpl.php index ee82187..11d92cc 100644 --- a/modules/user/user-picture.tpl.php +++ b/modules/user/user-picture.tpl.php @@ -17,7 +17,7 @@ */ ?> -
                +
                diff --git a/modules/user/user.api.php b/modules/user/user.api.php index e88c7a9..f205a85 100644 --- a/modules/user/user.api.php +++ b/modules/user/user.api.php @@ -123,8 +123,8 @@ function hook_user_cancel($edit, $account, $method) { * description is NOT used for the radio button, but instead should provide * additional explanation to the user seeking to cancel their account. * - access: (optional) A boolean value indicating whether the user can access - * a method. If #access is defined, the method cannot be configured as default - * method. + * a method. If access is defined, the method cannot be configured as the + * default method. * * @param $methods * An array containing user account cancellation methods, keyed by method id. @@ -183,7 +183,23 @@ function hook_user_operations() { } /** - * Retrieve a list of user setting or profile information categories. + * Define a list of user settings or profile information categories. + * + * There are two steps to using hook_user_categories(): + * - Create the category with hook_user_categories(). + * - Display that category on the form ID of "user_profile_form" with + * hook_form_FORM_ID_alter(). + * + * Step one builds out the category but it won't be visible on your form until + * you explicitly tell it to do so. + * + * The function in step two should contain the following code in order to + * display your new category: + * @code + * if ($form['#user_category'] == 'mycategory') { + * // Return your form here. + * } + * @endcode * * @return * An array of associative arrays. Each inner array has elements: @@ -327,14 +343,6 @@ function hook_user_logout($account) { * The module should format its custom additions for display and add them to the * $account->content array. * - * Note that when this hook is invoked, the changes have not yet been written to - * the database, because a database transaction is still in progress. The - * transaction is not finalized until the save operation is entirely completed - * and user_save() goes out of scope. You should not rely on data in the - * database at this time as it is not updated yet. You should also note that any - * write/update database queries executed from this hook are also not committed - * immediately. Check user_save() and db_transaction() for more info. - * * @param $account * The user object on which the operation is being performed. * @param $view_mode diff --git a/modules/user/user.info b/modules/user/user.info index f3cf393..83e2b8e 100644 --- a/modules/user/user.info +++ b/modules/user/user.info @@ -9,8 +9,8 @@ required = TRUE configure = admin/config/people stylesheets[all][] = user.css -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/modules/user/user.install b/modules/user/user.install index 4e1a3c2..7a74766 100644 --- a/modules/user/user.install +++ b/modules/user/user.install @@ -49,6 +49,9 @@ function user_schema() { 'columns' => array('uid' => 'uid'), ), ), + 'indexes' => array( + 'uid_module' => array('uid', 'module'), + ), ); $schema['role_permission'] = array( @@ -81,7 +84,7 @@ function user_schema() { ), 'foreign keys' => array( 'role' => array( - 'table' => 'roles', + 'table' => 'role', 'columns' => array('rid' => 'rid'), ), ), @@ -278,7 +281,7 @@ function user_schema() { 'columns' => array('uid' => 'uid'), ), 'role' => array( - 'table' => 'roles', + 'table' => 'role', 'columns' => array('rid' => 'rid'), ), ), @@ -356,11 +359,13 @@ function user_update_dependencies() { 'filter' => 7000, ); - // user_update_7012() uses the file API, which relies on the {file_managed} - // table, so it must run after system_update_7034(), which creates that - // table. + // user_update_7012() uses the file API and inserts records into the + // {file_managed} table, so it therefore must run after system_update_7061(), + // which inserts files with specific IDs into the table and therefore relies + // on the table being empty (otherwise it would accidentally overwrite + // existing records). $dependencies['user'][7012] = array( - 'system' => 7034, + 'system' => 7061, ); // user_update_7013() uses the file usage API, which relies on the @@ -908,6 +913,15 @@ function user_update_7018() { } } +/** + * Ensure there is a combined index on {authmap}.uid and {authmap}.module. + */ +function user_update_7019() { + // Check first in case it was already added manually. + if (!db_index_exists('authmap', 'uid_module')) { + db_add_index('authmap', 'uid_module', array('uid', 'module')); + } +} /** * @} End of "addtogroup updates-7.x-extra". */ diff --git a/modules/user/user.js b/modules/user/user.js index d182066..4cf9816 100644 --- a/modules/user/user.js +++ b/modules/user/user.js @@ -93,6 +93,8 @@ Drupal.behaviors.password = { * Returns the estimated strength and the relevant output message. */ Drupal.evaluatePasswordStrength = function (password, translate) { + password = $.trim(password); + var weaknesses = 0, strength = 100, msg = []; var hasLowercase = /[a-z]+/.test(password); diff --git a/modules/user/user.module b/modules/user/user.module index 60f32a1..12ca280 100644 --- a/modules/user/user.module +++ b/modules/user/user.module @@ -32,7 +32,7 @@ define('USER_REGISTER_VISITORS', 1); define('USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL', 2); /** - * Implement hook_help(). + * Implements hook_help(). */ function user_help($path, $arg) { global $user; @@ -418,13 +418,11 @@ function user_load_by_name($name) { * * @return * A fully-loaded $user object upon successful save or FALSE if the save failed. - * - * @todo D8: Drop $edit and fix user_save() to be consistent with others. */ function user_save($account, $edit = array(), $category = 'account') { $transaction = db_transaction(); try { - if (!empty($edit['pass'])) { + if (isset($edit['pass']) && strlen(trim($edit['pass'])) > 0) { // Allow alternate password hashing schemes. require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc'); $edit['pass'] = user_hash_password(trim($edit['pass'])); @@ -791,7 +789,7 @@ function user_role_permissions($roles = array()) { * (optional) The account to check, if not given use currently logged in user. * * @return - * Boolean TRUE if the current user has the requested permission. + * Boolean TRUE if the user has the requested permission. * * All permission checks in Drupal should go through this function. This * way, we guarantee consistent behavior, and ensure that the superuser @@ -848,6 +846,26 @@ function user_is_blocked($name) { ->execute()->fetchObject(); } +/** + * Checks if a user has a role. + * + * @param int $rid + * A role ID. + * + * @param object|null $account + * (optional) A user account. Defaults to the current user. + * + * @return bool + * TRUE if the user has the role, or FALSE if not. + */ +function user_has_role($rid, $account = NULL) { + if (!$account) { + $account = $GLOBALS['user']; + } + + return isset($account->roles[$rid]); +} + /** * Implements hook_permission(). */ @@ -938,6 +956,8 @@ function user_search_access() { */ function user_search_execute($keys = NULL, $conditions = NULL) { $find = array(); + // Escape for LIKE matching. + $keys = db_like($keys); // Replace wildcards with MySQL/PostgreSQL wildcards. $keys = preg_replace('!\*+!', '%', $keys); $query = db_select('users')->extend('PagerDefault'); @@ -947,13 +967,13 @@ function user_search_execute($keys = NULL, $conditions = NULL) { // and they don't need to be restricted to only active users. $query->fields('users', array('mail')); $query->condition(db_or()-> - condition('name', '%' . db_like($keys) . '%', 'LIKE')-> - condition('mail', '%' . db_like($keys) . '%', 'LIKE')); + condition('name', '%' . $keys . '%', 'LIKE')-> + condition('mail', '%' . $keys . '%', 'LIKE')); } else { // Regular users can only search via usernames, and we do not show them // blocked accounts. - $query->condition('name', '%' . db_like($keys) . '%', 'LIKE') + $query->condition('name', '%' . $keys . '%', 'LIKE') ->condition('status', 1); } $uids = $query @@ -1068,13 +1088,16 @@ function user_account_form(&$form, &$form_state) { '#description' => t('To change the current user password, enter the new password in both fields.'), ); // To skip the current password field, the user must have logged in via a - // one-time link and have the token in the URL. - $pass_reset = isset($_SESSION['pass_reset_' . $account->uid]) && isset($_GET['pass-reset-token']) && ($_GET['pass-reset-token'] == $_SESSION['pass_reset_' . $account->uid]); + // one-time link and have the token in the URL. Store this in $form_state + // so it persists even on subsequent Ajax requests. + if (!isset($form_state['user_pass_reset'])) { + $form_state['user_pass_reset'] = isset($_SESSION['pass_reset_' . $account->uid]) && isset($_GET['pass-reset-token']) && ($_GET['pass-reset-token'] == $_SESSION['pass_reset_' . $account->uid]); + } $protected_values = array(); $current_pass_description = ''; // The user may only change their own password without their current // password if they logged in via a one-time login link. - if (!$pass_reset) { + if (!$form_state['user_pass_reset']) { $protected_values['mail'] = $form['account']['mail']['#title']; $protected_values['pass'] = t('Password'); $request_new = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.')))); @@ -1140,7 +1163,7 @@ function user_account_form(&$form, &$form_state) { $form['account']['roles'] = array( '#type' => 'checkboxes', '#title' => t('Roles'), - '#default_value' => (!$register && isset($account->roles) ? array_keys($account->roles) : array()), + '#default_value' => (!$register && !empty($account->roles) ? array_keys(array_filter($account->roles)) : array()), '#options' => $roles, '#access' => $roles && user_access('administer permissions'), DRUPAL_AUTHENTICATED_RID => $checkbox_authenticated, @@ -1210,7 +1233,7 @@ function user_validate_current_pass(&$form, &$form_state) { // that prevent them from being empty if they are changed. if ((strlen(trim($form_state['values'][$key])) > 0) && ($form_state['values'][$key] != $account->$key)) { require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc'); - $current_pass_failed = empty($form_state['values']['current_pass']) || !user_check_password($form_state['values']['current_pass'], $account); + $current_pass_failed = strlen(trim($form_state['values']['current_pass'])) == 0 || !user_check_password($form_state['values']['current_pass'], $account); if ($current_pass_failed) { form_set_error('current_pass', t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => $name))); form_set_error($key); @@ -1286,10 +1309,12 @@ function user_user_presave(&$edit, $account, $category) { elseif (!empty($edit['picture_delete'])) { $edit['picture'] = NULL; } - // Prepare user roles. - if (isset($edit['roles'])) { - $edit['roles'] = array_filter($edit['roles']); - } + } + + // Filter out roles with empty values to avoid granting extra roles when + // processing custom form submissions. + if (isset($edit['roles'])) { + $edit['roles'] = array_filter($edit['roles']); } // Move account cancellation information into $user->data. @@ -1731,9 +1756,11 @@ function user_menu() { $items['admin/people/create'] = array( 'title' => 'Add user', + 'page callback' => 'user_admin', 'page arguments' => array('create'), 'access arguments' => array('administer users'), 'type' => MENU_LOCAL_ACTION, + 'file' => 'user.admin.inc', ); // Administration pages. @@ -1891,13 +1918,13 @@ function user_menu_link_alter(&$link) { // for authenticated users. Authenticated users should see "My account", but // anonymous users should not see it at all. Therefore, invoke // user_translated_menu_link_alter() to conditionally hide the link. - if ($link['link_path'] == 'user' && $link['module'] == 'system') { + if ($link['link_path'] == 'user' && isset($link['module']) && $link['module'] == 'system') { $link['options']['alter'] = TRUE; } // Force the Logout link to appear on the top-level of 'user-menu' menu by // default (i.e., unless it has been customized). - if ($link['link_path'] == 'user/logout' && $link['module'] == 'system' && empty($link['customized'])) { + if ($link['link_path'] == 'user/logout' && isset($link['module']) && $link['module'] == 'system' && empty($link['customized'])) { $link['plid'] = 0; } } @@ -2141,7 +2168,7 @@ function user_login_name_validate($form, &$form_state) { */ function user_login_authenticate_validate($form, &$form_state) { $password = trim($form_state['values']['pass']); - if (!empty($form_state['values']['name']) && !empty($password)) { + if (!empty($form_state['values']['name']) && strlen(trim($password)) > 0) { // Do not allow any login from the current user's IP if the limit has been // reached. Default is 50 failed attempts allowed in one hour. This is // independent of the per-user limit to catch attempts from one IP to log @@ -2205,7 +2232,11 @@ function user_login_final_validate($form, &$form_state) { } } else { - form_set_error('name', t('Sorry, unrecognized username or password. Have you forgotten your password?', array('@password' => url('user/password', array('query' => array('name' => $form_state['values']['name'])))))); + // Use $form_state['input']['name'] here to guarantee that we send + // exactly what the user typed in. $form_state['values']['name'] may have + // been modified by validation handlers that ran earlier than this one. + $query = isset($form_state['input']['name']) ? array('name' => $form_state['input']['name']) : array(); + form_set_error('name', t('Sorry, unrecognized username or password. Have you forgotten your password?', array('@password' => url('user/password', array('query' => $query))))); watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_state['values']['name'])); } } @@ -2228,7 +2259,7 @@ function user_login_final_validate($form, &$form_state) { */ function user_authenticate($name, $password) { $uid = FALSE; - if (!empty($name) && !empty($password)) { + if (!empty($name) && strlen(trim($password)) > 0) { $account = user_load_by_name($name); if ($account) { // Allow alternate password hashing schemes. @@ -2335,7 +2366,7 @@ function user_external_login_register($name, $module) { */ function user_pass_reset_url($account) { $timestamp = REQUEST_TIME; - return url("user/reset/$account->uid/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE)); + return url("user/reset/$account->uid/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), array('absolute' => TRUE)); } /** @@ -2357,7 +2388,7 @@ function user_pass_reset_url($account) { */ function user_cancel_url($account) { $timestamp = REQUEST_TIME; - return url("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE)); + return url("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), array('absolute' => TRUE)); } /** @@ -2377,12 +2408,33 @@ function user_cancel_url($account) { * A UNIX timestamp, typically REQUEST_TIME. * @param int $login * The UNIX timestamp of the user's last login. + * @param int $uid + * The user ID of the user account. * * @return * A string that is safe for use in URLs and SQL statements. */ -function user_pass_rehash($password, $timestamp, $login) { - return drupal_hmac_base64($timestamp . $login, drupal_get_hash_salt() . $password); +function user_pass_rehash($password, $timestamp, $login, $uid) { + // Backwards compatibility: Try to determine a $uid if one was not passed. + // (Since $uid is a required parameter to this function, a PHP warning will + // be generated if it's not provided, which is an indication that the calling + // code should be updated. But the code below will try to generate a correct + // hash in the meantime.) + if (!isset($uid)) { + $uids = db_query_range('SELECT uid FROM {users} WHERE pass = :password AND login = :login AND uid > 0', 0, 2, array(':password' => $password, ':login' => $login))->fetchCol(); + // If exactly one user account matches the provided password and login + // timestamp, proceed with that $uid. + if (count($uids) == 1) { + $uid = reset($uids); + } + // Otherwise there is no safe hash to return, so return a random string + // that will never be treated as a valid token. + else { + return drupal_random_key(); + } + } + + return drupal_hmac_base64($timestamp . $login . $uid, drupal_get_hash_salt() . $password); } /** @@ -2447,7 +2499,9 @@ function user_cancel($edit, $uid, $method) { } /** - * Last batch processing step for cancelling a user account. + * Implements callback_batch_operation(). + * + * Last step for cancelling a user account. * * Since batch and session API require a valid user account, the actual * cancellation of a user account needs to happen last. @@ -2495,6 +2549,8 @@ function _user_cancel($edit, $account, $method) { } /** + * Implements callback_batch_finished(). + * * Finished batch processing callback for cancelling a user account. * * @see user_cancel() @@ -2998,6 +3054,11 @@ function user_role_delete($role) { $role = user_role_load_by_name($role); } + // If this is the administrator role, delete the user_admin_role variable. + if ($role->rid == variable_get('user_admin_role')) { + variable_del('user_admin_role'); + } + db_delete('role') ->condition('rid', $role->rid) ->execute(); @@ -3613,12 +3674,7 @@ function user_form_process_password_confirm($element) { ); $element['#attached']['js'][] = drupal_get_path('module', 'user') . '/user.js'; - // Ensure settings are only added once per page. - static $already_added = FALSE; - if (!$already_added) { - $already_added = TRUE; - $element['#attached']['js'][] = array('data' => $js_settings, 'type' => 'setting'); - } + $element['#attached']['js'][] = array('data' => $js_settings, 'type' => 'setting'); return $element; } diff --git a/modules/user/user.pages.inc b/modules/user/user.pages.inc index 8ec2348..2a1b291 100644 --- a/modules/user/user.pages.inc +++ b/modules/user/user.pages.inc @@ -44,6 +44,12 @@ function user_pass() { $form['name']['#value'] = $user->mail; $form['mail'] = array( '#prefix' => '

                ', + // As of https://www.drupal.org/node/889772 the user no longer must log + // out (if they are still logged in when using the password reset link, + // they will be logged out automatically then), but this text is kept as + // is to avoid breaking translations as well as to encourage the user to + // log out manually at a time of their own choosing (when it will not + // interrupt anything else they may have been in the middle of doing). '#markup' => t('Password reset instructions will be mailed to %email. You must log out to use the password reset link in the e-mail.', array('%email' => $user->mail)), '#suffix' => '

                ', ); @@ -54,6 +60,11 @@ function user_pass() { return $form; } +/** + * Form validation handler for user_pass(). + * + * @see user_pass_submit() + */ function user_pass_validate($form, &$form_state) { $name = trim($form_state['values']['name']); // Try to load by email. @@ -72,6 +83,11 @@ function user_pass_validate($form, &$form_state) { } } +/** + * Form submission handler for user_pass(). + * + * @see user_pass_validate() + */ function user_pass_submit($form, &$form_state) { global $language; @@ -96,22 +112,33 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a // When processing the one-time login link, we have to make sure that a user // isn't already logged in. if ($user->uid) { - // The existing user is already logged in. + // The existing user is already logged in. Log them out and reload the + // current page so the password reset process can continue. if ($user->uid == $uid) { - drupal_set_message(t('You are logged in as %user. Change your password.', array('%user' => $user->name, '!user_edit' => url("user/$user->uid/edit")))); + // Preserve the current destination (if any) and ensure the redirect goes + // back to the current page; any custom destination set in + // hook_user_logout() and intended for regular logouts would not be + // appropriate here. + $destination = array(); + if (isset($_GET['destination'])) { + $destination = drupal_get_destination(); + } + user_logout_current_user(); + unset($_GET['destination']); + drupal_goto(current_path(), array('query' => drupal_get_query_parameters() + $destination)); } // A different user is already logged in on the computer. else { $reset_link_account = user_load($uid); if (!empty($reset_link_account)) { drupal_set_message(t('Another user (%other_user) is already logged into the site on this computer, but you tried to use a one-time link for user %resetting_user. Please logout and try using the link again.', - array('%other_user' => $user->name, '%resetting_user' => $reset_link_account->name, '!logout' => url('user/logout')))); + array('%other_user' => $user->name, '%resetting_user' => $reset_link_account->name, '!logout' => url('user/logout'))), 'warning'); } else { // Invalid one-time link specifies an unknown user. - drupal_set_message(t('The one-time login link you clicked is invalid.')); + drupal_set_message(t('The one-time login link you clicked is invalid.'), 'error'); } + drupal_goto(); } - drupal_goto(); } else { // Time out, in seconds, until login URL expires. Defaults to 24 hours = @@ -123,10 +150,10 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a if ($timestamp <= $current && $account = reset($users)) { // No time out for first time login. if ($account->login && $current - $timestamp > $timeout) { - drupal_set_message(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.')); + drupal_set_message(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'error'); drupal_goto('user/password'); } - elseif ($account->uid && $timestamp >= $account->login && $timestamp <= $current && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login)) { + elseif ($account->uid && $timestamp >= $account->login && $timestamp <= $current && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid)) { // First stage is a confirmation form, then login if ($action == 'login') { // Set the new user. @@ -151,7 +178,7 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a } } else { - drupal_set_message(t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.')); + drupal_set_message(t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'), 'error'); drupal_goto('user/password'); } } @@ -168,6 +195,14 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a * Menu callback; logs the current user out, and redirects to the home page. */ function user_logout() { + user_logout_current_user(); + drupal_goto(); +} + +/** + * Logs the current user out. + */ +function user_logout_current_user() { global $user; watchdog('user', 'Session closed for %name.', array('%name' => $user->name)); @@ -176,8 +211,6 @@ function user_logout() { // Destroy the current session, and reset $user to the anonymous user. session_destroy(); - - drupal_goto(); } /** @@ -294,14 +327,18 @@ function user_profile_form($form, &$form_state, $account, $category = 'account') } /** - * Validation function for the user account and profile editing form. + * Form validation handler for user_profile_form(). + * + * @see user_profile_form_submit() */ function user_profile_form_validate($form, &$form_state) { entity_form_field_validate('user', $form, $form_state); } /** - * Submit function for the user account and profile editing form. + * Form submission handler for user_profile_form(). + * + * @see user_profile_form_validate() */ function user_profile_form_submit($form, &$form_state) { $account = $form_state['user']; @@ -359,7 +396,6 @@ function user_cancel_confirm_form($form, &$form_state, $account) { $form['_account'] = array('#type' => 'value', '#value' => $account); // Display account cancellation method selection, if allowed. - $default_method = variable_get('user_cancel_method', 'user_cancel_block'); $admin_access = user_access('administer users'); $can_select_method = $admin_access || user_access('select account cancellation method'); $form['user_cancel_method'] = array( @@ -523,7 +559,7 @@ function user_cancel_confirm($account, $timestamp = 0, $hashed_pass = '') { // Basic validation of arguments. if (isset($account->data['user_cancel_method']) && !empty($timestamp) && !empty($hashed_pass)) { // Validate expiration and hashed password/login. - if ($timestamp <= $current && $current - $timestamp < $timeout && $account->uid && $timestamp >= $account->login && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login)) { + if ($timestamp <= $current && $current - $timestamp < $timeout && $account->uid && $timestamp >= $account->login && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid)) { $edit = array( 'user_cancel_notify' => isset($account->data['user_cancel_notify']) ? $account->data['user_cancel_notify'] : variable_get('user_mail_status_canceled_notify', FALSE), ); @@ -534,7 +570,7 @@ function user_cancel_confirm($account, $timestamp = 0, $hashed_pass = '') { batch_process(''); } else { - drupal_set_message(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.')); + drupal_set_message(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), 'error'); drupal_goto("user/$account->uid/cancel"); } } diff --git a/modules/user/user.test b/modules/user/user.test index 03f0bbc..0875e0a 100644 --- a/modules/user/user.test +++ b/modules/user/user.test @@ -465,6 +465,19 @@ class UserPasswordResetTestCase extends DrupalWebTestCase { ); } + /** + * Retrieves password reset email and extracts the login link. + */ + public function getResetURL() { + // Assume the most recent email. + $_emails = $this->drupalGetMails(); + $email = end($_emails); + $urls = array(); + preg_match('#.+user/reset/.+#', $email['body'], $urls); + + return $urls[0]; + } + /** * Tests password reset functionality. */ @@ -478,6 +491,77 @@ class UserPasswordResetTestCase extends DrupalWebTestCase { $this->drupalPost('user/password', $edit, t('E-mail new password')); // Confirm the password reset. $this->assertText(t('Further instructions have been sent to your e-mail address.'), 'Password reset instructions mailed message displayed.'); + + // Create an image field to enable an Ajax request on the user profile page. + $field = array( + 'field_name' => 'field_avatar', + 'type' => 'image', + 'settings' => array(), + 'cardinality' => 1, + ); + field_create_field($field); + + $instance = array( + 'field_name' => $field['field_name'], + 'entity_type' => 'user', + 'label' => 'Avatar', + 'bundle' => 'user', + 'required' => FALSE, + 'settings' => array(), + 'widget' => array( + 'type' => 'image_image', + 'settings' => array(), + ), + ); + field_create_instance($instance); + + $resetURL = $this->getResetURL(); + $this->drupalGet($resetURL); + + // Check successful login. + $this->drupalPost(NULL, NULL, t('Log in')); + + // Make sure the Ajax request from uploading a file does not invalidate the + // reset token. + $image = current($this->drupalGetTestFiles('image')); + $edit = array( + 'files[field_avatar_und_0]' => drupal_realpath($image->uri), + ); + $this->drupalPostAJAX(NULL, $edit, 'field_avatar_und_0_upload_button'); + + // Change the forgotten password. + $password = user_password(); + $edit = array('pass[pass1]' => $password, 'pass[pass2]' => $password); + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertText(t('The changes have been saved.'), 'Forgotten password changed.'); + } + + /** + * Test user password reset while logged in. + */ + function testUserPasswordResetLoggedIn() { + $account = $this->drupalCreateUser(); + $this->drupalLogin($account); + // Make sure the test account has a valid password. + user_save($account, array('pass' => user_password())); + + // Generate one time login link. + $reset_url = user_pass_reset_url($account); + $this->drupalGet($reset_url); + + $this->assertText('Reset password'); + $this->drupalPost(NULL, NULL, t('Log in')); + + $this->assertText('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.'); + + $pass = user_password(); + $edit = array( + 'pass[pass1]' => $pass, + 'pass[pass2]' => $pass, + ); + $this->drupalPost(NULL, $edit, t('Save')); + + $this->assertText('The changes have been saved.'); } /** @@ -498,7 +582,7 @@ class UserPasswordResetTestCase extends DrupalWebTestCase { // To attempt an expired password reset, create a password reset link as if // its request time was 60 seconds older than the allowed limit of timeout. $bogus_timestamp = REQUEST_TIME - variable_get('user_password_reset_timeout', 86400) - 60; - $this->drupalGet("user/reset/$account->uid/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login)); + $this->drupalGet("user/reset/$account->uid/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login, $account->uid)); $this->assertText(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'Expired password reset request rejected.'); } @@ -519,6 +603,74 @@ class UserPasswordResetTestCase extends DrupalWebTestCase { $this->assertFieldByName('name', $edit['name'], 'User name found.'); } + /** + * Make sure that users cannot forge password reset URLs of other users. + */ + function testResetImpersonation() { + // Make sure user 1 has a valid password, so it does not interfere with the + // test user accounts that are created below. + $account = user_load(1); + user_save($account, array('pass' => user_password())); + + // Create two identical user accounts except for the user name. They must + // have the same empty password, so we can't use $this->drupalCreateUser(). + $edit = array(); + $edit['name'] = $this->randomName(); + $edit['mail'] = $edit['name'] . '@example.com'; + $edit['status'] = 1; + + $user1 = user_save(drupal_anonymous_user(), $edit); + + $edit['name'] = $this->randomName(); + $user2 = user_save(drupal_anonymous_user(), $edit); + + // The password reset URL must not be valid for the second user when only + // the user ID is changed in the URL. + $reset_url = user_pass_reset_url($user1); + $attack_reset_url = str_replace("user/reset/$user1->uid", "user/reset/$user2->uid", $reset_url); + $this->drupalGet($attack_reset_url); + $this->assertNoText($user2->name, 'The invalid password reset page does not show the user name.'); + $this->assertUrl('user/password', array(), 'The user is redirected to the password reset request page.'); + $this->assertText('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'); + + // When legacy code calls user_pass_rehash() without providing the $uid + // parameter, neither password reset URL should be valid since it is + // impossible for the system to determine which user account the token was + // intended for. + $timestamp = REQUEST_TIME; + // Pass an explicit NULL for the $uid parameter of user_pass_rehash() + // rather than not passing it at all, to avoid triggering PHP warnings in + // the test. + $reset_url_token = user_pass_rehash($user1->pass, $timestamp, $user1->login, NULL); + $reset_url = url("user/reset/$user1->uid/$timestamp/$reset_url_token", array('absolute' => TRUE)); + $this->drupalGet($reset_url); + $this->assertNoText($user1->name, 'The invalid password reset page does not show the user name.'); + $this->assertUrl('user/password', array(), 'The user is redirected to the password reset request page.'); + $this->assertText('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'); + $attack_reset_url = str_replace("user/reset/$user1->uid", "user/reset/$user2->uid", $reset_url); + $this->drupalGet($attack_reset_url); + $this->assertNoText($user2->name, 'The invalid password reset page does not show the user name.'); + $this->assertUrl('user/password', array(), 'The user is redirected to the password reset request page.'); + $this->assertText('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'); + + // To verify that user_pass_rehash() never returns a valid result in the + // above situation (even if legacy code also called it to attempt to + // validate the token, rather than just to generate the URL), check that a + // second call with the same parameters produces a different result. + $new_reset_url_token = user_pass_rehash($user1->pass, $timestamp, $user1->login, NULL); + $this->assertNotEqual($reset_url_token, $new_reset_url_token); + + // However, when the duplicate account is removed, the password reset URL + // should be valid. + user_delete($user2->uid); + $reset_url_token = user_pass_rehash($user1->pass, $timestamp, $user1->login, NULL); + $reset_url = url("user/reset/$user1->uid/$timestamp/$reset_url_token", array('absolute' => TRUE)); + $this->drupalGet($reset_url); + $this->assertText($user1->name, 'The valid password reset page shows the user name.'); + $this->assertUrl($reset_url, array(), 'The user remains on the password reset login page.'); + $this->assertNoText('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'); + } + } /** @@ -558,7 +710,7 @@ class UserCancelTestCase extends DrupalWebTestCase { // Attempt bogus account cancellation request confirmation. $timestamp = $account->login; - $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login)); + $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid)); $this->assertResponse(403, 'Bogus cancelling request rejected.'); $account = user_load($account->uid); $this->assertTrue($account->status == 1, 'User account was not canceled.'); @@ -631,14 +783,14 @@ class UserCancelTestCase extends DrupalWebTestCase { // Attempt bogus account cancellation request confirmation. $bogus_timestamp = $timestamp + 60; - $this->drupalGet("user/$account->uid/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login)); + $this->drupalGet("user/$account->uid/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login, $account->uid)); $this->assertText(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), 'Bogus cancelling request rejected.'); $account = user_load($account->uid); $this->assertTrue($account->status == 1, 'User account was not canceled.'); // Attempt expired account cancellation request confirmation. $bogus_timestamp = $timestamp - 86400 - 60; - $this->drupalGet("user/$account->uid/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login)); + $this->drupalGet("user/$account->uid/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login, $account->uid)); $this->assertText(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), 'Expired cancel account request rejected.'); $accounts = user_load_multiple(array($account->uid), array('status' => 1)); $this->assertTrue(reset($accounts), 'User account was not canceled.'); @@ -675,7 +827,7 @@ class UserCancelTestCase extends DrupalWebTestCase { $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), 'Account cancellation request mailed message displayed.'); // Confirm account cancellation request. - $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login)); + $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid)); $account = user_load($account->uid, TRUE); $this->assertTrue($account->status == 0, 'User has been blocked.'); @@ -713,7 +865,7 @@ class UserCancelTestCase extends DrupalWebTestCase { $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), 'Account cancellation request mailed message displayed.'); // Confirm account cancellation request. - $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login)); + $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid)); $account = user_load($account->uid, TRUE); $this->assertTrue($account->status == 0, 'User has been blocked.'); @@ -763,7 +915,7 @@ class UserCancelTestCase extends DrupalWebTestCase { $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), 'Account cancellation request mailed message displayed.'); // Confirm account cancellation request. - $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login)); + $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid)); $this->assertFalse(user_load($account->uid, TRUE), 'User is not found in the database.'); // Confirm that user's content has been attributed to anonymous user. @@ -827,7 +979,7 @@ class UserCancelTestCase extends DrupalWebTestCase { $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), 'Account cancellation request mailed message displayed.'); // Confirm account cancellation request. - $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login)); + $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid)); $this->assertFalse(user_load($account->uid, TRUE), 'User is not found in the database.'); // Confirm that user's content has been deleted. @@ -1433,7 +1585,13 @@ class UserTimeZoneFunctionalTest extends DrupalWebTestCase { // Setup date/time settings for Los Angeles time. variable_set('date_default_timezone', 'America/Los_Angeles'); variable_set('configurable_timezones', 1); - variable_set('date_format_medium', 'Y-m-d H:i T'); + + // Override the 'medium' date format, which is the default for node + // creation time. Since we are testing time zones with Daylight Saving + // Time, and need to future proof against changes to the zoneinfo database, + // we choose the 'I' format placeholder instead of a human-readable zone + // name. With 'I', a 1 means the date is in DST, and 0 if not. + variable_set('date_format_medium', 'Y-m-d H:i I'); // Create a user account and login. $web_user = $this->drupalCreateUser(); @@ -1451,11 +1609,11 @@ class UserTimeZoneFunctionalTest extends DrupalWebTestCase { // Confirm date format and time zone. $this->drupalGet("node/$node1->nid"); - $this->assertText('2007-03-09 21:00 PST', 'Date should be PST.'); + $this->assertText('2007-03-09 21:00 0', 'Date should be PST.'); $this->drupalGet("node/$node2->nid"); - $this->assertText('2007-03-11 01:00 PST', 'Date should be PST.'); + $this->assertText('2007-03-11 01:00 0', 'Date should be PST.'); $this->drupalGet("node/$node3->nid"); - $this->assertText('2007-03-20 21:00 PDT', 'Date should be PDT.'); + $this->assertText('2007-03-20 21:00 1', 'Date should be PDT.'); // Change user time zone to Santiago time. $edit = array(); @@ -1466,11 +1624,11 @@ class UserTimeZoneFunctionalTest extends DrupalWebTestCase { // Confirm date format and time zone. $this->drupalGet("node/$node1->nid"); - $this->assertText('2007-03-10 02:00 CLST', 'Date should be Chile summer time; five hours ahead of PST.'); + $this->assertText('2007-03-10 02:00 1', 'Date should be Chile summer time; five hours ahead of PST.'); $this->drupalGet("node/$node2->nid"); - $this->assertText('2007-03-11 05:00 CLT', 'Date should be Chile time; four hours ahead of PST'); + $this->assertText('2007-03-11 05:00 0', 'Date should be Chile time; four hours ahead of PST'); $this->drupalGet("node/$node3->nid"); - $this->assertText('2007-03-21 00:00 CLT', 'Date should be Chile time; three hours ahead of PDT.'); + $this->assertText('2007-03-21 00:00 0', 'Date should be Chile time; three hours ahead of PDT.'); } } @@ -1781,6 +1939,19 @@ class UserCreateTestCase extends DrupalWebTestCase { $this->drupalGet('admin/people'); $this->assertText($edit['name'], 'User found in list of users'); } + + // Test that the password '0' is considered a password. + $name = $this->randomName(); + $edit = array( + 'name' => $name, + 'mail' => $name . '@example.com', + 'pass[pass1]' => 0, + 'pass[pass2]' => 0, + 'notify' => FALSE, + ); + $this->drupalPost('admin/people/create', $edit, t('Create new account')); + $this->assertText(t('Created a new user account for @name. No e-mail has been sent.', array('@name' => $edit['name'])), 'User created with password 0'); + $this->assertNoText('Password field is required'); } } @@ -1858,6 +2029,74 @@ class UserEditTestCase extends DrupalWebTestCase { $this->drupalLogin($user1); $this->drupalLogout(); } + + /** + * Tests setting the password to "0". + */ + public function testUserWith0Password() { + $admin = $this->drupalCreateUser(array('administer users')); + $this->drupalLogin($admin); + // Create a regular user. + $user1 = $this->drupalCreateUser(array()); + + $edit = array('pass[pass1]' => '0', 'pass[pass2]' => '0'); + $this->drupalPost("user/" . $user1->uid . "/edit", $edit, t('Save')); + $this->assertRaw(t("The changes have been saved.")); + + $this->drupalLogout(); + $user1->pass_raw = '0'; + $this->drupalLogin($user1); + $this->drupalLogout(); + } +} + +/** + * Tests editing a user account with and without a form rebuild. + */ +class UserEditRebuildTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'User edit with form rebuild', + 'description' => 'Test user edit page when a form rebuild is triggered.', + 'group' => 'User', + ); + } + + function setUp() { + parent::setUp('user_form_test'); + } + + /** + * Test user edit page when the form is set to rebuild. + */ + function testUserEditFormRebuild() { + $user1 = $this->drupalCreateUser(array('change own username')); + $this->drupalLogin($user1); + + $roles = array_keys($user1->roles); + // Save the user form twice. + $edit = array(); + $edit['current_pass'] = $user1->pass_raw; + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); + $this->assertRaw(t("The changes have been saved.")); + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertRaw(t("The changes have been saved.")); + $saved_user1 = entity_load_unchanged('user', $user1->uid); + $this->assertEqual(count($roles), count($saved_user1->roles), 'Count of user roles in database matches original count.'); + $diff = array_diff(array_keys($saved_user1->roles), $roles); + $this->assertTrue(empty($diff), format_string('User roles in database match original: @roles', array('@roles' => implode(', ', $saved_user1->roles)))); + // Set variable that causes the form to be rebuilt in user_form_test.module. + variable_set('user_form_test_user_profile_form_rebuild', TRUE); + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); + $this->assertRaw(t("The changes have been saved.")); + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertRaw(t("The changes have been saved.")); + $saved_user1 = entity_load_unchanged('user', $user1->uid); + $this->assertEqual(count($roles), count($saved_user1->roles), 'Count of user roles in database matches original count.'); + $diff = array_diff(array_keys($saved_user1->roles), $roles); + $this->assertTrue(empty($diff), format_string('User roles in database match original: @roles', array('@roles' => implode(', ', $saved_user1->roles)))); + } } /** @@ -2027,12 +2266,16 @@ class UserRoleAdminTestCase extends DrupalWebTestCase { $this->assertFalse(user_role_load_by_name($old_name), 'The role can no longer be retrieved from the database using its old name.'); $this->assertTrue(is_object(user_role_load_by_name($role_name)), 'The role can be retrieved from the database using its new name.'); - // Test deleting a role. + // Test deleting the default administrator role. + $role_name = 'administrator'; + $role = user_role_load_by_name($role_name); $this->drupalPost("admin/people/permissions/roles/edit/{$role->rid}", NULL, t('Delete role')); $this->drupalPost(NULL, NULL, t('Delete')); $this->assertText(t('The role has been deleted.'), 'The role has been deleted'); $this->assertNoLinkByHref("admin/people/permissions/roles/edit/{$role->rid}", 'Role edit link removed.'); $this->assertFalse(user_role_load_by_name($role_name), 'A deleted role can no longer be loaded.'); + // Make sure this role is no longer configured as the administrator role. + $this->assertNull(variable_get('user_admin_role'), 'The administrator role is no longer configured as the administrator role.'); // Make sure that the system-defined roles cannot be edited via the user // interface. @@ -2158,6 +2401,20 @@ class UserUserSearchTestCase extends DrupalWebTestCase { $this->drupalPost('search/user/', $edit, t('Search')); $this->assertText($keys); + // Verify that wildcard search works. + $keys = $user1->name; + $keys = substr($keys, 0, 2) . '*' . substr($keys, 4, 2); + $edit = array('keys' => $keys); + $this->drupalPost('search/user/', $edit, t('Search')); + $this->assertText($user1->name, 'Search for username wildcard resulted in user name on page for administrative user.'); + + // Verify that wildcard search works for email. + $keys = $user1->mail; + $keys = substr($keys, 0, 2) . '*' . substr($keys, 4, 2); + $edit = array('keys' => $keys); + $this->drupalPost('search/user/', $edit, t('Search')); + $this->assertText($user1->name, 'Search for email wildcard resulted in user name on page for administrative user.'); + // Create a blocked user. $blocked_user = $this->drupalCreateUser(); $edit = array('status' => 0); diff --git a/profiles/README.txt b/profiles/README.txt new file mode 100644 index 0000000..91d012b --- /dev/null +++ b/profiles/README.txt @@ -0,0 +1,28 @@ +Installation profiles define additional steps that run after the base +installation provided by Drupal core when Drupal is first installed. + +WHAT TO PLACE IN THIS DIRECTORY? +-------------------------------- + +Place downloaded and custom installation profiles in this directory. +Installation profiles are generally provided as part of a Drupal distribution. +They only impact the installation of your site. They do not have any effect on +an already running site. + +DOWNLOAD ADDITIONAL DISTRIBUTIONS +--------------------------------- + +Contributed distributions from the Drupal community may be downloaded at +https://www.drupal.org/project/project_distribution. + +MULTISITE CONFIGURATION +----------------------- + +In multisite configurations, installation profiles found in this directory are +available to all sites during their initial site installation. + +MORE INFORMATION +---------------- + +Refer to the "Installation profiles" section of the README.txt in the Drupal +root directory for further information on extending Drupal with custom profiles. diff --git a/profiles/minimal/minimal.info b/profiles/minimal/minimal.info index 6d6ce1a..1b363ab 100644 --- a/profiles/minimal/minimal.info +++ b/profiles/minimal/minimal.info @@ -5,8 +5,8 @@ core = 7.x dependencies[] = block dependencies[] = dblog -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/profiles/standard/standard.info b/profiles/standard/standard.info index 81eba8c..a3fd9e2 100644 --- a/profiles/standard/standard.info +++ b/profiles/standard/standard.info @@ -24,8 +24,8 @@ dependencies[] = field_ui dependencies[] = file dependencies[] = rdf -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info b/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info index 0b1d314..7a11e32 100644 --- a/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info +++ b/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info @@ -6,8 +6,8 @@ core = 7.x hidden = TRUE files[] = drupal_system_listing_compatible_test.test -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info b/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info index 21c1326..d2bd947 100644 --- a/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info +++ b/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info @@ -8,8 +8,8 @@ version = VERSION core = 6.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/profiles/testing/testing.info b/profiles/testing/testing.info index df8cdfe..e9fec6d 100644 --- a/profiles/testing/testing.info +++ b/profiles/testing/testing.info @@ -4,8 +4,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/robots.txt b/robots.txt index 6f20eaf..a2ee32e 100644 --- a/robots.txt +++ b/robots.txt @@ -12,12 +12,42 @@ # # For more information about the robots.txt standard, see: # http://www.robotstxt.org/robotstxt.html -# -# For syntax checking, see: -# http://www.frobee.com/robots-txt-check User-agent: * Crawl-delay: 10 +# CSS, JS, Images +Allow: /misc/*.css$ +Allow: /misc/*.css? +Allow: /misc/*.js$ +Allow: /misc/*.js? +Allow: /misc/*.gif +Allow: /misc/*.jpg +Allow: /misc/*.jpeg +Allow: /misc/*.png +Allow: /modules/*.css$ +Allow: /modules/*.css? +Allow: /modules/*.js$ +Allow: /modules/*.js? +Allow: /modules/*.gif +Allow: /modules/*.jpg +Allow: /modules/*.jpeg +Allow: /modules/*.png +Allow: /profiles/*.css$ +Allow: /profiles/*.css? +Allow: /profiles/*.js$ +Allow: /profiles/*.js? +Allow: /profiles/*.gif +Allow: /profiles/*.jpg +Allow: /profiles/*.jpeg +Allow: /profiles/*.png +Allow: /themes/*.css$ +Allow: /themes/*.css? +Allow: /themes/*.js$ +Allow: /themes/*.js? +Allow: /themes/*.gif +Allow: /themes/*.jpg +Allow: /themes/*.jpeg +Allow: /themes/*.png # Directories Disallow: /includes/ Disallow: /misc/ diff --git a/scripts/generate-d6-content.sh b/scripts/generate-d6-content.sh index fc4c68f..cd33e4d 100644 --- a/scripts/generate-d6-content.sh +++ b/scripts/generate-d6-content.sh @@ -67,6 +67,7 @@ for ($i = 0; $i < 24; $i++) { ++$voc_id; $vocabulary['name'] = "vocabulary $voc_id (i=$i)"; $vocabulary['description'] = "description of ". $vocabulary['name']; + $vocabulary['help'] = "help for ". $vocabulary['name']; $vocabulary['nodes'] = $i > 11 ? array('page' => TRUE) : array(); $vocabulary['multiple'] = $multiple[$i % 12]; $vocabulary['required'] = $required[$i % 12]; diff --git a/scripts/password-hash.sh b/scripts/password-hash.sh index 004421a..1afe438 100755 --- a/scripts/password-hash.sh +++ b/scripts/password-hash.sh @@ -1,4 +1,4 @@ -#!/usr/bin/php +#!/usr/bin/env php useDefaults(array('test_id'))->execute(); // Execute tests. -simpletest_script_execute_batch($test_id, simpletest_script_get_test_list()); +$status = simpletest_script_execute_batch($test_id, simpletest_script_get_test_list()); // Retrieve the last database prefix used for testing and the last test class // that was run from. Use the information to read the lgo file in case any @@ -100,7 +104,7 @@ if ($args['xml']) { simpletest_clean_results_table($test_id); // Test complete, exit. -exit; +exit($status); /** * Print help text. @@ -142,6 +146,8 @@ All arguments are long options. --file Run tests identified by specific file names, instead of group names. Specify the path and the extension (i.e. 'modules/user/user.test'). + --directory Run all tests found within the specified file directory. + --xml If provided, test results will be written as xml files to this path. @@ -190,6 +196,7 @@ function simpletest_script_parse_args() { 'all' => FALSE, 'class' => FALSE, 'file' => FALSE, + 'directory' => '', 'color' => FALSE, 'verbose' => FALSE, 'test_names' => array(), @@ -222,7 +229,7 @@ function simpletest_script_parse_args() { else { // Argument not found in list. simpletest_script_print_error("Unknown argument '$arg'."); - exit; + exit(SIMPLETEST_SCRIPT_EXIT_FAILURE); } } else { @@ -235,7 +242,7 @@ function simpletest_script_parse_args() { // Validate the concurrency argument if (!is_numeric($args['concurrency']) || $args['concurrency'] <= 0) { simpletest_script_print_error("--concurrency must be a strictly positive integer."); - exit; + exit(SIMPLETEST_SCRIPT_EXIT_FAILURE); } return array($args, $count); @@ -265,7 +272,7 @@ function simpletest_script_init($server_software) { else { simpletest_script_print_error('Unable to automatically determine the path to the PHP interpreter. Supply the --php command line argument.'); simpletest_script_help(); - exit(); + exit(SIMPLETEST_SCRIPT_EXIT_FAILURE); } // Get URL from arguments. @@ -310,6 +317,8 @@ function simpletest_script_init($server_software) { function simpletest_script_execute_batch($test_id, $test_classes) { global $args; + $total_status = SIMPLETEST_SCRIPT_EXIT_SUCCESS; + // Multi-process execution. $children = array(); while (!empty($test_classes) || !empty($children)) { @@ -325,7 +334,7 @@ function simpletest_script_execute_batch($test_id, $test_classes) { if (!is_resource($process)) { echo "Unable to fork test process. Aborting.\n"; - exit; + exit(SIMPLETEST_SCRIPT_EXIT_FAILURE); } // Register our new child. @@ -345,13 +354,22 @@ function simpletest_script_execute_batch($test_id, $test_classes) { if (empty($status['running'])) { // The child exited, unregister it. proc_close($child['process']); - if ($status['exitcode']) { + if ($status['exitcode'] == SIMPLETEST_SCRIPT_EXIT_FAILURE) { + if ($status['exitcode'] > $total_status) { + $total_status = $status['exitcode']; + } + } + elseif ($status['exitcode']) { + $total_status = $status['exitcode']; echo 'FATAL ' . $test_class . ': test runner returned a non-zero error code (' . $status['exitcode'] . ').' . "\n"; } + + // Remove this child. unset($children[$cid]); } } } + return $total_status; } /** @@ -374,11 +392,14 @@ function simpletest_script_run_one_test($test_id, $test_class) { simpletest_script_print($info['name'] . ' ' . _simpletest_format_summary_line($test->results) . "\n", simpletest_script_color_code($status)); // Finished, kill this runner. - exit(0); + if ($had_fails || $had_exceptions) { + exit(SIMPLETEST_SCRIPT_EXIT_FAILURE); + } + exit(SIMPLETEST_SCRIPT_EXIT_SUCCESS); } catch (Exception $e) { echo (string) $e; - exit(1); + exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION); } } @@ -432,7 +453,7 @@ function simpletest_script_get_test_list() { } simpletest_script_print_error('Test class not found: ' . $test_class); simpletest_script_print_alternatives($test_class, $all_classes, 6); - exit(1); + exit(SIMPLETEST_SCRIPT_EXIT_FAILURE); } } } @@ -451,6 +472,51 @@ function simpletest_script_get_test_list() { } } } + elseif ($args['directory']) { + // Extract test case class names from specified directory. + // Find all tests in the PSR-X structure; Drupal\$extension\Tests\*.php + // Since we do not want to hard-code too many structural file/directory + // assumptions about PSR-0/4 files and directories, we check for the + // minimal conditions only; i.e., a '*.php' file that has '/Tests/' in + // its path. + // Ignore anything from third party vendors, and ignore template files used in tests. + // And any api.php files. + $ignore = array('nomask' => '/vendor|\.tpl\.php|\.api\.php/'); + $files = array(); + if ($args['directory'][0] === '/') { + $directory = $args['directory']; + } + else { + $directory = DRUPAL_ROOT . "/" . $args['directory']; + } + $file_list = file_scan_directory($directory, '/\.php|\.test$/', $ignore); + foreach ($file_list as $file) { + // '/Tests/' can be contained anywhere in the file's path (there can be + // sub-directories below /Tests), but must be contained literally. + // Case-insensitive to match all Simpletest and PHPUnit tests: + // ./lib/Drupal/foo/Tests/Bar/Baz.php + // ./foo/src/Tests/Bar/Baz.php + // ./foo/tests/Drupal/foo/Tests/FooTest.php + // ./foo/tests/src/FooTest.php + // $file->filename doesn't give us a directory, so we use $file->uri + // Strip the drupal root directory and trailing slash off the URI + $filename = substr($file->uri, strlen(DRUPAL_ROOT)+1); + if (stripos($filename, '/Tests/')) { + $files[drupal_realpath($filename)] = 1; + } else if (stripos($filename, '.test')){ + $files[drupal_realpath($filename)] = 1; + } + } + + // Check for valid class names. + foreach ($all_tests as $class_name) { + $refclass = new ReflectionClass($class_name); + $classfile = $refclass->getFileName(); + if (isset($files[$classfile])) { + $test_list[] = $class_name; + } + } + } else { // Check for valid group names and get all valid classes in group. foreach ($args['test_names'] as $group_name) { @@ -460,7 +526,7 @@ function simpletest_script_get_test_list() { else { simpletest_script_print_error('Test group not found: ' . $group_name); simpletest_script_print_alternatives($group_name, array_keys($groups)); - exit(1); + exit(SIMPLETEST_SCRIPT_EXIT_FAILURE); } } } @@ -468,7 +534,7 @@ function simpletest_script_get_test_list() { if (empty($test_list)) { simpletest_script_print_error('No valid tests were specified.'); - exit; + exit(SIMPLETEST_SCRIPT_EXIT_FAILURE); } return $test_list; } diff --git a/themes/bartik/bartik.info b/themes/bartik/bartik.info index 60135d8..7633680 100644 --- a/themes/bartik/bartik.info +++ b/themes/bartik/bartik.info @@ -34,8 +34,8 @@ regions[footer] = Footer settings[shortcut_module_link] = 0 -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/themes/garland/garland.info b/themes/garland/garland.info index 12b8e35..3a81d19 100644 --- a/themes/garland/garland.info +++ b/themes/garland/garland.info @@ -7,8 +7,8 @@ stylesheets[all][] = style.css stylesheets[print][] = print.css settings[garland_width] = fluid -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/themes/garland/template.php b/themes/garland/template.php index 416a43a..ef7d477 100644 --- a/themes/garland/template.php +++ b/themes/garland/template.php @@ -19,22 +19,22 @@ function garland_breadcrumb($variables) { /** * Override or insert variables into the maintenance page template. */ -function garland_preprocess_maintenance_page(&$vars) { +function garland_preprocess_maintenance_page(&$variables) { // While markup for normal pages is split into page.tpl.php and html.tpl.php, // the markup for the maintenance page is all in the single // maintenance-page.tpl.php template. So, to have what's done in // garland_preprocess_html() also happen on the maintenance page, it has to be // called here. - garland_preprocess_html($vars); + garland_preprocess_html($variables); } /** * Override or insert variables into the html template. */ -function garland_preprocess_html(&$vars) { +function garland_preprocess_html(&$variables) { // Toggle fixed or fluid width. if (theme_get_setting('garland_width') == 'fluid') { - $vars['classes_array'][] = 'fluid-width'; + $variables['classes_array'][] = 'fluid-width'; } // Add conditional CSS for IE6. drupal_add_css(path_to_theme() . '/fix-ie.css', array('group' => CSS_THEME, 'browsers' => array('IE' => 'lt IE 7', '!IE' => FALSE), 'preprocess' => FALSE)); @@ -43,27 +43,27 @@ function garland_preprocess_html(&$vars) { /** * Override or insert variables into the html template. */ -function garland_process_html(&$vars) { +function garland_process_html(&$variables) { // Hook into color.module if (module_exists('color')) { - _color_html_alter($vars); + _color_html_alter($variables); } } /** * Override or insert variables into the page template. */ -function garland_preprocess_page(&$vars) { +function garland_preprocess_page(&$variables) { // Move secondary tabs into a separate variable. - $vars['tabs2'] = array( + $variables['tabs2'] = array( '#theme' => 'menu_local_tasks', - '#secondary' => $vars['tabs']['#secondary'], + '#secondary' => $variables['tabs']['#secondary'], ); - unset($vars['tabs']['#secondary']); + unset($variables['tabs']['#secondary']); - if (isset($vars['main_menu'])) { - $vars['primary_nav'] = theme('links__system_main_menu', array( - 'links' => $vars['main_menu'], + if (isset($variables['main_menu'])) { + $variables['primary_nav'] = theme('links__system_main_menu', array( + 'links' => $variables['main_menu'], 'attributes' => array( 'class' => array('links', 'inline', 'main-menu'), ), @@ -75,11 +75,11 @@ function garland_preprocess_page(&$vars) { )); } else { - $vars['primary_nav'] = FALSE; + $variables['primary_nav'] = FALSE; } - if (isset($vars['secondary_menu'])) { - $vars['secondary_nav'] = theme('links__system_secondary_menu', array( - 'links' => $vars['secondary_menu'], + if (isset($variables['secondary_menu'])) { + $variables['secondary_nav'] = theme('links__system_secondary_menu', array( + 'links' => $variables['secondary_menu'], 'attributes' => array( 'class' => array('links', 'inline', 'secondary-menu'), ), @@ -91,66 +91,66 @@ function garland_preprocess_page(&$vars) { )); } else { - $vars['secondary_nav'] = FALSE; + $variables['secondary_nav'] = FALSE; } // Prepare header. $site_fields = array(); - if (!empty($vars['site_name'])) { - $site_fields[] = $vars['site_name']; + if (!empty($variables['site_name'])) { + $site_fields[] = $variables['site_name']; } - if (!empty($vars['site_slogan'])) { - $site_fields[] = $vars['site_slogan']; + if (!empty($variables['site_slogan'])) { + $site_fields[] = $variables['site_slogan']; } - $vars['site_title'] = implode(' ', $site_fields); + $variables['site_title'] = implode(' ', $site_fields); if (!empty($site_fields)) { $site_fields[0] = '' . $site_fields[0] . ''; } - $vars['site_html'] = implode(' ', $site_fields); + $variables['site_html'] = implode(' ', $site_fields); // Set a variable for the site name title and logo alt attributes text. - $slogan_text = $vars['site_slogan']; - $site_name_text = $vars['site_name']; - $vars['site_name_and_slogan'] = $site_name_text . ' ' . $slogan_text; + $slogan_text = $variables['site_slogan']; + $site_name_text = $variables['site_name']; + $variables['site_name_and_slogan'] = $site_name_text . ' ' . $slogan_text; } /** * Override or insert variables into the node template. */ -function garland_preprocess_node(&$vars) { - $vars['submitted'] = $vars['date'] . ' — ' . $vars['name']; +function garland_preprocess_node(&$variables) { + $variables['submitted'] = $variables['date'] . ' — ' . $variables['name']; } /** * Override or insert variables into the comment template. */ -function garland_preprocess_comment(&$vars) { - $vars['submitted'] = $vars['created'] . ' — ' . $vars['author']; +function garland_preprocess_comment(&$variables) { + $variables['submitted'] = $variables['created'] . ' — ' . $variables['author']; } /** * Override or insert variables into the block template. */ -function garland_preprocess_block(&$vars) { - $vars['title_attributes_array']['class'][] = 'title'; - $vars['classes_array'][] = 'clearfix'; +function garland_preprocess_block(&$variables) { + $variables['title_attributes_array']['class'][] = 'title'; + $variables['classes_array'][] = 'clearfix'; } /** * Override or insert variables into the page template. */ -function garland_process_page(&$vars) { +function garland_process_page(&$variables) { // Hook into color.module if (module_exists('color')) { - _color_page_alter($vars); + _color_page_alter($variables); } } /** * Override or insert variables into the region template. */ -function garland_preprocess_region(&$vars) { - if ($vars['region'] == 'header') { - $vars['classes_array'][] = 'clearfix'; +function garland_preprocess_region(&$variables) { + if ($variables['region'] == 'header') { + $variables['classes_array'][] = 'clearfix'; } } diff --git a/themes/seven/seven.info b/themes/seven/seven.info index 9141455..ce455f5 100644 --- a/themes/seven/seven.info +++ b/themes/seven/seven.info @@ -13,8 +13,8 @@ regions[page_bottom] = Page bottom regions[sidebar_first] = First sidebar regions_hidden[] = sidebar_first -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/themes/stark/stark.info b/themes/stark/stark.info index 23c3611..6c1bc20 100644 --- a/themes/stark/stark.info +++ b/themes/stark/stark.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x stylesheets[all][] = layout.css -; Information added by Drupal.org packaging script on 2014-11-19 -version = "7.34" +; Information added by Drupal.org packaging script on 2018-03-28 +version = "7.58" project = "drupal" -datestamp = "1416429488" +datestamp = "1522264019" diff --git a/web.config b/web.config index 09983d9..c6fb5c8 100644 --- a/web.config +++ b/web.config @@ -6,7 +6,7 @@ - +