diff --git a/core/includes/unicode.inc b/core/includes/unicode.inc index f6b3ee70dc..6a157dc98c 100644 --- a/core/includes/unicode.inc +++ b/core/includes/unicode.inc @@ -10,7 +10,16 @@ /** * Returns Unicode library status and errors. */ +/** + * Moves unicode_requirements() logic to system_requirements(). + * + * @deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. + * + * @see https://www.drupal.org/node/2884698 + */ function unicode_requirements() { + @trigger_error('unicode_requirements() is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. There is no replacement; system_requirements() now includes the logic instead. See https://www.drupal.org/node/2884698', E_USER_DEPRECATED); + $libraries = [ Unicode::STATUS_SINGLEBYTE => t('Standard PHP'), Unicode::STATUS_MULTIBYTE => t('PHP Mbstring Extension'), diff --git a/core/lib/Drupal/Component/Serialization/YamlPecl.php b/core/lib/Drupal/Component/Serialization/YamlPecl.php index ec48a29ca6..a01f980fd7 100644 --- a/core/lib/Drupal/Component/Serialization/YamlPecl.php +++ b/core/lib/Drupal/Component/Serialization/YamlPecl.php @@ -32,6 +32,8 @@ public static function decode($raw) { // Decode binary, since Symfony YAML parser encodes binary from 3.1 // onwards. ini_set('yaml.decode_binary', 1); + // We never want to unserialize !php/object. + ini_set('yaml.decode_php', 0); $init = TRUE; } // yaml_parse() will error with an empty value. diff --git a/core/lib/Drupal/Core/Test/ObjectSerialization.php b/core/lib/Drupal/Core/Test/ObjectSerialization.php new file mode 100644 index 0000000000..99d44a55c2 --- /dev/null +++ b/core/lib/Drupal/Core/Test/ObjectSerialization.php @@ -0,0 +1,24 @@ +drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import')); $this->assertRaw(t('Configuration %name depends on the %owner module that will not be installed after import.', ['%name' => 'config_test.dynamic.second', '%owner' => 'does_not_exist'])); + + // Try to preform an update which would create a PHP object if Yaml parsing + // not securely set up. + // Perform an update. + $import = << 'config_test', + 'import' => $import, + ]; + $this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import')); + $this->assertRaw(t('Are you sure you want to update the %name @type?', ['%name' => 'second', '@type' => 'test configuration'])); + $this->drupalPostForm(NULL, [], t('Confirm')); + $entity = $storage->load('second'); + $this->assertRaw(t('The configuration was imported successfully.')); + $this->assertTrue(is_string($entity->label()), 'Entity label is a string'); + $this->assertTrue(strpos($entity->label(), 'ObjectSerialization') > 0, 'Label contains serialized object'); } /** diff --git a/core/modules/dblog/config/optional/views.view.watchdog.yml b/core/modules/dblog/config/optional/views.view.watchdog.yml new file mode 100644 index 0000000000..a805ca5988 --- /dev/null +++ b/core/modules/dblog/config/optional/views.view.watchdog.yml @@ -0,0 +1,709 @@ +langcode: en +status: true +dependencies: + module: + - dblog + - user +id: watchdog +label: Watchdog +module: views +description: 'Recent log messages' +tag: '' +base_table: watchdog +base_field: wid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access site reports' + cache: + type: none + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Filter + reset_button: true + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: false + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 50 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + style: + type: table + options: + grouping: { } + row_class: '{{ type }} {{ severity }}' + default_row_class: true + override: true + sticky: false + caption: '' + summary: '' + description: '' + columns: + nothing: nothing + wid: wid + severity: severity + type: type + timestamp: timestamp + message: message + name: name + link: link + info: + nothing: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: priority-medium + wid: + sortable: false + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: priority-low + severity: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: priority-low + type: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: priority-medium + timestamp: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: priority-low + message: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + name: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: priority-medium + link: + align: '' + separator: '' + empty_column: false + responsive: priority-low + default: wid + empty_table: false + row: + type: fields + fields: + nothing: + id: nothing + table: views + field: nothing + relationship: none + group_type: group + admin_label: Icon + label: '' + exclude: false + alter: + alter_text: true + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: icon + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: false + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: false + plugin_id: custom + wid: + id: wid + table: watchdog + field: wid + relationship: none + group_type: group + admin_label: '' + label: WID + exclude: true + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + plugin_id: standard + severity: + id: severity + table: watchdog + field: severity + relationship: none + group_type: group + admin_label: '' + label: Severity + exclude: true + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + machine_name: false + plugin_id: machine_name + type: + id: type + table: watchdog + field: type + relationship: none + group_type: group + admin_label: '' + label: Type + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + plugin_id: standard + timestamp: + id: timestamp + table: watchdog + field: timestamp + relationship: none + group_type: group + admin_label: '' + label: Date + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + date_format: short + custom_date_format: '' + timezone: '' + plugin_id: date + message: + id: message + table: watchdog + field: message + relationship: none + group_type: group + admin_label: '' + label: Message + exclude: false + alter: + alter_text: false + text: '' + make_link: true + path: 'admin/reports/dblog/event/{{ wid }}' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '{{ message }}' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 56 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: true + trim: true + preserve_tags: '' + html: true + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + replace_variables: true + plugin_id: dblog_message + name: + id: name + table: users_field_data + field: name + relationship: uid + group_type: group + admin_label: '' + label: User + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: user_name + settings: + link_to_entity: true + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: user + entity_field: name + plugin_id: field + link: + id: link + table: watchdog + field: link + relationship: none + group_type: group + admin_label: '' + label: Operations + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + plugin_id: dblog_operations + filters: + type: + id: type + table: watchdog + field: type + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: type_op + label: Type + description: '' + use_operator: false + operator: type_op + identifier: type + required: false + remember: false + multiple: true + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: dblog_types + severity: + id: severity + table: watchdog + field: severity + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: severity_op + label: Severity + description: '' + use_operator: false + operator: severity_op + identifier: severity + required: false + remember: false + multiple: true + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: in_operator + sorts: + wid: + id: wid + table: watchdog + field: wid + relationship: none + group_type: group + admin_label: '' + order: DESC + exposed: false + expose: + label: '' + plugin_id: standard + title: 'Recent log messages' + header: { } + footer: { } + empty: + area: + id: area + table: views + field: area + relationship: none + group_type: group + admin_label: '' + empty: true + tokenize: false + content: + value: 'No log messages available.' + format: basic_html + plugin_id: text + relationships: + uid: + id: uid + table: watchdog + field: uid + relationship: none + group_type: group + admin_label: User + required: false + plugin_id: standard + arguments: { } + display_extenders: { } + filter_groups: + operator: AND + groups: + 1: AND + css_class: admin-dblog + cache_metadata: + max-age: 0 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user.permissions + tags: { } + page: + display_plugin: page + id: page + display_title: Page + position: 1 + display_options: + display_extenders: { } + path: admin/reports/dblog + cache_metadata: + max-age: 0 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user.permissions + tags: { } diff --git a/core/modules/dblog/dblog.module b/core/modules/dblog/dblog.module index f33f74902f..700ceeaa6b 100644 --- a/core/modules/dblog/dblog.module +++ b/core/modules/dblog/dblog.module @@ -13,6 +13,7 @@ use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\views\ViewEntityInterface; +use Drupal\views\ViewExecutable; /** * Implements hook_help(). @@ -187,3 +188,12 @@ function dblog_view_presave(ViewEntityInterface $view) { $view->set('display', $displays); } } + +/** + * Implements hook_views_pre_render(). + */ +function dblog_views_pre_render(ViewExecutable $view) { + if (isset($view) && ($view->storage->get('base_table') == 'watchdog')) { + $view->element['#attached']['library'][] = 'dblog/drupal.dblog'; + } +} diff --git a/core/modules/dblog/dblog.post_update.php b/core/modules/dblog/dblog.post_update.php new file mode 100644 index 0000000000..6021ec6b52 --- /dev/null +++ b/core/modules/dblog/dblog.post_update.php @@ -0,0 +1,35 @@ +moduleExists('views')) { + if (!View::load('watchdog')) { + // Save the watchdog view to config. + $module_handler = \Drupal::moduleHandler(); + $optional_install_path = $module_handler->getModule('dblog')->getPath() . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY; + $storage = new FileStorage($optional_install_path); + + \Drupal::entityTypeManager() + ->getStorage('view') + ->create($storage->read('views.view.watchdog')) + ->save(); + + return t('The watchdog view has been created.'); + } + + return t("The watchdog view already exists and was not replaced. To replace the 'Recent log messages' with a view, rename the watchdog view and uninstall and install the 'Database Log' module"); + } +} diff --git a/core/modules/dblog/src/Tests/Update/DblogRecentLogsUsingViewsUpdateTest.php b/core/modules/dblog/src/Tests/Update/DblogRecentLogsUsingViewsUpdateTest.php new file mode 100644 index 0000000000..4ea6d49f9b --- /dev/null +++ b/core/modules/dblog/src/Tests/Update/DblogRecentLogsUsingViewsUpdateTest.php @@ -0,0 +1,40 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz', + ]; + } + + /** + * Ensures that update hook is run for dblog module. + */ + public function testUpdate() { + // Make sure the watchog view doesn't exist before the updates. + $view = \Drupal::entityTypeManager()->getStorage('view')->load('watchdog'); + $this->assertNull($view); + + $this->runUpdates(); + + // Ensure the watchdog view is present after run updates. + $view = \Drupal::entityTypeManager()->getStorage('view')->load('watchdog'); + $displays = $view->get('display'); + + $this->assertIdentical($displays['page']['display_options']['path'], 'admin/reports/dblog', 'Recent logs message view exists.'); + } + +} diff --git a/core/modules/dblog/tests/src/Functional/DbLogTest.php b/core/modules/dblog/tests/src/Functional/DbLogTest.php index 76bdd6151d..5b82282d6a 100644 --- a/core/modules/dblog/tests/src/Functional/DbLogTest.php +++ b/core/modules/dblog/tests/src/Functional/DbLogTest.php @@ -208,18 +208,13 @@ private function runCron() { private function generateLogEntries($count, $options = []) { global $base_root; - // This long URL makes it just a little bit harder to pass the link part of - // the test with a mix of English words and a repeating series of random - // percent-encoded Chinese characters. - $link = urldecode('/content/xo%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A-lake-isabelle'); - // Prepare the fields to be logged $log = $options + [ 'channel' => 'custom', 'message' => 'Dblog test log message', 'variables' => [], 'severity' => RfcLogLevel::NOTICE, - 'link' => $link, + 'link' => NULL, 'user' => $this->adminUser, 'uid' => $this->adminUser->id(), 'request_uri' => $base_root . \Drupal::request()->getRequestUri(), @@ -253,10 +248,10 @@ protected function clearLogsEntries() { */ protected function filterLogsEntries($type = NULL, $severity = NULL) { $edit = []; - if (!is_null($type)) { + if (isset($type)) { $edit['type[]'] = $type; } - if (!is_null($severity)) { + if (isset($severity)) { $edit['severity[]'] = $severity; } $this->drupalPostForm(NULL, $edit, t('Filter')); @@ -283,6 +278,12 @@ private function verifyReports($response = 200) { $this->assertText(t('Recent log messages'), 'DBLog report was displayed'); } + $this->drupalGet('admin/reports/dblog/confirm'); + $this->assertResponse($response); + if ($response == 200) { + $this->assertText(t('Are you sure you want to delete the recent logs?'), 'DBLog clear logs form was displayed'); + } + // View the database log page-not-found report page. $this->drupalGet('admin/reports/page-not-found'); $this->assertResponse($response); @@ -804,7 +805,7 @@ public function testOverviewLinks() { $this->drupalGet('admin/reports/dblog'); $this->assertResponse(200); // Make sure HTML tags are filtered out. - $this->assertRaw('title="alert('foo');Lorem ipsum dolor sit amet, consectetur adipiscing & elit. Entry #0"><script>alert('foo');</script>Lorem ipsum dolor sit…'); + $this->assertRaw('title="alert('foo');Lorem'); $this->assertNoRaw(""); // Make sure HTML tags are filtered out in admin/reports/dblog/event/ too. @@ -815,4 +816,22 @@ public function testOverviewLinks() { $this->assertRaw("alert('foo'); Lorem ipsum"); } + /** + * Test sorting for entries with the same timestamp. + */ + public function testSameTimestampEntries() { + $this->drupalLogin($this->adminUser); + + $this->generateLogEntries(1, ['timestamp' => 1498062000, 'type' => 'same_time', 'message' => 'First']); + $this->generateLogEntries(1, ['timestamp' => 1498062000, 'type' => 'same_time', 'message' => 'Second']); + $this->generateLogEntries(1, ['timestamp' => 1498062000, 'type' => 'same_time', 'message' => 'Third']); + + $this->drupalGet('admin/reports/dblog'); + + $entries = $this->getLogEntries(); + $this->assertEquals($entries[0]['message'], 'Third Entry #0'); + $this->assertEquals($entries[1]['message'], 'Second Entry #0'); + $this->assertEquals($entries[2]['message'], 'First Entry #0'); + } + } diff --git a/core/modules/dblog/tests/src/Functional/DbLogViewsTest.php b/core/modules/dblog/tests/src/Functional/DbLogViewsTest.php new file mode 100644 index 0000000000..d04d9ba154 --- /dev/null +++ b/core/modules/dblog/tests/src/Functional/DbLogViewsTest.php @@ -0,0 +1,70 @@ +xpath('.//table[contains(@class, "views-view-table")]/tbody/tr'); + } + + /** + * {@inheritdoc} + */ + protected function filterLogsEntries($type = NULL, $severity = NULL) { + $query = []; + if (isset($type)) { + $query['type[]'] = $type; + } + if (isset($severity)) { + $query['severity[]'] = $severity; + } + + $this->drupalGet('admin/reports/dblog', ['query' => $query]); + } + + /** + * {@inheritdoc} + */ + public function testDBLogAddAndClear() { + // Is necesary to create the basic_html format because if absent after + // delete the logs, a new log entry is created indicating that basic_html + // format do not exists. + $basic_html_format = FilterFormat::create([ + 'format' => 'basic_html', + 'name' => 'Basic HTML', + 'filters' => [ + 'filter_html' => [ + 'status' => 1, + 'settings' => [ + 'allowed_html' => '


', + ], + ], + ], + ]); + $basic_html_format->save(); + + parent::testDBLogAddAndClear(); + } + +} diff --git a/core/modules/dblog/tests/src/Kernel/DbLogControllerTest.php b/core/modules/dblog/tests/src/Kernel/DbLogControllerTest.php index d6d88faa2d..ce7490f32e 100644 --- a/core/modules/dblog/tests/src/Kernel/DbLogControllerTest.php +++ b/core/modules/dblog/tests/src/Kernel/DbLogControllerTest.php @@ -17,11 +17,49 @@ class DbLogControllerTest extends KernelTestBase { */ public static $modules = ['dblog', 'user']; + public function setUp() { + parent::setUp(); + $this->installEntitySchema('user'); + $this->installSchema('dblog', ['watchdog']); + } + + /** + * Tests links with non latin characters. + */ + public function testNonLatinCharacters() { + + $link = 'hello- + 科州的小九寨沟绝美高山湖泊酱凉拌素鸡照烧鸡黄玫瑰 + 科州的小九寨沟绝美高山湖泊酱凉拌素鸡照烧鸡黄玫瑰 + 科州的小九寨沟绝美高山湖泊酱凉拌素鸡照烧鸡黄玫瑰 + 科州的小九寨沟绝美高山湖泊酱凉拌素鸡照烧鸡黄玫瑰 + 科州的小九寨沟绝美高山湖泊酱凉拌素鸡照烧鸡黄玫瑰 + 科州的小九寨沟绝美高山湖泊酱凉拌素鸡照烧鸡黄玫瑰 + 科州的小九寨沟绝美高山湖泊酱凉拌素鸡照烧鸡黄玫瑰 + 科州的小九寨沟绝美高山湖泊酱凉拌素鸡照烧鸡黄玫瑰 + 科州的小九寨沟绝美高山湖泊酱凉拌素鸡照烧鸡黄玫瑰 + 科州的小九寨沟绝美高山湖泊酱凉拌素鸡照烧鸡黄玫瑰 + 科州的小九寨沟绝美高山湖泊酱凉拌素鸡照烧鸡黄玫瑰 + 科州的小九寨沟绝美高山湖泊酱凉拌素鸡照烧鸡黄玫瑰 + 科州的小九寨沟绝美高山湖泊酱凉拌素鸡照烧鸡黄玫瑰 + 科州的小九寨沟绝美高山湖泊酱凉拌素鸡照烧鸡黄玫瑰'; + + \Drupal::logger('my_module')->warning('test', ['link' => $link]); + + $log = \Drupal::database() + ->select('watchdog', 'w') + ->fields('w', ['link']) + ->condition('link', '', '<>') + ->execute() + ->fetchField(); + + $this->assertEquals($log, $link); + } + /** * Tests corrupted log entries can still display available data. */ public function testDbLogCorrupted() { - $this->installEntitySchema('user'); $dblog_controller = DbLogController::create($this->container); // Check message with properly serialized data. diff --git a/core/modules/file/file.module b/core/modules/file/file.module index a6fc3f6ebe..6902db6812 100644 --- a/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -904,6 +904,14 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL // If we made it this far it's safe to record this file in the database. $file->save(); $files[$i] = $file; + // Allow an anonymous user who creates a non-public file to see it. See + // \Drupal\file\FileAccessControlHandler::checkAccess(). + if ($user->isAnonymous() && $destination_scheme !== 'public') { + $session = \Drupal::request()->getSession(); + $allowed_temp_files = $session->get('anonymous_allowed_file_ids', []); + $allowed_temp_files[$file->id()] = $file->id(); + $session->set('anonymous_allowed_file_ids', $allowed_temp_files); + } } // Add files to the cache. diff --git a/core/modules/file/src/FileAccessControlHandler.php b/core/modules/file/src/FileAccessControlHandler.php index a82c460539..f4bda3cb7d 100644 --- a/core/modules/file/src/FileAccessControlHandler.php +++ b/core/modules/file/src/FileAccessControlHandler.php @@ -40,8 +40,20 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter } elseif ($entity->getOwnerId() == $account->id()) { // This case handles new nodes, or detached files. The user who uploaded - // the file can always access if it's not yet used. - return AccessResult::allowed(); + // the file can access it even if it's not yet used. + if ($account->isAnonymous()) { + // For anonymous users, only the browser session that uploaded the + // file is positively allowed access to it. See file_save_upload(). + // @todo Implement \Drupal\Core\Entity\EntityHandlerInterface so that + // services can be more properly injected. + $allowed_fids = \Drupal::service('session')->get('anonymous_allowed_file_ids', []); + if (!empty($allowed_fids[$entity->id()])) { + return AccessResult::allowed(); + } + } + else { + return AccessResult::allowed(); + } } } @@ -79,9 +91,24 @@ protected function getFileReferences(FileInterface $file) { * {@inheritdoc} */ protected function checkFieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) { - // No user can edit the status of a file. Prevents saving a new file as - // persistent before even validating it. - if ($field_definition->getName() === 'status' && $operation === 'edit') { + // Deny access to fields that should only be set on file creation, and + // "status" which should only be changed based on a file's usage. + $create_only_fields = [ + 'uri', + 'filemime', + 'filesize', + ]; + // The operation is 'edit' when the entity is being created or updated. + // Determine if the entity is being updated by checking if it is new. + $field_name = $field_definition->getName(); + if ($operation === 'edit' && $items && ($entity = $items->getEntity()) && !$entity->isNew() && in_array($field_name, $create_only_fields, TRUE)) { + return AccessResult::forbidden(); + } + // Regardless of whether the entity exists access should be denied to the + // status field as this is managed via other APIs, for example: + // - \Drupal\file\FileUsage\FileUsageBase::add() + // - \Drupal\file\Plugin\EntityReferenceSelection\FileSelection::createNewEntity() + if ($operation === 'edit' && $field_name === 'status') { return AccessResult::forbidden(); } return parent::checkFieldAccess($operation, $field_definition, $account, $items); diff --git a/core/modules/file/src/Tests/FilePrivateTest.php b/core/modules/file/src/Tests/FilePrivateTest.php index 7ecd485394..6808a4782b 100644 --- a/core/modules/file/src/Tests/FilePrivateTest.php +++ b/core/modules/file/src/Tests/FilePrivateTest.php @@ -6,6 +6,7 @@ use Drupal\Component\Utility\SafeMarkup; use Drupal\file\Entity\File; use Drupal\node\Entity\NodeType; +use Drupal\user\RoleInterface; /** * Uploads a test to a private node and checks access. @@ -110,6 +111,118 @@ public function testPrivateFile() { $this->drupalLogin($account); $this->drupalGet($file_url); $this->assertResponse(403, 'Confirmed that access is denied for another user to the temporary file.'); + + // As an anonymous user, create a temporary file with no references and + // confirm that only the session that uploaded it may view it. + $this->drupalLogout(); + user_role_change_permissions( + RoleInterface::ANONYMOUS_ID, + [ + "create $type_name content" => TRUE, + 'access content' => TRUE, + ] + ); + $test_file = $this->getTestFile('text'); + $this->drupalGet('node/add/' . $type_name); + $edit = ['files[' . $field_name . '_0]' => drupal_realpath($test_file->getFileUri())]; + $this->drupalPostForm(NULL, $edit, t('Upload')); + /** @var \Drupal\file\FileStorageInterface $file_storage */ + $file_storage = $this->container->get('entity.manager')->getStorage('file'); + $files = $file_storage->loadByProperties(['uid' => 0]); + $this->assertEqual(1, count($files), 'Loaded one anonymous file.'); + $file = end($files); + $this->assertTrue($file->isTemporary(), 'File is temporary.'); + $usage = $this->container->get('file.usage')->listUsage($file); + $this->assertFalse($usage, 'No file usage found.'); + $file_url = file_create_url($file->getFileUri()); + $this->drupalGet($file_url); + $this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the temporary file.'); + // Close the prior connection and remove the session cookie. + $this->curlClose(); + $this->curlCookies = []; + $this->cookies = []; + $this->drupalGet($file_url); + $this->assertResponse(403, 'Confirmed that another anonymous user cannot access the temporary file.'); + + // As an anonymous user, create a permanent file, then remove all + // references to the file (so that it becomes temporary again) and confirm + // that only the session that uploaded it may view it. + $test_file = $this->getTestFile('text'); + $this->drupalGet('node/add/' . $type_name); + $edit = []; + $edit['title[0][value]'] = $this->randomMachineName(); + $edit['files[' . $field_name . '_0]'] = drupal_realpath($test_file->getFileUri()); + $this->drupalPostForm(NULL, $edit, t('Save')); + $new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']); + $file_id = $new_node->{$field_name}->target_id; + $file = File::load($file_id); + $this->assertTrue($file->isPermanent(), 'File is permanent.'); + // Remove the reference to this file. + $new_node->{$field_name} = []; + $new_node->save(); + $file = File::load($file_id); + $this->assertTrue($file->isTemporary(), 'File is temporary.'); + $usage = $this->container->get('file.usage')->listUsage($file); + $this->assertFalse($usage, 'No file usage found.'); + $file_url = file_create_url($file->getFileUri()); + $this->drupalGet($file_url); + $this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the file whose references were removed.'); + // Close the prior connection and remove the session cookie. + $this->curlClose(); + $this->curlCookies = []; + $this->cookies = []; + $this->drupalGet($file_url); + $this->assertResponse(403, 'Confirmed that another anonymous user cannot access the file whose references were removed.'); + + // As an anonymous user, create a permanent file that is referenced by a + // published node and confirm that all anonymous users may view it. + $test_file = $this->getTestFile('text'); + $this->drupalGet('node/add/' . $type_name); + $edit = []; + $edit['title[0][value]'] = $this->randomMachineName(); + $edit['files[' . $field_name . '_0]'] = drupal_realpath($test_file->getFileUri()); + $this->drupalPostForm(NULL, $edit, t('Save')); + $new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']); + $file = File::load($new_node->{$field_name}->target_id); + $this->assertTrue($file->isPermanent(), 'File is permanent.'); + $usage = $this->container->get('file.usage')->listUsage($file); + $this->assertTrue($usage, 'File usage found.'); + $file_url = file_create_url($file->getFileUri()); + $this->drupalGet($file_url); + $this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the permanent file that is referenced by a published node.'); + // Close the prior connection and remove the session cookie. + $this->curlClose(); + $this->curlCookies = []; + $this->cookies = []; + $this->drupalGet($file_url); + $this->assertResponse(200, 'Confirmed that another anonymous user also has access to the permanent file that is referenced by a published node.'); + + // As an anonymous user, create a permanent file that is referenced by an + // unpublished node and confirm that no anonymous users may view it (even + // the session that uploaded the file) because they cannot view the + // unpublished node. + $test_file = $this->getTestFile('text'); + $this->drupalGet('node/add/' . $type_name); + $edit = []; + $edit['title[0][value]'] = $this->randomMachineName(); + $edit['files[' . $field_name . '_0]'] = drupal_realpath($test_file->getFileUri()); + $this->drupalPostForm(NULL, $edit, t('Save')); + $new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']); + $new_node->setPublished(FALSE); + $new_node->save(); + $file = File::load($new_node->{$field_name}->target_id); + $this->assertTrue($file->isPermanent(), 'File is permanent.'); + $usage = $this->container->get('file.usage')->listUsage($file); + $this->assertTrue($usage, 'File usage found.'); + $file_url = file_create_url($file->getFileUri()); + $this->drupalGet($file_url); + $this->assertResponse(403, 'Confirmed that the anonymous uploader cannot access the permanent file when it is referenced by an unpublished node.'); + // Close the prior connection and remove the session cookie. + $this->curlClose(); + $this->curlCookies = []; + $this->cookies = []; + $this->drupalGet($file_url); + $this->assertResponse(403, 'Confirmed that another anonymous user cannot access the permanent file when it is referenced by an unpublished node.'); } } diff --git a/core/modules/file/tests/src/Kernel/AccessTest.php b/core/modules/file/tests/src/Kernel/AccessTest.php index c71d7e80e7..cfd8e56838 100644 --- a/core/modules/file/tests/src/Kernel/AccessTest.php +++ b/core/modules/file/tests/src/Kernel/AccessTest.php @@ -85,11 +85,30 @@ public function testOnlyOwnerCanDeleteUpdateFile() { } /** - * Tests that the status field is not editable. + * Tests file entity field access. + * + * @see \Drupal\file\FileAccessControlHandler::checkFieldAccess() */ - public function testStatusFieldIsNotEditable() { + public function testCheckFieldAccess() { \Drupal::currentUser()->setAccount($this->user1); - $this->assertFalse($this->file->get('status')->access('edit')); + /** @var \Drupal\file\FileInterface $file */ + $file = File::create([ + 'uri' => 'public://test.png' + ]); + // While creating a file entity access will be allowed for create-only + // fields. + $this->assertTrue($file->get('uri')->access('edit')); + $this->assertTrue($file->get('filemime')->access('edit')); + $this->assertTrue($file->get('filesize')->access('edit')); + // Access to the status field is denied whilst creating a file entity. + $this->assertFalse($file->get('status')->access('edit')); + $file->save(); + // After saving the entity is no longer new and, therefore, access to + // create-only fields and the status field will be denied. + $this->assertFalse($file->get('uri')->access('edit')); + $this->assertFalse($file->get('filemime')->access('edit')); + $this->assertFalse($file->get('filesize')->access('edit')); + $this->assertFalse($file->get('status')->access('edit')); } /** diff --git a/core/modules/file/tests/src/Kernel/FileItemValidationTest.php b/core/modules/file/tests/src/Kernel/FileItemValidationTest.php index a5c2d69f72..c9a247bf40 100644 --- a/core/modules/file/tests/src/Kernel/FileItemValidationTest.php +++ b/core/modules/file/tests/src/Kernel/FileItemValidationTest.php @@ -45,6 +45,7 @@ protected function setUp() { 'status' => 1, ]); $this->user->save(); + $this->container->get('current_user')->setAccount($this->user); } /** @@ -85,6 +86,7 @@ public function testFileValidationConstraint($file_type) { // Test for max filesize. $file = File::create([ 'uri' => 'vfs://drupal_root/sites/default/files/test.txt', + 'uid' => $this->user->id(), ]); $file->setPermanent(); $file->save(); diff --git a/core/modules/media/tests/src/Kernel/MediaKernelTestBase.php b/core/modules/media/tests/src/Kernel/MediaKernelTestBase.php index 5d75bed69b..fa39962c36 100644 --- a/core/modules/media/tests/src/Kernel/MediaKernelTestBase.php +++ b/core/modules/media/tests/src/Kernel/MediaKernelTestBase.php @@ -7,6 +7,7 @@ use Drupal\media\Entity\Media; use Drupal\media\Entity\MediaType; use Drupal\media\MediaTypeInterface; +use Drupal\user\Entity\User; use org\bovigo\vfs\vfsStream; /** @@ -44,6 +45,13 @@ protected $testConstraintsMediaType; /** + * A user. + * + * @var \Drupal\user\UserInterface + */ + protected $user; + + /** * {@inheritdoc} */ protected function setUp() { @@ -52,6 +60,7 @@ protected function setUp() { $this->installEntitySchema('user'); $this->installEntitySchema('file'); $this->installSchema('file', 'file_usage'); + $this->installSchema('system', 'sequences'); $this->installEntitySchema('media'); $this->installConfig(['field', 'system', 'image', 'file', 'media']); @@ -59,6 +68,13 @@ protected function setUp() { $this->testMediaType = $this->createMediaType('test'); // Create a test media type with constraints. $this->testConstraintsMediaType = $this->createMediaType('test_constraints'); + + $this->user = User::create([ + 'name' => 'username', + 'status' => 1, + ]); + $this->user->save(); + $this->container->get('current_user')->setAccount($this->user); } /** @@ -117,6 +133,7 @@ protected function generateMedia($filename, MediaTypeInterface $media_type) { $file = File::create([ 'uri' => 'vfs://drupal_root/sites/default/files/' . $filename, + 'uid' => $this->user->id(), ]); $file->setPermanent(); $file->save(); diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MigrateUpgrade6Test.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MigrateUpgrade6Test.php index 0578675c05..5299f38e24 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MigrateUpgrade6Test.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MigrateUpgrade6Test.php @@ -65,7 +65,7 @@ protected function getEntityCounts() { 'user' => 7, 'user_role' => 6, 'menu_link_content' => 4, - 'view' => 14, + 'view' => 15, 'date_format' => 11, 'entity_form_display' => 19, 'entity_form_mode' => 1, diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php index 6bb46a9fec..1d412191ab 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php @@ -66,7 +66,7 @@ protected function getEntityCounts() { 'user' => 4, 'user_role' => 3, 'menu_link_content' => 7, - 'view' => 14, + 'view' => 15, 'date_format' => 11, 'entity_form_display' => 18, 'entity_form_mode' => 1, diff --git a/core/modules/rdf/tests/src/Functional/EntityReferenceFieldAttributesTest.php b/core/modules/rdf/tests/src/Functional/EntityReferenceFieldAttributesTest.php index 41a03657af..629c4aa810 100644 --- a/core/modules/rdf/tests/src/Functional/EntityReferenceFieldAttributesTest.php +++ b/core/modules/rdf/tests/src/Functional/EntityReferenceFieldAttributesTest.php @@ -3,7 +3,7 @@ namespace Drupal\Tests\rdf\Functional; use Drupal\Core\Field\FieldStorageDefinitionInterface; -use Drupal\taxonomy\Tests\TaxonomyTestBase; +use Drupal\Tests\taxonomy\Functional\TaxonomyTestBase; /** * Tests RDFa markup generation for taxonomy term fields. diff --git a/core/modules/rdf/tests/src/Functional/TaxonomyAttributesTest.php b/core/modules/rdf/tests/src/Functional/TaxonomyAttributesTest.php index ea74ce5c27..346ffb7eb9 100644 --- a/core/modules/rdf/tests/src/Functional/TaxonomyAttributesTest.php +++ b/core/modules/rdf/tests/src/Functional/TaxonomyAttributesTest.php @@ -2,7 +2,7 @@ namespace Drupal\Tests\rdf\Functional; -use Drupal\taxonomy\Tests\TaxonomyTestBase; +use Drupal\Tests\taxonomy\Functional\TaxonomyTestBase; /** * Tests the RDFa markup of Taxonomy terms. diff --git a/core/modules/system/src/Plugin/Block/SystemMenuBlock.php b/core/modules/system/src/Plugin/Block/SystemMenuBlock.php index 52daa098e4..4489eb3015 100644 --- a/core/modules/system/src/Plugin/Block/SystemMenuBlock.php +++ b/core/modules/system/src/Plugin/Block/SystemMenuBlock.php @@ -135,6 +135,25 @@ public function build() { $parameters->setMaxDepth(min($level + $depth - 1, $this->menuTree->maxDepth())); } + // For menu blocks with start level greater than 1, only show menu items + // from the current active trail. Adjust the root according to the current + // position in the menu in order to determine if we can show the subtree. + if ($level > 1) { + if (count($parameters->activeTrail) >= $level) { + // Active trail array is child-first. Reverse it, and pull the new menu + // root based on the parent of the configured start level. + $menu_trail_ids = array_reverse(array_values($parameters->activeTrail)); + $menu_root = $menu_trail_ids[$level - 1]; + $parameters->setRoot($menu_root)->setMinDepth(1); + if ($depth > 0) { + $parameters->setMaxDepth(min($level - 1 + $depth - 1, $this->menuTree->maxDepth())); + } + } + else { + return []; + } + } + $tree = $this->menuTree->load($menu_name, $parameters); $manipulators = [ ['callable' => 'menu.default_tree_manipulators:checkAccess'], diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 056419d947..90e62910ef 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -9,6 +9,7 @@ use Drupal\Component\Utility\Environment; use Drupal\Component\FileSystem\FileSystem; use Drupal\Component\Utility\OpCodeCache; +use Drupal\Component\Utility\Unicode; use Drupal\Core\Path\AliasStorage; use Drupal\Core\Url; use Drupal\Core\Database\Database; @@ -808,9 +809,46 @@ function system_requirements($phase) { } } - // Test Unicode library - include_once DRUPAL_ROOT . '/core/includes/unicode.inc'; - $requirements = array_merge($requirements, unicode_requirements()); + // Returns Unicode library status and errors. + $libraries = [ + Unicode::STATUS_SINGLEBYTE => t('Standard PHP'), + Unicode::STATUS_MULTIBYTE => t('PHP Mbstring Extension'), + Unicode::STATUS_ERROR => t('Error'), + ]; + $severities = [ + Unicode::STATUS_SINGLEBYTE => REQUIREMENT_WARNING, + Unicode::STATUS_MULTIBYTE => NULL, + Unicode::STATUS_ERROR => REQUIREMENT_ERROR, + ]; + $failed_check = Unicode::check(); + $library = Unicode::getStatus(); + + $requirements['unicode'] = [ + 'title' => t('Unicode library'), + 'value' => $libraries[$library], + 'severity' => $severities[$library], + ]; + switch ($failed_check) { + case 'mb_strlen': + $requirements['unicode']['description'] = t('Operations on Unicode strings are emulated on a best-effort basis. Install the PHP mbstring extension for improved Unicode support.'); + break; + + case 'mbstring.func_overload': + $requirements['unicode']['description'] = t('Multibyte string function overloading in PHP is active and must be disabled. Check the php.ini mbstring.func_overload setting. Please refer to the PHP mbstring documentation for more information.'); + break; + + case 'mbstring.encoding_translation': + $requirements['unicode']['description'] = t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini mbstring.encoding_translation setting. Please refer to the PHP mbstring documentation for more information.'); + break; + + case 'mbstring.http_input': + $requirements['unicode']['description'] = t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini mbstring.http_input setting. Please refer to the PHP mbstring documentation for more information.'); + break; + + case 'mbstring.http_output': + $requirements['unicode']['description'] = t('Multibyte string output conversion in PHP is active and must be disabled. Check the php.ini mbstring.http_output setting. Please refer to the PHP mbstring documentation for more information.'); + break; + } if ($phase == 'runtime') { // Check for update status module. diff --git a/core/modules/system/tests/src/Kernel/Block/SystemMenuBlockTest.php b/core/modules/system/tests/src/Kernel/Block/SystemMenuBlockTest.php index 6a9825e120..07cf684b11 100644 --- a/core/modules/system/tests/src/Kernel/Block/SystemMenuBlockTest.php +++ b/core/modules/system/tests/src/Kernel/Block/SystemMenuBlockTest.php @@ -218,9 +218,7 @@ public function testConfigLevelDepth() { 'test.example6' => [], 'test.example8' => [], ]; - $no_active_trail_expectations['level_2_only'] = [ - 'test.example7' => [], - ]; + $no_active_trail_expectations['level_2_only'] = []; $no_active_trail_expectations['level_3_only'] = []; $no_active_trail_expectations['level_1_and_beyond'] = $no_active_trail_expectations['all']; $no_active_trail_expectations['level_2_and_beyond'] = $no_active_trail_expectations['level_2_only']; @@ -266,7 +264,6 @@ public function testConfigLevelDepth() { ]; $active_trail_expectations['level_2_only'] = [ 'test.example3' => [], - 'test.example7' => [], ]; $active_trail_expectations['level_3_only'] = [ 'test.example4' => [], @@ -276,7 +273,6 @@ public function testConfigLevelDepth() { 'test.example3' => [ 'test.example4' => [], ], - 'test.example7' => [], ]; $active_trail_expectations['level_3_and_beyond'] = $active_trail_expectations['level_3_only']; foreach ($blocks as $id => $block) { diff --git a/core/modules/taxonomy/src/Tests/TaxonomyTestBase.php b/core/modules/taxonomy/src/Tests/TaxonomyTestBase.php index 838a9b02c9..b4f941da5b 100644 --- a/core/modules/taxonomy/src/Tests/TaxonomyTestBase.php +++ b/core/modules/taxonomy/src/Tests/TaxonomyTestBase.php @@ -2,8 +2,11 @@ namespace Drupal\taxonomy\Tests; +@trigger_error(__NAMESPACE__ . '\TaxonomyTestBase is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\taxonomy\Functional\TaxonomyTestBase', E_USER_DEPRECATED); + use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait; use Drupal\simpletest\WebTestBase; +use Drupal\Tests\taxonomy\Functional\TaxonomyTestTrait; /** * Provides common helper methods for Taxonomy module tests. diff --git a/core/modules/taxonomy/src/Tests/TaxonomyTestTrait.php b/core/modules/taxonomy/src/Tests/TaxonomyTestTrait.php index 744ab1b28b..8c4a667270 100644 --- a/core/modules/taxonomy/src/Tests/TaxonomyTestTrait.php +++ b/core/modules/taxonomy/src/Tests/TaxonomyTestTrait.php @@ -2,6 +2,8 @@ namespace Drupal\taxonomy\Tests; +@trigger_error(__NAMESPACE__ . '\TaxonomyTestTrait is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\taxonomy\Functional\TaxonomyTestTrait', E_USER_DEPRECATED); + use Drupal\Component\Utility\Unicode; use Drupal\Core\Language\LanguageInterface; use Drupal\taxonomy\Entity\Vocabulary; @@ -9,6 +11,9 @@ /** * Provides common helper methods for Taxonomy module tests. + * + * @deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. + * Use \Drupal\Tests\taxonomy\Functional\TaxonomyTestTrait */ trait TaxonomyTestTrait { diff --git a/core/modules/taxonomy/src/Tests/TaxonomyTranslationTestTrait.php b/core/modules/taxonomy/src/Tests/TaxonomyTranslationTestTrait.php index 3607fc6b0a..644211714a 100644 --- a/core/modules/taxonomy/src/Tests/TaxonomyTranslationTestTrait.php +++ b/core/modules/taxonomy/src/Tests/TaxonomyTranslationTestTrait.php @@ -2,6 +2,8 @@ namespace Drupal\taxonomy\Tests; +@trigger_error(__NAMESPACE__ . '\TaxonomyTranslationTestTrait is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\taxonomy\Functional\TaxonomyTranslationTestTrait', E_USER_DEPRECATED); + use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait; use Drupal\field\Entity\FieldStorageConfig; @@ -9,6 +11,9 @@ /** * Provides common testing base for translated taxonomy terms. + * + * @deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. + * Use \Drupal\Tests\taxonomy\Functional\TaxonomyTranslationTestTrait */ trait TaxonomyTranslationTestTrait { diff --git a/core/modules/taxonomy/src/Tests/TaxonomyImageTest.php b/core/modules/taxonomy/tests/src/Functional/TaxonomyImageTest.php similarity index 94% rename from core/modules/taxonomy/src/Tests/TaxonomyImageTest.php rename to core/modules/taxonomy/tests/src/Functional/TaxonomyImageTest.php index 7d2ee2b639..504c0da6c4 100644 --- a/core/modules/taxonomy/src/Tests/TaxonomyImageTest.php +++ b/core/modules/taxonomy/tests/src/Functional/TaxonomyImageTest.php @@ -1,8 +1,9 @@ xpath('//a[@href="' . $this->term1->url() . '"]'); $this->assertEqual(count($actual), 2, 'Correct number of taxonomy term1 links'); - $this->assertEqual($actual[0]->__toString(), $this->term1->label()); - $this->assertEqual($actual[1]->__toString(), $this->term1->label()); + $this->assertEqual($actual[0]->getText(), $this->term1->label()); + $this->assertEqual($actual[1]->getText(), $this->term1->label()); $this->assertEscaped($this->term1->label()); $actual = $this->xpath('//a[@href="' . $this->term2->url() . '"]'); $this->assertEqual(count($actual), 2, 'Correct number of taxonomy term2 links'); - $this->assertEqual($actual[0]->__toString(), $this->term2->label()); - $this->assertEqual($actual[1]->__toString(), $this->term2->label()); + $this->assertEqual($actual[0]->getText(), $this->term2->label()); + $this->assertEqual($actual[1]->getText(), $this->term2->label()); } /** diff --git a/core/modules/taxonomy/src/Tests/Views/TaxonomyFieldFilterTest.php b/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyFieldFilterTest.php similarity index 96% rename from core/modules/taxonomy/src/Tests/Views/TaxonomyFieldFilterTest.php rename to core/modules/taxonomy/tests/src/Functional/Views/TaxonomyFieldFilterTest.php index 86f67ff3de..1b8bbcaa51 100644 --- a/core/modules/taxonomy/src/Tests/Views/TaxonomyFieldFilterTest.php +++ b/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyFieldFilterTest.php @@ -1,11 +1,11 @@ save(); diff --git a/core/modules/taxonomy/src/Tests/Views/TaxonomyFieldTidTest.php b/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyFieldTidTest.php similarity index 94% rename from core/modules/taxonomy/src/Tests/Views/TaxonomyFieldTidTest.php rename to core/modules/taxonomy/tests/src/Functional/Views/TaxonomyFieldTidTest.php index b068f083a1..0a44d4cb2e 100644 --- a/core/modules/taxonomy/src/Tests/Views/TaxonomyFieldTidTest.php +++ b/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyFieldTidTest.php @@ -1,6 +1,6 @@ term1->set('parent', $this->term2->id()); diff --git a/core/modules/taxonomy/src/Tests/Views/TaxonomyTermArgumentDepthTest.php b/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyTermArgumentDepthTest.php similarity index 89% rename from core/modules/taxonomy/src/Tests/Views/TaxonomyTermArgumentDepthTest.php rename to core/modules/taxonomy/tests/src/Functional/Views/TaxonomyTermArgumentDepthTest.php index 311118dfea..3505784a07 100644 --- a/core/modules/taxonomy/src/Tests/Views/TaxonomyTermArgumentDepthTest.php +++ b/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyTermArgumentDepthTest.php @@ -1,6 +1,6 @@ createTerm(['name' => 'First']); diff --git a/core/modules/taxonomy/src/Tests/Views/TaxonomyTermFilterDepthTest.php b/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyTermFilterDepthTest.php similarity index 96% rename from core/modules/taxonomy/src/Tests/Views/TaxonomyTermFilterDepthTest.php rename to core/modules/taxonomy/tests/src/Functional/Views/TaxonomyTermFilterDepthTest.php index 10ccad260e..4d7a4fdf03 100644 --- a/core/modules/taxonomy/src/Tests/Views/TaxonomyTermFilterDepthTest.php +++ b/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyTermFilterDepthTest.php @@ -1,6 +1,6 @@ adminUser = $this->drupalCreateUser(['administer taxonomy', 'bypass node access']); diff --git a/core/modules/taxonomy/src/Tests/Views/TaxonomyTestBase.php b/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyTestBase.php similarity index 97% rename from core/modules/taxonomy/src/Tests/Views/TaxonomyTestBase.php rename to core/modules/taxonomy/tests/src/Functional/Views/TaxonomyTestBase.php index 6ff5514188..a2d8ec14a3 100644 --- a/core/modules/taxonomy/src/Tests/Views/TaxonomyTestBase.php +++ b/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyTestBase.php @@ -1,11 +1,11 @@ vocabularies[] = $this->vocabulary; diff --git a/core/modules/taxonomy/src/Tests/Views/TermNameFieldTest.php b/core/modules/taxonomy/tests/src/Functional/Views/TermNameFieldTest.php similarity index 96% rename from core/modules/taxonomy/src/Tests/Views/TermNameFieldTest.php rename to core/modules/taxonomy/tests/src/Functional/Views/TermNameFieldTest.php index 98d1ab4b48..6fbe0c0b2b 100644 --- a/core/modules/taxonomy/src/Tests/Views/TermNameFieldTest.php +++ b/core/modules/taxonomy/tests/src/Functional/Views/TermNameFieldTest.php @@ -1,6 +1,6 @@ config('system.site')->get('name'); $this->assertTitle(t('Don\'t Panic | @site-name', ['@site-name' => $site_name]), 'The page title contains the escaped character.'); - $this->assertNoTitle(t('Don't Panic | @site-name', ['@site-name' => $site_name]), 'The page title does not contain an encoded character.'); } /** diff --git a/core/modules/taxonomy/tests/src/Kernel/TermKernelTest.php b/core/modules/taxonomy/tests/src/Kernel/TermKernelTest.php index 0d70d840da..832e5111bf 100644 --- a/core/modules/taxonomy/tests/src/Kernel/TermKernelTest.php +++ b/core/modules/taxonomy/tests/src/Kernel/TermKernelTest.php @@ -4,7 +4,7 @@ use Drupal\taxonomy\Entity\Term; use Drupal\KernelTests\KernelTestBase; -use Drupal\taxonomy\Tests\TaxonomyTestTrait; +use Drupal\Tests\taxonomy\Functional\TaxonomyTestTrait; /** * Kernel tests for taxonomy term functions. diff --git a/core/modules/views/tests/src/Functional/TaxonomyGlossaryTest.php b/core/modules/views/tests/src/Functional/TaxonomyGlossaryTest.php index c34e6f2a02..fbf5f41458 100644 --- a/core/modules/views/tests/src/Functional/TaxonomyGlossaryTest.php +++ b/core/modules/views/tests/src/Functional/TaxonomyGlossaryTest.php @@ -2,7 +2,7 @@ namespace Drupal\Tests\views\Functional; -use Drupal\taxonomy\Tests\TaxonomyTestTrait; +use Drupal\Tests\taxonomy\Functional\TaxonomyTestTrait; /** * Tests glossary functionality of taxonomy views. diff --git a/core/tests/Drupal/Tests/Component/Serialization/YamlPeclTest.php b/core/tests/Drupal/Tests/Component/Serialization/YamlPeclTest.php index 38840310d8..c2e0a0d888 100644 --- a/core/tests/Drupal/Tests/Component/Serialization/YamlPeclTest.php +++ b/core/tests/Drupal/Tests/Component/Serialization/YamlPeclTest.php @@ -27,6 +27,16 @@ public function testEncodeDecode($data) { } /** + * Ensures that php object support is disabled. + */ + public function testObjectSupportDisabled() { + $object = new \stdClass(); + $object->foo = 'bar'; + $this->assertEquals(['O:8:"stdClass":1:{s:3:"foo";s:3:"bar";}'], YamlPecl::decode(YamlPecl::encode([$object]))); + $this->assertEquals(0, ini_get('yaml.decode_php')); + } + + /** * Tests decoding YAML node anchors. * * @covers ::decode diff --git a/core/tests/Drupal/Tests/Component/Serialization/YamlSymfonyTest.php b/core/tests/Drupal/Tests/Component/Serialization/YamlSymfonyTest.php index f9c2fb9d94..86c818c18e 100644 --- a/core/tests/Drupal/Tests/Component/Serialization/YamlSymfonyTest.php +++ b/core/tests/Drupal/Tests/Component/Serialization/YamlSymfonyTest.php @@ -63,4 +63,16 @@ public function testError() { YamlSymfony::decode('foo: [ads'); } + /** + * Ensures that php object support is disabled. + * + * @covers ::encode + */ + public function testObjectSupportDisabled() { + $this->setExpectedException(InvalidDataTypeException::class, 'Object support when dumping a YAML file has been disabled.'); + $object = new \stdClass(); + $object->foo = 'bar'; + YamlSymfony::encode([$object]); + } + } diff --git a/core/tests/Drupal/Tests/Component/Serialization/YamlTest.php b/core/tests/Drupal/Tests/Component/Serialization/YamlTest.php index 841ac05ff4..cee0a94b12 100644 --- a/core/tests/Drupal/Tests/Component/Serialization/YamlTest.php +++ b/core/tests/Drupal/Tests/Component/Serialization/YamlTest.php @@ -78,6 +78,23 @@ public function testYamlFiles($file) { } /** + * Ensures that decoding php objects is similar for PECL and Symfony. + * + * @requires extension yaml + */ + public function testObjectSupportDisabled() { + $object = new \stdClass(); + $object->foo = 'bar'; + // In core all Yaml encoding is done via Symfony and it does not support + // objects so in order to encode an object we hace to use the PECL + // extension. + // @see \Drupal\Component\Serialization\Yaml::encode() + $yaml = YamlPecl::encode([$object]); + $this->assertEquals(['O:8:"stdClass":1:{s:3:"foo";s:3:"bar";}'], YamlPecl::decode($yaml)); + $this->assertEquals(['!php/object "O:8:\"stdClass\":1:{s:3:\"foo\";s:3:\"bar\";}"'], YamlSymfony::decode($yaml)); + } + + /** * Data provider that lists all YAML files in core. */ public function providerYamlFilesInCore() {