updated core to 7.72
This commit is contained in:
+43
-1
@@ -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
@@ -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
|
- (provisional) Drew Webber 'mcdruid' https://www.drupal.org/u/mcdruid
|
||||||
- Stefan Ruijsenaars 'stefan.r' https://www.drupal.org/u/stefanr-0
|
|
||||||
- (provisional) Pol Dellaiera 'Pol' https://www.drupal.org/u/pol
|
|
||||||
|
|
||||||
|
|
||||||
Component maintainers
|
Component maintainers
|
||||||
|
|||||||
@@ -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
@@ -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,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_all('watchdog', $log_entry);
|
||||||
module_invoke($module, '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
@@ -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
|
* - data: An array containing the values for the request body or a string
|
||||||
* 'param=value¶m=value&...'; to generate this, use http_build_query().
|
* containing the request body, formatted as
|
||||||
* Defaults to NULL.
|
* 'param=value¶m=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 = '';
|
// Sort the JavaScript so that it appears in the correct order.
|
||||||
// The index counter is used to keep aggregated and non-aggregated files in
|
uasort($items, 'drupal_sort_css_js');
|
||||||
// order by weight.
|
|
||||||
$index = 1;
|
// Provide the page with information about the individual JavaScript files
|
||||||
$processed = array();
|
// used, information not otherwise available when aggregation is enabled.
|
||||||
$files = array();
|
$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.
|
$files = array();
|
||||||
uasort($items, 'drupal_sort_css_js');
|
|
||||||
|
|
||||||
// Provide the page with information about the individual JavaScript files
|
$scripts = isset($elements['scripts']) ? $elements['scripts'] : array();
|
||||||
// used, information not otherwise available when aggregation is enabled.
|
$scripts += array('#weight' => 0);
|
||||||
$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
|
$settings = isset($elements['settings']) ? $elements['settings'] : array();
|
||||||
// that drupal_get_js() is running, so add the setting to this output as well
|
$settings += array('#weight' => $scripts['#weight'] + 10);
|
||||||
// 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
|
// The index counter is used to keep aggregated and non-aggregated files in
|
||||||
// stripped off settings, potentially in order to override how settings get
|
// order by weight. Use existing scripts count as a starting point.
|
||||||
// output, so in this case, do not add the setting to this output.
|
$index = count(element_children($scripts)) + 1;
|
||||||
if ($scope == 'header' && isset($items['settings'])) {
|
|
||||||
$items['settings']['data'][] = $setting;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
// Keep the order of JS files consistent as some are preprocessed and others
|
||||||
// Make sure any inline or JS setting variables appear last after libraries have loaded.
|
// are not. Make sure any inline or JS setting variables appear last after
|
||||||
return implode('', $processed) . $output;
|
// 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),
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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');
|
if (isset($path['source'])) {
|
||||||
foreach ($criteria as $field => $value) {
|
$query = db_delete('url_alias');
|
||||||
$query->condition($field, $value);
|
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']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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) {
|
$default_no_striping = ($tag === 'tfoot');
|
||||||
// Check if we're dealing with a simple or complex row
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)) {
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/vendor/
|
||||||
|
/phpunit.xml
|
||||||
|
/.composer.lock
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
Polyfill unserialize [](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.
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
[](https://scrutinizer-ci.com/g/TYPO3/phar-stream-wrapper/?branch=v2)
|
[](https://scrutinizer-ci.com/g/TYPO3/phar-stream-wrapper/?branch=v2)
|
||||||
[](https://travis-ci.org/TYPO3/phar-stream-wrapper)
|
[](https://travis-ci.org/TYPO3/phar-stream-wrapper)
|
||||||
|
[](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`.
|
||||||
|
|||||||
@@ -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/"
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
$invocation = Manager::instance()->resolve($path);
|
||||||
if ($baseFile === null) {
|
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';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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>';
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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 = substr($hex, 1);
|
||||||
$hex = $hex[1] . $hex[1] . $hex[2] . $hex[2] . $hex[3] . $hex[3];
|
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) {
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ class NumberFieldTestCase extends DrupalWebTestCase {
|
|||||||
),
|
),
|
||||||
'display' => array(
|
'display' => array(
|
||||||
'default' => array(
|
'default' => array(
|
||||||
'type' => 'number_float',
|
'type' => 'number_decimal',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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) {
|
foreach ($instances as $instance) {
|
||||||
$field = field_info_field($field_name);
|
$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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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)) {
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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.');
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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'];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
@@ -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
|
; Information added by Drupal.org packaging script on 2020-06-17
|
||||||
version = "7.66"
|
version = "7.72"
|
||||||
project = "drupal"
|
project = "drupal"
|
||||||
datestamp = "1555533576"
|
datestamp = "1592419104"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user