Browse Source

updated core to 7.72

Bachir Soussi Chiadmi 3 years ago
parent
commit
d58e084fe3
100 changed files with 2538 additions and 261 deletions
  1. 43 1
      CHANGELOG.txt
  2. 1 4
      MAINTAINERS.txt
  3. 1 0
      includes/ajax.inc
  4. 7 8
      includes/batch.inc
  5. 8 6
      includes/bootstrap.inc
  6. 100 41
      includes/common.inc
  7. 3 0
      includes/file.inc
  8. 14 0
      includes/file.phar.inc
  9. 1 1
      includes/filetransfer/filetransfer.inc
  10. 9 5
      includes/form.inc
  11. 3 0
      includes/menu.inc
  12. 31 1
      includes/pager.inc
  13. 8 6
      includes/path.inc
  14. 1 1
      includes/request-sanitizer.inc
  15. 24 1
      includes/session.inc
  16. 52 15
      includes/theme.inc
  17. 19 0
      misc/ajax.js
  18. 4 0
      misc/brumann/polyfill-unserialize/.gitignore
  19. 20 0
      misc/brumann/polyfill-unserialize/.travis.yml
  20. 21 0
      misc/brumann/polyfill-unserialize/LICENSE
  21. 61 0
      misc/brumann/polyfill-unserialize/README.md
  22. 26 0
      misc/brumann/polyfill-unserialize/composer.json
  23. 25 0
      misc/brumann/polyfill-unserialize/phpunit.xml.dist
  24. 58 0
      misc/brumann/polyfill-unserialize/src/Unserialize.php
  25. 251 0
      misc/jquery-html-prefilter-3.5.0-backport.js
  26. 70 4
      misc/typo3/phar-stream-wrapper/README.md
  27. 7 1
      misc/typo3/phar-stream-wrapper/composer.json
  28. 37 0
      misc/typo3/phar-stream-wrapper/src/Collectable.php
  29. 20 4
      misc/typo3/phar-stream-wrapper/src/Helper.php
  30. 88 0
      misc/typo3/phar-stream-wrapper/src/Interceptor/ConjunctionInterceptor.php
  31. 4 4
      misc/typo3/phar-stream-wrapper/src/Interceptor/PharExtensionInterceptor.php
  32. 73 0
      misc/typo3/phar-stream-wrapper/src/Interceptor/PharMetaDataInterceptor.php
  33. 56 6
      misc/typo3/phar-stream-wrapper/src/Manager.php
  34. 59 0
      misc/typo3/phar-stream-wrapper/src/Phar/Container.php
  35. 18 0
      misc/typo3/phar-stream-wrapper/src/Phar/DeserializationException.php
  36. 176 0
      misc/typo3/phar-stream-wrapper/src/Phar/Manifest.php
  37. 254 0
      misc/typo3/phar-stream-wrapper/src/Phar/Reader.php
  38. 18 0
      misc/typo3/phar-stream-wrapper/src/Phar/ReaderException.php
  39. 65 0
      misc/typo3/phar-stream-wrapper/src/Phar/Stub.php
  40. 37 3
      misc/typo3/phar-stream-wrapper/src/PharStreamWrapper.php
  41. 24 0
      misc/typo3/phar-stream-wrapper/src/Resolvable.php
  42. 125 0
      misc/typo3/phar-stream-wrapper/src/Resolver/PharInvocation.php
  43. 156 0
      misc/typo3/phar-stream-wrapper/src/Resolver/PharInvocationCollection.php
  44. 249 0
      misc/typo3/phar-stream-wrapper/src/Resolver/PharInvocationResolver.php
  45. 3 3
      modules/aggregator/aggregator.info
  46. 3 3
      modules/aggregator/tests/aggregator_test.info
  47. 3 3
      modules/block/block.info
  48. 1 2
      modules/block/block.module
  49. 3 3
      modules/block/tests/block_test.info
  50. 3 3
      modules/block/tests/themes/block_test_theme/block_test_theme.info
  51. 3 3
      modules/blog/blog.info
  52. 3 3
      modules/book/book.info
  53. 3 3
      modules/color/color.info
  54. 3 2
      modules/color/color.module
  55. 3 3
      modules/comment/comment.info
  56. 0 3
      modules/comment/comment.install
  57. 55 0
      modules/comment/comment.test
  58. 3 3
      modules/contact/contact.info
  59. 3 3
      modules/contextual/contextual.info
  60. 3 3
      modules/dashboard/dashboard.info
  61. 3 3
      modules/dblog/dblog.info
  62. 3 3
      modules/field/field.info
  63. 3 3
      modules/field/modules/field_sql_storage/field_sql_storage.info
  64. 3 3
      modules/field/modules/list/list.info
  65. 3 3
      modules/field/modules/list/tests/list_test.info
  66. 3 3
      modules/field/modules/number/number.info
  67. 1 1
      modules/field/modules/number/number.test
  68. 3 3
      modules/field/modules/options/options.info
  69. 3 3
      modules/field/modules/text/text.info
  70. 3 3
      modules/field/tests/field_test.info
  71. 3 3
      modules/field/tests/field_test.storage.inc
  72. 4 0
      modules/field_ui/field_ui.admin.inc
  73. 3 3
      modules/field_ui/field_ui.info
  74. 6 1
      modules/field_ui/field_ui.module
  75. 3 3
      modules/file/file.info
  76. 1 1
      modules/file/tests/file.test
  77. 3 3
      modules/file/tests/file_module_test.info
  78. 2 2
      modules/filter/filter.api.php
  79. 3 3
      modules/filter/filter.info
  80. 3 3
      modules/forum/forum.info
  81. 2 1
      modules/forum/forum.module
  82. 3 3
      modules/help/help.info
  83. 3 3
      modules/image/image.info
  84. 3 3
      modules/image/tests/image_module_test.info
  85. 3 3
      modules/locale/locale.info
  86. 3 3
      modules/locale/tests/locale_test.info
  87. 3 3
      modules/menu/menu.info
  88. 3 3
      modules/node/node.info
  89. 1 1
      modules/node/node.module
  90. 3 3
      modules/node/tests/node_access_test.info
  91. 3 3
      modules/node/tests/node_test.info
  92. 3 3
      modules/node/tests/node_test_exception.info
  93. 3 3
      modules/openid/openid.info
  94. 3 3
      modules/openid/tests/openid_test.info
  95. 3 3
      modules/overlay/overlay.info
  96. 3 3
      modules/path/path.info
  97. 3 3
      modules/php/php.info
  98. 3 3
      modules/poll/poll.info
  99. 3 3
      modules/profile/profile.info
  100. 3 3
      modules/rdf/rdf.info

+ 43 - 1
CHANGELOG.txt

@@ -1,5 +1,47 @@
-Drupal 7.xx, xxxx-xx-xx (development version)
+Drupal 7.72, 2020-06-17
 -----------------------
 -----------------------
+- Fixed security issues:
+   - SA-CORE-2020-004
+
+Drupal 7.71, 2020-06-03
+-----------------------
+- Fix for jQuery Form bug in Chromium-based browsers
+- Full support for PHP 7.4
+
+Drupal 7.70, 2020-05-19
+-----------------------
+- Fixed security issues:
+   - SA-CORE-2020-002
+   - SA-CORE-2020-003
+
+Drupal 7.69, 2019-12-18
+-----------------------
+- Fixed security issues:
+   - SA-CORE-2019-012
+
+Drupal 7.68, 2019-12-04
+-----------------------
+- Fixed: Hide toolbar when printing
+- Fixed: Settings returned via ajax are not run through hook_js_alter()
+- Fixed: Use drupal_http_build_query() in drupal_http_request()
+- Fixed: DrupalRequestSanitizer not found fatal error when bootstrap phase order is changed
+- Fixed: Block web.config in .htaccess (and vice-versa)
+- Fixed: Create "scripts" element to align rendering workflow to how "styles" are handled
+- PHP 7.3: Fixed 'Cannot change session id when session is active'
+- PHP 7.1: Fixed 'A non-numeric value encountered in theme_pager()'
+- PHP 7.x: Fixed file.inc generated .htaccess does not cover PHP 7
+- PHP 5.3: Fixed check_plain() 'Invalid multibyte sequence in argument' test failures
+- Fixed: Allow passing data as array to drupal_http_request()
+- Fixed: Skip module_invoke/module_hook in calling hook_watchdog (excessive function_exist)
+- Fixed: HTTP status 200 returned for 'Additional uncaught exception thrown while handling exception'
+- Fixed: theme_table() should take an optional footer variable and produce <tfoot>
+- Fixed: 'uasort() expects parameter 1 to be array, null given in node_view_multiple()'
+- [regression] Fix default.settings.php permission
+
+Drupal 7.67, 2019-05-08
+-----------------------
+- Fixed security issues:
+   - SA-CORE-2019-007
 
 
 Drupal 7.66, 2019-04-17
 Drupal 7.66, 2019-04-17
 -----------------------
 -----------------------

+ 1 - 4
MAINTAINERS.txt

@@ -11,11 +11,8 @@ The Drupal Core branch maintainers oversee the development of Drupal as a whole.
 The branch maintainers for Drupal 7 are:
 The branch maintainers for Drupal 7 are:
 
 
 - Dries Buytaert 'dries' https://www.drupal.org/u/dries
 - 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
 - Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx
-- David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein
-- Stefan Ruijsenaars 'stefan.r' https://www.drupal.org/u/stefanr-0
-- (provisional) Pol Dellaiera 'Pol' https://www.drupal.org/u/pol
+- (provisional) Drew Webber 'mcdruid' https://www.drupal.org/u/mcdruid
 
 
 
 
 Component maintainers
 Component maintainers

+ 1 - 0
includes/ajax.inc

@@ -294,6 +294,7 @@ function ajax_render($commands = array()) {
 
 
   // Now add a command to merge changes and additions to Drupal.settings.
   // Now add a command to merge changes and additions to Drupal.settings.
   $scripts = drupal_add_js();
   $scripts = drupal_add_js();
+  drupal_alter('js', $scripts);
   if (!empty($scripts['settings'])) {
   if (!empty($scripts['settings'])) {
     $settings = $scripts['settings'];
     $settings = $scripts['settings'];
     array_unshift($commands, ajax_command_settings(drupal_array_merge_deep_array($settings['data']), TRUE));
     array_unshift($commands, ajax_command_settings(drupal_array_merge_deep_array($settings['data']), TRUE));

+ 7 - 8
includes/batch.inc

@@ -478,18 +478,17 @@ function _batch_finished() {
         $queue->deleteQueue();
         $queue->deleteQueue();
       }
       }
     }
     }
+    // Clean-up the session. Not needed for CLI updates.
+    if (isset($_SESSION)) {
+      unset($_SESSION['batches'][$batch['id']]);
+      if (empty($_SESSION['batches'])) {
+        unset($_SESSION['batches']);
+      }
+    }
   }
   }
   $_batch = $batch;
   $_batch = $batch;
   $batch = NULL;
   $batch = NULL;
 
 
-  // Clean-up the session. Not needed for CLI updates.
-  if (isset($_SESSION)) {
-    unset($_SESSION['batches'][$batch['id']]);
-    if (empty($_SESSION['batches'])) {
-      unset($_SESSION['batches']);
-    }
-  }
-
   // Redirect if needed.
   // Redirect if needed.
   if ($_batch['progressive']) {
   if ($_batch['progressive']) {
     // Revert the 'destination' that was saved in batch_process().
     // Revert the 'destination' that was saved in batch_process().

+ 8 - 6
includes/bootstrap.inc

@@ -8,7 +8,7 @@
 /**
 /**
  * The current system version.
  * The current system version.
  */
  */
-define('VERSION', '7.66');
+define('VERSION', '7.72');
 
 
 /**
 /**
  * Core API compatibility.
  * Core API compatibility.
@@ -1998,7 +1998,7 @@ function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NO
 
 
   // It is possible that the error handling will itself trigger an error. In that case, we could
   // It is possible that the error handling will itself trigger an error. In that case, we could
   // end up in an infinite loop. To avoid that, we implement a simple static semaphore.
   // end up in an infinite loop. To avoid that, we implement a simple static semaphore.
-  if (!$in_error_state && function_exists('module_implements')) {
+  if (!$in_error_state && function_exists('module_invoke_all')) {
     $in_error_state = TRUE;
     $in_error_state = TRUE;
 
 
     // The user object may not exist in all conditions, so 0 is substituted if needed.
     // The user object may not exist in all conditions, so 0 is substituted if needed.
@@ -2021,9 +2021,7 @@ function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NO
     );
     );
 
 
     // Call the logging hooks to log/process the message
     // Call the logging hooks to log/process the message
-    foreach (module_implements('watchdog') as $module) {
-      module_invoke($module, 'watchdog', $log_entry);
-    }
+    module_invoke_all('watchdog', $log_entry);
 
 
     // It is critical that the semaphore is only cleared here, in the parent
     // It is critical that the semaphore is only cleared here, in the parent
     // watchdog() call (not outside the loop), to prevent recursive execution.
     // watchdog() call (not outside the loop), to prevent recursive execution.
@@ -2518,6 +2516,7 @@ function drupal_bootstrap($phase = NULL, $new_phase = TRUE) {
 
 
       switch ($current_phase) {
       switch ($current_phase) {
         case DRUPAL_BOOTSTRAP_CONFIGURATION:
         case DRUPAL_BOOTSTRAP_CONFIGURATION:
+          require_once DRUPAL_ROOT . '/includes/request-sanitizer.inc';
           _drupal_bootstrap_configuration();
           _drupal_bootstrap_configuration();
           break;
           break;
 
 
@@ -2622,6 +2621,10 @@ function _drupal_exception_handler($exception) {
     _drupal_log_error(_drupal_decode_exception($exception), TRUE);
     _drupal_log_error(_drupal_decode_exception($exception), TRUE);
   }
   }
   catch (Exception $exception2) {
   catch (Exception $exception2) {
+    // Add a 500 status code in case an exception was thrown before the 500
+    // status could be set (e.g. while loading a maintenance theme from cache).
+    drupal_add_http_header('Status', '500 Internal Server Error');
+
     // Another uncaught exception was thrown while handling the first one.
     // Another uncaught exception was thrown while handling the first one.
     // If we are displaying errors, then do so with no possibility of a further uncaught exception being thrown.
     // If we are displaying errors, then do so with no possibility of a further uncaught exception being thrown.
     if (error_displayable()) {
     if (error_displayable()) {
@@ -2647,7 +2650,6 @@ function _drupal_bootstrap_configuration() {
   drupal_settings_initialize();
   drupal_settings_initialize();
 
 
   // Sanitize unsafe keys from the request.
   // Sanitize unsafe keys from the request.
-  require_once DRUPAL_ROOT . '/includes/request-sanitizer.inc';
   DrupalRequestSanitizer::sanitize();
   DrupalRequestSanitizer::sanitize();
 }
 }
 
 

+ 100 - 41
includes/common.inc

@@ -391,7 +391,7 @@ function drupal_add_feed($url = NULL, $title = '') {
  */
  */
 function drupal_get_feeds($delimiter = "\n") {
 function drupal_get_feeds($delimiter = "\n") {
   $feeds = drupal_add_feed();
   $feeds = drupal_add_feed();
-  return implode($feeds, $delimiter);
+  return implode($delimiter, $feeds);
 }
 }
 
 
 /**
 /**
@@ -684,7 +684,10 @@ function drupal_goto($path = '', array $options = array(), $http_response_code =
   // We do not allow absolute URLs to be passed via $_GET, as this can be an attack vector.
   // We do not allow absolute URLs to be passed via $_GET, as this can be an attack vector.
   if (isset($_GET['destination']) && !url_is_external($_GET['destination'])) {
   if (isset($_GET['destination']) && !url_is_external($_GET['destination'])) {
     $destination = drupal_parse_url($_GET['destination']);
     $destination = drupal_parse_url($_GET['destination']);
-    $path = $destination['path'];
+    // Double check the path derived by drupal_parse_url() is not external.
+    if (!url_is_external($destination['path'])) {
+      $path = $destination['path'];
+    }
     $options['query'] = $destination['query'];
     $options['query'] = $destination['query'];
     $options['fragment'] = $destination['fragment'];
     $options['fragment'] = $destination['fragment'];
   }
   }
@@ -760,9 +763,10 @@ function drupal_access_denied() {
  *   (optional) An array that can have one or more of the following elements:
  *   (optional) An array that can have one or more of the following elements:
  *   - headers: An array containing request headers to send as name/value pairs.
  *   - headers: An array containing request headers to send as name/value pairs.
  *   - method: A string containing the request method. Defaults to 'GET'.
  *   - method: A string containing the request method. Defaults to 'GET'.
- *   - data: A string containing the request body, formatted as
- *     'param=value&param=value&...'; to generate this, use http_build_query().
- *     Defaults to NULL.
+ *   - data: An array containing the values for the request body or a string
+ *     containing the request body, formatted as
+ *     'param=value&param=value&...'; to generate this, use
+ *     drupal_http_build_query(). Defaults to NULL.
  *   - max_redirects: An integer representing how many times a redirect
  *   - max_redirects: An integer representing how many times a redirect
  *     may be followed. Defaults to 3.
  *     may be followed. Defaults to 3.
  *   - timeout: A float representing the maximum number of seconds the function
  *   - timeout: A float representing the maximum number of seconds the function
@@ -788,7 +792,7 @@ function drupal_access_denied() {
  *     easy access the array keys are returned in lower case.
  *     easy access the array keys are returned in lower case.
  *   - data: A string containing the response body that was received.
  *   - data: A string containing the response body that was received.
  *
  *
- * @see http_build_query()
+ * @see drupal_http_build_query()
  */
  */
 function drupal_http_request($url, array $options = array()) {
 function drupal_http_request($url, array $options = array()) {
   // Allow an alternate HTTP client library to replace Drupal's default
   // Allow an alternate HTTP client library to replace Drupal's default
@@ -930,6 +934,11 @@ function drupal_http_request($url, array $options = array()) {
     $path .= '?' . $uri['query'];
     $path .= '?' . $uri['query'];
   }
   }
 
 
+  // Convert array $options['data'] to query string.
+  if (is_array($options['data'])) {
+    $options['data'] = drupal_http_build_query($options['data']);
+  }
+
   // Only add Content-Length if we actually have any content or if it is a POST
   // Only add Content-Length if we actually have any content or if it is a POST
   // or PUT request. Some non-standard servers get confused by Content-Length in
   // or PUT request. Some non-standard servers get confused by Content-Length in
   // at least HEAD/GET requests, and Squid always requires Content-Length in
   // at least HEAD/GET requests, and Squid always requires Content-Length in
@@ -3734,7 +3743,7 @@ function _drupal_build_css_path($matches, $base = NULL) {
   }
   }
 
 
   // Prefix with base and remove '../' segments where possible.
   // Prefix with base and remove '../' segments where possible.
-  $path = $_base . $matches[1];
+  $path = $_base . (isset($matches[1]) ? $matches[1] : '');
   $last = '';
   $last = '';
   while ($path != $last) {
   while ($path != $last) {
     $last = $path;
     $last = $path;
@@ -4441,12 +4450,54 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
     }
     }
   }
   }
 
 
-  $output = '';
-  // The index counter is used to keep aggregated and non-aggregated files in
-  // order by weight.
-  $index = 1;
-  $processed = array();
-  $files = array();
+  // Sort the JavaScript so that it appears in the correct order.
+  uasort($items, 'drupal_sort_css_js');
+
+  // Provide the page with information about the individual JavaScript files
+  // used, information not otherwise available when aggregation is enabled.
+  $setting['ajaxPageState']['js'] = array_fill_keys(array_keys($items), 1);
+  unset($setting['ajaxPageState']['js']['settings']);
+  drupal_add_js($setting, 'setting');
+
+  // If we're outputting the header scope, then this might be the final time
+  // that drupal_get_js() is running, so add the setting to this output as well
+  // as to the drupal_add_js() cache. If $items['settings'] doesn't exist, it's
+  // because drupal_get_js() was intentionally passed a $javascript argument
+  // stripped off settings, potentially in order to override how settings get
+  // output, so in this case, do not add the setting to this output.
+  if ($scope == 'header' && isset($items['settings'])) {
+    $items['settings']['data'][] = $setting;
+  }
+
+  $elements = array(
+    '#type' => 'scripts',
+    '#items' => $items,
+  );
+
+  return drupal_render($elements);
+}
+
+/**
+ * The #pre_render callback for the "scripts" element.
+ *
+ * This callback adds elements needed for <script> tags to be rendered.
+ *
+ * @param array $elements
+ *   A render array containing:
+ *   - '#items': The JS items as returned by drupal_add_js() and altered by
+ *     drupal_get_js().
+ *
+ * @return array
+ *   The $elements variable passed as argument with two more children keys:
+ *     - "scripts": contains the Javascript items
+ *     - "settings": contains the Javascript settings items.
+ *   If those keys are already existing, then the items will be appended and
+ *   their keys will be preserved.
+ *
+ * @see drupal_get_js()
+ * @see drupal_add_js()
+ */
+function drupal_pre_render_scripts(array $elements) {
   $preprocess_js = (variable_get('preprocess_js', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
   $preprocess_js = (variable_get('preprocess_js', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
 
 
   // A dummy query-string is added to filenames, to gain control over
   // A dummy query-string is added to filenames, to gain control over
@@ -4467,34 +4518,29 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
   // third-party code might require the use of a different query string.
   // third-party code might require the use of a different query string.
   $js_version_string = variable_get('drupal_js_version_query_string', 'v=');
   $js_version_string = variable_get('drupal_js_version_query_string', 'v=');
 
 
-  // Sort the JavaScript so that it appears in the correct order.
-  uasort($items, 'drupal_sort_css_js');
+  $files = array();
 
 
-  // Provide the page with information about the individual JavaScript files
-  // used, information not otherwise available when aggregation is enabled.
-  $setting['ajaxPageState']['js'] = array_fill_keys(array_keys($items), 1);
-  unset($setting['ajaxPageState']['js']['settings']);
-  drupal_add_js($setting, 'setting');
+  $scripts = isset($elements['scripts']) ? $elements['scripts'] : array();
+  $scripts += array('#weight' => 0);
 
 
-  // If we're outputting the header scope, then this might be the final time
-  // that drupal_get_js() is running, so add the setting to this output as well
-  // as to the drupal_add_js() cache. If $items['settings'] doesn't exist, it's
-  // because drupal_get_js() was intentionally passed a $javascript argument
-  // stripped off settings, potentially in order to override how settings get
-  // output, so in this case, do not add the setting to this output.
-  if ($scope == 'header' && isset($items['settings'])) {
-    $items['settings']['data'][] = $setting;
-  }
+  $settings = isset($elements['settings']) ? $elements['settings'] : array();
+  $settings += array('#weight' => $scripts['#weight'] + 10);
+
+  // The index counter is used to keep aggregated and non-aggregated files in
+  // order by weight. Use existing scripts count as a starting point.
+  $index = count(element_children($scripts)) + 1;
 
 
   // Loop through the JavaScript to construct the rendered output.
   // Loop through the JavaScript to construct the rendered output.
   $element = array(
   $element = array(
+    '#type' => 'html_tag',
     '#tag' => 'script',
     '#tag' => 'script',
     '#value' => '',
     '#value' => '',
     '#attributes' => array(
     '#attributes' => array(
       'type' => 'text/javascript',
       'type' => 'text/javascript',
     ),
     ),
   );
   );
-  foreach ($items as $item) {
+
+  foreach ($elements['#items'] as $item) {
     $query_string =  empty($item['version']) ? $default_query_string : $js_version_string . $item['version'];
     $query_string =  empty($item['version']) ? $default_query_string : $js_version_string . $item['version'];
 
 
     switch ($item['type']) {
     switch ($item['type']) {
@@ -4503,7 +4549,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
         $js_element['#value_prefix'] = $embed_prefix;
         $js_element['#value_prefix'] = $embed_prefix;
         $js_element['#value'] = 'jQuery.extend(Drupal.settings, ' . drupal_json_encode(drupal_array_merge_deep_array($item['data'])) . ");";
         $js_element['#value'] = 'jQuery.extend(Drupal.settings, ' . drupal_json_encode(drupal_array_merge_deep_array($item['data'])) . ");";
         $js_element['#value_suffix'] = $embed_suffix;
         $js_element['#value_suffix'] = $embed_suffix;
-        $output .= theme('html_tag', array('element' => $js_element));
+        $settings[] = $js_element;
         break;
         break;
 
 
       case 'inline':
       case 'inline':
@@ -4514,7 +4560,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
         $js_element['#value_prefix'] = $embed_prefix;
         $js_element['#value_prefix'] = $embed_prefix;
         $js_element['#value'] = $item['data'];
         $js_element['#value'] = $item['data'];
         $js_element['#value_suffix'] = $embed_suffix;
         $js_element['#value_suffix'] = $embed_suffix;
-        $processed[$index++] = theme('html_tag', array('element' => $js_element));
+        $scripts[$index++] = $js_element;
         break;
         break;
 
 
       case 'file':
       case 'file':
@@ -4525,7 +4571,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
           }
           }
           $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?';
           $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?';
           $js_element['#attributes']['src'] = file_create_url($item['data']) . $query_string_separator . ($item['cache'] ? $query_string : REQUEST_TIME);
           $js_element['#attributes']['src'] = file_create_url($item['data']) . $query_string_separator . ($item['cache'] ? $query_string : REQUEST_TIME);
-          $processed[$index++] = theme('html_tag', array('element' => $js_element));
+          $scripts[$index++] = $js_element;
         }
         }
         else {
         else {
           // By increasing the index for each aggregated file, we maintain
           // By increasing the index for each aggregated file, we maintain
@@ -4536,7 +4582,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
           // leading to better front-end performance of a website as a whole.
           // leading to better front-end performance of a website as a whole.
           // See drupal_add_js() for details.
           // See drupal_add_js() for details.
           $key = 'aggregate_' . $item['group'] . '_' . $item['every_page'] . '_' . $index;
           $key = 'aggregate_' . $item['group'] . '_' . $item['every_page'] . '_' . $index;
-          $processed[$key] = '';
+          $scripts[$key] = '';
           $files[$key][$item['data']] = $item;
           $files[$key][$item['data']] = $item;
         }
         }
         break;
         break;
@@ -4548,7 +4594,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
           $js_element['#attributes']['defer'] = 'defer';
           $js_element['#attributes']['defer'] = 'defer';
         }
         }
         $js_element['#attributes']['src'] = $item['data'];
         $js_element['#attributes']['src'] = $item['data'];
-        $processed[$index++] = theme('html_tag', array('element' => $js_element));
+        $scripts[$index++] = $js_element;
         break;
         break;
     }
     }
   }
   }
@@ -4563,14 +4609,18 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
         $preprocess_file = file_create_url($uri);
         $preprocess_file = file_create_url($uri);
         $js_element = $element;
         $js_element = $element;
         $js_element['#attributes']['src'] = $preprocess_file;
         $js_element['#attributes']['src'] = $preprocess_file;
-        $processed[$key] = theme('html_tag', array('element' => $js_element));
+        $scripts[$key] = $js_element;
       }
       }
     }
     }
   }
   }
 
 
-  // Keep the order of JS files consistent as some are preprocessed and others are not.
-  // Make sure any inline or JS setting variables appear last after libraries have loaded.
-  return implode('', $processed) . $output;
+  // Keep the order of JS files consistent as some are preprocessed and others
+  // are not. Make sure any inline or JS setting variables appear last after
+  // libraries have loaded.
+  $element['scripts'] = $scripts;
+  $element['settings'] = $settings;
+
+  return $element;
 }
 }
 
 
 /**
 /**
@@ -6606,7 +6656,7 @@ function element_children(&$elements, $sort = FALSE) {
   $children = array();
   $children = array();
   $sortable = FALSE;
   $sortable = FALSE;
   foreach ($elements as $key => $value) {
   foreach ($elements as $key => $value) {
-    if ($key === '' || $key[0] !== '#') {
+    if (is_int($key) || $key === '' || $key[0] !== '#') {
       $children[$key] = $value;
       $children[$key] = $value;
       if (is_array($value) && isset($value['#weight'])) {
       if (is_array($value) && isset($value['#weight'])) {
         $sortable = TRUE;
         $sortable = TRUE;
@@ -6952,7 +7002,16 @@ function drupal_common_theme() {
       'variables' => array(),
       'variables' => array(),
     ),
     ),
     'table' => array(
     'table' => array(
-      'variables' => array('header' => NULL, 'rows' => NULL, 'attributes' => array(), 'caption' => NULL, 'colgroups' => array(), 'sticky' => TRUE, 'empty' => ''),
+      'variables' => array(
+        'header' => NULL,
+        'footer' => NULL,
+        'rows' => NULL,
+        'attributes' => array(),
+        'caption' => NULL,
+        'colgroups' => array(),
+        'sticky' => TRUE,
+        'empty' => '',
+      ),
     ),
     ),
     'tablesort_indicator' => array(
     'tablesort_indicator' => array(
       'variables' => array('style' => NULL),
       'variables' => array('style' => NULL),

+ 3 - 0
includes/file.inc

@@ -532,6 +532,9 @@ SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
 <IfModule mod_php5.c>
 <IfModule mod_php5.c>
   php_flag engine off
   php_flag engine off
 </IfModule>
 </IfModule>
+<IfModule mod_php7.c>
+  php_flag engine off
+</IfModule>
 EOF;
 EOF;
 
 
   if ($private) {
   if ($private) {

+ 14 - 0
includes/file.phar.inc

@@ -18,7 +18,21 @@ function file_register_phar_wrapper() {
   include_once $directory . '/Helper.php';
   include_once $directory . '/Helper.php';
   include_once $directory . '/Manager.php';
   include_once $directory . '/Manager.php';
   include_once $directory . '/PharStreamWrapper.php';
   include_once $directory . '/PharStreamWrapper.php';
+  include_once $directory . '/Collectable.php';
+  include_once $directory . '/Interceptor/ConjunctionInterceptor.php';
+  include_once $directory . '/Interceptor/PharMetaDataInterceptor.php';
+  include_once $directory . '/Phar/Container.php';
+  include_once $directory . '/Phar/DeserializationException.php';
+  include_once $directory . '/Phar/Manifest.php';
+  include_once $directory . '/Phar/Reader.php';
+  include_once $directory . '/Phar/ReaderException.php';
+  include_once $directory . '/Phar/Stub.php';
+  include_once $directory . '/Resolvable.php';
+  include_once $directory . '/Resolver/PharInvocation.php';
+  include_once $directory . '/Resolver/PharInvocationCollection.php';
+  include_once $directory . '/Resolver/PharInvocationResolver.php';
   include_once DRUPAL_ROOT . '/misc/typo3/drupal-security/PharExtensionInterceptor.php';
   include_once DRUPAL_ROOT . '/misc/typo3/drupal-security/PharExtensionInterceptor.php';
+  include_once DRUPAL_ROOT . '/misc/brumann/polyfill-unserialize/src/Unserialize.php';
 
 
   // Set up a stream wrapper to handle insecurities due to PHP's built-in
   // Set up a stream wrapper to handle insecurities due to PHP's built-in
   // phar stream wrapper.
   // phar stream wrapper.

+ 1 - 1
includes/filetransfer/filetransfer.inc

@@ -301,7 +301,7 @@ abstract class FileTransfer {
     $parts = explode('/', $path);
     $parts = explode('/', $path);
     $chroot = '';
     $chroot = '';
     while (count($parts)) {
     while (count($parts)) {
-      $check = implode($parts, '/');
+      $check = implode('/', $parts);
       if ($this->isFile($check . '/' . drupal_basename(__FILE__))) {
       if ($this->isFile($check . '/' . drupal_basename(__FILE__))) {
         // Remove the trailing slash.
         // Remove the trailing slash.
         return substr($chroot, 0, -1);
         return substr($chroot, 0, -1);

+ 9 - 5
includes/form.inc

@@ -1135,12 +1135,8 @@ function drupal_prepare_form($form_id, &$form, &$form_state) {
  * Helper function to call form_set_error() if there is a token error.
  * Helper function to call form_set_error() if there is a token error.
  */
  */
 function _drupal_invalid_token_set_form_error() {
 function _drupal_invalid_token_set_form_error() {
-  $path = current_path();
-  $query = drupal_get_query_parameters();
-  $url = url($path, array('query' => $query));
-
   // Setting this error will cause the form to fail validation.
   // Setting this error will cause the form to fail validation.
-  form_set_error('form_token', t('The form has become outdated. Copy any unsaved work in the form below and then <a href="@link">reload this page</a>.', array('@link' => $url)));
+  form_set_error('form_token', t('The form has become outdated. Press the back button, copy any unsaved work in the form, and then reload the page.'));
 }
 }
 
 
 /**
 /**
@@ -1181,6 +1177,11 @@ function drupal_validate_form($form_id, &$form, &$form_state) {
   if (!empty($form['#token'])) {
   if (!empty($form['#token'])) {
     if (!drupal_valid_token($form_state['values']['form_token'], $form['#token']) || !empty($form_state['invalid_token'])) {
     if (!drupal_valid_token($form_state['values']['form_token'], $form['#token']) || !empty($form_state['invalid_token'])) {
       _drupal_invalid_token_set_form_error();
       _drupal_invalid_token_set_form_error();
+      // Ignore all submitted values.
+      $form_state['input'] = array();
+      $_POST = array();
+      // Make sure file uploads do not get processed.
+      $_FILES = array();
       // Stop here and don't run any further validation handlers, because they
       // Stop here and don't run any further validation handlers, because they
       // could invoke non-safe operations which opens the door for CSRF
       // could invoke non-safe operations which opens the door for CSRF
       // vulnerabilities.
       // vulnerabilities.
@@ -1848,6 +1849,9 @@ function form_builder($form_id, &$element, &$form_state) {
           _drupal_invalid_token_set_form_error();
           _drupal_invalid_token_set_form_error();
           // This value is checked in _form_builder_handle_input_element().
           // This value is checked in _form_builder_handle_input_element().
           $form_state['invalid_token'] = TRUE;
           $form_state['invalid_token'] = TRUE;
+          // Ignore all submitted values.
+          $form_state['input'] = array();
+          $_POST = array();
           // Make sure file uploads do not get processed.
           // Make sure file uploads do not get processed.
           $_FILES = array();
           $_FILES = array();
         }
         }

+ 3 - 0
includes/menu.inc

@@ -2483,6 +2483,9 @@ function menu_link_get_preferred($path = NULL, $selected_menu = NULL) {
     // untranslated paths). Afterwards, the most relevant path is picked from
     // untranslated paths). Afterwards, the most relevant path is picked from
     // the menus, ordered by menu preference.
     // the menus, ordered by menu preference.
     $item = menu_get_item($path);
     $item = menu_get_item($path);
+    if ($item === FALSE) {
+      return FALSE;
+    }
     $path_candidates = array();
     $path_candidates = array();
     // 1. The current item href.
     // 1. The current item href.
     $path_candidates[$item['href']] = $item['href'];
     $path_candidates[$item['href']] = $item['href'];

+ 31 - 1
includes/pager.inc

@@ -321,9 +321,19 @@ function theme_pager($variables) {
   $tags = $variables['tags'];
   $tags = $variables['tags'];
   $element = $variables['element'];
   $element = $variables['element'];
   $parameters = $variables['parameters'];
   $parameters = $variables['parameters'];
-  $quantity = $variables['quantity'];
+  $quantity = empty($variables['quantity']) ? 0 : $variables['quantity'];
   global $pager_page_array, $pager_total;
   global $pager_page_array, $pager_total;
 
 
+  // Nothing to do if there is no pager.
+  if (!isset($pager_page_array[$element]) || !isset($pager_total[$element])) {
+    return;
+  }
+
+  // Nothing to do if there is only one page.
+  if ($pager_total[$element] <= 1) {
+    return;
+  }
+
   // Calculate various markers within this pager piece:
   // Calculate various markers within this pager piece:
   // Middle is used to "center" pages around the current page.
   // Middle is used to "center" pages around the current page.
   $pager_middle = ceil($quantity / 2);
   $pager_middle = ceil($quantity / 2);
@@ -455,6 +465,11 @@ function theme_pager_first($variables) {
   global $pager_page_array;
   global $pager_page_array;
   $output = '';
   $output = '';
 
 
+  // Nothing to do if there is no pager.
+  if (!isset($pager_page_array[$element])) {
+    return;
+  }
+
   // If we are anywhere but the first page
   // If we are anywhere but the first page
   if ($pager_page_array[$element] > 0) {
   if ($pager_page_array[$element] > 0) {
     $output = theme('pager_link', array('text' => $text, 'page_new' => pager_load_array(0, $element, $pager_page_array), 'element' => $element, 'parameters' => $parameters));
     $output = theme('pager_link', array('text' => $text, 'page_new' => pager_load_array(0, $element, $pager_page_array), 'element' => $element, 'parameters' => $parameters));
@@ -485,6 +500,11 @@ function theme_pager_previous($variables) {
   global $pager_page_array;
   global $pager_page_array;
   $output = '';
   $output = '';
 
 
+  // Nothing to do if there is no pager.
+  if (!isset($pager_page_array[$element])) {
+    return;
+  }
+
   // If we are anywhere but the first page
   // If we are anywhere but the first page
   if ($pager_page_array[$element] > 0) {
   if ($pager_page_array[$element] > 0) {
     $page_new = pager_load_array($pager_page_array[$element] - $interval, $element, $pager_page_array);
     $page_new = pager_load_array($pager_page_array[$element] - $interval, $element, $pager_page_array);
@@ -524,6 +544,11 @@ function theme_pager_next($variables) {
   global $pager_page_array, $pager_total;
   global $pager_page_array, $pager_total;
   $output = '';
   $output = '';
 
 
+  // Nothing to do if there is no pager.
+  if (!isset($pager_page_array[$element]) || !isset($pager_total[$element])) {
+    return;
+  }
+
   // If we are anywhere but the last page
   // If we are anywhere but the last page
   if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
   if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
     $page_new = pager_load_array($pager_page_array[$element] + $interval, $element, $pager_page_array);
     $page_new = pager_load_array($pager_page_array[$element] + $interval, $element, $pager_page_array);
@@ -560,6 +585,11 @@ function theme_pager_last($variables) {
   global $pager_page_array, $pager_total;
   global $pager_page_array, $pager_total;
   $output = '';
   $output = '';
 
 
+  // Nothing to do if there is no pager.
+  if (!isset($pager_page_array[$element]) || !isset($pager_total[$element])) {
+    return;
+  }
+
   // If we are anywhere but the last page
   // If we are anywhere but the last page
   if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
   if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
     $output = theme('pager_link', array('text' => $text, 'page_new' => pager_load_array($pager_total[$element] - 1, $element, $pager_page_array), 'element' => $element, 'parameters' => $parameters));
     $output = theme('pager_link', array('text' => $text, 'page_new' => pager_load_array($pager_total[$element] - 1, $element, $pager_page_array), 'element' => $element, 'parameters' => $parameters));

+ 8 - 6
includes/path.inc

@@ -466,13 +466,15 @@ function path_delete($criteria) {
     $criteria = array('pid' => $criteria);
     $criteria = array('pid' => $criteria);
   }
   }
   $path = path_load($criteria);
   $path = path_load($criteria);
-  $query = db_delete('url_alias');
-  foreach ($criteria as $field => $value) {
-    $query->condition($field, $value);
+  if (isset($path['source'])) {
+    $query = db_delete('url_alias');
+    foreach ($criteria as $field => $value) {
+      $query->condition($field, $value);
+    }
+    $query->execute();
+    module_invoke_all('path_delete', $path);
+    drupal_clear_path_cache($path['source']);
   }
   }
-  $query->execute();
-  module_invoke_all('path_delete', $path);
-  drupal_clear_path_cache($path['source']);
 }
 }
 
 
 /**
 /**

+ 1 - 1
includes/request-sanitizer.inc

@@ -99,7 +99,7 @@ class DrupalRequestSanitizer {
   protected static function stripDangerousValues($input, array $whitelist, array &$sanitized_keys) {
   protected static function stripDangerousValues($input, array $whitelist, array &$sanitized_keys) {
     if (is_array($input)) {
     if (is_array($input)) {
       foreach ($input as $key => $value) {
       foreach ($input as $key => $value) {
-        if ($key !== '' && $key[0] === '#' && !in_array($key, $whitelist, TRUE)) {
+        if ($key !== '' && is_string($key) && $key[0] === '#' && !in_array($key, $whitelist, TRUE)) {
           unset($input[$key]);
           unset($input[$key]);
           $sanitized_keys[] = $key;
           $sanitized_keys[] = $key;
         }
         }

+ 24 - 1
includes/session.inc

@@ -371,8 +371,11 @@ function drupal_session_regenerate() {
 
 
   if (drupal_session_started()) {
   if (drupal_session_started()) {
     $old_session_id = session_id();
     $old_session_id = session_id();
+    _drupal_session_regenerate_existing();
+  }
+  else {
+    session_id(drupal_random_key());
   }
   }
-  session_id(drupal_random_key());
 
 
   if (isset($old_session_id)) {
   if (isset($old_session_id)) {
     $params = session_get_cookie_params();
     $params = session_get_cookie_params();
@@ -412,6 +415,26 @@ function drupal_session_regenerate() {
   date_default_timezone_set(drupal_get_user_timezone());
   date_default_timezone_set(drupal_get_user_timezone());
 }
 }
 
 
+/**
+ * Regenerates an existing session.
+ */
+function _drupal_session_regenerate_existing() {
+  global $user;
+  // Preserve existing settings for the saving of sessions.
+  $original_save_session_status = drupal_save_session();
+  // Turn off saving of sessions.
+  drupal_save_session(FALSE);
+  session_write_close();
+  drupal_session_started(FALSE);
+  // Preserve the user object, as starting a new session will reset it.
+  $original_user = $user;
+  session_id(drupal_random_key());
+  drupal_session_start();
+  $user = $original_user;
+  // Restore the original settings for the saving of sessions.
+  drupal_save_session($original_save_session_status);
+}
+
 /**
 /**
  * Session handler assigned by session_set_save_handler().
  * Session handler assigned by session_set_save_handler().
  *
  *

+ 52 - 15
includes/theme.inc

@@ -1911,7 +1911,7 @@ function theme_breadcrumb($variables) {
 /**
 /**
  * Returns HTML for a table.
  * Returns HTML for a table.
  *
  *
- * @param $variables
+ * @param array $variables
  *   An associative array containing:
  *   An associative array containing:
  *   - header: An array containing the table headers. Each element of the array
  *   - header: An array containing the table headers. Each element of the array
  *     can be either a localized string or an associative array with the
  *     can be either a localized string or an associative array with the
@@ -1948,6 +1948,11 @@ function theme_breadcrumb($variables) {
  *       )
  *       )
  *     );
  *     );
  *     @endcode
  *     @endcode
+ *   - footer: An array of table rows which will be printed within a <tfoot>
+ *     tag, in the same format as the rows element (see above).
+ *     The structure is the same the one defined for the "rows" key except
+ *     that the no_striping boolean has no effect, there is no rows striping
+ *     for the table footer.
  *   - attributes: An array of HTML attributes to apply to the table tag.
  *   - attributes: An array of HTML attributes to apply to the table tag.
  *   - caption: A localized string to use for the <caption> tag.
  *   - caption: A localized string to use for the <caption> tag.
  *   - colgroups: An array of column groups. Each element of the array can be
  *   - colgroups: An array of column groups. Each element of the array can be
@@ -1984,8 +1989,11 @@ function theme_breadcrumb($variables) {
  *   - sticky: Use a "sticky" table header.
  *   - sticky: Use a "sticky" table header.
  *   - empty: The message to display in an extra row if table does not have any
  *   - empty: The message to display in an extra row if table does not have any
  *     rows.
  *     rows.
+ *
+ * @return string
+ *   The HTML output.
  */
  */
-function theme_table($variables) {
+function theme_table(array $variables) {
   $header = $variables['header'];
   $header = $variables['header'];
   $rows = $variables['rows'];
   $rows = $variables['rows'];
   $attributes = $variables['attributes'];
   $attributes = $variables['attributes'];
@@ -2049,17 +2057,27 @@ function theme_table($variables) {
     if (!empty($header)) {
     if (!empty($header)) {
       foreach ($header as $header_cell) {
       foreach ($header as $header_cell) {
         if (is_array($header_cell)) {
         if (is_array($header_cell)) {
-          $header_count += isset($header_cell['colspan']) ? $header_cell['colspan'] : 1;
+          $header_count += isset($header_cell['colspan']) ?
+            $header_cell['colspan'] : 1;
         }
         }
         else {
         else {
           $header_count++;
           $header_count++;
         }
         }
       }
       }
     }
     }
-    $rows[] = array(array('data' => $empty, 'colspan' => $header_count, 'class' => array('empty', 'message')));
+    $rows[] = array(
+      array(
+        'data' => $empty,
+        'colspan' => $header_count,
+        'class' => array(
+          'empty',
+          'message'
+        ),
+      ),
+    );
   }
   }
 
 
-  // Format the table header:
+  // Format the table header.
   if (!empty($header)) {
   if (!empty($header)) {
     $ts = tablesort_init($header);
     $ts = tablesort_init($header);
     // HTML requires that the thead tag has tr tags in it followed by tbody
     // HTML requires that the thead tag has tr tags in it followed by tbody
@@ -2069,23 +2087,39 @@ function theme_table($variables) {
       $cell = tablesort_header($cell, $header, $ts);
       $cell = tablesort_header($cell, $header, $ts);
       $output .= _theme_table_cell($cell, TRUE);
       $output .= _theme_table_cell($cell, TRUE);
     }
     }
-    // Using ternary operator to close the tags based on whether or not there are rows
+    // Using ternary operator to close the tags based on whether
+    // or not there are rows.
     $output .= (!empty($rows) ? " </tr></thead>\n" : "</tr>\n");
     $output .= (!empty($rows) ? " </tr></thead>\n" : "</tr>\n");
   }
   }
   else {
   else {
     $ts = array();
     $ts = array();
   }
   }
 
 
-  // Format the table rows:
+  // Format the table and footer rows.
+  $sections = array();
+
   if (!empty($rows)) {
   if (!empty($rows)) {
-    $output .= "<tbody>\n";
+    $sections['tbody'] = $rows;
+  }
+
+  if (!empty($variables['footer'])) {
+    $sections['tfoot'] = $variables['footer'];
+  }
+
+  // tbody and tfoot have the same structure and are built using the same
+  // procedure.
+  foreach ($sections as $tag => $content) {
+    $output .= "<" . $tag . ">\n";
     $flip = array('even' => 'odd', 'odd' => 'even');
     $flip = array('even' => 'odd', 'odd' => 'even');
     $class = 'even';
     $class = 'even';
-    foreach ($rows as $number => $row) {
-      // Check if we're dealing with a simple or complex row
+    $default_no_striping = ($tag === 'tfoot');
+
+    foreach ($content as $number => $row) {
+      // Check if we're dealing with a simple or complex row.
       if (isset($row['data'])) {
       if (isset($row['data'])) {
         $cells = $row['data'];
         $cells = $row['data'];
-        $no_striping = isset($row['no_striping']) ? $row['no_striping'] : FALSE;
+        $no_striping = isset($row['no_striping']) ?
+          $row['no_striping'] : $default_no_striping;
 
 
         // Set the attributes array and exclude 'data' and 'no_striping'.
         // Set the attributes array and exclude 'data' and 'no_striping'.
         $attributes = $row;
         $attributes = $row;
@@ -2095,16 +2129,17 @@ function theme_table($variables) {
       else {
       else {
         $cells = $row;
         $cells = $row;
         $attributes = array();
         $attributes = array();
-        $no_striping = FALSE;
+        $no_striping = $default_no_striping;
       }
       }
+
       if (!empty($cells)) {
       if (!empty($cells)) {
-        // Add odd/even class
+        // Add odd/even class.
         if (!$no_striping) {
         if (!$no_striping) {
           $class = $flip[$class];
           $class = $flip[$class];
           $attributes['class'][] = $class;
           $attributes['class'][] = $class;
         }
         }
 
 
-        // Build row
+        // Build row.
         $output .= ' <tr' . drupal_attributes($attributes) . '>';
         $output .= ' <tr' . drupal_attributes($attributes) . '>';
         $i = 0;
         $i = 0;
         foreach ($cells as $cell) {
         foreach ($cells as $cell) {
@@ -2114,10 +2149,12 @@ function theme_table($variables) {
         $output .= " </tr>\n";
         $output .= " </tr>\n";
       }
       }
     }
     }
-    $output .= "</tbody>\n";
+
+    $output .= "</" . $tag . ">\n";
   }
   }
 
 
   $output .= "</table>\n";
   $output .= "</table>\n";
+
   return $output;
   return $output;
 }
 }
 
 

+ 19 - 0
misc/ajax.js

@@ -198,6 +198,25 @@ Drupal.ajax = function (base, element, element_settings) {
     type: 'POST'
     type: 'POST'
   };
   };
 
 
+  // For multipart forms (e.g., file uploads), jQuery Form targets the form
+  // submission to an iframe instead of using an XHR object. The initial "src"
+  // of the iframe, prior to the form submission, is set to options.iframeSrc.
+  // "about:blank" is the semantically correct, standards-compliant, way to
+  // initialize a blank iframe; however, some old IE versions (possibly only 6)
+  // incorrectly report a mixed content warning when iframes with an
+  // "about:blank" src are added to a parent document with an https:// origin.
+  // jQuery Form works around this by defaulting to "javascript:false" instead,
+  // but that breaks on Chrome 83, so here we force the semantically correct
+  // behavior for all browsers except old IE.
+  // @see https://www.drupal.org/project/drupal/issues/3143016
+  // @see https://github.com/jquery-form/form/blob/df9cb101b9c9c085c8d75ad980c7ff1cf62063a1/jquery.form.js#L68
+  // @see https://bugs.chromium.org/p/chromium/issues/detail?id=1084874
+  // @see https://html.spec.whatwg.org/multipage/browsers.html#creating-browsing-contexts
+  // @see https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
+  if (navigator.userAgent.indexOf("MSIE") === -1) {
+    ajax.options.iframeSrc = 'about:blank';
+  }
+
   // Bind the ajaxSubmit function to the element event.
   // Bind the ajaxSubmit function to the element event.
   $(ajax.element).bind(element_settings.event, function (event) {
   $(ajax.element).bind(element_settings.event, function (event) {
     if (!Drupal.settings.urlIsAjaxTrusted[ajax.url] && !Drupal.urlIsLocal(ajax.url)) {
     if (!Drupal.settings.urlIsAjaxTrusted[ajax.url] && !Drupal.urlIsLocal(ajax.url)) {

+ 4 - 0
misc/brumann/polyfill-unserialize/.gitignore

@@ -0,0 +1,4 @@
+/vendor/
+/phpunit.xml
+/.composer.lock
+

+ 20 - 0
misc/brumann/polyfill-unserialize/.travis.yml

@@ -0,0 +1,20 @@
+language: php
+
+sudo: false
+
+php:
+  - '5.3'
+  - '5.4'
+  - '5.5'
+  - '5.6'
+  - '7.0'
+  - '7.1'
+
+before_install:
+  - phpenv config-rm xdebug.ini
+  - composer self-update
+
+install:
+  - composer install
+
+script: phpunit

+ 21 - 0
misc/brumann/polyfill-unserialize/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2016 Denis Brumann
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 61 - 0
misc/brumann/polyfill-unserialize/README.md

@@ -0,0 +1,61 @@
+Polyfill unserialize [![Build Status](https://travis-ci.org/dbrumann/polyfill-unserialize.svg?branch=master)](https://travis-ci.org/dbrumann/polyfill-unserialize)
+===
+
+Backports unserialize options introduced in PHP 7.0 to older PHP versions.
+This was originally designed as a Proof of Concept for Symfony Issue [#21090](https://github.com/symfony/symfony/pull/21090).
+
+You can use this package in projects that rely on PHP versions older than PHP 7.0.
+In case you are using PHP 7.0+ the original `unserialize()` will be used instead.
+
+From the [documentation](https://secure.php.net/manual/en/function.unserialize.php):
+
+> Warning: Do not pass untrusted user input to unserialize(). Unserialization can
+> result in code being loaded and executed due to object instantiation
+> and autoloading, and a malicious user may be able to exploit this.
+
+This warning holds true even when `allowed_classes` is used.
+
+Requirements
+------------
+
+ - PHP 5.3+
+
+Installation
+------------
+
+You can install this package via composer:
+
+```
+composer require brumann/polyfill-unserialize "^1.0"
+```
+
+Known Issues
+------------
+
+There is a mismatch in behavior when `allowed_classes` in `$options` is not
+of the correct type (array or boolean). PHP 7.1 will issue a warning, whereas
+PHP 7.0 will not. I opted to copy the behavior of the former.
+
+Tests
+-----
+
+You can run the test suite using PHPUnit. It is intentionally not bundled as
+dev dependency to make sure this package has the lowest restrictions on the
+implementing system as possible.
+
+Please read the [PHPUnit Manual](https://phpunit.de/manual/current/en/installation.html)
+for information how to install it on your system.
+
+You can run the test suite as follows:
+
+```
+phpunit -c phpunit.xml.dist tests/
+```
+
+Contributing
+------------
+
+This package is considered feature complete. As such I will likely not update it
+unless there are security issues.
+
+Should you find any bugs or have questions, feel free to submit an Issue or a Pull Request.

+ 26 - 0
misc/brumann/polyfill-unserialize/composer.json

@@ -0,0 +1,26 @@
+{
+    "name": "brumann/polyfill-unserialize",
+    "description": "Backports unserialize options introduced in PHP 7.0 to older PHP versions.",
+    "type": "library",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Denis Brumann",
+            "email": "denis.brumann@sensiolabs.de"
+        }
+    ],
+    "autoload": {
+        "psr-4": {
+            "Brumann\\Polyfill\\": "src/"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "Tests\\Brumann\\Polyfill\\": "tests/"
+        }
+    },
+    "minimum-stability": "stable",
+    "require": {
+        "php": "^5.3|^7.0"
+    }
+}

+ 25 - 0
misc/brumann/polyfill-unserialize/phpunit.xml.dist

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
+    backupGlobals="false"
+    colors="true"
+    bootstrap="vendor/autoload.php"
+>
+    <php>
+        <ini name="error_reporting" value="-1" />
+    </php>
+
+    <testsuites>
+        <testsuite name="Brumann\Polyfill Test Suite">
+            <directory>./tests/</directory>
+        </testsuite>
+    </testsuites>
+
+    <filter>
+        <whitelist>
+            <directory>./src/</directory>
+        </whitelist>
+    </filter>
+</phpunit>

+ 58 - 0
misc/brumann/polyfill-unserialize/src/Unserialize.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace Brumann\Polyfill;
+
+final class Unserialize
+{
+    /**
+     * @see https://secure.php.net/manual/en/function.unserialize.php
+     *
+     * @param string $serialized Serialized data
+     * @param array $options Associative array containing options
+     *
+     * @return mixed
+     */
+    public static function unserialize($serialized, array $options = array())
+    {
+        if (PHP_VERSION_ID >= 70000) {
+            return \unserialize($serialized, $options);
+        }
+        if (!array_key_exists('allowed_classes', $options)) {
+            $options['allowed_classes'] = true;
+        }
+        $allowedClasses = $options['allowed_classes'];
+        if (true === $allowedClasses) {
+            return \unserialize($serialized);
+        }
+        if (false === $allowedClasses) {
+            $allowedClasses = array();
+        }
+        if (!is_array($allowedClasses)) {
+            trigger_error(
+                'unserialize(): allowed_classes option should be array or boolean',
+                E_USER_WARNING
+            );
+            $allowedClasses = array();
+        }
+
+        $sanitizedSerialized = preg_replace_callback(
+            '/(^|;)O:\d+:"([^"]*)":(\d+):{/',
+            function ($match) use ($allowedClasses) {
+                list($completeMatch, $leftBorder, $className, $objectSize) = $match;
+                if (in_array($className, $allowedClasses)) {
+                    return $completeMatch;
+                } else {
+                    return sprintf(
+                        '%sO:22:"__PHP_Incomplete_Class":%d:{s:27:"__PHP_Incomplete_Class_Name";%s',
+                        $leftBorder,
+                        $objectSize + 1, // size of object + 1 for added string
+                        \serialize($className)
+                    );
+                }
+            },
+            $serialized
+        );
+
+        return \unserialize($sanitizedSerialized);
+    }
+}

+ 251 - 0
misc/jquery-html-prefilter-3.5.0-backport.js

@@ -0,0 +1,251 @@
+/**
+ * For jQuery versions less than 3.5.0, this replaces the jQuery.htmlPrefilter()
+ * function with one that fixes these security vulnerabilities while also
+ * retaining the pre-3.5.0 behavior where it's safe to do so.
+ * - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11022
+ * - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11023
+ *
+ * Additionally, for jQuery versions that do not have a jQuery.htmlPrefilter()
+ * function (1.x prior to 1.12 and 2.x prior to 2.2), this adds it, and
+ * extends the functions that need to call it to do so.
+ *
+ * Drupal core's jQuery version is 1.4.4, but jQuery Update can provide a
+ * different version, so this covers all versions between 1.4.4 and 3.4.1.
+ * The GitHub links in the code comments below link to jQuery 1.5 code, because
+ * 1.4.4 isn't on GitHub, but the referenced code didn't change from 1.4.4 to
+ * 1.5.
+ */
+
+(function (jQuery) {
+
+  // Parts of this backport differ by jQuery version.
+  var versionParts = jQuery.fn.jquery.split('.');
+  var majorVersion = parseInt(versionParts[0]);
+  var minorVersion = parseInt(versionParts[1]);
+
+  // No backport is needed if we're already on jQuery 3.5 or higher.
+  if ( (majorVersion > 3) || (majorVersion === 3 && minorVersion >= 5) ) {
+    return;
+  }
+
+  // Prior to jQuery 3.5, jQuery converted XHTML-style self-closing tags to
+  // their XML equivalent: e.g., "<div />" to "<div></div>". This is
+  // problematic for several reasons, including that it's vulnerable to XSS
+  // attacks. However, since this was jQuery's behavior for many years, many
+  // Drupal modules and jQuery plugins may be relying on it. Therefore, we
+  // preserve that behavior, but for a limited set of tags only, that we believe
+  // to not be vulnerable. This is the set of HTML tags that satisfy all of the
+  // following conditions:
+  // - In DOMPurify's list of HTML tags. If an HTML tag isn't safe enough to
+  //   appear in that list, then we don't want to mess with it here either.
+  //   @see https://github.com/cure53/DOMPurify/blob/2.0.11/dist/purify.js#L128
+  // - A normal element (not a void, template, text, or foreign element).
+  //   @see https://html.spec.whatwg.org/multipage/syntax.html#elements-2
+  // - An element that is still defined by the current HTML specification
+  //   (not a deprecated element), because we do not want to rely on how
+  //   browsers parse deprecated elements.
+  //   @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element
+  // - Not 'html', 'head', or 'body', because this pseudo-XHTML expansion is
+  //   designed for fragments, not entire documents.
+  // - Not 'colgroup', because due to an idiosyncrasy of jQuery's original
+  //   regular expression, it didn't match on colgroup, and we don't want to
+  //   introduce a behavior change for that.
+  var selfClosingTagsToReplace = [
+    'a', 'abbr', 'address', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo',
+    'blockquote', 'button', 'canvas', 'caption', 'cite', 'code', 'data',
+    'datalist', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt', 'em',
+    'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3',
+    'h4', 'h5', 'h6', 'header', 'hgroup', 'i', 'ins', 'kbd', 'label', 'legend',
+    'li', 'main', 'map', 'mark', 'menu', 'meter', 'nav', 'ol', 'optgroup',
+    'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt',
+    'ruby', 's', 'samp', 'section', 'select', 'small', 'source', 'span',
+    'strong', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th',
+    'thead', 'time', 'tr', 'u', 'ul', 'var', 'video'
+  ];
+
+  // Define regular expressions for <TAG/> and <TAG ATTRIBUTES/>. Doing this as
+  // two expressions makes it easier to target <a/> without also targeting
+  // every tag that starts with "a".
+  var xhtmlRegExpGroup = '(' + selfClosingTagsToReplace.join('|') + ')';
+  var whitespace = '[\\x20\\t\\r\\n\\f]';
+  var rxhtmlTagWithoutSpaceOrAttributes = new RegExp('<' + xhtmlRegExpGroup + '\\/>', 'gi');
+  var rxhtmlTagWithSpaceAndMaybeAttributes = new RegExp('<' + xhtmlRegExpGroup + '(' + whitespace + '[^>]*)\\/>', 'gi');
+
+  // jQuery 3.5 also fixed a vulnerability for when </select> appears within
+  // an <option> or <optgroup>, but it did that in local code that we can't
+  // backport directly. Instead, we filter such cases out. To do so, we need to
+  // determine when jQuery would otherwise invoke the vulnerable code, which it
+  // uses this regular expression to determine. The regular expression changed
+  // for version 3.0.0 and changed again for 3.4.0.
+  // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L4958
+  // @see https://github.com/jquery/jquery/blob/3.0.0/dist/jquery.js#L4584
+  // @see https://github.com/jquery/jquery/blob/3.4.0/dist/jquery.js#L4712
+  var rtagName;
+  if (majorVersion < 3) {
+    rtagName = /<([\w:]+)/;
+  }
+  else if (minorVersion < 4) {
+    rtagName = /<([a-z][^\/\0>\x20\t\r\n\f]+)/i;
+  }
+  else {
+    rtagName = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i;
+  }
+
+  // The regular expression that jQuery uses to determine which self-closing
+  // tags to expand to open and close tags. This is vulnerable, because it
+  // matches all tag names except the few excluded ones. We only use this
+  // expression for determining vulnerability. The expression changed for
+  // version 3, but we only need to check for vulnerability in versions 1 and 2,
+  // so we use the expression from those versions.
+  // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L4957
+  var rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi;
+
+  jQuery.extend({
+    htmlPrefilter: function (html) {
+      // This is how jQuery determines the first tag in the HTML.
+      // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L5521
+      var tag = ( rtagName.exec( html ) || [ "", "" ] )[ 1 ].toLowerCase();
+
+      // It is not valid HTML for <option> or <optgroup> to have <select> as
+      // either a descendant or sibling, and attempts to inject one can cause
+      // XSS on jQuery versions before 3.5. Since this is invalid HTML and a
+      // possible XSS attack, reject the entire string.
+      // @see https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11023
+      if ((tag === 'option' || tag === 'optgroup') && html.match(/<\/?select/i)) {
+        html = '';
+      }
+
+      // Retain jQuery's prior to 3.5 conversion of pseudo-XHTML, but for only
+      // the tags in the `selfClosingTagsToReplace` list defined above.
+      // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L5518
+      // @see https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11022
+      html = html.replace(rxhtmlTagWithoutSpaceOrAttributes, "<$1></$1>");
+      html = html.replace(rxhtmlTagWithSpaceAndMaybeAttributes, "<$1$2></$1>");
+
+      // Prior to jQuery 1.12 and 2.2, this function gets called (via code later
+      // in this file) in addition to, rather than instead of, the unsafe
+      // expansion of self-closing tags (including ones not in the list above).
+      // We can't prevent that unsafe expansion from running, so instead we
+      // check to make sure that it doesn't affect the DOM returned by the
+      // browser's parsing logic. If it does affect it, then it's vulnerable to
+      // XSS, so we reject the entire string.
+      if ( (majorVersion === 1 && minorVersion < 12) || (majorVersion === 2 && minorVersion < 2) ) {
+        var htmlRisky = html.replace(rxhtmlTag, "<$1></$2>");
+        if (htmlRisky !== html) {
+          // Even though htmlRisky and html are different strings, they might
+          // represent the same HTML structure once parsed, in which case,
+          // htmlRisky is actually safe. We can ask the browser to parse both
+          // to find out, but the browser can't parse table fragments (e.g., a
+          // root-level "<td>"), so we need to wrap them. We just need this
+          // technique to work on all supported browsers; we don't need to
+          // copy from the specific jQuery version we're using.
+          // @see https://github.com/jquery/jquery/blob/3.5.1/dist/jquery.js#L4939
+          var wrapMap = {
+            thead: [ 1, "<table>", "</table>" ],
+            col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
+            tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+            td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+          };
+          wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+          wrapMap.th = wrapMap.td;
+
+          // Function to wrap HTML into something that a browser can parse.
+          // @see https://github.com/jquery/jquery/blob/3.5.1/dist/jquery.js#L5032
+          var getWrappedHtml = function (html) {
+            var wrap = wrapMap[tag];
+            if (wrap) {
+              html = wrap[1] + html + wrap[2];
+            }
+            return html;
+          };
+
+          // Function to return canonical HTML after parsing it. This parses
+          // only; it doesn't execute scripts.
+          // @see https://github.com/jquery/jquery-migrate/blob/3.3.0/src/jquery/manipulation.js#L5
+          var getParsedHtml = function (html) {
+            var doc = window.document.implementation.createHTMLDocument( "" );
+            doc.body.innerHTML = html;
+            return doc.body ? doc.body.innerHTML : '';
+          };
+
+          // If the browser couldn't parse either one successfully, or if
+          // htmlRisky parses differently than html, then html is vulnerable,
+          // so reject it.
+          var htmlParsed = getParsedHtml(getWrappedHtml(html));
+          var htmlRiskyParsed = getParsedHtml(getWrappedHtml(htmlRisky));
+          if (htmlRiskyParsed === '' || htmlParsed === '' || (htmlRiskyParsed !== htmlParsed)) {
+            html = '';
+          }
+        }
+      }
+
+      return html;
+    }
+  });
+
+  // Prior to jQuery 1.12 and 2.2, jQuery.clean(), jQuery.buildFragment(), and
+  // jQuery.fn.html() did not call jQuery.htmlPrefilter(), so we add that.
+  if ( (majorVersion === 1 && minorVersion < 12) || (majorVersion === 2 && minorVersion < 2) ) {
+    // Filter the HTML coming into jQuery.fn.html().
+    var fnOriginalHtml = jQuery.fn.html;
+    jQuery.fn.extend({
+      // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L5147
+      html: function (value) {
+        if (typeof value === "string") {
+          value = jQuery.htmlPrefilter(value);
+        }
+        // .html() can be called as a setter (with an argument) or as a getter
+        // (without an argument), so invoke fnOriginalHtml() the same way that
+        // we were invoked.
+        return fnOriginalHtml.apply(this, arguments.length ? [value] : []);
+      }
+    });
+
+    // The regular expression that jQuery uses to determine if a string is HTML.
+    // Used by both clean() and buildFragment().
+    // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L4960
+    var rhtml = /<|&#?\w+;/;
+
+    // Filter HTML coming into:
+    // - jQuery.clean() for versions prior to 1.9.
+    // - jQuery.buildFragment() for 1.9 and above.
+    //
+    // The looping constructs in the two functions might be essentially
+    // identical, but they're each expressed here in the way that most closely
+    // matches their original expression in jQuery, so that we filter all of
+    // the items and only the items that jQuery will treat as HTML strings.
+    if (majorVersion === 1 && minorVersion < 9) {
+      var originalClean = jQuery.clean;
+      jQuery.extend({
+        // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L5493
+        'clean': function (elems, context, fragment, scripts) {
+          for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+            if ( typeof elem === "string" && rhtml.test( elem ) ) {
+              elems[i] = elem = jQuery.htmlPrefilter(elem);
+            }
+          }
+          return originalClean.call(this, elems, context, fragment, scripts);
+        }
+      });
+    }
+    else {
+      var originalBuildFragment = jQuery.buildFragment;
+      jQuery.extend({
+        // @see https://github.com/jquery/jquery/blob/1.9.0/jquery.js#L6419
+        'buildFragment': function (elems, context, scripts, selection) {
+          var l = elems.length;
+          for ( var i = 0; i < l; i++ ) {
+            var elem = elems[i];
+            if (elem || elem === 0) {
+              if ( jQuery.type( elem ) !== "object" && rhtml.test( elem ) ) {
+                elems[i] = elem = jQuery.htmlPrefilter(elem);
+              }
+            }
+          }
+          return originalBuildFragment.call(this, elems, context, scripts, selection);
+        }
+      });
+    }
+  }
+
+})(jQuery);

+ 70 - 4
misc/typo3/phar-stream-wrapper/README.md

@@ -1,5 +1,6 @@
 [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/TYPO3/phar-stream-wrapper/badges/quality-score.png?b=v2)](https://scrutinizer-ci.com/g/TYPO3/phar-stream-wrapper/?branch=v2)
 [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/TYPO3/phar-stream-wrapper/badges/quality-score.png?b=v2)](https://scrutinizer-ci.com/g/TYPO3/phar-stream-wrapper/?branch=v2)
 [![Travis CI Build Status](https://travis-ci.org/TYPO3/phar-stream-wrapper.svg?branch=v2)](https://travis-ci.org/TYPO3/phar-stream-wrapper)
 [![Travis CI Build Status](https://travis-ci.org/TYPO3/phar-stream-wrapper.svg?branch=v2)](https://travis-ci.org/TYPO3/phar-stream-wrapper)
+[![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/q4ls5tg4w1d6sf4i/branch/v2?svg=true)](https://ci.appveyor.com/project/ohader/phar-stream-wrapper)
 
 
 # PHP Phar Stream Wrapper
 # PHP Phar Stream Wrapper
 
 
@@ -21,9 +22,11 @@ and has been addressed concerning the specific attack vector and for this generi
 `PharStreamWrapper` in TYPO3 versions 7.6.30 LTS, 8.7.17 LTS and 9.3.1 on 12th
 `PharStreamWrapper` in TYPO3 versions 7.6.30 LTS, 8.7.17 LTS and 9.3.1 on 12th
 July 2018.
 July 2018.
 
 
-* https://typo3.org/security/advisory/typo3-core-sa-2018-002/
 * https://blog.secarma.co.uk/labs/near-phar-dangerous-unserialization-wherever-you-are
 * https://blog.secarma.co.uk/labs/near-phar-dangerous-unserialization-wherever-you-are
 * https://youtu.be/GePBmsNJw6Y
 * https://youtu.be/GePBmsNJw6Y
+* https://typo3.org/security/advisory/typo3-psa-2018-001/
+* https://typo3.org/security/advisory/typo3-psa-2019-007/
+* https://typo3.org/security/advisory/typo3-psa-2019-008/
 
 
 ## License
 ## License
 
 
@@ -63,7 +66,7 @@ adjusted to according requirements.
 
 
 ```
 ```
 $behavior = new \TYPO3\PharStreamWrapper\Behavior();
 $behavior = new \TYPO3\PharStreamWrapper\Behavior();
-Manager::initialize(
+\TYPO3\PharStreamWrapper\Manager::initialize(
     $behavior->withAssertion(new PharExtensionInterceptor())
     $behavior->withAssertion(new PharExtensionInterceptor())
 );
 );
 
 
@@ -90,7 +93,7 @@ if (in_array('phar', stream_get_wrappers())) {
   + `COMMAND_UNLINK`
   + `COMMAND_UNLINK`
   + `COMMAND_URL_STAT`
   + `COMMAND_URL_STAT`
 
 
-## Interceptor
+## Interceptors
 
 
 The following interceptor is shipped with the package and ready to use in order
 The following interceptor is shipped with the package and ready to use in order
 to block any Phar invocation of files not having a `.phar` suffix. Besides that
 to block any Phar invocation of files not having a `.phar` suffix. Besides that
@@ -137,9 +140,72 @@ class PharExtensionInterceptor implements Assertable
 }
 }
 ```
 ```
 
 
+### ConjunctionInterceptor
+
+This interceptor combines multiple interceptors implementing `Assertable`.
+It succeeds when all nested interceptors succeed as well (logical `AND`).
+
+```
+$behavior = new \TYPO3\PharStreamWrapper\Behavior();
+\TYPO3\PharStreamWrapper\Manager::initialize(
+    $behavior->withAssertion(new ConjunctionInterceptor(array(
+        new PharExtensionInterceptor(),
+        new PharMetaDataInterceptor()
+    )))
+);
+```
+
+### PharExtensionInterceptor
+
+This (basic) interceptor just checks whether the invoked Phar archive has
+an according `.phar` file extension. Resolving symbolic links as well as
+Phar internal alias resolving are considered as well.
+
+```
+$behavior = new \TYPO3\PharStreamWrapper\Behavior();
+\TYPO3\PharStreamWrapper\Manager::initialize(
+    $behavior->withAssertion(new PharExtensionInterceptor())
+);
+```
+
+### PharMetaDataInterceptor
+
+This interceptor is actually checking serialized Phar meta-data against
+PHP objects and would consider a Phar archive malicious in case not only
+scalar values are found. A custom low-level `Phar\Reader` is used in order to
+avoid using PHP's `Phar` object which would trigger the initial vulnerability.
+
+```
+$behavior = new \TYPO3\PharStreamWrapper\Behavior();
+\TYPO3\PharStreamWrapper\Manager::initialize(
+    $behavior->withAssertion(new PharMetaDataInterceptor())
+);
+```
+
+## Reader
+
+* `Phar\Reader::__construct(string $fileName)`: Creates low-level reader for Phar archive
+* `Phar\Reader::resolveContainer(): Phar\Container`: Resolves model representing Phar archive
+* `Phar\Container::getStub(): Phar\Stub`: Resolves (plain PHP) stub section of Phar archive
+* `Phar\Container::getManifest(): Phar\Manifest`: Resolves parsed Phar archive manifest as
+  documented at http://php.net/manual/en/phar.fileformat.manifestfile.php
+* `Phar\Stub::getMappedAlias(): string`: Resolves internal Phar archive alias defined in stub
+  using `Phar::mapPhar('alias.phar')` - actually the plain PHP source is analyzed here
+* `Phar\Manifest::getAlias(): string` - Resolves internal Phar archive alias defined in manifest
+  using `Phar::setAlias('alias.phar')`
+* `Phar\Manifest::getMetaData(): string`: Resolves serialized Phar archive meta-data
+* `Phar\Manifest::deserializeMetaData(): mixed`: Resolves deserialized Phar archive meta-data
+  containing only scalar values - in case an object is determined, an according
+  `Phar\DeserializationException` will be thrown
+
+```
+$reader = new Phar\Reader('example.phar');
+var_dump($reader->resolveContainer()->getManifest()->deserializeMetaData());
+```
+
 ## Helper
 ## Helper
 
 
-* `Helper::determineBaseFile(string $path)`: Determines base file that can be
+* `Helper::determineBaseFile(string $path): string`: Determines base file that can be
   accessed using the regular file system. For instance the following path
   accessed using the regular file system. For instance the following path
   `phar:///home/user/bundle.phar/content.txt` would be resolved to
   `phar:///home/user/bundle.phar/content.txt` would be resolved to
   `/home/user/bundle.phar`.
   `/home/user/bundle.phar`.

+ 7 - 1
misc/typo3/phar-stream-wrapper/composer.json

@@ -6,11 +6,17 @@
     "homepage": "https://typo3.org/",
     "homepage": "https://typo3.org/",
     "keywords": ["php", "phar", "stream-wrapper", "security"],
     "keywords": ["php", "phar", "stream-wrapper", "security"],
     "require": {
     "require": {
-        "php": "^5.3.3|^7.0"
+        "php": "^5.3.3|^7.0",
+        "ext-json": "*",
+        "brumann/polyfill-unserialize": "^1.0"
     },
     },
     "require-dev": {
     "require-dev": {
+        "ext-xdebug": "*",
         "phpunit/phpunit": "^4.8.36"
         "phpunit/phpunit": "^4.8.36"
     },
     },
+    "suggest": {
+        "ext-fileinfo": "For PHP builtin file type guessing, otherwise uses internal processing"
+    },
     "autoload": {
     "autoload": {
         "psr-4": {
         "psr-4": {
             "TYPO3\\PharStreamWrapper\\": "src/"
             "TYPO3\\PharStreamWrapper\\": "src/"

+ 37 - 0
misc/typo3/phar-stream-wrapper/src/Collectable.php

@@ -0,0 +1,37 @@
+<?php
+namespace TYPO3\PharStreamWrapper;
+
+/*
+ * This file is part of the TYPO3 project.
+ *
+ * It is free software; you can redistribute it and/or modify it under the terms
+ * of the MIT License (MIT). For the full copyright and license information,
+ * please read the LICENSE file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\PharStreamWrapper\Resolver\PharInvocation;
+
+interface Collectable
+{
+    /**
+     * @param PharInvocation $invocation
+     * @return bool
+     */
+    public function has(PharInvocation $invocation);
+
+    /**
+     * @param PharInvocation $invocation
+     * @param null $flags
+     * @return bool
+     */
+    public function collect(PharInvocation $invocation, $flags = null);
+
+    /**
+     * @param callable $callback
+     * @param bool $reverse
+     * @return null|PharInvocation
+     */
+    public function findByCallback($callback, $reverse = false);
+}

+ 20 - 4
misc/typo3/phar-stream-wrapper/src/Helper.php

@@ -11,6 +11,13 @@ namespace TYPO3\PharStreamWrapper;
  * The TYPO3 project - inspiring people to share!
  * The TYPO3 project - inspiring people to share!
  */
  */
 
 
+/**
+ * Helper provides low-level tools on file name resolving. However it does not
+ * (and should not) maintain any runtime state information. In order to resolve
+ * Phar archive paths according resolvers have to be used.
+ *
+ * @see \TYPO3\PharStreamWrapper\Resolvable::resolve()
+ */
 class Helper
 class Helper
 {
 {
     /*
     /*
@@ -45,7 +52,7 @@ class Helper
 
 
         while (count($parts)) {
         while (count($parts)) {
             $currentPath = implode('/', $parts);
             $currentPath = implode('/', $parts);
-            if (@is_file($currentPath)) {
+            if (@is_file($currentPath) && realpath($currentPath) !== false) {
                 return $currentPath;
                 return $currentPath;
             }
             }
             array_pop($parts);
             array_pop($parts);
@@ -54,6 +61,15 @@ class Helper
         return null;
         return null;
     }
     }
 
 
+    /**
+     * @param string $path
+     * @return bool
+     */
+    public static function hasPharPrefix($path)
+    {
+        return stripos($path, 'phar://') === 0;
+    }
+
     /**
     /**
      * @param string $path
      * @param string $path
      * @return string
      * @return string
@@ -61,7 +77,7 @@ class Helper
     public static function removePharPrefix($path)
     public static function removePharPrefix($path)
     {
     {
         $path = trim($path);
         $path = trim($path);
-        if (stripos($path, 'phar://') !== 0) {
+        if (!static::hasPharPrefix($path)) {
             return $path;
             return $path;
         }
         }
         return substr($path, 7);
         return substr($path, 7);
@@ -77,7 +93,7 @@ class Helper
     public static function normalizePath($path)
     public static function normalizePath($path)
     {
     {
         return rtrim(
         return rtrim(
-            static::getCanonicalPath(
+            static::normalizeWindowsPath(
                 static::removePharPrefix($path)
                 static::removePharPrefix($path)
             ),
             ),
             '/'
             '/'
@@ -90,7 +106,7 @@ class Helper
      * @param string $path File path to process
      * @param string $path File path to process
      * @return string
      * @return string
      */
      */
-    private static function normalizeWindowsPath($path)
+    public static function normalizeWindowsPath($path)
     {
     {
         return str_replace('\\', '/', $path);
         return str_replace('\\', '/', $path);
     }
     }

+ 88 - 0
misc/typo3/phar-stream-wrapper/src/Interceptor/ConjunctionInterceptor.php

@@ -0,0 +1,88 @@
+<?php
+namespace TYPO3\PharStreamWrapper\Interceptor;
+
+/*
+ * This file is part of the TYPO3 project.
+ *
+ * It is free software; you can redistribute it and/or modify it under the terms
+ * of the MIT License (MIT). For the full copyright and license information,
+ * please read the LICENSE file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\PharStreamWrapper\Assertable;
+use TYPO3\PharStreamWrapper\Exception;
+
+class ConjunctionInterceptor implements Assertable
+{
+    /**
+     * @var Assertable[]
+     */
+    private $assertions;
+
+    public function __construct(array $assertions)
+    {
+        $this->assertAssertions($assertions);
+        $this->assertions = $assertions;
+    }
+
+    /**
+     * Executes assertions based on all contained assertions.
+     *
+     * @param string $path
+     * @param string $command
+     * @return bool
+     * @throws Exception
+     */
+    public function assert($path, $command)
+    {
+        if ($this->invokeAssertions($path, $command)) {
+            return true;
+        }
+        throw new Exception(
+            sprintf(
+                'Assertion failed in "%s"',
+                $path
+            ),
+            1539625084
+        );
+    }
+
+    /**
+     * @param Assertable[] $assertions
+     */
+    private function assertAssertions(array $assertions)
+    {
+        foreach ($assertions as $assertion) {
+            if (!$assertion instanceof Assertable) {
+                throw new \InvalidArgumentException(
+                    sprintf(
+                        'Instance %s must implement Assertable',
+                        get_class($assertion)
+                    ),
+                    1539624719
+                );
+            }
+        }
+    }
+
+    /**
+     * @param string $path
+     * @param string $command
+     * @return bool
+     */
+    private function invokeAssertions($path, $command)
+    {
+        try {
+            foreach ($this->assertions as $assertion) {
+                if (!$assertion->assert($path, $command)) {
+                    return false;
+                }
+            }
+        } catch (Exception $exception) {
+            return false;
+        }
+        return true;
+    }
+}

+ 4 - 4
misc/typo3/phar-stream-wrapper/src/Interceptor/PharExtensionInterceptor.php

@@ -12,8 +12,8 @@ namespace TYPO3\PharStreamWrapper\Interceptor;
  */
  */
 
 
 use TYPO3\PharStreamWrapper\Assertable;
 use TYPO3\PharStreamWrapper\Assertable;
-use TYPO3\PharStreamWrapper\Helper;
 use TYPO3\PharStreamWrapper\Exception;
 use TYPO3\PharStreamWrapper\Exception;
+use TYPO3\PharStreamWrapper\Manager;
 
 
 class PharExtensionInterceptor implements Assertable
 class PharExtensionInterceptor implements Assertable
 {
 {
@@ -45,11 +45,11 @@ class PharExtensionInterceptor implements Assertable
      */
      */
     private function baseFileContainsPharExtension($path)
     private function baseFileContainsPharExtension($path)
     {
     {
-        $baseFile = Helper::determineBaseFile($path);
-        if ($baseFile === null) {
+        $invocation = Manager::instance()->resolve($path);
+        if ($invocation === null) {
             return false;
             return false;
         }
         }
-        $fileExtension = pathinfo($baseFile, PATHINFO_EXTENSION);
+        $fileExtension = pathinfo($invocation->getBaseName(), PATHINFO_EXTENSION);
         return strtolower($fileExtension) === 'phar';
         return strtolower($fileExtension) === 'phar';
     }
     }
 }
 }

+ 73 - 0
misc/typo3/phar-stream-wrapper/src/Interceptor/PharMetaDataInterceptor.php

@@ -0,0 +1,73 @@
+<?php
+namespace TYPO3\PharStreamWrapper\Interceptor;
+
+/*
+ * This file is part of the TYPO3 project.
+ *
+ * It is free software; you can redistribute it and/or modify it under the terms
+ * of the MIT License (MIT). For the full copyright and license information,
+ * please read the LICENSE file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\PharStreamWrapper\Assertable;
+use TYPO3\PharStreamWrapper\Exception;
+use TYPO3\PharStreamWrapper\Manager;
+use TYPO3\PharStreamWrapper\Phar\DeserializationException;
+use TYPO3\PharStreamWrapper\Phar\Reader;
+
+/**
+ * @internal Experimental implementation of checking against serialized objects in Phar meta-data
+ * @internal This functionality has not been 100% pentested...
+ */
+class PharMetaDataInterceptor implements Assertable
+{
+    /**
+     * Determines whether the according Phar archive contains
+     * (potential insecure) serialized objects.
+     *
+     * @param string $path
+     * @param string $command
+     * @return bool
+     * @throws Exception
+     */
+    public function assert($path, $command)
+    {
+        if ($this->baseFileDoesNotHaveMetaDataIssues($path)) {
+            return true;
+        }
+        throw new Exception(
+            sprintf(
+                'Problematic meta-data in "%s"',
+                $path
+            ),
+            1539632368
+        );
+    }
+
+    /**
+     * @param string $path
+     * @return bool
+     */
+    private function baseFileDoesNotHaveMetaDataIssues($path)
+    {
+        $invocation = Manager::instance()->resolve($path);
+        if ($invocation === null) {
+            return false;
+        }
+        // directly return in case invocation was checked before
+        if ($invocation->getVariable(__CLASS__) === true) {
+            return true;
+        }
+        // otherwise analyze meta-data
+        try {
+            $reader = new Reader($invocation->getBaseName());
+            $reader->resolveContainer()->getManifest()->deserializeMetaData();
+            $invocation->setVariable(__CLASS__, true);
+        } catch (DeserializationException $exception) {
+            return false;
+        }
+        return true;
+    }
+}

+ 56 - 6
misc/typo3/phar-stream-wrapper/src/Manager.php

@@ -11,7 +11,11 @@ namespace TYPO3\PharStreamWrapper;
  * The TYPO3 project - inspiring people to share!
  * The TYPO3 project - inspiring people to share!
  */
  */
 
 
-class Manager implements Assertable
+use TYPO3\PharStreamWrapper\Resolver\PharInvocation;
+use TYPO3\PharStreamWrapper\Resolver\PharInvocationCollection;
+use TYPO3\PharStreamWrapper\Resolver\PharInvocationResolver;
+
+class Manager
 {
 {
     /**
     /**
      * @var self
      * @var self
@@ -23,14 +27,29 @@ class Manager implements Assertable
      */
      */
     private $behavior;
     private $behavior;
 
 
+    /**
+     * @var Resolvable
+     */
+    private $resolver;
+
+    /**
+     * @var Collectable
+     */
+    private $collection;
+
     /**
     /**
      * @param Behavior $behaviour
      * @param Behavior $behaviour
+     * @param Resolvable $resolver
+     * @param Collectable $collection
      * @return self
      * @return self
      */
      */
-    public static function initialize(Behavior $behaviour)
-    {
+    public static function initialize(
+        Behavior $behaviour,
+        Resolvable $resolver = null,
+        Collectable $collection = null
+    ) {
         if (self::$instance === null) {
         if (self::$instance === null) {
-            self::$instance = new self($behaviour);
+            self::$instance = new self($behaviour, $resolver, $collection);
             return self::$instance;
             return self::$instance;
         }
         }
         throw new \LogicException(
         throw new \LogicException(
@@ -67,9 +86,22 @@ class Manager implements Assertable
 
 
     /**
     /**
      * @param Behavior $behaviour
      * @param Behavior $behaviour
+     * @param Resolvable $resolver
+     * @param Collectable $collection
      */
      */
-    private function __construct(Behavior $behaviour)
-    {
+    private function __construct(
+        Behavior $behaviour,
+        Resolvable $resolver = null,
+        Collectable $collection = null
+    ) {
+        if ($collection === null) {
+            $collection = new PharInvocationCollection();
+        }
+        if ($resolver === null) {
+            $resolver = new PharInvocationResolver();
+        }
+        $this->collection = $collection;
+        $this->resolver = $resolver;
         $this->behavior = $behaviour;
         $this->behavior = $behaviour;
     }
     }
 
 
@@ -82,4 +114,22 @@ class Manager implements Assertable
     {
     {
         return $this->behavior->assert($path, $command);
         return $this->behavior->assert($path, $command);
     }
     }
+
+    /**
+     * @param string $path
+     * @param null|int $flags
+     * @return null|PharInvocation
+     */
+    public function resolve($path, $flags = null)
+    {
+        return $this->resolver->resolve($path, $flags);
+    }
+
+    /**
+     * @return Collectable
+     */
+    public function getCollection()
+    {
+        return $this->collection;
+    }
 }
 }

+ 59 - 0
misc/typo3/phar-stream-wrapper/src/Phar/Container.php

@@ -0,0 +1,59 @@
+<?php
+namespace TYPO3\PharStreamWrapper\Phar;
+
+/*
+ * This file is part of the TYPO3 project.
+ *
+ * It is free software; you can redistribute it and/or modify it under the terms
+ * of the MIT License (MIT). For the full copyright and license information,
+ * please read the LICENSE file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+class Container
+{
+    /**
+     * @var Stub
+     */
+    private $stub;
+
+    /**
+     * @var Manifest
+     */
+    private $manifest;
+
+    /**
+     * @param Stub $stub
+     * @param Manifest $manifest
+     */
+    public function __construct(Stub $stub, Manifest $manifest)
+    {
+        $this->stub = $stub;
+        $this->manifest = $manifest;
+    }
+
+    /**
+     * @return Stub
+     */
+    public function getStub()
+    {
+        return $this->stub;
+    }
+
+    /**
+     * @return Manifest
+     */
+    public function getManifest()
+    {
+        return $this->manifest;
+    }
+
+    /**
+     * @return string
+     */
+    public function getAlias()
+    {
+        return $this->manifest->getAlias() ?: $this->stub->getMappedAlias();
+    }
+}

+ 18 - 0
misc/typo3/phar-stream-wrapper/src/Phar/DeserializationException.php

@@ -0,0 +1,18 @@
+<?php
+namespace TYPO3\PharStreamWrapper\Phar;
+
+/*
+ * This file is part of the TYPO3 project.
+ *
+ * It is free software; you can redistribute it and/or modify it under the terms
+ * of the MIT License (MIT). For the full copyright and license information,
+ * please read the LICENSE file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\PharStreamWrapper\Exception;
+
+class DeserializationException extends Exception
+{
+}

+ 176 - 0
misc/typo3/phar-stream-wrapper/src/Phar/Manifest.php

@@ -0,0 +1,176 @@
+<?php
+namespace TYPO3\PharStreamWrapper\Phar;
+
+/*
+ * This file is part of the TYPO3 project.
+ *
+ * It is free software; you can redistribute it and/or modify it under the terms
+ * of the MIT License (MIT). For the full copyright and license information,
+ * please read the LICENSE file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Brumann\Polyfill\Unserialize;
+
+class Manifest
+{
+    /**
+     * @param string $content
+     * @return self
+     * @see http://php.net/manual/en/phar.fileformat.phar.php
+     */
+    public static function fromContent($content)
+    {
+        $target = new static();
+        $target->manifestLength = Reader::resolveFourByteLittleEndian($content, 0);
+        $target->amountOfFiles = Reader::resolveFourByteLittleEndian($content, 4);
+        $target->flags = Reader::resolveFourByteLittleEndian($content, 10);
+        $target->aliasLength = Reader::resolveFourByteLittleEndian($content, 14);
+        $target->alias = substr($content, 18, $target->aliasLength);
+        $target->metaDataLength = Reader::resolveFourByteLittleEndian($content, 18 + $target->aliasLength);
+        $target->metaData = substr($content, 22 + $target->aliasLength, $target->metaDataLength);
+
+        $apiVersionNibbles = Reader::resolveTwoByteBigEndian($content, 8);
+        $target->apiVersion = implode('.', array(
+            ($apiVersionNibbles & 0xf000) >> 12,
+            ($apiVersionNibbles & 0x0f00) >> 8,
+            ($apiVersionNibbles & 0x00f0) >> 4,
+        ));
+
+        return $target;
+    }
+
+    /**
+     * @var int
+     */
+    private $manifestLength;
+
+    /**
+     * @var int
+     */
+    private $amountOfFiles;
+
+    /**
+     * @var string
+     */
+    private $apiVersion;
+
+    /**
+     * @var int
+     */
+    private $flags;
+
+    /**
+     * @var int
+     */
+    private $aliasLength;
+
+    /**
+     * @var string
+     */
+    private $alias;
+
+    /**
+     * @var int
+     */
+    private $metaDataLength;
+
+    /**
+     * @var string
+     */
+    private $metaData;
+
+    /**
+     * Avoid direct instantiation.
+     */
+    private function __construct()
+    {
+    }
+
+    /**
+     * @return int
+     */
+    public function getManifestLength()
+    {
+        return $this->manifestLength;
+    }
+
+    /**
+     * @return int
+     */
+    public function getAmountOfFiles()
+    {
+        return $this->amountOfFiles;
+    }
+
+    /**
+     * @return string
+     */
+    public function getApiVersion()
+    {
+        return $this->apiVersion;
+    }
+
+    /**
+     * @return int
+     */
+    public function getFlags()
+    {
+        return $this->flags;
+    }
+
+    /**
+     * @return int
+     */
+    public function getAliasLength()
+    {
+        return $this->aliasLength;
+    }
+
+    /**
+     * @return string
+     */
+    public function getAlias()
+    {
+        return $this->alias;
+    }
+
+    /**
+     * @return int
+     */
+    public function getMetaDataLength()
+    {
+        return $this->metaDataLength;
+    }
+
+    /**
+     * @return string
+     */
+    public function getMetaData()
+    {
+        return $this->metaData;
+    }
+
+    /**
+     * @return mixed|null
+     */
+    public function deserializeMetaData()
+    {
+        if (empty($this->metaData)) {
+            return null;
+        }
+
+        $result = Unserialize::unserialize($this->metaData, array('allowed_classes' => false));
+
+        $serialized = json_encode($result);
+        if (strpos($serialized, '__PHP_Incomplete_Class_Name') !== false) {
+            throw new DeserializationException(
+                'Meta-data contains serialized object',
+                1539623382
+            );
+        }
+
+        return $result;
+    }
+}

+ 254 - 0
misc/typo3/phar-stream-wrapper/src/Phar/Reader.php

@@ -0,0 +1,254 @@
+<?php
+namespace TYPO3\PharStreamWrapper\Phar;
+
+/*
+ * This file is part of the TYPO3 project.
+ *
+ * It is free software; you can redistribute it and/or modify it under the terms
+ * of the MIT License (MIT). For the full copyright and license information,
+ * please read the LICENSE file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+class Reader
+{
+    /**
+     * @var string
+     */
+    private $fileName;
+
+    /**
+     * Mime-type in order to use zlib, bzip2 or no compression.
+     * In case ext-fileinfo is not present only the relevant types
+     * 'application/x-gzip' and 'application/x-bzip2' are assigned
+     * to this class property.
+     *
+     * @var string
+     */
+    private $fileType;
+
+    /**
+     * @param string $fileName
+     */
+    public function __construct($fileName)
+    {
+        if (strpos($fileName, '://') !== false) {
+            throw new ReaderException(
+                'File name must not contain stream prefix',
+                1539623708
+            );
+        }
+
+        $this->fileName = $fileName;
+        $this->fileType = $this->determineFileType();
+    }
+
+    /**
+     * @return Container
+     */
+    public function resolveContainer()
+    {
+        $data = $this->extractData($this->resolveStream() . $this->fileName);
+
+        if ($data['stubContent'] === null) {
+            throw new ReaderException(
+                'Cannot resolve stub',
+                1547807881
+            );
+        }
+        if ($data['manifestContent'] === null || $data['manifestLength'] === null) {
+            throw new ReaderException(
+                'Cannot resolve manifest',
+                1547807882
+            );
+        }
+        if (strlen($data['manifestContent']) < $data['manifestLength']) {
+            throw new ReaderException(
+                sprintf(
+                    'Exected manifest length %d, got %d',
+                    strlen($data['manifestContent']),
+                    $data['manifestLength']
+                ),
+                1547807883
+            );
+        }
+
+        return new Container(
+            Stub::fromContent($data['stubContent']),
+            Manifest::fromContent($data['manifestContent'])
+        );
+    }
+
+    /**
+     * @param string $fileName e.g. '/path/file.phar' or 'compress.zlib:///path/file.phar'
+     * @return array
+     */
+    private function extractData($fileName)
+    {
+        $stubContent = null;
+        $manifestContent = null;
+        $manifestLength = null;
+
+        $resource = fopen($fileName, 'r');
+        if (!is_resource($resource)) {
+            throw new ReaderException(
+                sprintf('Resource %s could not be opened', $fileName),
+                1547902055
+            );
+        }
+
+        while (!feof($resource)) {
+            $line = fgets($resource);
+            // stop reading file when manifest can be extracted
+            if ($manifestLength !== null && $manifestContent !== null && strlen($manifestContent) >= $manifestLength) {
+                break;
+            }
+
+            $manifestPosition = strpos($line, '__HALT_COMPILER();');
+
+            // first line contains start of manifest
+            if ($stubContent === null && $manifestContent === null && $manifestPosition !== false) {
+                $stubContent = substr($line, 0, $manifestPosition - 1);
+                $manifestContent = preg_replace('#^.*__HALT_COMPILER\(\);(?>[ \n]\?>(?>\r\n|\n)?)?#', '', $line);
+                $manifestLength = $this->resolveManifestLength($manifestContent);
+            // line contains start of stub
+            } elseif ($stubContent === null) {
+                $stubContent = $line;
+            // line contains start of manifest
+            } elseif ($manifestContent === null && $manifestPosition !== false) {
+                $manifestContent = preg_replace('#^.*__HALT_COMPILER\(\);(?>[ \n]\?>(?>\r\n|\n)?)?#', '', $line);
+                $manifestLength = $this->resolveManifestLength($manifestContent);
+            // manifest has been started (thus is cannot be stub anymore), add content
+            } elseif ($manifestContent !== null) {
+                $manifestContent .= $line;
+                $manifestLength = $this->resolveManifestLength($manifestContent);
+            // stub has been started (thus cannot be manifest here, yet), add content
+            } elseif ($stubContent !== null) {
+                $stubContent .= $line;
+            }
+        }
+        fclose($resource);
+
+        return array(
+            'stubContent' => $stubContent,
+            'manifestContent' => $manifestContent,
+            'manifestLength' => $manifestLength,
+        );
+    }
+
+    /**
+     * Resolves stream in order to handle compressed Phar archives.
+     *
+     * @return string
+     */
+    private function resolveStream()
+    {
+        if ($this->fileType === 'application/x-gzip' || $this->fileType === 'application/gzip') {
+            return 'compress.zlib://';
+        } elseif ($this->fileType === 'application/x-bzip2') {
+            return 'compress.bzip2://';
+        }
+        return '';
+    }
+
+    /**
+     * @return string
+     */
+    private function determineFileType()
+    {
+        if (class_exists('\\finfo')) {
+            $fileInfo = new \finfo();
+            return $fileInfo->file($this->fileName, FILEINFO_MIME_TYPE);
+        }
+        return $this->determineFileTypeByHeader();
+    }
+
+    /**
+     * In case ext-fileinfo is not present only the relevant types
+     * 'application/x-gzip' and 'application/x-bzip2' are resolved.
+     *
+     * @return string
+     */
+    private function determineFileTypeByHeader()
+    {
+        $resource = fopen($this->fileName, 'r');
+        if (!is_resource($resource)) {
+            throw new ReaderException(
+                sprintf('Resource %s could not be opened', $this->fileName),
+                1557753055
+            );
+        }
+        $header = fgets($resource, 4);
+        fclose($resource);
+        $mimeType = '';
+        if (strpos($header, "\x42\x5a\x68") === 0) {
+            $mimeType = 'application/x-bzip2';
+        } elseif (strpos($header, "\x1f\x8b") === 0) {
+            $mimeType = 'application/x-gzip';
+        }
+        return $mimeType;
+    }
+
+    /**
+     * @param string $content
+     * @return int|null
+     */
+    private function resolveManifestLength($content)
+    {
+        if (strlen($content) < 4) {
+            return null;
+        }
+        return static::resolveFourByteLittleEndian($content, 0);
+    }
+
+    /**
+     * @param string $content
+     * @param int $start
+     * @return int
+     */
+    public static function resolveFourByteLittleEndian($content, $start)
+    {
+        $payload = substr($content, $start, 4);
+        if (!is_string($payload)) {
+            throw new ReaderException(
+                sprintf('Cannot resolve value at offset %d', $start),
+                1539614260
+            );
+        }
+
+        $value = unpack('V', $payload);
+        if (!isset($value[1])) {
+            throw new ReaderException(
+                sprintf('Cannot resolve value at offset %d', $start),
+                1539614261
+            );
+        }
+        return $value[1];
+    }
+
+    /**
+     * @param string $content
+     * @param int $start
+     * @return int
+     */
+    public static function resolveTwoByteBigEndian($content, $start)
+    {
+        $payload = substr($content, $start, 2);
+        if (!is_string($payload)) {
+            throw new ReaderException(
+                sprintf('Cannot resolve value at offset %d', $start),
+                1539614263
+            );
+        }
+
+        $value = unpack('n', $payload);
+        if (!isset($value[1])) {
+            throw new ReaderException(
+                sprintf('Cannot resolve value at offset %d', $start),
+                1539614264
+            );
+        }
+        return $value[1];
+    }
+}

+ 18 - 0
misc/typo3/phar-stream-wrapper/src/Phar/ReaderException.php

@@ -0,0 +1,18 @@
+<?php
+namespace TYPO3\PharStreamWrapper\Phar;
+
+/*
+ * This file is part of the TYPO3 project.
+ *
+ * It is free software; you can redistribute it and/or modify it under the terms
+ * of the MIT License (MIT). For the full copyright and license information,
+ * please read the LICENSE file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\PharStreamWrapper\Exception;
+
+class ReaderException extends Exception
+{
+}

+ 65 - 0
misc/typo3/phar-stream-wrapper/src/Phar/Stub.php

@@ -0,0 +1,65 @@
+<?php
+namespace TYPO3\PharStreamWrapper\Phar;
+
+/*
+ * This file is part of the TYPO3 project.
+ *
+ * It is free software; you can redistribute it and/or modify it under the terms
+ * of the MIT License (MIT). For the full copyright and license information,
+ * please read the LICENSE file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * @internal Experimental implementation of Phar archive internals
+ */
+class Stub
+{
+    /**
+     * @param string $content
+     * @return self
+     */
+    public static function fromContent($content)
+    {
+        $target = new static();
+        $target->content = $content;
+
+        if (
+            stripos($content, 'Phar::mapPhar(') !== false
+            && preg_match('#Phar\:\:mapPhar\(([^)]+)\)#', $content, $matches)
+        ) {
+            // remove spaces, single & double quotes
+            // @todo `'my' . 'alias' . '.phar'` is not evaluated here
+            $target->mappedAlias = trim($matches[1], ' \'"');
+        }
+
+        return $target;
+    }
+
+    /**
+     * @var string
+     */
+    private $content;
+
+    /**
+     * @var string
+     */
+    private $mappedAlias = '';
+
+    /**
+     * @return string
+     */
+    public function getContent()
+    {
+        return $this->content;
+    }
+
+    /**
+     * @return string
+     */
+    public function getMappedAlias()
+    {
+        return $this->mappedAlias;
+    }
+}

+ 37 - 3
misc/typo3/phar-stream-wrapper/src/PharStreamWrapper.php

@@ -11,6 +11,8 @@ namespace TYPO3\PharStreamWrapper;
  * The TYPO3 project - inspiring people to share!
  * The TYPO3 project - inspiring people to share!
  */
  */
 
 
+use TYPO3\PharStreamWrapper\Resolver\PharInvocation;
+
 class PharStreamWrapper
 class PharStreamWrapper
 {
 {
     /**
     /**
@@ -29,6 +31,11 @@ class PharStreamWrapper
      */
      */
     protected $internalResource;
     protected $internalResource;
 
 
+    /**
+     * @var PharInvocation
+     */
+    protected $invocation;
+
     /**
     /**
      * @return bool
      * @return bool
      */
      */
@@ -409,7 +416,8 @@ class PharStreamWrapper
      */
      */
     protected function assert($path, $command)
     protected function assert($path, $command)
     {
     {
-        if ($this->resolveAssertable()->assert($path, $command) === true) {
+        if (Manager::instance()->assert($path, $command) === true) {
+            $this->collectInvocation($path);
             return;
             return;
         }
         }
 
 
@@ -424,7 +432,33 @@ class PharStreamWrapper
     }
     }
 
 
     /**
     /**
-     * @return Assertable
+     * @param string $path
+     */
+    protected function collectInvocation($path)
+    {
+        if (isset($this->invocation)) {
+            return;
+        }
+
+        $manager = Manager::instance();
+        $this->invocation = $manager->resolve($path);
+        if ($this->invocation === null) {
+            throw new Exception(
+                'Expected invocation could not be resolved',
+                1556389591
+            );
+        }
+        // confirm, previous interceptor(s) validated invocation
+        $this->invocation->confirm();
+        $collection = $manager->getCollection();
+        if (!$collection->has($this->invocation)) {
+            $collection->collect($this->invocation);
+        }
+    }
+
+    /**
+     * @return Manager|Assertable
+     * @deprecated Use Manager::instance() directly
      */
      */
     protected function resolveAssertable()
     protected function resolveAssertable()
     {
     {
@@ -442,7 +476,7 @@ class PharStreamWrapper
     {
     {
         $arguments = func_get_args();
         $arguments = func_get_args();
         array_shift($arguments);
         array_shift($arguments);
-        $silentExecution = $functionName{0} === '@';
+        $silentExecution = $functionName[0] === '@';
         $functionName = ltrim($functionName, '@');
         $functionName = ltrim($functionName, '@');
         $this->restoreInternalSteamWrapper();
         $this->restoreInternalSteamWrapper();
 
 

+ 24 - 0
misc/typo3/phar-stream-wrapper/src/Resolvable.php

@@ -0,0 +1,24 @@
+<?php
+namespace TYPO3\PharStreamWrapper;
+
+/*
+ * This file is part of the TYPO3 project.
+ *
+ * It is free software; you can redistribute it and/or modify it under the terms
+ * of the MIT License (MIT). For the full copyright and license information,
+ * please read the LICENSE file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\PharStreamWrapper\Resolver\PharInvocation;
+
+interface Resolvable
+{
+    /**
+     * @param string $path
+     * @param null|int $flags
+     * @return null|PharInvocation
+     */
+    public function resolve($path, $flags = null);
+}

+ 125 - 0
misc/typo3/phar-stream-wrapper/src/Resolver/PharInvocation.php

@@ -0,0 +1,125 @@
+<?php
+namespace TYPO3\PharStreamWrapper\Resolver;
+
+/*
+ * This file is part of the TYPO3 project.
+ *
+ * It is free software; you can redistribute it and/or modify it under the terms
+ * of the MIT License (MIT). For the full copyright and license information,
+ * please read the LICENSE file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\PharStreamWrapper\Exception;
+
+class PharInvocation
+{
+    /**
+     * @var string
+     */
+    private $baseName;
+
+    /**
+     * @var string
+     */
+    private $alias;
+
+    /**
+     * @var bool
+     * @see \TYPO3\PharStreamWrapper\PharStreamWrapper::collectInvocation()
+     */
+    private $confirmed = false;
+
+    /**
+     * Arbitrary variables to be used by interceptors as registry
+     * (e.g. in order to avoid duplicate processing and assertions)
+     *
+     * @var array
+     */
+    private $variables;
+
+    /**
+     * @param string $baseName
+     * @param string $alias
+     */
+    public function __construct($baseName, $alias = '')
+    {
+        if ($baseName === '') {
+            throw new Exception(
+                'Base-name cannot be empty',
+                1551283689
+            );
+        }
+        $this->baseName = $baseName;
+        $this->alias = $alias;
+    }
+
+    /**
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->baseName;
+    }
+
+    /**
+     * @return string
+     */
+    public function getBaseName()
+    {
+        return $this->baseName;
+    }
+
+    /**
+     * @return null|string
+     */
+    public function getAlias()
+    {
+        return $this->alias;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isConfirmed()
+    {
+        return $this->confirmed;
+    }
+
+    public function confirm()
+    {
+        $this->confirmed = true;
+    }
+
+    /**
+     * @param string $name
+     * @return mixed|null
+     */
+    public function getVariable($name)
+    {
+        if (!isset($this->variables[$name])) {
+            return null;
+        }
+        return $this->variables[$name];
+    }
+
+    /**
+     * @param string $name
+     * @param mixed $value
+     */
+    public function setVariable($name, $value)
+    {
+        $this->variables[$name] = $value;
+    }
+
+    /**
+     * @param PharInvocation $other
+     * @return bool
+     */
+    public function equals(PharInvocation $other)
+    {
+        return $other->baseName === $this->baseName
+            && $other->alias === $this->alias;
+    }
+}

+ 156 - 0
misc/typo3/phar-stream-wrapper/src/Resolver/PharInvocationCollection.php

@@ -0,0 +1,156 @@
+<?php
+namespace TYPO3\PharStreamWrapper\Resolver;
+
+/*
+ * This file is part of the TYPO3 project.
+ *
+ * It is free software; you can redistribute it and/or modify it under the terms
+ * of the MIT License (MIT). For the full copyright and license information,
+ * please read the LICENSE file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\PharStreamWrapper\Collectable;
+
+class PharInvocationCollection implements Collectable
+{
+    const UNIQUE_INVOCATION = 1;
+    const UNIQUE_BASE_NAME = 2;
+    const DUPLICATE_ALIAS_WARNING = 32;
+
+    /**
+     * @var PharInvocation[]
+     */
+    private $invocations = array();
+
+    /**
+     * @param PharInvocation $invocation
+     * @return bool
+     */
+    public function has(PharInvocation $invocation)
+    {
+        return in_array($invocation, $this->invocations, true);
+    }
+
+    /**
+     * @param PharInvocation $invocation
+     * @param null|int $flags
+     * @return bool
+     */
+    public function collect(PharInvocation $invocation, $flags = null)
+    {
+        if ($flags === null) {
+            $flags = static::UNIQUE_INVOCATION | static::DUPLICATE_ALIAS_WARNING;
+        }
+        if ($invocation->getBaseName() === ''
+            || $invocation->getAlias() === ''
+            || !$this->assertUniqueBaseName($invocation, $flags)
+            || !$this->assertUniqueInvocation($invocation, $flags)
+        ) {
+            return false;
+        }
+        if ($flags & static::DUPLICATE_ALIAS_WARNING) {
+            $this->triggerDuplicateAliasWarning($invocation);
+        }
+
+        $this->invocations[] = $invocation;
+        return true;
+    }
+
+    /**
+     * @param callable $callback
+     * @param bool $reverse
+     * @return null|PharInvocation
+     */
+    public function findByCallback($callback, $reverse = false)
+    {
+        foreach ($this->getInvocations($reverse) as $invocation) {
+            if (call_user_func($callback, $invocation) === true) {
+                return $invocation;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Asserts that base-name is unique. This disallows having multiple invocations for
+     * same base-name but having different alias names.
+     *
+     * @param PharInvocation $invocation
+     * @param int $flags
+     * @return bool
+     */
+    private function assertUniqueBaseName(PharInvocation $invocation, $flags)
+    {
+        if (!($flags & static::UNIQUE_BASE_NAME)) {
+            return true;
+        }
+        return $this->findByCallback(
+                function (PharInvocation $candidate) use ($invocation) {
+                    return $candidate->getBaseName() === $invocation->getBaseName();
+                }
+            ) === null;
+    }
+
+    /**
+     * Asserts that combination of base-name and alias is unique. This allows having multiple
+     * invocations for same base-name but having different alias names (for whatever reason).
+     *
+     * @param PharInvocation $invocation
+     * @param int $flags
+     * @return bool
+     */
+    private function assertUniqueInvocation(PharInvocation $invocation, $flags)
+    {
+        if (!($flags & static::UNIQUE_INVOCATION)) {
+            return true;
+        }
+        return $this->findByCallback(
+                function (PharInvocation $candidate) use ($invocation) {
+                    return $candidate->equals($invocation);
+                }
+            ) === null;
+    }
+
+    /**
+     * Triggers warning for invocations with same alias and same confirmation state.
+     *
+     * @param PharInvocation $invocation
+     * @see \TYPO3\PharStreamWrapper\PharStreamWrapper::collectInvocation()
+     */
+    private function triggerDuplicateAliasWarning(PharInvocation $invocation)
+    {
+        $sameAliasInvocation = $this->findByCallback(
+            function (PharInvocation $candidate) use ($invocation) {
+                return $candidate->isConfirmed() === $invocation->isConfirmed()
+                    && $candidate->getAlias() === $invocation->getAlias();
+            },
+            true
+        );
+        if ($sameAliasInvocation === null) {
+            return;
+        }
+        trigger_error(
+            sprintf(
+                'Alias %s cannot be used by %s, already used by %s',
+                $invocation->getAlias(),
+                $invocation->getBaseName(),
+                $sameAliasInvocation->getBaseName()
+            ),
+            E_USER_WARNING
+        );
+    }
+
+    /**
+     * @param bool $reverse
+     * @return PharInvocation[]
+     */
+    private function getInvocations($reverse = false)
+    {
+        if ($reverse) {
+            return array_reverse($this->invocations);
+        }
+        return $this->invocations;
+    }
+}

+ 249 - 0
misc/typo3/phar-stream-wrapper/src/Resolver/PharInvocationResolver.php

@@ -0,0 +1,249 @@
+<?php
+namespace TYPO3\PharStreamWrapper\Resolver;
+
+/*
+ * This file is part of the TYPO3 project.
+ *
+ * It is free software; you can redistribute it and/or modify it under the terms
+ * of the MIT License (MIT). For the full copyright and license information,
+ * please read the LICENSE file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\PharStreamWrapper\Helper;
+use TYPO3\PharStreamWrapper\Manager;
+use TYPO3\PharStreamWrapper\Phar\Reader;
+use TYPO3\PharStreamWrapper\Phar\ReaderException;
+use TYPO3\PharStreamWrapper\Resolvable;
+
+class PharInvocationResolver implements Resolvable
+{
+    const RESOLVE_REALPATH = 1;
+    const RESOLVE_ALIAS = 2;
+    const ASSERT_INTERNAL_INVOCATION = 32;
+
+    /**
+     * @var string[]
+     */
+    private $invocationFunctionNames = array(
+        'include',
+        'include_once',
+        'require',
+        'require_once'
+    );
+
+    /**
+     * Contains resolved base names in order to reduce file IO.
+     *
+     * @var string[]
+     */
+    private $baseNames = array();
+
+    /**
+     * Resolves PharInvocation value object (baseName and optional alias).
+     *
+     * Phar aliases are intended to be used only inside Phar archives, however
+     * PharStreamWrapper needs this information exposed outside of Phar as well
+     * It is possible that same alias is used for different $baseName values.
+     * That's why PharInvocationCollection behaves like a stack when resolving
+     * base-name for a given alias. On the other hand it is not possible that
+     * one $baseName is referring to multiple aliases.
+     * @see https://secure.php.net/manual/en/phar.setalias.php
+     * @see https://secure.php.net/manual/en/phar.mapphar.php
+     *
+     * @param string $path
+     * @param int|null $flags
+     * @return null|PharInvocation
+     */
+    public function resolve($path, $flags = null)
+    {
+        $hasPharPrefix = Helper::hasPharPrefix($path);
+        if ($flags === null) {
+            $flags = static::RESOLVE_REALPATH | static::RESOLVE_ALIAS;
+        }
+
+        if ($hasPharPrefix && $flags & static::RESOLVE_ALIAS) {
+            $invocation = $this->findByAlias($path);
+            if ($invocation !== null) {
+                return $invocation;
+            }
+        }
+
+        $baseName = $this->resolveBaseName($path, $flags);
+        if ($baseName === null) {
+            return null;
+        }
+
+        if ($flags & static::RESOLVE_REALPATH) {
+            $baseName = $this->baseNames[$baseName];
+        }
+
+        return $this->retrieveInvocation($baseName, $flags);
+    }
+
+    /**
+     * Retrieves PharInvocation, either existing in collection or created on demand
+     * with resolving a potential alias name used in the according Phar archive.
+     *
+     * @param string $baseName
+     * @param int $flags
+     * @return PharInvocation
+     */
+    private function retrieveInvocation($baseName, $flags)
+    {
+        $invocation = $this->findByBaseName($baseName);
+        if ($invocation !== null) {
+            return $invocation;
+        }
+
+        if ($flags & static::RESOLVE_ALIAS) {
+            $reader = new Reader($baseName);
+            $alias = $reader->resolveContainer()->getAlias();
+        } else {
+            $alias = '';
+        }
+        // add unconfirmed(!) new invocation to collection
+        $invocation = new PharInvocation($baseName, $alias);
+        Manager::instance()->getCollection()->collect($invocation);
+        return $invocation;
+    }
+
+    /**
+     * @param string $path
+     * @param int $flags
+     * @return null|string
+     */
+    private function resolveBaseName($path, $flags)
+    {
+        $baseName = $this->findInBaseNames($path);
+        if ($baseName !== null) {
+            return $baseName;
+        }
+
+        $baseName = Helper::determineBaseFile($path);
+        if ($baseName !== null) {
+            $this->addBaseName($baseName);
+            return $baseName;
+        }
+
+        $possibleAlias = $this->resolvePossibleAlias($path);
+        if (!($flags & static::RESOLVE_ALIAS) || $possibleAlias === null) {
+            return null;
+        }
+
+        $trace = debug_backtrace();
+        foreach ($trace as $item) {
+            if (!isset($item['function']) || !isset($item['args'][0])
+                || !in_array($item['function'], $this->invocationFunctionNames, true)) {
+                continue;
+            }
+            $currentPath = $item['args'][0];
+            if (Helper::hasPharPrefix($currentPath)) {
+                continue;
+            }
+            $currentBaseName = Helper::determineBaseFile($currentPath);
+            if ($currentBaseName === null) {
+                continue;
+            }
+            // ensure the possible alias name (how we have been called initially) matches
+            // the resolved alias name that was retrieved by the current possible base name
+            try {
+                $reader = new Reader($currentBaseName);
+                $currentAlias = $reader->resolveContainer()->getAlias();
+            } catch (ReaderException $exception) {
+                // most probably that was not a Phar file
+                continue;
+            }
+            if (empty($currentAlias) || $currentAlias !== $possibleAlias) {
+                continue;
+            }
+            $this->addBaseName($currentBaseName);
+            return $currentBaseName;
+        }
+
+        return null;
+    }
+
+    /**
+     * @param string $path
+     * @return null|string
+     */
+    private function resolvePossibleAlias($path)
+    {
+        $normalizedPath = Helper::normalizePath($path);
+        return strstr($normalizedPath, '/', true) ?: null;
+    }
+
+    /**
+     * @param string $baseName
+     * @return null|PharInvocation
+     */
+    private function findByBaseName($baseName)
+    {
+        return Manager::instance()->getCollection()->findByCallback(
+            function (PharInvocation $candidate) use ($baseName) {
+                return $candidate->getBaseName() === $baseName;
+            },
+            true
+        );
+    }
+
+    /**
+     * @param string $path
+     * @return null|string
+     */
+    private function findInBaseNames($path)
+    {
+        // return directly if the resolved base name was submitted
+        if (in_array($path, $this->baseNames, true)) {
+            return $path;
+        }
+
+        $parts = explode('/', Helper::normalizePath($path));
+
+        while (count($parts)) {
+            $currentPath = implode('/', $parts);
+            if (isset($this->baseNames[$currentPath])) {
+                return $currentPath;
+            }
+            array_pop($parts);
+        }
+
+        return null;
+    }
+
+    /**
+     * @param string $baseName
+     */
+    private function addBaseName($baseName)
+    {
+        if (isset($this->baseNames[$baseName])) {
+            return;
+        }
+        $this->baseNames[$baseName] = Helper::normalizeWindowsPath(
+            realpath($baseName)
+        );
+    }
+
+    /**
+     * Finds confirmed(!) invocations by alias.
+     *
+     * @param string $path
+     * @return null|PharInvocation
+     * @see \TYPO3\PharStreamWrapper\PharStreamWrapper::collectInvocation()
+     */
+    private function findByAlias($path)
+    {
+        $possibleAlias = $this->resolvePossibleAlias($path);
+        if ($possibleAlias === null) {
+            return null;
+        }
+        return Manager::instance()->getCollection()->findByCallback(
+            function (PharInvocation $candidate) use ($possibleAlias) {
+                return $candidate->isConfirmed() && $candidate->getAlias() === $possibleAlias;
+            },
+            true
+        );
+    }
+}

+ 3 - 3
modules/aggregator/aggregator.info

@@ -7,7 +7,7 @@ files[] = aggregator.test
 configure = admin/config/services/aggregator/settings
 configure = admin/config/services/aggregator/settings
 stylesheets[all][] = aggregator.css
 stylesheets[all][] = aggregator.css
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/aggregator/tests/aggregator_test.info

@@ -5,7 +5,7 @@ version = VERSION
 core = 7.x
 core = 7.x
 hidden = TRUE
 hidden = TRUE
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/block/block.info

@@ -6,7 +6,7 @@ core = 7.x
 files[] = block.test
 files[] = block.test
 configure = admin/structure/block
 configure = admin/structure/block
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 1 - 2
modules/block/block.module

@@ -263,7 +263,7 @@ function block_page_build(&$page) {
   $all_regions = system_region_list($theme);
   $all_regions = system_region_list($theme);
 
 
   $item = menu_get_item();
   $item = menu_get_item();
-  if ($item['path'] != 'admin/structure/block/demo/' . $theme) {
+  if ($item === FALSE || $item['path'] != 'admin/structure/block/demo/' . $theme) {
     // Load all region content assigned via blocks.
     // Load all region content assigned via blocks.
     foreach (array_keys($all_regions) as $region) {
     foreach (array_keys($all_regions) as $region) {
       // Assign blocks to region.
       // Assign blocks to region.
@@ -283,7 +283,6 @@ function block_page_build(&$page) {
   }
   }
   else {
   else {
     // Append region description if we are rendering the regions demo page.
     // Append region description if we are rendering the regions demo page.
-    $item = menu_get_item();
     if ($item['path'] == 'admin/structure/block/demo/' . $theme) {
     if ($item['path'] == 'admin/structure/block/demo/' . $theme) {
       foreach (system_region_list($theme, REGIONS_VISIBLE, FALSE) as $region) {
       foreach (system_region_list($theme, REGIONS_VISIBLE, FALSE) as $region) {
         $description = '<div class="block-region">' . $all_regions[$region] . '</div>';
         $description = '<div class="block-region">' . $all_regions[$region] . '</div>';

+ 3 - 3
modules/block/tests/block_test.info

@@ -5,7 +5,7 @@ version = VERSION
 core = 7.x
 core = 7.x
 hidden = TRUE
 hidden = TRUE
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/block/tests/themes/block_test_theme/block_test_theme.info

@@ -13,7 +13,7 @@ regions[footer] = Footer
 regions[highlighted] = Highlighted
 regions[highlighted] = Highlighted
 regions[help] = Help
 regions[help] = Help
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/blog/blog.info

@@ -5,7 +5,7 @@ version = VERSION
 core = 7.x
 core = 7.x
 files[] = blog.test
 files[] = blog.test
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/book/book.info

@@ -7,7 +7,7 @@ files[] = book.test
 configure = admin/content/book/settings
 configure = admin/content/book/settings
 stylesheets[all][] = book.css
 stylesheets[all][] = book.css
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/color/color.info

@@ -5,7 +5,7 @@ version = VERSION
 core = 7.x
 core = 7.x
 files[] = color.test
 files[] = color.test
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 2
modules/color/color.module

@@ -734,8 +734,9 @@ function _color_blend($img, $hex1, $hex2, $alpha) {
  * Converts a hex color into an RGB triplet.
  * Converts a hex color into an RGB triplet.
  */
  */
 function _color_unpack($hex, $normalize = FALSE) {
 function _color_unpack($hex, $normalize = FALSE) {
-  if (strlen($hex) == 4) {
-    $hex = $hex[1] . $hex[1] . $hex[2] . $hex[2] . $hex[3] . $hex[3];
+  $hex = substr($hex, 1);
+  if (strlen($hex) == 3) {
+    $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
   }
   }
   $c = hexdec($hex);
   $c = hexdec($hex);
   for ($i = 16; $i >= 0; $i -= 8) {
   for ($i = 16; $i >= 0; $i -= 8) {

+ 3 - 3
modules/comment/comment.info

@@ -9,7 +9,7 @@ files[] = comment.test
 configure = admin/content/comment
 configure = admin/content/comment
 stylesheets[all][] = comment.css
 stylesheets[all][] = comment.css
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 0 - 3
modules/comment/comment.install

@@ -9,9 +9,6 @@
  * Implements hook_uninstall().
  * Implements hook_uninstall().
  */
  */
 function comment_uninstall() {
 function comment_uninstall() {
-  // Delete comment_body field.
-  field_delete_field('comment_body');
-
   // Remove variables.
   // Remove variables.
   variable_del('comment_block_count');
   variable_del('comment_block_count');
   $node_types = array_keys(node_type_get_types());
   $node_types = array_keys(node_type_get_types());

+ 55 - 0
modules/comment/comment.test

@@ -6,6 +6,7 @@
  */
  */
 
 
 class CommentHelperCase extends DrupalWebTestCase {
 class CommentHelperCase extends DrupalWebTestCase {
+  protected $super_user;
   protected $admin_user;
   protected $admin_user;
   protected $web_user;
   protected $web_user;
   protected $node;
   protected $node;
@@ -19,6 +20,7 @@ class CommentHelperCase extends DrupalWebTestCase {
     parent::setUp($modules);
     parent::setUp($modules);
 
 
     // Create users and test node.
     // Create users and test node.
+    $this->super_user = $this->drupalCreateUser(array('access administration pages', 'administer modules'));
     $this->admin_user = $this->drupalCreateUser(array('administer content types', 'administer comments', 'administer blocks', 'administer actions', 'administer fields'));
     $this->admin_user = $this->drupalCreateUser(array('administer content types', 'administer comments', 'administer blocks', 'administer actions', 'administer fields'));
     $this->web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'create article content', 'edit own comments'));
     $this->web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'create article content', 'edit own comments'));
     $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->web_user->uid));
     $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->web_user->uid));
@@ -2264,3 +2266,56 @@ class CommentNodeChangesTestCase extends CommentHelperCase {
     $this->assertFalse(comment_load($comment->id), 'The comment could not be loaded after the node was deleted.');
     $this->assertFalse(comment_load($comment->id), 'The comment could not be loaded after the node was deleted.');
   }
   }
 }
 }
+
+/**
+ * Tests uninstalling the comment module.
+ */
+class CommentUninstallTestCase extends CommentHelperCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Comment module uninstallation',
+      'description' => 'Tests that the comments module can be properly uninstalled.',
+      'group' => 'Comment',
+    );
+  }
+
+  function testCommentUninstall() {
+    $this->drupalLogin($this->super_user);
+
+    // Disable comment module.
+    $edit['modules[Core][comment][enable]'] = FALSE;
+    $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+    $this->assertText(t('The configuration options have been saved.'), 'Comment module was disabled.');
+
+    // Uninstall comment module.
+    $edit = array('uninstall[comment]' => 'comment');
+    $this->drupalPost('admin/modules/uninstall', $edit, t('Uninstall'));
+    $this->drupalPost(NULL, NULL, t('Uninstall'));
+    $this->assertText(t('The selected modules have been uninstalled.'), 'Comment module was uninstalled.');
+
+    // Run cron and clear field cache so that comment fields and instances
+    // marked for deletion are actually removed.
+    $this->cronRun();
+    field_cache_clear();
+
+    // Verify that comment fields have been removed.
+    $all_fields = array_keys(field_info_field_map());
+    $this->assertFalse(in_array('comment_body', $all_fields), 'Comment fields were removed by uninstall.');
+
+    // Verify that comment field instances have been removed (or at least marked
+    // for deletion).
+    // N.B. field_read_instances does an INNER JOIN on field_config so if the
+    // comment_body row has been removed successfully from there no instances
+    // will be returned, but that does not guarantee that no rows are left over
+    // in the field_config_instance table.
+    $count = db_select('field_config_instance', 'fci')
+      ->condition('entity_type', 'comment')
+      ->condition('field_name', 'comment_body')
+      ->condition('deleted', 0)
+      ->countQuery()
+      ->execute()
+      ->fetchField();
+    $this->assertTrue($count == 0, 'Comment field instances were removed by uninstall.');
+  }
+}

+ 3 - 3
modules/contact/contact.info

@@ -6,7 +6,7 @@ core = 7.x
 files[] = contact.test
 files[] = contact.test
 configure = admin/structure/contact
 configure = admin/structure/contact
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/contextual/contextual.info

@@ -5,7 +5,7 @@ version = VERSION
 core = 7.x
 core = 7.x
 files[] = contextual.test
 files[] = contextual.test
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/dashboard/dashboard.info

@@ -7,7 +7,7 @@ files[] = dashboard.test
 dependencies[] = block
 dependencies[] = block
 configure = admin/dashboard/customize
 configure = admin/dashboard/customize
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/dblog/dblog.info

@@ -5,7 +5,7 @@ version = VERSION
 core = 7.x
 core = 7.x
 files[] = dblog.test
 files[] = dblog.test
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/field/field.info

@@ -11,7 +11,7 @@ dependencies[] = field_sql_storage
 required = TRUE
 required = TRUE
 stylesheets[all][] = theme/field.css
 stylesheets[all][] = theme/field.css
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/field/modules/field_sql_storage/field_sql_storage.info

@@ -7,7 +7,7 @@ dependencies[] = field
 files[] = field_sql_storage.test
 files[] = field_sql_storage.test
 required = TRUE
 required = TRUE
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/field/modules/list/list.info

@@ -7,7 +7,7 @@ dependencies[] = field
 dependencies[] = options
 dependencies[] = options
 files[] = tests/list.test
 files[] = tests/list.test
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/field/modules/list/tests/list_test.info

@@ -5,7 +5,7 @@ package = Testing
 version = VERSION
 version = VERSION
 hidden = TRUE
 hidden = TRUE
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/field/modules/number/number.info

@@ -6,7 +6,7 @@ core = 7.x
 dependencies[] = field
 dependencies[] = field
 files[] = number.test
 files[] = number.test
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 1 - 1
modules/field/modules/number/number.test

@@ -174,7 +174,7 @@ class NumberFieldTestCase extends DrupalWebTestCase {
       ),
       ),
       'display' => array(
       'display' => array(
         'default' => array(
         'default' => array(
-          'type' => 'number_float',
+          'type' => 'number_decimal',
         ),
         ),
       ),
       ),
     );
     );

+ 3 - 3
modules/field/modules/options/options.info

@@ -6,7 +6,7 @@ core = 7.x
 dependencies[] = field
 dependencies[] = field
 files[] = options.test
 files[] = options.test
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/field/modules/text/text.info

@@ -7,7 +7,7 @@ dependencies[] = field
 files[] = text.test
 files[] = text.test
 required = TRUE
 required = TRUE
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/field/tests/field_test.info

@@ -6,7 +6,7 @@ files[] = field_test.entity.inc
 version = VERSION
 version = VERSION
 hidden = TRUE
 hidden = TRUE
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/field/tests/field_test.storage.inc

@@ -455,13 +455,13 @@ function field_test_field_attach_rename_bundle($bundle_old, $bundle_new) {
 function field_test_field_attach_delete_bundle($entity_type, $bundle, $instances) {
 function field_test_field_attach_delete_bundle($entity_type, $bundle, $instances) {
   $data = _field_test_storage_data();
   $data = _field_test_storage_data();
 
 
-  foreach ($instances as $field_name => $instance) {
-    $field = field_info_field($field_name);
+  foreach ($instances as $instance) {
+    $field = field_info_field_by_id($instance['field_id']);
     if ($field['storage']['type'] == 'field_test_storage') {
     if ($field['storage']['type'] == 'field_test_storage') {
       $field_data = &$data[$field['id']];
       $field_data = &$data[$field['id']];
       foreach (array('current', 'revisions') as $sub_table) {
       foreach (array('current', 'revisions') as $sub_table) {
         foreach ($field_data[$sub_table] as &$row) {
         foreach ($field_data[$sub_table] as &$row) {
-          if ($row->bundle == $bundle_old) {
+          if ($row->bundle == $bundle) {
             $row->deleted = TRUE;
             $row->deleted = TRUE;
           }
           }
         }
         }

+ 4 - 0
modules/field_ui/field_ui.admin.inc

@@ -1026,6 +1026,10 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund
 
 
     $instance['display'][$view_mode]['type'] = $formatter_type;
     $instance['display'][$view_mode]['type'] = $formatter_type;
     $formatter = field_info_formatter_types($formatter_type);
     $formatter = field_info_formatter_types($formatter_type);
+    // For hidden fields, $formatter will be NULL, but we expect an array later.
+    // To maintain BC, but avoid PHP 7.4 Notices, ensure $formatter is an array
+    // with a 'module' element.
+    $formatter['module'] = isset($formatter['module']) ? $formatter['module'] : '';
     $instance['display'][$view_mode]['module'] = $formatter['module'];
     $instance['display'][$view_mode]['module'] = $formatter['module'];
     $instance['display'][$view_mode]['settings'] = $settings;
     $instance['display'][$view_mode]['settings'] = $settings;
 
 

+ 3 - 3
modules/field_ui/field_ui.info

@@ -6,7 +6,7 @@ core = 7.x
 dependencies[] = field
 dependencies[] = field
 files[] = field_ui.test
 files[] = field_ui.test
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 6 - 1
modules/field_ui/field_ui.module

@@ -265,6 +265,12 @@ function field_ui_menu_title($instance) {
  * Menu access callback for the 'view mode display settings' pages.
  * Menu access callback for the 'view mode display settings' pages.
  */
  */
 function _field_ui_view_mode_menu_access($entity_type, $bundle, $view_mode, $access_callback) {
 function _field_ui_view_mode_menu_access($entity_type, $bundle, $view_mode, $access_callback) {
+  // It's good practice to call func_get_args() at the beginning of a function
+  // to avoid problems with function parameters being modified later. The
+  // behavior of func_get_args() changed in PHP7.
+  // @see https://www.php.net/manual/en/migration70.incompatible.php#migration70.incompatible.other.func-parameter-modified
+  $all_args = func_get_args();
+
   // First, determine visibility according to the 'use custom display'
   // First, determine visibility according to the 'use custom display'
   // setting for the view mode.
   // setting for the view mode.
   $bundle = field_extract_bundle($entity_type, $bundle);
   $bundle = field_extract_bundle($entity_type, $bundle);
@@ -275,7 +281,6 @@ function _field_ui_view_mode_menu_access($entity_type, $bundle, $view_mode, $acc
   // part of _menu_check_access().
   // part of _menu_check_access().
   if ($visibility) {
   if ($visibility) {
     // Grab the variable 'access arguments' part.
     // Grab the variable 'access arguments' part.
-    $all_args = func_get_args();
     $args = array_slice($all_args, 4);
     $args = array_slice($all_args, 4);
     $callback = empty($access_callback) ? 0 : trim($access_callback);
     $callback = empty($access_callback) ? 0 : trim($access_callback);
     if (is_numeric($callback)) {
     if (is_numeric($callback)) {

+ 3 - 3
modules/file/file.info

@@ -6,7 +6,7 @@ core = 7.x
 dependencies[] = field
 dependencies[] = field
 files[] = tests/file.test
 files[] = tests/file.test
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 1 - 1
modules/file/tests/file.test

@@ -409,7 +409,7 @@ class FileManagedFileElementTestCase extends FileFieldTestCase {
           'form_token' => 'invalid token',
           'form_token' => 'invalid token',
         );
         );
         $this->drupalPost($path, $edit, t('Save'));
         $this->drupalPost($path, $edit, t('Save'));
-        $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
+        $this->assertText('The form has become outdated.');
         $last_fid = $this->getLastFileId();
         $last_fid = $this->getLastFileId();
         $this->assertEqual($last_fid_prior, $last_fid, 'File was not saved when uploaded with an invalid form token.');
         $this->assertEqual($last_fid_prior, $last_fid, 'File was not saved when uploaded with an invalid form token.');
 
 

+ 3 - 3
modules/file/tests/file_module_test.info

@@ -5,7 +5,7 @@ version = VERSION
 core = 7.x
 core = 7.x
 hidden = TRUE
 hidden = TRUE
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 2 - 2
modules/filter/filter.api.php

@@ -202,7 +202,7 @@ function callback_filter_settings($form, &$form_state, $filter, $format, $defaul
  */
  */
 function callback_filter_prepare($text, $filter, $format, $langcode, $cache, $cache_id) {
 function callback_filter_prepare($text, $filter, $format, $langcode, $cache, $cache_id) {
   // Escape <code> and </code> tags.
   // Escape <code> and </code> tags.
-  $text = preg_replace('|<code>(.+?)</code>|se', "[codefilter_code]$1[/codefilter_code]", $text);
+  $text = preg_replace('|<code>(.+?)</code>|s', "[codefilter_code]$1[/codefilter_code]", $text);
   return $text;
   return $text;
 }
 }
 
 
@@ -234,7 +234,7 @@ function callback_filter_prepare($text, $filter, $format, $langcode, $cache, $ca
  * @ingroup callbacks
  * @ingroup callbacks
  */
  */
 function callback_filter_process($text, $filter, $format, $langcode, $cache, $cache_id) {
 function callback_filter_process($text, $filter, $format, $langcode, $cache, $cache_id) {
-  $text = preg_replace('|\[codefilter_code\](.+?)\[/codefilter_code\]|se', "<pre>$1</pre>", $text);
+  $text = preg_replace('|\[codefilter_code\](.+?)\[/codefilter_code\]|s', "<pre>$1</pre>", $text);
 
 
   return $text;
   return $text;
 }
 }

+ 3 - 3
modules/filter/filter.info

@@ -7,7 +7,7 @@ files[] = filter.test
 required = TRUE
 required = TRUE
 configure = admin/config/content/formats
 configure = admin/config/content/formats
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/forum/forum.info

@@ -9,7 +9,7 @@ files[] = forum.test
 configure = admin/structure/forum
 configure = admin/structure/forum
 stylesheets[all][] = forum.css
 stylesheets[all][] = forum.css
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 2 - 1
modules/forum/forum.module

@@ -922,7 +922,8 @@ function forum_get_topics($tid, $sortby, $forum_per_page) {
   );
   );
 
 
   $order = _forum_get_topic_order($sortby);
   $order = _forum_get_topic_order($sortby);
-  for ($i = 0; $i < count($forum_topic_list_header); $i++) {
+  // Skip element with index 0 which is NULL.
+  for ($i = 1; $i < count($forum_topic_list_header); $i++) {
     if ($forum_topic_list_header[$i]['field'] == $order['field']) {
     if ($forum_topic_list_header[$i]['field'] == $order['field']) {
       $forum_topic_list_header[$i]['sort'] = $order['sort'];
       $forum_topic_list_header[$i]['sort'] = $order['sort'];
     }
     }

+ 3 - 3
modules/help/help.info

@@ -5,7 +5,7 @@ version = VERSION
 core = 7.x
 core = 7.x
 files[] = help.test
 files[] = help.test
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/image/image.info

@@ -7,7 +7,7 @@ dependencies[] = file
 files[] = image.test
 files[] = image.test
 configure = admin/config/media/image-styles
 configure = admin/config/media/image-styles
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/image/tests/image_module_test.info

@@ -6,7 +6,7 @@ core = 7.x
 files[] = image_module_test.module
 files[] = image_module_test.module
 hidden = TRUE
 hidden = TRUE
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/locale/locale.info

@@ -6,7 +6,7 @@ core = 7.x
 files[] = locale.test
 files[] = locale.test
 configure = admin/config/regional/language
 configure = admin/config/regional/language
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/locale/tests/locale_test.info

@@ -5,7 +5,7 @@ package = Testing
 version = VERSION
 version = VERSION
 hidden = TRUE
 hidden = TRUE
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/menu/menu.info

@@ -6,7 +6,7 @@ core = 7.x
 files[] = menu.test
 files[] = menu.test
 configure = admin/structure/menu
 configure = admin/structure/menu
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/node/node.info

@@ -9,7 +9,7 @@ required = TRUE
 configure = admin/structure/types
 configure = admin/structure/types
 stylesheets[all][] = node.css
 stylesheets[all][] = node.css
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 1 - 1
modules/node/node.module

@@ -2659,7 +2659,7 @@ function node_feed($nids = FALSE, $channel = array()) {
  *   An array in the format expected by drupal_render().
  *   An array in the format expected by drupal_render().
  */
  */
 function node_view_multiple($nodes, $view_mode = 'teaser', $weight = 0, $langcode = NULL) {
 function node_view_multiple($nodes, $view_mode = 'teaser', $weight = 0, $langcode = NULL) {
-  $build = array();
+  $build = array('nodes' => array());
   $entities_by_view_mode = entity_view_mode_prepare('node', $nodes, $view_mode, $langcode);
   $entities_by_view_mode = entity_view_mode_prepare('node', $nodes, $view_mode, $langcode);
   foreach ($entities_by_view_mode as $entity_view_mode => $entities) {
   foreach ($entities_by_view_mode as $entity_view_mode => $entities) {
     field_attach_prepare_view('node', $entities, $entity_view_mode, $langcode);
     field_attach_prepare_view('node', $entities, $entity_view_mode, $langcode);

+ 3 - 3
modules/node/tests/node_access_test.info

@@ -5,7 +5,7 @@ version = VERSION
 core = 7.x
 core = 7.x
 hidden = TRUE
 hidden = TRUE
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/node/tests/node_test.info

@@ -5,7 +5,7 @@ version = VERSION
 core = 7.x
 core = 7.x
 hidden = TRUE
 hidden = TRUE
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/node/tests/node_test_exception.info

@@ -5,7 +5,7 @@ version = VERSION
 core = 7.x
 core = 7.x
 hidden = TRUE
 hidden = TRUE
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/openid/openid.info

@@ -5,7 +5,7 @@ package = Core
 core = 7.x
 core = 7.x
 files[] = openid.test
 files[] = openid.test
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/openid/tests/openid_test.info

@@ -6,7 +6,7 @@ core = 7.x
 dependencies[] = openid
 dependencies[] = openid
 hidden = TRUE
 hidden = TRUE
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/overlay/overlay.info

@@ -4,7 +4,7 @@ package = Core
 version = VERSION
 version = VERSION
 core = 7.x
 core = 7.x
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/path/path.info

@@ -6,7 +6,7 @@ core = 7.x
 files[] = path.test
 files[] = path.test
 configure = admin/config/search/path
 configure = admin/config/search/path
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/php/php.info

@@ -5,7 +5,7 @@ version = VERSION
 core = 7.x
 core = 7.x
 files[] = php.test
 files[] = php.test
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/poll/poll.info

@@ -6,7 +6,7 @@ core = 7.x
 files[] = poll.test
 files[] = poll.test
 stylesheets[all][] = poll.css
 stylesheets[all][] = poll.css
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/profile/profile.info

@@ -11,7 +11,7 @@ configure = admin/config/people/profile
 ; See user_system_info_alter().
 ; See user_system_info_alter().
 hidden = TRUE
 hidden = TRUE
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

+ 3 - 3
modules/rdf/rdf.info

@@ -5,7 +5,7 @@ version = VERSION
 core = 7.x
 core = 7.x
 files[] = rdf.test
 files[] = rdf.test
 
 
-; Information added by Drupal.org packaging script on 2019-04-17
-version = "7.66"
+; Information added by Drupal.org packaging script on 2020-06-17
+version = "7.72"
 project = "drupal"
 project = "drupal"
-datestamp = "1555533576"
+datestamp = "1592419104"

Some files were not shown because too many files changed in this diff