Update Composer, update everything

This commit is contained in:
Oliver Davies 2018-11-23 12:29:20 +00:00
parent ea3e94409f
commit dda5c284b6
19527 changed files with 1135420 additions and 351004 deletions

View file

@ -1,5 +1,5 @@
uuid: ''
name: 'Drupal'
name: ''
mail: ''
slogan: ''
page:

View file

@ -5,8 +5,11 @@ system.site:
label: 'Site information'
mapping:
uuid:
type: string
type: uuid
label: 'Site UUID'
constraints:
Uuid: []
NotNull: []
name:
type: label
label: 'Site name'
@ -185,13 +188,6 @@ system.performance:
gzip:
type: boolean
label: 'Compress JavaScript files.'
response:
type: mapping
label: 'Response performance settings'
mapping:
gzip:
type: boolean
label: 'Compress cached pages'
stale_file_threshold:
type: integer
label: 'Stale file threshold'

View file

@ -11,7 +11,7 @@
float: right;
}
.ajax-progress-throbber .throbber {
background: transparent url(../../../../misc/throbber-active.gif) no-repeat 0px center;
background: transparent url(../../../../misc/throbber-active.gif) no-repeat 0 center;
display: inline;
padding: 1px 5px 2px;
}

View file

@ -27,7 +27,7 @@
.progress__percentage {
color: #555;
overflow: hidden;
font-size: .875em;
font-size: 0.875em;
margin-top: 0.2em;
}
.progress__description {

View file

@ -5,11 +5,11 @@
.system-status-report-counters__item {
width: 100%;
padding: .5em 0;
padding: 0.5em 0;
text-align: center;
white-space: nowrap;
background-color: rgba(0, 0, 0, 0.063);
margin-bottom: .5em;
margin-bottom: 0.5em;
}
@media screen and (min-width: 60em) {

View file

@ -117,8 +117,8 @@ small .admin-link:after {
text-transform: none;
}
.system-modules td details a {
color: #5C5C5B;
border: 0px;
color: #5c5c5b;
border: 0;
}
.system-modules td details {
border: 0;
@ -211,7 +211,7 @@ small .admin-link:after {
background-color: transparent;
}
[dir="rtl"] .system-status-report__status-title {
padding: 10px 40px 10px 6px;
padding: 10px 40px 10px 6px;
}
.system-status-report__status-icon:before {
content: "";
@ -223,7 +223,7 @@ small .admin-link:after {
left: 12px; /* LTR */
top: 12px;
}
[dir="rtl"] .system-status-report__status-icon:before {
[dir="rtl"] .system-status-report__status-icon:before {
left: auto;
right: 12px;
}
@ -234,7 +234,7 @@ small .admin-link:after {
background-image: url(../../../misc/icons/e29700/warning.svg);
}
.system-status-report__entry__value {
padding: 1em .5em;
padding: 1em 0.5em;
}
/**
@ -394,3 +394,8 @@ small .admin-link:after {
.cron-description__run-cron {
display: block;
}
.system-cron-settings__link {
overflow-wrap: break-word;
word-wrap: break-word;
}

View file

@ -4,7 +4,6 @@
table.diff {
border-spacing: 4px;
margin-bottom: 20px;
table-layout: fixed;
width: 100%;
}
table.diff .diff-context {

View file

@ -0,0 +1,62 @@
/**
* @file
* Provides date format preview feature.
*/
(function($, Drupal, drupalSettings) {
const dateFormats = drupalSettings.dateFormats;
/**
* Display the preview for date format entered.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attach behavior for previewing date formats on input elements.
*/
Drupal.behaviors.dateFormat = {
attach(context) {
const $context = $(context);
const $source = $context
.find('[data-drupal-date-formatter="source"]')
.once('dateFormat');
const $target = $context
.find('[data-drupal-date-formatter="preview"]')
.once('dateFormat');
const $preview = $target.find('em');
// All elements have to exist.
if (!$source.length || !$target.length) {
return;
}
/**
* Event handler that replaces date characters with value.
*
* @param {jQuery.Event} e
* The jQuery event triggered.
*/
function dateFormatHandler(e) {
const baseValue = $(e.target).val() || '';
const dateString = baseValue.replace(
/\\?(.?)/gi,
(key, value) => (dateFormats[key] ? dateFormats[key] : value),
);
$preview.html(dateString);
$target.toggleClass('js-hide', !dateString.length);
}
/**
* On given event triggers the date character replacement.
*/
$source
.on(
'keyup.dateFormat change.dateFormat input.dateFormat',
dateFormatHandler,
)
// Initialize preview.
.trigger('keyup');
},
};
})(jQuery, Drupal, drupalSettings);

View file

@ -1,40 +1,24 @@
/**
* @file
* Provides date format preview feature.
*/
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(function ($, Drupal, drupalSettings) {
'use strict';
var dateFormats = drupalSettings.dateFormats;
/**
* Display the preview for date format entered.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attach behavior for previewing date formats on input elements.
*/
Drupal.behaviors.dateFormat = {
attach: function (context) {
attach: function attach(context) {
var $context = $(context);
var $source = $context.find('[data-drupal-date-formatter="source"]').once('dateFormat');
var $target = $context.find('[data-drupal-date-formatter="preview"]').once('dateFormat');
var $preview = $target.find('em');
// All elements have to exist.
if (!$source.length || !$target.length) {
return;
}
/**
* Event handler that replaces date characters with value.
*
* @param {jQuery.Event} e
* The jQuery event triggered.
*/
function dateFormatHandler(e) {
var baseValue = $(e.target).val() || '';
var dateString = baseValue.replace(/\\?(.?)/gi, function (key, value) {
@ -45,13 +29,7 @@
$target.toggleClass('js-hide', !dateString.length);
}
/**
* On given event triggers the date character replacement.
*/
$source.on('keyup.dateFormat change.dateFormat input.dateFormat', dateFormatHandler)
// Initialize preview.
.trigger('keyup');
$source.on('keyup.dateFormat change.dateFormat input.dateFormat', dateFormatHandler).trigger('keyup');
}
};
})(jQuery, Drupal, drupalSettings);
})(jQuery, Drupal, drupalSettings);

View file

@ -0,0 +1,84 @@
/**
* @file
* System behaviors.
*/
(function($, Drupal, drupalSettings) {
// Cache IDs in an array for ease of use.
const ids = [];
/**
* Attaches field copy behavior from input fields to other input fields.
*
* When a field is filled out, apply its value to other fields that will
* likely use the same value. In the installer this is used to populate the
* administrator email address with the same value as the site email address.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches the field copy behavior to an input field.
*/
Drupal.behaviors.copyFieldValue = {
attach(context) {
// List of fields IDs on which to bind the event listener.
// Create an array of IDs to use with jQuery.
Object.keys(drupalSettings.copyFieldValue || {}).forEach(element => {
ids.push(element);
});
if (ids.length) {
// Listen to value:copy events on all dependent fields.
// We have to use body and not document because of the way jQuery events
// bubble up the DOM tree.
$('body')
.once('copy-field-values')
.on('value:copy', this.valueTargetCopyHandler);
// Listen on all source elements.
$(`#${ids.join(', #')}`)
.once('copy-field-values')
.on('blur', this.valueSourceBlurHandler);
}
},
detach(context, settings, trigger) {
if (trigger === 'unload' && ids.length) {
$('body')
.removeOnce('copy-field-values')
.off('value:copy');
$(`#${ids.join(', #')}`)
.removeOnce('copy-field-values')
.off('blur');
}
},
/**
* Event handler that fill the target element with the specified value.
*
* @param {jQuery.Event} e
* Event object.
* @param {string} value
* Custom value from jQuery trigger.
*/
valueTargetCopyHandler(e, value) {
const $target = $(e.target);
if ($target.val() === '') {
$target.val(value);
}
},
/**
* Handler for a Blur event on a source field.
*
* This event handler will trigger a 'value:copy' event on all dependent
* fields.
*
* @param {jQuery.Event} e
* The event triggered.
*/
valueSourceBlurHandler(e) {
const value = $(e.target).val();
const targetIds = drupalSettings.copyFieldValue[e.target.id];
$(`#${targetIds.join(', #')}`).trigger('value:copy', value);
},
};
})(jQuery, Drupal, drupalSettings);

View file

@ -1,81 +1,41 @@
/**
* @file
* System behaviors.
*/
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(function ($, Drupal, drupalSettings) {
'use strict';
// Cache IDs in an array for ease of use.
var ids = [];
/**
* Attaches field copy behavior from input fields to other input fields.
*
* When a field is filled out, apply its value to other fields that will
* likely use the same value. In the installer this is used to populate the
* administrator email address with the same value as the site email address.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches the field copy behavior to an input field.
*/
Drupal.behaviors.copyFieldValue = {
attach: function (context) {
// List of fields IDs on which to bind the event listener.
// Create an array of IDs to use with jQuery.
for (var sourceId in drupalSettings.copyFieldValue) {
if (drupalSettings.copyFieldValue.hasOwnProperty(sourceId)) {
ids.push(sourceId);
}
}
attach: function attach(context) {
Object.keys(drupalSettings.copyFieldValue || {}).forEach(function (element) {
ids.push(element);
});
if (ids.length) {
// Listen to value:copy events on all dependent fields.
// We have to use body and not document because of the way jQuery events
// bubble up the DOM tree.
$('body').once('copy-field-values').on('value:copy', this.valueTargetCopyHandler);
// Listen on all source elements.
$('#' + ids.join(', #')).once('copy-field-values').on('blur', this.valueSourceBlurHandler);
}
},
detach: function (context, settings, trigger) {
detach: function detach(context, settings, trigger) {
if (trigger === 'unload' && ids.length) {
$('body').removeOnce('copy-field-values').off('value:copy');
$('#' + ids.join(', #')).removeOnce('copy-field-values').off('blur');
}
},
/**
* Event handler that fill the target element with the specified value.
*
* @param {jQuery.Event} e
* Event object.
* @param {string} value
* Custom value from jQuery trigger.
*/
valueTargetCopyHandler: function (e, value) {
valueTargetCopyHandler: function valueTargetCopyHandler(e, value) {
var $target = $(e.target);
if ($target.val() === '') {
$target.val(value);
}
},
/**
* Handler for a Blur event on a source field.
*
* This event handler will trigger a 'value:copy' event on all dependent
* fields.
*
* @param {jQuery.Event} e
* The event triggered.
*/
valueSourceBlurHandler: function (e) {
valueSourceBlurHandler: function valueSourceBlurHandler(e) {
var value = $(e.target).val();
var targetIds = drupalSettings.copyFieldValue[e.target.id];
$('#' + targetIds.join(', #')).trigger('value:copy', value);
}
};
})(jQuery, Drupal, drupalSettings);
})(jQuery, Drupal, drupalSettings);

View file

@ -0,0 +1,102 @@
/**
* @file
* Module page behaviors.
*/
(function($, Drupal, debounce) {
/**
* Filters the module list table by a text input search string.
*
* Additionally accounts for multiple tables being wrapped in "package" details
* elements.
*
* Text search input: input.table-filter-text
* Target table: input.table-filter-text[data-table]
* Source text: .table-filter-text-source, .module-name, .module-description
*
* @type {Drupal~behavior}
*/
Drupal.behaviors.tableFilterByText = {
attach(context, settings) {
const $input = $('input.table-filter-text').once('table-filter-text');
const $table = $($input.attr('data-table'));
let $rowsAndDetails;
let $rows;
let $details;
let searching = false;
function hidePackageDetails(index, element) {
const $packDetails = $(element);
const $visibleRows = $packDetails.find('tbody tr:visible');
$packDetails.toggle($visibleRows.length > 0);
}
function filterModuleList(e) {
const query = $(e.target).val();
// Case insensitive expression to find query at the beginning of a word.
const re = new RegExp(`\\b${query}`, 'i');
function showModuleRow(index, row) {
const $row = $(row);
const $sources = $row.find(
'.table-filter-text-source, .module-name, .module-description',
);
const textMatch = $sources.text().search(re) !== -1;
$row.closest('tr').toggle(textMatch);
}
// Search over all rows and packages.
$rowsAndDetails.show();
// Filter if the length of the query is at least 2 characters.
if (query.length >= 2) {
searching = true;
$rows.each(showModuleRow);
// Note that we first open all <details> to be able to use ':visible'.
// Mark the <details> elements that were closed before filtering, so
// they can be reclosed when filtering is removed.
$details
.not('[open]')
.attr('data-drupal-system-state', 'forced-open');
// Hide the package <details> if they don't have any visible rows.
// Note that we first show() all <details> to be able to use ':visible'.
$details.attr('open', true).each(hidePackageDetails);
Drupal.announce(
Drupal.t('!modules modules are available in the modified list.', {
'!modules': $rowsAndDetails.find('tbody tr:visible').length,
}),
);
} else if (searching) {
searching = false;
$rowsAndDetails.show();
// Return <details> elements that had been closed before filtering
// to a closed state.
$details
.filter('[data-drupal-system-state="forced-open"]')
.removeAttr('data-drupal-system-state')
.attr('open', false);
}
}
function preventEnterKey(event) {
if (event.which === 13) {
event.preventDefault();
event.stopPropagation();
}
}
if ($table.length) {
$rowsAndDetails = $table.find('tr, details');
$rows = $table.find('tbody tr');
$details = $rowsAndDetails.filter('.package-listing');
$input.on({
keyup: debounce(filterModuleList, 200),
keydown: preventEnterKey,
});
}
},
};
})(jQuery, Drupal, Drupal.debounce);

View file

@ -1,31 +1,18 @@
/**
* @file
* Module page behaviors.
*/
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(function ($, Drupal, debounce) {
'use strict';
/**
* Filters the module list table by a text input search string.
*
* Additionally accounts for multiple tables being wrapped in "package" details
* elements.
*
* Text search input: input.table-filter-text
* Target table: input.table-filter-text[data-table]
* Source text: .table-filter-text-source, .module-name, .module-description
*
* @type {Drupal~behavior}
*/
Drupal.behaviors.tableFilterByText = {
attach: function (context, settings) {
attach: function attach(context, settings) {
var $input = $('input.table-filter-text').once('table-filter-text');
var $table = $($input.attr('data-table'));
var $rowsAndDetails;
var $rows;
var $details;
var $rowsAndDetails = void 0;
var $rows = void 0;
var $details = void 0;
var searching = false;
function hidePackageDetails(index, element) {
@ -36,7 +23,7 @@
function filterModuleList(e) {
var query = $(e.target).val();
// Case insensitive expression to find query at the beginning of a word.
var re = new RegExp('\\b' + query, 'i');
function showModuleRow(index, row) {
@ -45,38 +32,25 @@
var textMatch = $sources.text().search(re) !== -1;
$row.closest('tr').toggle(textMatch);
}
// Search over all rows and packages.
$rowsAndDetails.show();
// Filter if the length of the query is at least 2 characters.
if (query.length >= 2) {
searching = true;
$rows.each(showModuleRow);
// Note that we first open all <details> to be able to use ':visible'.
// Mark the <details> elements that were closed before filtering, so
// they can be reclosed when filtering is removed.
$details.not('[open]').attr('data-drupal-system-state', 'forced-open');
// Hide the package <details> if they don't have any visible rows.
// Note that we first show() all <details> to be able to use ':visible'.
$details.attr('open', true).each(hidePackageDetails);
Drupal.announce(
Drupal.t(
'!modules modules are available in the modified list.',
{'!modules': $rowsAndDetails.find('tbody tr:visible').length}
)
);
}
else if (searching) {
Drupal.announce(Drupal.t('!modules modules are available in the modified list.', {
'!modules': $rowsAndDetails.find('tbody tr:visible').length
}));
} else if (searching) {
searching = false;
$rowsAndDetails.show();
// Return <details> elements that had been closed before filtering
// to a closed state.
$details.filter('[data-drupal-system-state="forced-open"]')
.removeAttr('data-drupal-system-state')
.attr('open', false);
$details.filter('[data-drupal-system-state="forced-open"]').removeAttr('data-drupal-system-state').attr('open', false);
}
}
@ -99,5 +73,4 @@
}
}
};
}(jQuery, Drupal, Drupal.debounce));
})(jQuery, Drupal, Drupal.debounce);

View file

@ -2,12 +2,14 @@ id: d6_date_formats
label: Date format configuration
migration_tags:
- Drupal 6
- Configuration
source:
plugin: variable_multirow
variables:
- date_format_long
- date_format_medium
- date_format_short
source_module: system
process:
id:
plugin: static_map

View file

@ -3,6 +3,7 @@ id: d6_menu
label: Menus
migration_tags:
- Drupal 6
- Configuration
source:
plugin: menu
process:

View file

@ -2,12 +2,14 @@ id: d6_system_cron
label: Cron settings
migration_tags:
- Drupal 6
- Configuration
source:
plugin: variable
variables:
- cron_threshold_warning
- cron_threshold_error
- cron_last
source_module: system
process:
'threshold/requirements_warning': cron_threshold_warning
'threshold/requirements_error': cron_threshold_error

View file

@ -2,12 +2,14 @@ id: d6_system_date
label: System date configuration
migration_tags:
- Drupal 6
- Configuration
source:
plugin: variable
variables:
- configurable_timezones
- date_first_day
- date_default_timezone
source_module: system
process:
'timezone/user/configurable': configurable_timezones
first_day: date_first_day

View file

@ -2,11 +2,13 @@ id: d6_system_file
label: File system configuration
migration_tags:
- Drupal 6
- Configuration
source:
plugin: variable
variables:
- file_directory_temp
- allow_insecure_uploads
source_module: system
process:
'path/temporary': file_directory_temp
allow_insecure_uploads:

View file

@ -2,6 +2,7 @@ id: d6_system_performance
label: Performance configuration
migration_tags:
- Drupal 6
- Configuration
source:
plugin: variable
variables:
@ -10,11 +11,11 @@ source:
- cache_lifetime
- cache
- page_compression
source_module: system
process:
'css/preprocess': preprocess_css
'js/preprocess': preprocess_js
'cache/page/max_age': cache_lifetime
'response/gzip': page_compression
destination:
plugin: config
config_name: system.performance

View file

@ -2,10 +2,12 @@ id: d7_global_theme_settings
label: D7 global theme settings
migration_tags:
- Drupal 7
- Configuration
source:
plugin: variable
variables:
- theme_settings
source_module: system
process:
'features/logo': theme_settings/toggle_logo
'features/name': theme_settings/toggle_name

View file

@ -2,6 +2,7 @@ id: d7_menu
label: Menus
migration_tags:
- Drupal 7
- Configuration
source:
plugin: menu
process:

View file

@ -1,10 +1,13 @@
id: d7_system_authorize
label: Drupal 7 file transfer authorize configuration
migration_tags:
- Drupal 7
- Configuration
source:
plugin: variable
variables:
- authorize_filetransfer_default
source_module: system
process:
filetransfer_default: authorize_filetransfer_default
destination:

View file

@ -2,11 +2,13 @@ id: d7_system_cron
label: Drupal 7 cron settings
migration_tags:
- Drupal 7
- Configuration
source:
plugin: variable
variables:
- cron_threshold_warning
- cron_threshold_error
source_module: system
process:
'threshold/requirements_warning': cron_threshold_warning
'threshold/requirements_error': cron_threshold_error

View file

@ -1,6 +1,8 @@
id: d7_system_date
label: Drupal 7 system date configuration
migration_tags:
- Drupal 7
- Configuration
source:
plugin: variable
variables:
@ -10,6 +12,7 @@ source:
- configurable_timezones
- empty_timezone_message
- user_default_timezone
source_module: system
process:
'country/default': site_default_country
first_day: date_first_day

View file

@ -2,11 +2,13 @@ id: d7_system_file
label: Drupal 7 file system configuration
migration_tags:
- Drupal 7
- Configuration
source:
plugin: variable
variables:
- allow_insecure_uploads
- file_temporary_path
source_module: system
process:
allow_insecure_uploads:
plugin: static_map

View file

@ -1,10 +1,13 @@
id: d7_system_mail
label: Drupal 7 system mail configuration
migration_tags:
- Drupal 7
- Configuration
source:
plugin: variable
variables:
- mail_system
source_module: system
process:
'interface/default':
plugin: static_map

View file

@ -2,6 +2,7 @@ id: d7_system_performance
label: Drupal 7 performance configuration
migration_tags:
- Drupal 7
- Configuration
source:
plugin: variable
variables:
@ -9,11 +10,11 @@ source:
- preprocess_js
- cache_lifetime
- page_compression
source_module: system
process:
'css/preprocess': preprocess_css
'js/preprocess': preprocess_js
'cache/page/max_age': cache_lifetime
'response/gzip': page_compression
destination:
plugin: config
config_name: system.performance

View file

@ -2,6 +2,7 @@ id: d7_theme_settings
label: D7 theme settings
migration_tags:
- Drupal 7
- Configuration
source:
plugin: d7_theme_settings
constants:

View file

@ -3,10 +3,12 @@ label: Image toolkit configuration
migration_tags:
- Drupal 6
- Drupal 7
- Configuration
source:
plugin: variable
variables:
- image_toolkit
source_module: system
process:
toolkit: image_toolkit
destination:

View file

@ -3,10 +3,12 @@ label: Image quality configuration
migration_tags:
- Drupal 6
- Drupal 7
- Configuration
source:
plugin: variable
variables:
- image_jpeg_quality
source_module: system
process:
jpeg_quality: image_jpeg_quality
destination:

View file

@ -3,10 +3,12 @@ label: System logging
migration_tags:
- Drupal 6
- Drupal 7
- Configuration
source:
plugin: variable
variables:
- error_level
source_module: system
process:
error_level:
plugin: static_map

View file

@ -3,10 +3,12 @@ label: Maintenance page configuration
migration_tags:
- Drupal 6
- Drupal 7
- Configuration
source:
plugin: variable
variables:
- site_offline_message
source_module: system
process:
message: site_offline_message
destination:

View file

@ -3,11 +3,13 @@ label: RSS configuration
migration_tags:
- Drupal 6
- Drupal 7
- Configuration
source:
plugin: variable
variables:
- feed_default_items
- feed_item_length
source_module: system
process:
'items/limit': feed_default_items
'items/view_mode': feed_item_length

View file

@ -3,6 +3,7 @@ label: Site configuration
migration_tags:
- Drupal 6
- Drupal 7
- Configuration
source:
plugin: variable
constants:
@ -16,6 +17,7 @@ source:
- site_404
- drupal_weight_select_max
- admin_compact_mode
source_module: system
process:
name: site_name
mail: site_mail

View file

@ -61,11 +61,22 @@ class BatchController implements ContainerInjectionInterface {
return $output;
}
elseif (isset($output)) {
$title = isset($output['#title']) ? $output['#title'] : NULL;
$page = [
'#type' => 'page',
'#title' => $title,
'#show_messages' => FALSE,
'content' => $output,
];
// Also inject title as a page header (if available).
if ($title) {
$page['header'] = [
'#type' => 'page_title',
'#title' => $title,
];
}
return $page;
}
}

View file

@ -335,11 +335,11 @@ class DbUpdateController extends ControllerBase {
// Warn the user if any updates were incompatible.
if ($incompatible_updates_exist) {
drupal_set_message($this->t('Some of the pending updates cannot be applied because their dependencies were not met.'), 'warning');
$this->messenger()->addWarning($this->t('Some of the pending updates cannot be applied because their dependencies were not met.'));
}
if (empty($count)) {
drupal_set_message($this->t('No pending updates.'));
$this->messenger()->addStatus($this->t('No pending updates.'));
unset($build);
$build['links'] = [
'#theme' => 'links',
@ -524,7 +524,7 @@ class DbUpdateController extends ControllerBase {
$build['status_report'] = [
'#type' => 'status_report',
'#requirements' => $requirements,
'#suffix' => $this->t('Check the messages and <a href=":url">try again</a>.', [':url' => $try_again_url])
'#suffix' => $this->t('Check the messages and <a href=":url">try again</a>.', [':url' => $try_again_url]),
];
$build['#title'] = $this->t('Requirements problem');

View file

@ -4,7 +4,6 @@ namespace Drupal\system\Controller;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Tags;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityAutocompleteMatcher;
use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
@ -81,7 +80,7 @@ class EntityAutocompleteController extends ControllerBase {
// Get the typed string from the URL, if it exists.
if ($input = $request->query->get('q')) {
$typed_string = Tags::explode($input);
$typed_string = Unicode::strtolower(array_pop($typed_string));
$typed_string = mb_strtolower(array_pop($typed_string));
// Selection settings are passed in as a hashed key of a serialized array
// stored in the key/value store.

View file

@ -9,6 +9,18 @@ use Drupal\Core\Controller\ControllerBase;
*/
class Http4xxController extends ControllerBase {
/**
* The default 4xx error content.
*
* @return array
* A render array containing the message to display for 4xx errors.
*/
public function on4xx() {
return [
'#markup' => $this->t('A client error happened'),
];
}
/**
* The default 401 content.
*

View file

@ -100,7 +100,7 @@ class SystemController extends ControllerBase {
public function overview($link_id) {
// Check for status report errors.
if ($this->systemManager->checkRequirements() && $this->currentUser()->hasPermission('administer site configuration')) {
drupal_set_message($this->t('One or more problems were detected with your Drupal installation. Check the <a href=":status">status report</a> for more information.', [':status' => $this->url('system.status')]), 'error');
$this->messenger()->addError($this->t('One or more problems were detected with your Drupal installation. Check the <a href=":status">status report</a> for more information.', [':status' => $this->url('system.status')]));
}
// Load all menu links below it.
$parameters = new MenuTreeParameters();
@ -187,7 +187,7 @@ class SystemController extends ControllerBase {
uasort($themes, 'system_sort_modules_by_info_name');
$theme_default = $config->get('default');
$theme_groups = ['installed' => [], 'uninstalled' => []];
$theme_groups = ['installed' => [], 'uninstalled' => []];
$admin_theme = $config->get('admin');
$admin_theme_options = [];

View file

@ -71,15 +71,15 @@ class ThemeController extends ControllerBase {
if (!empty($themes[$theme])) {
// Do not uninstall the default or admin theme.
if ($theme === $config->get('default') || $theme === $config->get('admin')) {
drupal_set_message($this->t('%theme is the default theme and cannot be uninstalled.', ['%theme' => $themes[$theme]->info['name']]), 'error');
$this->messenger()->addError($this->t('%theme is the default theme and cannot be uninstalled.', ['%theme' => $themes[$theme]->info['name']]));
}
else {
$this->themeHandler->uninstall([$theme]);
drupal_set_message($this->t('The %theme theme has been uninstalled.', ['%theme' => $themes[$theme]->info['name']]));
$this->messenger()->addStatus($this->t('The %theme theme has been uninstalled.', ['%theme' => $themes[$theme]->info['name']]));
}
}
else {
drupal_set_message($this->t('The %theme theme was not found.', ['%theme' => $theme]), 'error');
$this->messenger()->addError($this->t('The %theme theme was not found.', ['%theme' => $theme]));
}
return $this->redirect('system.themes_page');
@ -108,15 +108,15 @@ class ThemeController extends ControllerBase {
try {
if ($this->themeHandler->install([$theme])) {
$themes = $this->themeHandler->listInfo();
drupal_set_message($this->t('The %theme theme has been installed.', ['%theme' => $themes[$theme]->info['name']]));
$this->messenger()->addStatus($this->t('The %theme theme has been installed.', ['%theme' => $themes[$theme]->info['name']]));
}
else {
drupal_set_message($this->t('The %theme theme was not found.', ['%theme' => $theme]), 'error');
$this->messenger()->addError($this->t('The %theme theme was not found.', ['%theme' => $theme]));
}
}
catch (PreExistingConfigException $e) {
$config_objects = $e->flattenConfigObjects($e->getConfigObjects());
drupal_set_message(
$this->messenger()->addError(
$this->formatPlural(
count($config_objects),
'Unable to install @extension, %config_names already exists in active configuration.',
@ -124,12 +124,11 @@ class ThemeController extends ControllerBase {
[
'%config_names' => implode(', ', $config_objects),
'@extension' => $theme,
]),
'error'
])
);
}
catch (UnmetDependenciesException $e) {
drupal_set_message($e->getTranslatedMessage($this->getStringTranslation(), $theme), 'error');
$this->messenger()->addError($e->getTranslatedMessage($this->getStringTranslation(), $theme));
}
return $this->redirect('system.themes_page');
@ -171,17 +170,18 @@ class ThemeController extends ControllerBase {
// theme.
$admin_theme = $config->get('admin');
if ($admin_theme != 0 && $admin_theme != $theme) {
drupal_set_message($this->t('Please note that the administration theme is still set to the %admin_theme theme; consequently, the theme on this page remains unchanged. All non-administrative sections of the site, however, will show the selected %selected_theme theme by default.', [
'%admin_theme' => $themes[$admin_theme]->info['name'],
'%selected_theme' => $themes[$theme]->info['name'],
]));
$this->messenger()
->addStatus($this->t('Please note that the administration theme is still set to the %admin_theme theme; consequently, the theme on this page remains unchanged. All non-administrative sections of the site, however, will show the selected %selected_theme theme by default.', [
'%admin_theme' => $themes[$admin_theme]->info['name'],
'%selected_theme' => $themes[$theme]->info['name'],
]));
}
else {
drupal_set_message($this->t('%theme is now the default theme.', ['%theme' => $themes[$theme]->info['name']]));
$this->messenger()->addStatus($this->t('%theme is now the default theme.', ['%theme' => $themes[$theme]->info['name']]));
}
}
else {
drupal_set_message($this->t('The %theme theme was not found.', ['%theme' => $theme]), 'error');
$this->messenger()->addError($this->t('The %theme theme was not found.', ['%theme' => $theme]));
}
return $this->redirect('system.themes_page');

View file

@ -24,7 +24,7 @@ class TimezoneController {
* Daylight saving time indicator. If abbr does not exist then the time
* zone is searched solely by offset and isdst.
*
* @return JsonResponse
* @return \Symfony\Component\HttpFoundation\JsonResponse
* The timezone name in JsonResponse object.
*/
public function getTimezone($abbreviation = '', $offset = -1, $is_daylight_saving_time = NULL) {

View file

@ -36,7 +36,6 @@ class CronController extends ControllerBase {
return new static($container->get('cron'));
}
/**
* Run Cron once.
*
@ -58,10 +57,10 @@ class CronController extends ControllerBase {
*/
public function runManually() {
if ($this->cron->run()) {
drupal_set_message($this->t('Cron ran successfully.'));
$this->messenger()->addStatus($this->t('Cron ran successfully.'));
}
else {
drupal_set_message($this->t('Cron run failed.'), 'error');
$this->messenger()->addError($this->t('Cron run failed.'));
}
return $this->redirect('system.status');

View file

@ -14,18 +14,23 @@ use Drupal\Core\Session\AccountInterface;
*/
class DateFormatAccessControlHandler extends EntityAccessControlHandler {
/**
* {@inheritdoc}
*/
protected $viewLabelOperation = TRUE;
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
// There are no restrictions on viewing a date format.
if ($operation == 'view') {
// There are no restrictions on viewing the label of a date format.
if ($operation === 'view label') {
return AccessResult::allowed();
}
// Locked date formats cannot be updated or deleted.
elseif (in_array($operation, ['update', 'delete'])) {
if ($entity->isLocked()) {
return AccessResult::forbidden()->addCacheableDependency($entity);
return AccessResult::forbidden('The DateFormat config entity is locked.')->addCacheableDependency($entity);
}
else {
return parent::checkAccess($entity, $operation, $account)->addCacheableDependency($entity);

View file

@ -15,6 +15,13 @@ use Drupal\Component\Plugin\ConfigurablePluginInterface;
* @ConfigEntityType(
* id = "action",
* label = @Translation("Action"),
* label_collection = @Translation("Actions"),
* label_singular = @Translation("action"),
* label_plural = @Translation("actions"),
* label_count = @PluralTranslation(
* singular = "@count action",
* plural = "@count actions",
* ),
* admin_permission = "administer actions",
* entity_keys = {
* "id" = "id",

View file

@ -3,6 +3,7 @@
namespace Drupal\system\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\system\MenuInterface;
/**
@ -11,6 +12,13 @@ use Drupal\system\MenuInterface;
* @ConfigEntityType(
* id = "menu",
* label = @Translation("Menu"),
* label_collection = @Translation("Menus"),
* label_singular = @Translation("menu"),
* label_plural = @Translation("menus"),
* label_count = @PluralTranslation(
* singular = "@count menu",
* plural = "@count menus",
* ),
* handlers = {
* "access" = "Drupal\system\MenuAccessControlHandler"
* },
@ -71,4 +79,43 @@ class Menu extends ConfigEntityBase implements MenuInterface {
return (bool) $this->locked;
}
/**
* {@inheritdoc}
*/
public static function preDelete(EntityStorageInterface $storage, array $entities) {
parent::preDelete($storage, $entities);
/** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
$menu_link_manager = \Drupal::service('plugin.manager.menu.link');
foreach ($entities as $menu) {
// Delete all links from the menu.
$menu_link_manager->deleteLinksInMenu($menu->id());
}
}
/**
* {@inheritdoc}
*/
public function save() {
$return = parent::save();
\Drupal::cache('menu')->invalidateAll();
// Invalidate the block cache to update menu-based derivatives.
if (\Drupal::moduleHandler()->moduleExists('block')) {
\Drupal::service('plugin.manager.block')->clearCachedDefinitions();
}
return $return;
}
/**
* {@inheritdoc}
*/
public function delete() {
parent::delete();
\Drupal::cache('menu')->invalidateAll();
// Invalidate the block cache to update menu-based derivatives.
if (\Drupal::moduleHandler()->moduleExists('block')) {
\Drupal::service('plugin.manager.block')->clearCachedDefinitions();
}
}
}

View file

@ -4,10 +4,11 @@ namespace Drupal\system\EventSubscriber;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Adds the _admin_route option to each admin route.
* Adds the _admin_route option to each admin HTML route.
*/
class AdminRouteSubscriber extends RouteSubscriberBase {
@ -16,7 +17,7 @@ class AdminRouteSubscriber extends RouteSubscriberBase {
*/
protected function alterRoutes(RouteCollection $collection) {
foreach ($collection->all() as $route) {
if (strpos($route->getPath(), '/admin') === 0 && !$route->hasOption('_admin_route')) {
if (strpos($route->getPath(), '/admin') === 0 && !$route->hasOption('_admin_route') && static::isHtmlRoute($route)) {
$route->setOption('_admin_route', TRUE);
}
}
@ -36,4 +37,19 @@ class AdminRouteSubscriber extends RouteSubscriberBase {
return $events;
}
/**
* Determines whether the given route is a HTML route.
*
* @param \Symfony\Component\Routing\Route $route
* The route to analyze.
*
* @return bool
* TRUE if HTML is a valid format for this route.
*/
protected static function isHtmlRoute(Route $route) {
// If a route has no explicit format, then HTML is valid.
$format = $route->hasRequirement('_format') ? explode('|', $route->getRequirement('_format')) : ['html'];
return in_array('html', $format, TRUE);
}
}

View file

@ -14,8 +14,11 @@ use Drupal\Core\Form\ConfigFormBaseTrait;
/**
* Configure cron settings for this site.
*
* @internal
*/
class CronForm extends FormBase {
use ConfigFormBaseTrait;
/**
@ -42,7 +45,7 @@ class CronForm extends FormBase {
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
@ -104,6 +107,7 @@ class CronForm extends FormBase {
$form['run'] = [
'#type' => 'submit',
'#value' => t('Run cron'),
'#submit' => ['::runCron'],
];
$status = '<p>' . $this->t('Last run: %time ago.', ['%time' => $this->dateFormatter->formatTimeDiffSince($this->state->get('system.cron_last'))]) . '</p>';
$form['status'] = [
@ -112,7 +116,7 @@ class CronForm extends FormBase {
$cron_url = $this->url('system.cron', ['key' => $this->state->get('system.cron_key')], ['absolute' => TRUE]);
$form['cron_url'] = [
'#markup' => '<p>' . t('To run cron from outside the site, go to <a href=":cron">@cron</a>', [':cron' => $cron_url, '@cron' => $cron_url]) . '</p>',
'#markup' => '<p>' . t('To run cron from outside the site, go to <a href=":cron" class="system-cron-settings__link">@cron</a>', [':cron' => $cron_url, '@cron' => $cron_url]) . '</p>',
];
if (!$this->moduleHandler->moduleExists('automated_cron')) {
@ -131,7 +135,7 @@ class CronForm extends FormBase {
'#type' => 'checkbox',
'#title' => t('Detailed cron logging'),
'#default_value' => $this->config('system.cron')->get('logging'),
'#description' => 'Run times of individual cron jobs will be written to watchdog',
'#description' => $this->t('Run times of individual cron jobs will be written to watchdog'),
];
$form['actions']['#type'] = 'actions';
@ -145,22 +149,25 @@ class CronForm extends FormBase {
}
/**
* Runs cron and reloads the page.
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('system.cron')
->set('logging', $form_state->getValue('logging'))
->save();
drupal_set_message(t('The configuration options have been saved.'));
$this->messenger()->addStatus(t('The configuration options have been saved.'));
}
// Run cron manually from Cron form.
/**
* Form submission handler for running cron manually.
*/
public function runCron(array &$form, FormStateInterface $form_state) {
if ($this->cron->run()) {
drupal_set_message(t('Cron ran successfully.'));
$this->messenger()->addStatus($this->t('Cron ran successfully.'));
}
else {
drupal_set_message(t('Cron run failed.'), 'error');
$this->messenger()->addError($this->t('Cron run failed.'));
}
}
}

View file

@ -6,6 +6,8 @@ use Drupal\Core\Form\FormStateInterface;
/**
* Provides a form for adding a date format.
*
* @internal
*/
class DateFormatAddForm extends DateFormatFormBase {

View file

@ -8,6 +8,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Builds a form to delete a date format.
*
* @internal
*/
class DateFormatDeleteForm extends EntityDeleteForm {
@ -43,8 +45,8 @@ class DateFormatDeleteForm extends EntityDeleteForm {
public function getQuestion() {
return t('Are you sure you want to delete the format %name : %format?', [
'%name' => $this->entity->label(),
'%format' => $this->dateFormatter->format(REQUEST_TIME, $this->entity->id())]
);
'%format' => $this->dateFormatter->format(REQUEST_TIME, $this->entity->id()),
]);
}
}

View file

@ -6,6 +6,8 @@ use Drupal\Core\Form\FormStateInterface;
/**
* Provides a form for editing a date format.
*
* @internal
*/
class DateFormatEditForm extends DateFormatFormBase {

View file

@ -129,7 +129,7 @@ abstract class DateFormatFormBase extends EntityForm {
$pattern = trim($form_state->getValue('date_format_pattern'));
foreach ($this->dateFormatStorage->loadMultiple() as $format) {
if ($format->getPattern() == $pattern && ($format->id() == $this->entity->id())) {
drupal_set_message(t('The existing format/name combination has not been altered.'));
$this->messenger()->addStatus($this->t('The existing format/name combination has not been altered.'));
continue;
}
}
@ -149,10 +149,10 @@ abstract class DateFormatFormBase extends EntityForm {
public function save(array $form, FormStateInterface $form_state) {
$status = $this->entity->save();
if ($status == SAVED_UPDATED) {
drupal_set_message(t('Custom date format updated.'));
$this->messenger()->addStatus($this->t('Custom date format updated.'));
}
else {
drupal_set_message(t('Custom date format added.'));
$this->messenger()->addStatus($this->t('Custom date format added.'));
}
$form_state->setRedirectUrl($this->entity->urlInfo('collection'));
}

View file

@ -14,6 +14,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configure file system settings for this site.
*
* @internal
*/
class FileSystemForm extends ConfigFormBase {
@ -125,10 +127,10 @@ class FileSystemForm extends ConfigFormBase {
$period[0] = t('Never');
$form['temporary_maximum_age'] = [
'#type' => 'select',
'#title' => t('Delete orphaned files after'),
'#title' => t('Delete temporary files after'),
'#default_value' => $config->get('temporary_maximum_age'),
'#options' => $period,
'#description' => t('Orphaned files are not referenced from any content but remain in the file system and may appear in administrative listings. <strong>Warning:</strong> If enabled, orphaned files will be permanently deleted and may not be recoverable.'),
'#description' => t('Temporary files are not referenced, but are in the file system and therefore may show up in administrative lists. <strong>Warning:</strong> If enabled, temporary files will be permanently deleted and may not be recoverable.'),
];
return parent::buildForm($form, $form_state);

View file

@ -10,6 +10,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configures image toolkit settings for this site.
*
* @internal
*/
class ImageToolkitForm extends ConfigFormBase {

View file

@ -7,6 +7,8 @@ use Drupal\Core\Form\FormStateInterface;
/**
* Configure logging settings for this site.
*
* @internal
*/
class LoggingForm extends ConfigFormBase {

View file

@ -14,6 +14,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Builds a confirmation form for enabling modules with dependencies.
*
* @internal
*/
class ModulesListConfirmForm extends ConfirmFormBase {
@ -172,29 +174,27 @@ class ModulesListConfirmForm extends ConfirmFormBase {
}
catch (PreExistingConfigException $e) {
$config_objects = $e->flattenConfigObjects($e->getConfigObjects());
drupal_set_message(
$this->messenger()->addError(
$this->formatPlural(
count($config_objects),
'Unable to install @extension, %config_names already exists in active configuration.',
'Unable to install @extension, %config_names already exist in active configuration.',
[
'%config_names' => implode(', ', $config_objects),
'@extension' => $this->modules['install'][$e->getExtension()]
]),
'error'
'@extension' => $this->modules['install'][$e->getExtension()],
])
);
return;
}
catch (UnmetDependenciesException $e) {
drupal_set_message(
$e->getTranslatedMessage($this->getStringTranslation(), $this->modules['install'][$e->getExtension()]),
'error'
$this->messenger()->addError(
$e->getTranslatedMessage($this->getStringTranslation(), $this->modules['install'][$e->getExtension()])
);
return;
}
$module_names = array_values($this->modules['install']);
drupal_set_message($this->formatPlural(count($module_names), 'Module %name has been enabled.', '@count modules have been enabled: %names.', [
$this->messenger()->addStatus($this->formatPlural(count($module_names), 'Module %name has been enabled.', '@count modules have been enabled: %names.', [
'%name' => $module_names[0],
'%names' => implode(', ', $module_names),
]));

View file

@ -4,6 +4,8 @@ namespace Drupal\system\Form;
/**
* Builds a confirmation form for enabling experimental modules.
*
* @internal
*/
class ModulesListExperimentalConfirmForm extends ModulesListConfirmForm {
@ -25,7 +27,7 @@ class ModulesListExperimentalConfirmForm extends ModulesListConfirmForm {
* {@inheritdoc}
*/
protected function buildMessageList() {
drupal_set_message($this->t('<a href=":url">Experimental modules</a> are provided for testing purposes only. Use at your own risk.', [':url' => 'https://www.drupal.org/core/experimental']), 'warning');
$this->messenger()->addWarning($this->t('<a href=":url">Experimental modules</a> are provided for testing purposes only. Use at your own risk.', [':url' => 'https://www.drupal.org/core/experimental']));
$items = parent::buildMessageList();
// Add the list of experimental modules after any other messages.

View file

@ -7,6 +7,7 @@ use Drupal\Core\Config\PreExistingConfigException;
use Drupal\Core\Config\UnmetDependenciesException;
use Drupal\Core\Access\AccessManagerInterface;
use Drupal\Core\Extension\Extension;
use Drupal\Core\Extension\InfoParserException;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ModuleInstallerInterface;
use Drupal\Core\Form\FormBase;
@ -25,6 +26,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* each module's name, description, and information about which modules it
* requires. See \Drupal\Core\Extension\InfoParser for info on module.info.yml
* descriptors.
*
* @internal
*/
class ModulesListForm extends FormBase {
@ -141,8 +144,14 @@ class ModulesListForm extends FormBase {
];
// Sort all modules by their names.
$modules = system_rebuild_module_data();
uasort($modules, 'system_sort_modules_by_info_name');
try {
$modules = system_rebuild_module_data();
uasort($modules, 'system_sort_modules_by_info_name');
}
catch (InfoParserException $e) {
$this->messenger()->addError($this->t('Modules could not be listed due to an error: %error', ['%error' => $e->getMessage()]));
$modules = [];
}
// Iterate over each of the modules.
$form['modules']['#tree'] = TRUE;
@ -176,6 +185,7 @@ class ModulesListForm extends FormBase {
// Lastly, sort all packages by title.
uasort($form['modules'], ['\Drupal\Component\Utility\SortArray', 'sortByTitleProperty']);
$form['#attached']['library'][] = 'core/drupal.tableresponsive';
$form['#attached']['library'][] = 'system/drupal.system.modules';
$form['actions'] = ['#type' => 'actions'];
$form['actions']['submit'] = [
@ -386,7 +396,7 @@ class ModulesListForm extends FormBase {
}
// Add all dependencies to a list.
while (list($module) = each($modules['install'])) {
foreach ($modules['install'] as $module => $value) {
foreach (array_keys($data[$module]->requires) as $dependency) {
if (!isset($modules['install'][$dependency]) && !$this->moduleHandler->moduleExists($dependency)) {
$modules['dependencies'][$module][$dependency] = $data[$dependency]->info['name'];
@ -447,30 +457,28 @@ class ModulesListForm extends FormBase {
try {
$this->moduleInstaller->install(array_keys($modules['install']));
$module_names = array_values($modules['install']);
drupal_set_message($this->formatPlural(count($module_names), 'Module %name has been enabled.', '@count modules have been enabled: %names.', [
$this->messenger()->addStatus($this->formatPlural(count($module_names), 'Module %name has been enabled.', '@count modules have been enabled: %names.', [
'%name' => $module_names[0],
'%names' => implode(', ', $module_names),
]));
}
catch (PreExistingConfigException $e) {
$config_objects = $e->flattenConfigObjects($e->getConfigObjects());
drupal_set_message(
$this->messenger()->addError(
$this->formatPlural(
count($config_objects),
'Unable to install @extension, %config_names already exists in active configuration.',
'Unable to install @extension, %config_names already exist in active configuration.',
[
'%config_names' => implode(', ', $config_objects),
'@extension' => $modules['install'][$e->getExtension()]
]),
'error'
'@extension' => $modules['install'][$e->getExtension()],
])
);
return;
}
catch (UnmetDependenciesException $e) {
drupal_set_message(
$e->getTranslatedMessage($this->getStringTranslation(), $modules['install'][$e->getExtension()]),
'error'
$this->messenger()->addError(
$e->getTranslatedMessage($this->getStringTranslation(), $modules['install'][$e->getExtension()])
);
return;
}

View file

@ -14,6 +14,8 @@ use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface;
/**
* Builds a confirmation form to uninstall selected modules.
*
* @internal
*/
class ModulesUninstallConfirmForm extends ConfirmFormBase {
use ConfigDependencyDeleteFormTrait;
@ -129,7 +131,7 @@ class ModulesUninstallConfirmForm extends ConfirmFormBase {
// Prevent this page from showing when the module list is empty.
if (empty($this->modules)) {
drupal_set_message($this->t('The selected modules could not be uninstalled, either due to a website problem or due to the uninstall confirmation form timing out. Please try again.'), 'error');
$this->messenger()->addError($this->t('The selected modules could not be uninstalled, either due to a website problem or due to the uninstall confirmation form timing out. Please try again.'));
return $this->redirect('system.modules_uninstall');
}
@ -159,7 +161,7 @@ class ModulesUninstallConfirmForm extends ConfirmFormBase {
// Uninstall the modules.
$this->moduleInstaller->uninstall($this->modules);
drupal_set_message($this->t('The selected modules have been uninstalled.'));
$this->messenger()->addStatus($this->t('The selected modules have been uninstalled.'));
$form_state->setRedirectUrl($this->getCancelUrl());
}

View file

@ -11,6 +11,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a form for uninstalling modules.
*
* @internal
*/
class ModulesUninstallForm extends FormBase {
@ -114,8 +116,6 @@ class ModulesUninstallForm extends FormBase {
return $form;
}
$profile = drupal_get_profile();
// Sort all modules by their name.
uasort($uninstallable, 'system_sort_modules_by_info_name');
$validation_reasons = $this->moduleInstaller->validateUninstall(array_keys($uninstallable));
@ -140,10 +140,9 @@ class ModulesUninstallForm extends FormBase {
$form['uninstall'][$module->getName()]['#disabled'] = TRUE;
}
// All modules which depend on this one must be uninstalled first, before
// we can allow this module to be uninstalled. (The installation profile
// is excluded from this list.)
// we can allow this module to be uninstalled.
foreach (array_keys($module->required_by) as $dependent) {
if ($dependent != $profile && drupal_get_installed_schema_version($dependent) != SCHEMA_UNINSTALLED) {
if (drupal_get_installed_schema_version($dependent) != SCHEMA_UNINSTALLED) {
$name = isset($modules[$dependent]->info['name']) ? $modules[$dependent]->info['name'] : $dependent;
$form['modules'][$module->getName()]['#required_by'][] = $name;
$form['uninstall'][$module->getName()]['#disabled'] = TRUE;

View file

@ -3,25 +3,21 @@
namespace Drupal\system\Form;
use Drupal\Core\Asset\AssetCollectionOptimizerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configure performance settings for this site.
*
* @internal
*/
class PerformanceForm extends ConfigFormBase {
/**
* The render cache bin.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $renderCache;
/**
* The date formatter service.
*
@ -43,26 +39,34 @@ class PerformanceForm extends ConfigFormBase {
*/
protected $jsCollectionOptimizer;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructs a PerformanceForm object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The factory for configuration objects.
* @param \Drupal\Core\Cache\CacheBackendInterface $render_cache
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter service.
* @param \Drupal\Core\Asset\AssetCollectionOptimizerInterface $css_collection_optimizer
* The CSS asset collection optimizer service.
* @param \Drupal\Core\Asset\AssetCollectionOptimizerInterface $js_collection_optimizer
* The JavaScript asset collection optimizer service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(ConfigFactoryInterface $config_factory, CacheBackendInterface $render_cache, DateFormatterInterface $date_formatter, AssetCollectionOptimizerInterface $css_collection_optimizer, AssetCollectionOptimizerInterface $js_collection_optimizer) {
public function __construct(ConfigFactoryInterface $config_factory, DateFormatterInterface $date_formatter, AssetCollectionOptimizerInterface $css_collection_optimizer, AssetCollectionOptimizerInterface $js_collection_optimizer, ModuleHandlerInterface $module_handler) {
parent::__construct($config_factory);
$this->renderCache = $render_cache;
$this->dateFormatter = $date_formatter;
$this->cssCollectionOptimizer = $css_collection_optimizer;
$this->jsCollectionOptimizer = $js_collection_optimizer;
$this->moduleHandler = $module_handler;
}
/**
@ -71,10 +75,10 @@ class PerformanceForm extends ConfigFormBase {
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('cache.render'),
$container->get('date.formatter'),
$container->get('asset.css.collection_optimizer'),
$container->get('asset.js.collection_optimizer')
$container->get('asset.js.collection_optimizer'),
$container->get('module_handler')
);
}
@ -116,7 +120,6 @@ class PerformanceForm extends ConfigFormBase {
'#type' => 'details',
'#title' => t('Caching'),
'#open' => TRUE,
'#description' => $this->t('Note: Drupal provides an internal page cache module that is recommended for small to medium-sized websites.'),
];
// Identical options to the ones for block caching.
// @see \Drupal\Core\Block\BlockBase::buildConfigurationForm()
@ -125,10 +128,14 @@ class PerformanceForm extends ConfigFormBase {
$period[0] = '<' . t('no caching') . '>';
$form['caching']['page_cache_maximum_age'] = [
'#type' => 'select',
'#title' => t('Page cache maximum age'),
'#title' => t('Browser and proxy cache maximum age'),
'#default_value' => $config->get('cache.page.max_age'),
'#options' => $period,
'#description' => t('The maximum time a page can be cached by browsers and proxies. This is used as the value for max-age in Cache-Control headers.'),
'#description' => t('This is used as the value for max-age in Cache-Control headers.'),
];
$form['caching']['internal_page_cache'] = [
'#markup' => $this->t('Drupal provides an <a href=":module_enable">Internal Page Cache module</a> that is recommended for small to medium-sized websites.', [':module_enable' => Url::fromRoute('system.modules_list')->toString()]),
'#access' => !$this->moduleHandler->moduleExists('page_cache'),
];
$directory = 'public://';
@ -168,10 +175,6 @@ class PerformanceForm extends ConfigFormBase {
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->cssCollectionOptimizer->deleteAll();
$this->jsCollectionOptimizer->deleteAll();
// This form allows page compression settings to be changed, which can
// invalidate cached pages in the render cache, so it needs to be cleared on
// form submit.
$this->renderCache->deleteAll();
$this->config('system.performance')
->set('cache.page.max_age', $form_state->getValue('page_cache_maximum_age'))
@ -187,7 +190,7 @@ class PerformanceForm extends ConfigFormBase {
*/
public function submitCacheClear(array &$form, FormStateInterface $form_state) {
drupal_flush_all_caches();
drupal_set_message(t('Caches cleared.'));
$this->messenger()->addStatus($this->t('Caches cleared.'));
}
}

View file

@ -12,6 +12,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Provides a form removing module content entities data before uninstallation.
*
* @internal
*/
class PrepareModulesEntityUninstallForm extends ConfirmFormBase {
@ -157,7 +159,7 @@ class PrepareModulesEntityUninstallForm extends ConfirmFormBase {
'@entity_type_singular' => $entity_type->getSingularLabel(),
'@entity_type_plural' => $entity_type->getPluralLabel(),
]
)
),
];
}
@ -225,7 +227,7 @@ class PrepareModulesEntityUninstallForm extends ConfirmFormBase {
$storage->delete($entities);
}
// Sometimes deletes cause secondary deletes. For example, deleting a
// taxonomy term can cause it's children to be be deleted too.
// taxonomy term can cause its children to be be deleted too.
$context['sandbox']['progress'] = $context['sandbox']['max'] - $storage->getQuery()->count()->execute();
// Inform the batch engine that we are not finished and provide an
@ -248,7 +250,7 @@ class PrepareModulesEntityUninstallForm extends ConfirmFormBase {
*/
public static function moduleBatchFinished($success, $results, $operations) {
$entity_type_plural = \Drupal::entityTypeManager()->getDefinition($results['entity_type_id'])->getPluralLabel();
drupal_set_message(t('All @entity_type_plural have been deleted.', ['@entity_type_plural' => $entity_type_plural]));
\Drupal::messenger()->addStatus(t('All @entity_type_plural have been deleted.', ['@entity_type_plural' => $entity_type_plural]));
return new RedirectResponse(Url::fromRoute('system.modules_uninstall')->setAbsolute()->toString());
}

View file

@ -10,6 +10,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configure regional settings for this site.
*
* @internal
*/
class RegionalForm extends ConfigFormBase {
@ -65,7 +67,7 @@ class RegionalForm extends ConfigFormBase {
$system_date = $this->config('system.date');
// Date settings:
$zones = system_time_zones();
$zones = system_time_zones(NULL, TRUE);
$form['locale'] = [
'#type' => 'details',
@ -123,7 +125,7 @@ class RegionalForm extends ConfigFormBase {
'#type' => 'checkbox',
'#title' => t('Remind users at login if their time zone is not set'),
'#default_value' => $system_date->get('timezone.user.warn'),
'#description' => t('Only applied if users may set their own time zone.')
'#description' => t('Only applied if users may set their own time zone.'),
];
$form['timezone']['configurable_timezones_wrapper']['user_default_timezone'] = [
@ -135,7 +137,7 @@ class RegionalForm extends ConfigFormBase {
DRUPAL_USER_TIMEZONE_EMPTY => t('Empty time zone'),
DRUPAL_USER_TIMEZONE_SELECT => t('Users may set their own time zone at registration'),
],
'#description' => t('Only applied if users may set their own time zone.')
'#description' => t('Only applied if users may set their own time zone.'),
];
return parent::buildForm($form, $form_state);

View file

@ -6,7 +6,9 @@ use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Configure RSS settings for this site.
* Configure RSS settings for this site
*
* @internal
*/
class RssFeedsForm extends ConfigFormBase {
@ -33,7 +35,7 @@ class RssFeedsForm extends ConfigFormBase {
'#type' => 'textarea',
'#title' => t('Feed description'),
'#default_value' => $rss_config->get('channel.description'),
'#description' => t('Description of your site, included in each feed.')
'#description' => t('Description of your site, included in each feed.'),
];
$options = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30];
$form['feed_default_items'] = [
@ -41,7 +43,7 @@ class RssFeedsForm extends ConfigFormBase {
'#title' => t('Number of items in each feed'),
'#default_value' => $rss_config->get('items.limit'),
'#options' => array_combine($options, $options),
'#description' => t('Default number of items to include in each feed.')
'#description' => t('Default number of items to include in each feed.'),
];
$form['feed_view_mode'] = [
'#type' => 'select',
@ -52,7 +54,7 @@ class RssFeedsForm extends ConfigFormBase {
'teaser' => t('Titles plus teaser'),
'fulltext' => t('Full text'),
],
'#description' => t('Global setting for the default display of content items in each feed.')
'#description' => t('Global setting for the default display of content items in each feed.'),
];
return parent::buildForm($form, $form_state);

View file

@ -12,6 +12,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configure site information settings for this site.
*
* @internal
*/
class SiteInformationForm extends ConfigFormBase {
@ -172,7 +174,7 @@ class SiteInformationForm extends ConfigFormBase {
}
if (!$this->pathValidator->isValid($form_state->getValue('site_frontpage'))) {
$form_state->setErrorByName('site_frontpage', $this->t("The path '%path' is either invalid or you do not have access to it.", ['%path' => $form_state->getValue('site_frontpage')]));
$form_state->setErrorByName('site_frontpage', $this->t("Either the path '%path' is invalid or you do not have access to it.", ['%path' => $form_state->getValue('site_frontpage')]));
}
// Get the normal paths of both error pages.
if (!$form_state->isValueEmpty('site_403')) {
@ -189,11 +191,11 @@ class SiteInformationForm extends ConfigFormBase {
}
// Validate 403 error path.
if (!$form_state->isValueEmpty('site_403') && !$this->pathValidator->isValid($form_state->getValue('site_403'))) {
$form_state->setErrorByName('site_403', $this->t("The path '%path' is either invalid or you do not have access to it.", ['%path' => $form_state->getValue('site_403')]));
$form_state->setErrorByName('site_403', $this->t("Either the path '%path' is invalid or you do not have access to it.", ['%path' => $form_state->getValue('site_403')]));
}
// Validate 404 error path.
if (!$form_state->isValueEmpty('site_404') && !$this->pathValidator->isValid($form_state->getValue('site_404'))) {
$form_state->setErrorByName('site_404', $this->t("The path '%path' is either invalid or you do not have access to it.", ['%path' => $form_state->getValue('site_404')]));
$form_state->setErrorByName('site_404', $this->t("Either the path '%path' is invalid or you do not have access to it.", ['%path' => $form_state->getValue('site_404')]));
}
parent::validateForm($form, $form_state);

View file

@ -11,6 +11,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configure maintenance settings for this site.
*
* @internal
*/
class SiteMaintenanceModeForm extends ConfigFormBase {
@ -54,6 +56,7 @@ class SiteMaintenanceModeForm extends ConfigFormBase {
$container->get('user.permissions')
);
}
/**
* {@inheritdoc}
*/

View file

@ -0,0 +1,124 @@
<?php
namespace Drupal\system\Form;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginFormBase;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* The settings_tray form handler for the SystemBrandingBlock.
*
* @internal
*/
class SystemBrandingOffCanvasForm extends PluginFormBase implements ContainerInjectionInterface {
/**
* The block plugin.
*
* @var \Drupal\Core\Block\BlockPluginInterface
*/
protected $plugin;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* SystemBrandingOffCanvasForm constructor.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
*/
public function __construct(ConfigFactoryInterface $config_factory, AccountInterface $current_user) {
$this->configFactory = $config_factory;
$this->currentUser = $current_user;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = $this->plugin->buildConfigurationForm($form, $form_state);
$form['block_branding']['#type'] = 'details';
$form['block_branding']['#weight'] = 10;
// Unset links to Site Information form, we can make these changes here.
unset($form['block_branding']['use_site_name']['#description'], $form['block_branding']['use_site_slogan']['#description']);
$site_config = $this->configFactory->getEditable('system.site');
// Load the immutable config to load the overrides.
$site_config_immutable = $this->configFactory->get('system.site');
$form['site_information'] = [
'#type' => 'details',
'#title' => t('Site details'),
'#open' => TRUE,
'#access' => $this->currentUser->hasPermission('administer site configuration') && !$site_config_immutable->hasOverrides('name') && !$site_config_immutable->hasOverrides('slogan'),
];
$form['site_information']['site_name'] = [
'#type' => 'textfield',
'#title' => t('Site name'),
'#default_value' => $site_config->get('name'),
'#required' => TRUE,
];
$form['site_information']['site_slogan'] = [
'#type' => 'textfield',
'#title' => t('Slogan'),
'#default_value' => $site_config->get('slogan'),
'#description' => t("How this is used depends on your site's theme."),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->plugin->validateConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$site_config = $this->configFactory->get('system.site');
if (AccessResult::allowedIf(!$site_config->hasOverrides('name') && !$site_config->hasOverrides('slogan'))->isAllowed()) {
$site_info = $form_state->getValue('site_information');
$this->configFactory->getEditable('system.site')
->set('name', $site_info['site_name'])
->set('slogan', $site_info['site_slogan'])
->save();
}
$this->plugin->submitConfigurationForm($form, $form_state);
}
}

View file

@ -0,0 +1,181 @@
<?php
namespace Drupal\system\Form;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginFormBase;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RedirectDestinationTrait;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\system\MenuInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* The setting_tray form handler for the SystemMenuBlock.
*
* @internal
*/
class SystemMenuOffCanvasForm extends PluginFormBase implements ContainerInjectionInterface {
use StringTranslationTrait;
use RedirectDestinationTrait;
/**
* The plugin.
*
* @var \Drupal\Core\Block\BlockPluginInterface
*/
protected $plugin;
/**
* The menu entity that the block uses and that will be edited in this form.
*
* @var \Drupal\system\MenuInterface
*/
protected $menu;
/**
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $menuStorage;
/**
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* SystemMenuOffCanvasForm constructor.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $menu_storage
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
*/
public function __construct(EntityStorageInterface $menu_storage, EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation, ConfigFactoryInterface $config_factory) {
$this->menuStorage = $menu_storage;
$this->entityTypeManager = $entity_type_manager;
$this->stringTranslation = $string_translation;
$this->configFactory = $config_factory;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager')->getStorage('menu'),
$container->get('entity_type.manager'),
$container->get('string_translation'),
$container->get('config.factory')
);
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = $this->plugin->buildConfigurationForm([], $form_state);
// Move the menu levels section to the bottom.
$form['menu_levels']['#weight'] = 100;
$form['entity_form'] = [
'#type' => 'details',
'#title' => $this->t('Edit menu %label', ['%label' => $this->menu->label()]),
'#open' => TRUE,
'#access' => !$this->hasMenuOverrides() && $this->menu->access('edit'),
];
$form['entity_form'] += $this->getEntityForm($this->menu)->buildForm([], $form_state);
// Print the menu link titles as text instead of a link.
if (!empty($form['entity_form']['links']['links'])) {
foreach (Element::children($form['entity_form']['links']['links']) as $child) {
$title = $form['entity_form']['links']['links'][$child]['title'][1]['#title'];
$form['entity_form']['links']['links'][$child]['title'][1] = ['#markup' => $title];
}
}
// Change the header text.
$form['entity_form']['links']['links']['#header'][0] = $this->t('Link');
$form['entity_form']['links']['links']['#header'][1]['data'] = $this->t('On');
// Remove the label, ID, description, and buttons from the entity form.
unset($form['entity_form']['label'], $form['entity_form']['id'], $form['entity_form']['description'], $form['entity_form']['actions']);
// Since the overview form is further nested than expected, update the
// #parents. See \Drupal\menu_ui\MenuForm::form().
$form_state->set('menu_overview_form_parents', ['settings', 'entity_form', 'links']);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->plugin->validateConfigurationForm($form, $form_state);
if (!$this->hasMenuOverrides()) {
$this->getEntityForm($this->menu)->validateForm($form, $form_state);
}
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->plugin->submitConfigurationForm($form, $form_state);
if (!$this->hasMenuOverrides()) {
$this->getEntityForm($this->menu)->submitForm($form, $form_state);
$this->menu->save();
}
}
/**
* Gets the entity form for this menu.
*
* @param \Drupal\system\MenuInterface $menu
* The menu entity.
*
* @return \Drupal\Core\Entity\EntityFormInterface
* The entity form.
*/
protected function getEntityForm(MenuInterface $menu) {
$entity_form = $this->entityTypeManager->getFormObject('menu', 'edit');
$entity_form->setEntity($menu);
return $entity_form;
}
/**
* {@inheritdoc}
*/
public function setPlugin(PluginInspectionInterface $plugin) {
$this->plugin = $plugin;
$this->menu = $this->menuStorage->loadOverrideFree($this->plugin->getDerivativeId());
}
/**
* Determines if the menu has configuration overrides.
*
* @return bool
* TRUE if the menu has configuration overrides, otherwise FALSE.
*/
protected function hasMenuOverrides() {
// @todo Replace the following with $this->menu->hasOverrides() in https://www.drupal.org/project/drupal/issues/2910353
// and remove this function.
return $this->configFactory->get($this->menu->getEntityType()
->getConfigPrefix() . '.' . $this->menu->id())->hasOverrides();
}
}

View file

@ -7,6 +7,8 @@ use Drupal\Core\Form\FormStateInterface;
/**
* Form to select the administration theme.
*
* @internal
*/
class ThemeAdminForm extends ConfigFormBase {

View file

@ -16,6 +16,8 @@ use Drupal\Core\Theme\ThemeManagerInterface;
/**
* Displays theme configuration for entire site and individual themes.
*
* @internal
*/
class ThemeSettingsForm extends ConfigFormBase {
@ -134,11 +136,11 @@ class ThemeSettingsForm extends ConfigFormBase {
$form['var'] = [
'#type' => 'hidden',
'#value' => $var
'#value' => $var,
];
$form['config_key'] = [
'#type' => 'hidden',
'#value' => $config_key
'#value' => $config_key,
];
// Toggle settings
@ -212,7 +214,10 @@ class ThemeSettingsForm extends ConfigFormBase {
'#type' => 'file',
'#title' => t('Upload logo image'),
'#maxlength' => 40,
'#description' => t("If you don't have direct file access to the server, use this field to upload your logo.")
'#description' => t("If you don't have direct file access to the server, use this field to upload your logo."),
'#upload_validators' => [
'file_validate_is_image' => [],
],
];
}
@ -252,7 +257,12 @@ class ThemeSettingsForm extends ConfigFormBase {
$form['favicon']['settings']['favicon_upload'] = [
'#type' => 'file',
'#title' => t('Upload favicon image'),
'#description' => t("If you don't have direct file access to the server, use this field to upload your shortcut icon.")
'#description' => t("If you don't have direct file access to the server, use this field to upload your shortcut icon."),
'#upload_validators' => [
'file_validate_extensions' => [
'ico png gif jpg jpeg apng svg',
],
],
];
}
@ -324,9 +334,21 @@ class ThemeSettingsForm extends ConfigFormBase {
// Process the theme and all its base themes.
foreach ($theme_keys as $theme) {
// Include the theme-settings.php file.
$filename = DRUPAL_ROOT . '/' . $themes[$theme]->getPath() . '/theme-settings.php';
if (file_exists($filename)) {
require_once $filename;
$theme_path = drupal_get_path('theme', $theme);
$theme_settings_file = $theme_path . '/theme-settings.php';
$theme_file = $theme_path . '/' . $theme . '.theme';
$filenames = [$theme_settings_file, $theme_file];
foreach ($filenames as $filename) {
if (file_exists($filename)) {
require_once $filename;
// The file must be required for the cached form too.
$files = $form_state->getBuildInfo()['files'];
if (!in_array($filename, $files)) {
$files[] = $filename;
}
$form_state->addBuildInfo('files', $files);
}
}
// Call theme-specific settings.
@ -355,37 +377,23 @@ class ThemeSettingsForm extends ConfigFormBase {
parent::validateForm($form, $form_state);
if ($this->moduleHandler->moduleExists('file')) {
// Handle file uploads.
$validators = ['file_validate_is_image' => []];
// Check for a new uploaded logo.
$file = file_save_upload('logo_upload', $validators, FALSE, 0);
if (isset($file)) {
// File upload was attempted.
if (isset($form['logo'])) {
$file = _file_save_upload_from_form($form['logo']['settings']['logo_upload'], $form_state, 0);
if ($file) {
// Put the temporary file in form_values so we can save it on submit.
$form_state->setValue('logo_upload', $file);
}
else {
// File upload failed.
$form_state->setErrorByName('logo_upload', $this->t('The logo could not be uploaded.'));
}
}
$validators = ['file_validate_extensions' => ['ico png gif jpg jpeg apng svg']];
// Check for a new uploaded favicon.
$file = file_save_upload('favicon_upload', $validators, FALSE, 0);
if (isset($file)) {
// File upload was attempted.
if (isset($form['favicon'])) {
$file = _file_save_upload_from_form($form['favicon']['settings']['favicon_upload'], $form_state, 0);
if ($file) {
// Put the temporary file in form_values so we can save it on submit.
$form_state->setValue('favicon_upload', $file);
}
else {
// File upload failed.
$form_state->setErrorByName('favicon_upload', $this->t('The favicon could not be uploaded.'));
}
}
// When intending to use the default logo, unset the logo_path.
@ -480,7 +488,7 @@ class ThemeSettingsForm extends ConfigFormBase {
*/
protected function validatePath($path) {
// Absolute local file paths are invalid.
if (drupal_realpath($path) == $path) {
if (\Drupal::service('file_system')->realpath($path) == $path) {
return FALSE;
}
// A path relative to the Drupal root or a fully qualified URI is valid.

View file

@ -3,7 +3,6 @@
namespace Drupal\system;
use Drupal\Component\Transliteration\TransliterationInterface;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
@ -73,7 +72,7 @@ class MachineNameController implements ContainerInjectionInterface {
$transliterated = $this->transliteration->transliterate($text, $langcode, '_');
if ($lowercase) {
$transliterated = Unicode::strtolower($transliterated);
$transliterated = mb_strtolower($transliterated);
}
if (isset($replace_pattern) && isset($replace)) {

View file

@ -14,17 +14,23 @@ use Drupal\Core\Session\AccountInterface;
*/
class MenuAccessControlHandler extends EntityAccessControlHandler {
/**
* {@inheritdoc}
*/
protected $viewLabelOperation = TRUE;
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
if ($operation === 'view') {
// There are no restrictions on viewing the label of a date format.
if ($operation === 'view label') {
return AccessResult::allowed();
}
// Locked menus could not be deleted.
elseif ($operation == 'delete') {
elseif ($operation === 'delete') {
if ($entity->isLocked()) {
return AccessResult::forbidden()->addCacheableDependency($entity);
return AccessResult::forbidden('The Menu config entity is locked.')->addCacheableDependency($entity);
}
else {
return parent::checkAccess($entity, $operation, $account)->addCacheableDependency($entity);

View file

@ -11,6 +11,7 @@ use Drupal\Core\Controller\TitleResolverInterface;
use Drupal\Core\Link;
use Drupal\Core\ParamConverter\ParamNotConvertedException;
use Drupal\Core\Path\CurrentPathStack;
use Drupal\Core\Path\PathMatcherInterface;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Drupal\Core\Routing\RequestContext;
use Drupal\Core\Routing\RouteMatch;
@ -79,6 +80,20 @@ class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
*/
protected $currentUser;
/**
* The current path service.
*
* @var \Drupal\Core\Path\CurrentPathStack
*/
protected $currentPath;
/**
* The patch matcher service.
*
* @var \Drupal\Core\Path\PathMatcherInterface
*/
protected $pathMatcher;
/**
* Constructs the PathBasedBreadcrumbBuilder.
*
@ -98,8 +113,10 @@ class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
* The current user object.
* @param \Drupal\Core\Path\CurrentPathStack $current_path
* The current path.
* @param \Drupal\Core\Path\PathMatcherInterface $path_matcher
* The path matcher service.
*/
public function __construct(RequestContext $context, AccessManagerInterface $access_manager, RequestMatcherInterface $router, InboundPathProcessorInterface $path_processor, ConfigFactoryInterface $config_factory, TitleResolverInterface $title_resolver, AccountInterface $current_user, CurrentPathStack $current_path) {
public function __construct(RequestContext $context, AccessManagerInterface $access_manager, RequestMatcherInterface $router, InboundPathProcessorInterface $path_processor, ConfigFactoryInterface $config_factory, TitleResolverInterface $title_resolver, AccountInterface $current_user, CurrentPathStack $current_path, PathMatcherInterface $path_matcher = NULL) {
$this->context = $context;
$this->accessManager = $access_manager;
$this->router = $router;
@ -108,6 +125,7 @@ class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
$this->titleResolver = $title_resolver;
$this->currentUser = $current_user;
$this->currentPath = $current_path;
$this->pathMatcher = $path_matcher ?: \Drupal::service('path.matcher');
}
/**
@ -124,6 +142,15 @@ class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
$breadcrumb = new Breadcrumb();
$links = [];
// Add the url.path.parent cache context. This code ignores the last path
// part so the result only depends on the path parents.
$breadcrumb->addCacheContexts(['url.path.parent', 'url.path.is_front']);
// Do not display a breadcrumb on the frontpage.
if ($this->pathMatcher->isFrontPage()) {
return $breadcrumb;
}
// General path-based breadcrumbs. Use the actual request path, prior to
// resolving path aliases, so the breadcrumb can be defined by simply
// creating a hierarchy of path aliases.
@ -136,9 +163,6 @@ class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
// /user is just a redirect, so skip it.
// @todo Find a better way to deal with /user.
$exclude['/user'] = TRUE;
// Add the url.path.parent cache context. This code ignores the last path
// part so the result only depends on the path parents.
$breadcrumb->addCacheContexts(['url.path.parent']);
while (count($path_elements) > 1) {
array_pop($path_elements);
// Copy the path elements for up-casting.
@ -160,12 +184,10 @@ class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
$links[] = new Link($title, $url);
}
}
}
}
if ($path && '/' . $path != $front) {
// Add the Home link, except for the front page.
$links[] = Link::createFromRoute($this->t('Home'), '<front>');
}
// Add the Home link.
$links[] = Link::createFromRoute($this->t('Home'), '<front>');
return $breadcrumb->setLinks(array_reverse($links));
}

View file

@ -15,7 +15,10 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*
* @Block(
* id = "system_branding_block",
* admin_label = @Translation("Site branding")
* admin_label = @Translation("Site branding"),
* forms = {
* "settings_tray" = "Drupal\system\Form\SystemBrandingOffCanvasForm",
* },
* )
*/
class SystemBrandingBlock extends BlockBase implements ContainerFactoryPluginInterface {

View file

@ -10,7 +10,10 @@ use Drupal\Core\Block\MainContentBlockPluginInterface;
*
* @Block(
* id = "system_main_block",
* admin_label = @Translation("Main page content")
* admin_label = @Translation("Main page content"),
* forms = {
* "settings_tray" = FALSE,
* },
* )
*/
class SystemMainBlock extends BlockBase implements MainContentBlockPluginInterface {

View file

@ -5,7 +5,6 @@ namespace Drupal\system\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Menu\MenuActiveTrailInterface;
use Drupal\Core\Menu\MenuLinkTreeInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@ -17,7 +16,10 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* id = "system_menu_block",
* admin_label = @Translation("Menu"),
* category = @Translation("Menus"),
* deriver = "Drupal\system\Plugin\Derivative\SystemMenuBlock"
* deriver = "Drupal\system\Plugin\Derivative\SystemMenuBlock",
* forms = {
* "settings_tray" = "\Drupal\system\Form\SystemMenuOffCanvasForm",
* },
* )
*/
class SystemMenuBlock extends BlockBase implements ContainerFactoryPluginInterface {
@ -29,13 +31,6 @@ class SystemMenuBlock extends BlockBase implements ContainerFactoryPluginInterfa
*/
protected $menuTree;
/**
* The active menu trail service.
*
* @var \Drupal\Core\Menu\MenuActiveTrailInterface
*/
protected $menuActiveTrail;
/**
* Constructs a new SystemMenuBlock.
*
@ -47,13 +42,10 @@ class SystemMenuBlock extends BlockBase implements ContainerFactoryPluginInterfa
* The plugin implementation definition.
* @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree
* The menu tree service.
* @param \Drupal\Core\Menu\MenuActiveTrailInterface $menu_active_trail
* The active menu trail service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MenuLinkTreeInterface $menu_tree, MenuActiveTrailInterface $menu_active_trail) {
public function __construct(array $configuration, $plugin_id, $plugin_definition, MenuLinkTreeInterface $menu_tree) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->menuTree = $menu_tree;
$this->menuActiveTrail = $menu_active_trail;
}
/**
@ -64,8 +56,7 @@ class SystemMenuBlock extends BlockBase implements ContainerFactoryPluginInterfa
$configuration,
$plugin_id,
$plugin_definition,
$container->get('menu.link_tree'),
$container->get('menu.active_trail')
$container->get('menu.link_tree')
);
}
@ -147,6 +138,25 @@ class SystemMenuBlock extends BlockBase implements ContainerFactoryPluginInterfa
$parameters->setMaxDepth(min($level + $depth - 1, $this->menuTree->maxDepth()));
}
// For menu blocks with start level greater than 1, only show menu items
// from the current active trail. Adjust the root according to the current
// position in the menu in order to determine if we can show the subtree.
if ($level > 1) {
if (count($parameters->activeTrail) >= $level) {
// Active trail array is child-first. Reverse it, and pull the new menu
// root based on the parent of the configured start level.
$menu_trail_ids = array_reverse(array_values($parameters->activeTrail));
$menu_root = $menu_trail_ids[$level - 1];
$parameters->setRoot($menu_root)->setMinDepth(1);
if ($depth > 0) {
$parameters->setMaxDepth(min($level - 1 + $depth - 1, $this->menuTree->maxDepth()));
}
}
else {
return [];
}
}
$tree = $this->menuTree->load($menu_name, $parameters);
$manipulators = [
['callable' => 'menu.default_tree_manipulators:checkAccess'],

View file

@ -9,7 +9,7 @@ use Drupal\Core\Cache\Cache;
/**
* Provides a block to display the messages.
*
* @see drupal_set_message()
* @see @see \Drupal\Core\Messenger\MessengerInterface
*
* @Block(
* id = "system_messages_block",

View file

@ -2,7 +2,6 @@
namespace Drupal\system\Plugin\Condition;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Condition\ConditionPluginBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Path\AliasManagerInterface;
@ -139,7 +138,7 @@ class RequestPath extends ConditionPluginBase implements ContainerFactoryPluginI
public function evaluate() {
// Convert path to lowercase. This allows comparison of the same path
// with different case. Ex: /Page, /page, /PAGE.
$pages = Unicode::strtolower($this->configuration['pages']);
$pages = mb_strtolower($this->configuration['pages']);
if (!$pages) {
return TRUE;
}
@ -149,7 +148,7 @@ class RequestPath extends ConditionPluginBase implements ContainerFactoryPluginI
$path = $this->currentPath->getPath($request);
// Do not trim a trailing slash if that is the complete path.
$path = $path === '/' ? $path : rtrim($path, '/');
$path_alias = Unicode::strtolower($this->aliasManager->getAliasByPath($path));
$path_alias = mb_strtolower($this->aliasManager->getAliasByPath($path));
return $this->pathMatcher->matchPath($path_alias, $pages) || (($path != $path_alias) && $this->pathMatcher->matchPath($path, $pages));
}

View file

@ -3,7 +3,6 @@
namespace Drupal\system\Plugin\ImageToolkit;
use Drupal\Component\Utility\Color;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\ImageToolkit\ImageToolkitBase;
@ -224,7 +223,7 @@ class GDToolkit extends ImageToolkitBase {
$destination = drupal_tempnam('temporary://', 'gd_');
}
// Convert stream wrapper URI to normal path.
$destination = drupal_realpath($destination);
$destination = \Drupal::service('file_system')->realpath($destination);
}
$function = 'image' . image_type_to_extension($this->getType(), FALSE);
@ -395,7 +394,7 @@ class GDToolkit extends ImageToolkitBase {
foreach (static::supportedTypes() as $image_type) {
// @todo Automatically fetch possible extensions for each mime type.
// @see https://www.drupal.org/node/2311679
$extension = Unicode::strtolower(image_type_to_extension($image_type, FALSE));
$extension = mb_strtolower(image_type_to_extension($image_type, FALSE));
$extensions[] = $extension;
// Add some known similar extensions.
if ($extension === 'jpeg') {

View file

@ -45,7 +45,7 @@ class Rotate extends GDImageToolkitOperationBase {
// Validate or set background color argument.
if (!empty($arguments['background'])) {
// Validate the background color: Color::hexToRgb does so for us.
$background = Color::hexToRgb($arguments['background']) + [ 'alpha' => 0 ];
$background = Color::hexToRgb($arguments['background']) + ['alpha' => 0];
}
else {
// Background color is not specified: use transparent white as background.

View file

@ -20,6 +20,16 @@ class ScaleAndCrop extends GDImageToolkitOperationBase {
*/
protected function arguments() {
return [
'x' => [
'description' => 'The horizontal offset for the start of the crop, in pixels',
'required' => FALSE,
'default' => NULL,
],
'y' => [
'description' => 'The vertical offset for the start the crop, in pixels',
'required' => FALSE,
'default' => NULL,
],
'width' => [
'description' => 'The target width, in pixels',
],
@ -38,8 +48,12 @@ class ScaleAndCrop extends GDImageToolkitOperationBase {
$scaleFactor = max($arguments['width'] / $actualWidth, $arguments['height'] / $actualHeight);
$arguments['x'] = (int) round(($actualWidth * $scaleFactor - $arguments['width']) / 2);
$arguments['y'] = (int) round(($actualHeight * $scaleFactor - $arguments['height']) / 2);
$arguments['x'] = isset($arguments['x']) ?
(int) round($arguments['x']) :
(int) round(($actualWidth * $scaleFactor - $arguments['width']) / 2);
$arguments['y'] = isset($arguments['y']) ?
(int) round($arguments['y']) :
(int) round(($actualHeight * $scaleFactor - $arguments['height']) / 2);
$arguments['resize'] = [
'width' => (int) round($actualWidth * $scaleFactor),
'height' => (int) round($actualHeight * $scaleFactor),

View file

@ -14,6 +14,7 @@ use Drupal\migrate\Row;
* )
*/
class TimeZone extends ProcessPluginBase {
/**
* {@inheritdoc}
*/

View file

@ -0,0 +1,66 @@
<?php
namespace Drupal\system\Plugin\migrate\source;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Gets system data for a legacy extension.
*
* @MigrateSource(
* id = "extension",
* source_module = "system"
* )
*/
class Extension extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('system', 's')
->fields('s');
if (isset($this->configuration['name'])) {
$query->condition('name', (array) $this->configuration['name'], 'IN');
}
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = [
'filename' => $this->t('Filename'),
'name' => $this->t('Name'),
'type' => $this->t('Type'),
'owner' => $this->t('Owner'),
'status' => $this->t('Status'),
'throttle' => $this->t('Throttle'),
'bootstrap' => $this->t('Bootstrap'),
'schema_version' => $this->t('Schema version'),
'weight' => $this->t('Weight'),
'info' => $this->t('Information array'),
];
return $fields;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
$row->setSourceProperty('info', unserialize($row->getSourceProperty('info')));
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['name']['type'] = 'string';
return $ids;
}
}

View file

@ -9,7 +9,7 @@ use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
*
* @MigrateSource(
* id = "menu",
* source_provider = "menu"
* source_module = "menu"
* )
*/
class Menu extends DrupalSqlBase {

View file

@ -9,7 +9,7 @@ use Drupal\migrate_drupal\Plugin\migrate\source\VariableMultiRow;
*
* @MigrateSource(
* id = "d7_theme_settings",
* source_provider = "system"
* source_module = "system"
* )
*/
class ThemeSettings extends VariableMultiRow {

View file

@ -2,506 +2,20 @@
namespace Drupal\system\Plugin\views\field;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\RevisionableInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Routing\RedirectDestinationTrait;
use Drupal\Core\TypedData\TranslatableInterface;
use Drupal\views\Entity\Render\EntityTranslationRenderTrait;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\field\FieldPluginBase;
use Drupal\views\Plugin\views\field\UncacheableFieldHandlerTrait;
use Drupal\views\Plugin\views\style\Table;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
use Symfony\Component\DependencyInjection\ContainerInterface;
@trigger_error(__NAMESPACE__ . '\BulkForm is deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use \Drupal\views\Plugin\views\field\BulkForm instead. See https://www.drupal.org/node/2916716.', E_USER_DEPRECATED);
use Drupal\views\Plugin\views\field\BulkForm as ViewsBulkForm;
/**
* Defines a actions-based bulk operation form element.
*
* @ViewsField("bulk_form")
* @ViewsField("legacy_bulk_form")
*
* @deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use
* \Drupal\views\Plugin\views\field\BulkForm instead.
*
* @see https://www.drupal.org/node/2916716
*/
class BulkForm extends FieldPluginBase implements CacheableDependencyInterface {
use RedirectDestinationTrait;
use UncacheableFieldHandlerTrait;
use EntityTranslationRenderTrait;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The action storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $actionStorage;
/**
* An array of actions that can be executed.
*
* @var \Drupal\system\ActionConfigEntityInterface[]
*/
protected $actions = [];
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Constructs a new BulkForm object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityManager = $entity_manager;
$this->actionStorage = $entity_manager->getStorage('action');
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager'),
$container->get('language_manager')
);
}
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
$entity_type = $this->getEntityType();
// Filter the actions to only include those for this entity type.
$this->actions = array_filter($this->actionStorage->loadMultiple(), function ($action) use ($entity_type) {
return $action->getType() == $entity_type;
});
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
// @todo Consider making the bulk operation form cacheable. See
// https://www.drupal.org/node/2503009.
return 0;
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return $this->languageManager->isMultilingual() ? $this->getEntityTranslationRenderer()->getCacheContexts() : [];
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return [];
}
/**
* {@inheritdoc}
*/
public function getEntityTypeId() {
return $this->getEntityType();
}
/**
* {@inheritdoc}
*/
protected function getEntityManager() {
return $this->entityManager;
}
/**
* {@inheritdoc}
*/
protected function getLanguageManager() {
return $this->languageManager;
}
/**
* {@inheritdoc}
*/
protected function getView() {
return $this->view;
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['action_title'] = ['default' => $this->t('Action')];
$options['include_exclude'] = [
'default' => 'exclude',
];
$options['selected_actions'] = [
'default' => [],
];
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$form['action_title'] = [
'#type' => 'textfield',
'#title' => $this->t('Action title'),
'#default_value' => $this->options['action_title'],
'#description' => $this->t('The title shown above the actions dropdown.'),
];
$form['include_exclude'] = [
'#type' => 'radios',
'#title' => $this->t('Available actions'),
'#options' => [
'exclude' => $this->t('All actions, except selected'),
'include' => $this->t('Only selected actions'),
],
'#default_value' => $this->options['include_exclude'],
];
$form['selected_actions'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Selected actions'),
'#options' => $this->getBulkOptions(FALSE),
'#default_value' => $this->options['selected_actions'],
];
parent::buildOptionsForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function validateOptionsForm(&$form, FormStateInterface $form_state) {
parent::validateOptionsForm($form, $form_state);
$selected_actions = $form_state->getValue(['options', 'selected_actions']);
$form_state->setValue(['options', 'selected_actions'], array_values(array_filter($selected_actions)));
}
/**
* {@inheritdoc}
*/
public function preRender(&$values) {
parent::preRender($values);
// If the view is using a table style, provide a placeholder for a
// "select all" checkbox.
if (!empty($this->view->style_plugin) && $this->view->style_plugin instanceof Table) {
// Add the tableselect css classes.
$this->options['element_label_class'] .= 'select-all';
// Hide the actual label of the field on the table header.
$this->options['label'] = '';
}
}
/**
* {@inheritdoc}
*/
public function getValue(ResultRow $row, $field = NULL) {
return '<!--form-item-' . $this->options['id'] . '--' . $row->index . '-->';
}
/**
* Form constructor for the bulk form.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function viewsForm(&$form, FormStateInterface $form_state) {
// Make sure we do not accidentally cache this form.
// @todo Evaluate this again in https://www.drupal.org/node/2503009.
$form['#cache']['max-age'] = 0;
// Add the tableselect javascript.
$form['#attached']['library'][] = 'core/drupal.tableselect';
$use_revision = array_key_exists('revision', $this->view->getQuery()->getEntityTableInfo());
// Only add the bulk form options and buttons if there are results.
if (!empty($this->view->result)) {
// Render checkboxes for all rows.
$form[$this->options['id']]['#tree'] = TRUE;
foreach ($this->view->result as $row_index => $row) {
$entity = $this->getEntityTranslation($this->getEntity($row), $row);
$form[$this->options['id']][$row_index] = [
'#type' => 'checkbox',
// We are not able to determine a main "title" for each row, so we can
// only output a generic label.
'#title' => $this->t('Update this item'),
'#title_display' => 'invisible',
'#default_value' => !empty($form_state->getValue($this->options['id'])[$row_index]) ? 1 : NULL,
'#return_value' => $this->calculateEntityBulkFormKey($entity, $use_revision),
];
}
// Replace the form submit button label.
$form['actions']['submit']['#value'] = $this->t('Apply to selected items');
// Ensure a consistent container for filters/operations in the view header.
$form['header'] = [
'#type' => 'container',
'#weight' => -100,
];
// Build the bulk operations action widget for the header.
// Allow themes to apply .container-inline on this separate container.
$form['header'][$this->options['id']] = [
'#type' => 'container',
];
$form['header'][$this->options['id']]['action'] = [
'#type' => 'select',
'#title' => $this->options['action_title'],
'#options' => $this->getBulkOptions(),
];
// Duplicate the form actions into the action container in the header.
$form['header'][$this->options['id']]['actions'] = $form['actions'];
}
else {
// Remove the default actions build array.
unset($form['actions']);
}
}
/**
* Returns the available operations for this form.
*
* @param bool $filtered
* (optional) Whether to filter actions to selected actions.
* @return array
* An associative array of operations, suitable for a select element.
*/
protected function getBulkOptions($filtered = TRUE) {
$options = [];
// Filter the action list.
foreach ($this->actions as $id => $action) {
if ($filtered) {
$in_selected = in_array($id, $this->options['selected_actions']);
// If the field is configured to include only the selected actions,
// skip actions that were not selected.
if (($this->options['include_exclude'] == 'include') && !$in_selected) {
continue;
}
// Otherwise, if the field is configured to exclude the selected
// actions, skip actions that were selected.
elseif (($this->options['include_exclude'] == 'exclude') && $in_selected) {
continue;
}
}
$options[$id] = $action->label();
}
return $options;
}
/**
* Submit handler for the bulk form.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
* Thrown when the user tried to access an action without access to it.
*/
public function viewsFormSubmit(&$form, FormStateInterface $form_state) {
if ($form_state->get('step') == 'views_form_views_form') {
// Filter only selected checkboxes. Use the actual user input rather than
// the raw form values array, since the site data may change before the
// bulk form is submitted, which can lead to data loss.
$user_input = $form_state->getUserInput();
$selected = array_filter($user_input[$this->options['id']]);
$entities = [];
$action = $this->actions[$form_state->getValue('action')];
$count = 0;
foreach ($selected as $bulk_form_key) {
$entity = $this->loadEntityFromBulkFormKey($bulk_form_key);
// Skip execution if the user did not have access.
if (!$action->getPlugin()->access($entity, $this->view->getUser())) {
$this->drupalSetMessage($this->t('No access to execute %action on the @entity_type_label %entity_label.', [
'%action' => $action->label(),
'@entity_type_label' => $entity->getEntityType()->getLabel(),
'%entity_label' => $entity->label()
]), 'error');
continue;
}
$count++;
$entities[$bulk_form_key] = $entity;
}
$action->execute($entities);
$operation_definition = $action->getPluginDefinition();
if (!empty($operation_definition['confirm_form_route_name'])) {
$options = [
'query' => $this->getDestinationArray(),
];
$form_state->setRedirect($operation_definition['confirm_form_route_name'], [], $options);
}
else {
// Don't display the message unless there are some elements affected and
// there is no confirmation form.
if ($count) {
drupal_set_message($this->formatPlural($count, '%action was applied to @count item.', '%action was applied to @count items.', [
'%action' => $action->label(),
]));
}
}
}
}
/**
* Returns the message to be displayed when there are no selected items.
*
* @return string
* Message displayed when no items are selected.
*/
protected function emptySelectedMessage() {
return $this->t('No items selected.');
}
/**
* {@inheritdoc}
*/
public function viewsFormValidate(&$form, FormStateInterface $form_state) {
$selected = array_filter($form_state->getValue($this->options['id']));
if (empty($selected)) {
$form_state->setErrorByName('', $this->emptySelectedMessage());
}
}
/**
* {@inheritdoc}
*/
public function query() {
if ($this->languageManager->isMultilingual()) {
$this->getEntityTranslationRenderer()->query($this->query, $this->relationship);
}
}
/**
* {@inheritdoc}
*/
public function clickSortable() {
return FALSE;
}
/**
* Wraps drupal_set_message().
*/
protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
drupal_set_message($message, $type, $repeat);
}
/**
* Calculates a bulk form key.
*
* This generates a key that is used as the checkbox return value when
* submitting a bulk form. This key allows the entity for the row to be loaded
* totally independently of the executed view row.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to calculate a bulk form key for.
* @param bool $use_revision
* Whether the revision id should be added to the bulk form key. This should
* be set to TRUE only if the view is listing entity revisions.
*
* @return string
* The bulk form key representing the entity's id, language and revision (if
* applicable) as one string.
*
* @see self::loadEntityFromBulkFormKey()
*/
protected function calculateEntityBulkFormKey(EntityInterface $entity, $use_revision) {
$key_parts = [$entity->language()->getId(), $entity->id()];
if ($entity instanceof RevisionableInterface && $use_revision) {
$key_parts[] = $entity->getRevisionId();
}
// An entity ID could be an arbitrary string (although they are typically
// numeric). JSON then Base64 encoding ensures the bulk_form_key is
// safe to use in HTML, and that the key parts can be retrieved.
$key = json_encode($key_parts);
return base64_encode($key);
}
/**
* Loads an entity based on a bulk form key.
*
* @param string $bulk_form_key
* The bulk form key representing the entity's id, language and revision (if
* applicable) as one string.
*
* @return \Drupal\Core\Entity\EntityInterface
* The entity loaded in the state (language, optionally revision) specified
* as part of the bulk form key.
*/
protected function loadEntityFromBulkFormKey($bulk_form_key) {
$key = base64_decode($bulk_form_key);
$key_parts = json_decode($key);
$revision_id = NULL;
// If there are 3 items, vid will be last.
if (count($key_parts) === 3) {
$revision_id = array_pop($key_parts);
}
// The first two items will always be langcode and ID.
$id = array_pop($key_parts);
$langcode = array_pop($key_parts);
// Load the entity or a specific revision depending on the given key.
$storage = $this->entityManager->getStorage($this->getEntityType());
$entity = $revision_id ? $storage->loadRevision($revision_id) : $storage->load($id);
if ($entity instanceof TranslatableInterface) {
$entity = $entity->getTranslation($langcode);
}
return $entity;
}
class BulkForm extends ViewsBulkForm {
}

View file

@ -25,7 +25,7 @@ class SystemConfigSubscriber implements EventSubscriberInterface {
/**
* Constructs the SystemConfigSubscriber.
*
* @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder
* @param \Drupal\Core\Routing\RouteBuilderInterface $router_builder
* The router builder service.
*/
public function __construct(RouteBuilderInterface $router_builder) {
@ -68,10 +68,13 @@ class SystemConfigSubscriber implements EventSubscriberInterface {
* This event listener checks that the system.site:uuid's in the source and
* target match.
*
* @param ConfigImporterEvent $event
* @param \Drupal\Core\Config\ConfigImporterEvent $event
* The config import event.
*/
public function onConfigImporterValidateSiteUUID(ConfigImporterEvent $event) {
if (!$event->getConfigImporter()->getStorageComparer()->getSourceStorage()->exists('system.site')) {
$event->getConfigImporter()->logError($this->t('This import does not contain system.site configuration, so has been rejected.'));
}
if (!$event->getConfigImporter()->getStorageComparer()->validateSiteUuid()) {
$event->getConfigImporter()->logError($this->t('Site UUID in source storage does not match the target storage.'));
}

View file

@ -110,7 +110,7 @@ class SystemManager {
// Check run-time requirements and status information.
$requirements = $this->moduleHandler->invokeAll('requirements', ['runtime']);
uasort($requirements, function($a, $b) {
uasort($requirements, function ($a, $b) {
if (!isset($a['weight'])) {
if (!isset($b['weight'])) {
return strcasecmp($a['title'], $b['title']);
@ -152,7 +152,8 @@ class SystemManager {
* hidden, so we supply the contents of the block.
*
* @return array
* A render array suitable for drupal_render.
* A render array suitable for
* \Drupal\Core\Render\RendererInterface::render().
*/
public function getBlockContents() {
// We hard-code the menu name here since otherwise a link in the tools menu

View file

@ -1,77 +0,0 @@
<?php
namespace Drupal\system\Tests\Ajax;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Url;
/**
* Tests the usage of form caching for AJAX forms.
*
* @group Ajax
*/
class AjaxFormCacheTest extends AjaxTestBase {
/**
* Tests the usage of form cache for AJAX forms.
*/
public function testFormCacheUsage() {
/** @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface $key_value_expirable */
$key_value_expirable = \Drupal::service('keyvalue.expirable')->get('form');
$this->drupalLogin($this->rootUser);
// Ensure that the cache is empty.
$this->assertEqual(0, count($key_value_expirable->getAll()));
// Visit an AJAX form that is not cached, 3 times.
$uncached_form_url = Url::fromRoute('ajax_forms_test.commands_form');
$this->drupalGet($uncached_form_url);
$this->drupalGet($uncached_form_url);
$this->drupalGet($uncached_form_url);
// The number of cache entries should not have changed.
$this->assertEqual(0, count($key_value_expirable->getAll()));
}
/**
* Tests AJAX forms in blocks.
*/
public function testBlockForms() {
$this->container->get('module_installer')->install(['block', 'search']);
$this->rebuildContainer();
$this->container->get('router.builder')->rebuild();
$this->drupalLogin($this->rootUser);
$this->drupalPlaceBlock('search_form_block', ['weight' => -5]);
$this->drupalPlaceBlock('ajax_forms_test_block');
$this->drupalGet('');
$this->drupalPostAjaxForm(NULL, ['test1' => 'option1'], 'test1');
$this->assertOptionSelectedWithDrupalSelector('edit-test1', 'option1');
$this->assertOptionWithDrupalSelector('edit-test1', 'option3');
$this->drupalPostForm(NULL, ['test1' => 'option1'], 'Submit');
$this->assertText('Submission successful.');
}
/**
* Tests AJAX forms on pages with a query string.
*/
public function testQueryString() {
$this->container->get('module_installer')->install(['block']);
$this->drupalLogin($this->rootUser);
$this->drupalPlaceBlock('ajax_forms_test_block');
$url = Url::fromRoute('entity.user.canonical', ['user' => $this->rootUser->id()], ['query' => ['foo' => 'bar']]);
$this->drupalGet($url);
$this->drupalPostAjaxForm(NULL, ['test1' => 'option1'], 'test1');
$url->setOption('query', [
'foo' => 'bar',
FormBuilderInterface::AJAX_FORM_REQUEST => 1,
MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax',
]);
$this->assertUrl($url);
}
}

View file

@ -1,32 +0,0 @@
<?php
namespace Drupal\system\Tests\Ajax;
/**
* Tests that form elements in groups work correctly with AJAX.
*
* @group Ajax
*/
class AjaxInGroupTest extends AjaxTestBase {
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(['access content']));
}
/**
* Submits forms with select and checkbox elements via Ajax.
*/
public function testSimpleAjaxFormValue() {
$this->drupalGet('/ajax_forms_test_get_form');
$this->assertText('Test group');
$this->assertText('AJAX checkbox in a group');
$this->drupalPostAjaxForm(NULL, ['checkbox_in_group' => TRUE], 'checkbox_in_group');
$this->assertText('Test group');
$this->assertText('AJAX checkbox in a group');
$this->assertText('AJAX checkbox in a nested group');
$this->assertText('Another AJAX checkbox in a nested group');
}
}

View file

@ -1,158 +0,0 @@
<?php
namespace Drupal\system\Tests\Ajax;
use Drupal\Core\Ajax\AddCssCommand;
use Drupal\Core\Ajax\AfterCommand;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\AlertCommand;
use Drupal\Core\Ajax\AppendCommand;
use Drupal\Core\Ajax\BeforeCommand;
use Drupal\Core\Ajax\ChangedCommand;
use Drupal\Core\Ajax\CssCommand;
use Drupal\Core\Ajax\DataCommand;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Ajax\InsertCommand;
use Drupal\Core\Ajax\PrependCommand;
use Drupal\Core\Ajax\RemoveCommand;
use Drupal\Core\Ajax\RestripeCommand;
use Drupal\Core\Ajax\SettingsCommand;
use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Performs tests on AJAX framework commands.
*
* @group Ajax
*/
class CommandsTest extends AjaxTestBase {
/**
* Tests the various Ajax Commands.
*/
public function testAjaxCommands() {
$form_path = 'ajax_forms_test_ajax_commands_form';
$web_user = $this->drupalCreateUser(['access content']);
$this->drupalLogin($web_user);
$edit = [];
// Tests the 'add_css' command.
$commands = $this->drupalPostAjaxForm($form_path, $edit, ['op' => t("AJAX 'add_css' command")]);
$expected = new AddCssCommand('my/file.css');
$this->assertCommand($commands, $expected->render(), "'add_css' AJAX command issued with correct data.");
// Tests the 'after' command.
$commands = $this->drupalPostAjaxForm($form_path, $edit, ['op' => t("AJAX 'After': Click to put something after the div")]);
$expected = new AfterCommand('#after_div', 'This will be placed after');
$this->assertCommand($commands, $expected->render(), "'after' AJAX command issued with correct data.");
// Tests the 'alert' command.
$commands = $this->drupalPostAjaxForm($form_path, $edit, ['op' => t("AJAX 'Alert': Click to alert")]);
$expected = new AlertCommand(t('Alert'));
$this->assertCommand($commands, $expected->render(), "'alert' AJAX Command issued with correct text.");
// Tests the 'append' command.
$commands = $this->drupalPostAjaxForm($form_path, $edit, ['op' => t("AJAX 'Append': Click to append something")]);
$expected = new AppendCommand('#append_div', 'Appended text');
$this->assertCommand($commands, $expected->render(), "'append' AJAX command issued with correct data.");
// Tests the 'before' command.
$commands = $this->drupalPostAjaxForm($form_path, $edit, ['op' => t("AJAX 'before': Click to put something before the div")]);
$expected = new BeforeCommand('#before_div', 'Before text');
$this->assertCommand($commands, $expected->render(), "'before' AJAX command issued with correct data.");
// Tests the 'changed' command.
$commands = $this->drupalPostAjaxForm($form_path, $edit, ['op' => t("AJAX changed: Click to mark div changed.")]);
$expected = new ChangedCommand('#changed_div');
$this->assertCommand($commands, $expected->render(), "'changed' AJAX command issued with correct selector.");
// Tests the 'changed' command using the second argument.
$commands = $this->drupalPostAjaxForm($form_path, $edit, ['op' => t("AJAX changed: Click to mark div changed with asterisk.")]);
$expected = new ChangedCommand('#changed_div', '#changed_div_mark_this');
$this->assertCommand($commands, $expected->render(), "'changed' AJAX command (with asterisk) issued with correct selector.");
// Tests the 'css' command.
$commands = $this->drupalPostAjaxForm($form_path, $edit, ['op' => t("Set the '#box' div to be blue.")]);
$expected = new CssCommand('#css_div', ['background-color' => 'blue']);
$this->assertCommand($commands, $expected->render(), "'css' AJAX command issued with correct selector.");
// Tests the 'data' command.
$commands = $this->drupalPostAjaxForm($form_path, $edit, ['op' => t("AJAX data command: Issue command.")]);
$expected = new DataCommand('#data_div', 'testkey', 'testvalue');
$this->assertCommand($commands, $expected->render(), "'data' AJAX command issued with correct key and value.");
// Tests the 'html' command.
$commands = $this->drupalPostAjaxForm($form_path, $edit, ['op' => t("AJAX html: Replace the HTML in a selector.")]);
$expected = new HtmlCommand('#html_div', 'replacement text');
$this->assertCommand($commands, $expected->render(), "'html' AJAX command issued with correct data.");
// Tests the 'insert' command.
$commands = $this->drupalPostAjaxForm($form_path, $edit, ['op' => t("AJAX insert: Let client insert based on #ajax['method'].")]);
$expected = new InsertCommand('#insert_div', 'insert replacement text');
$this->assertCommand($commands, $expected->render(), "'insert' AJAX command issued with correct data.");
// Tests the 'invoke' command.
$commands = $this->drupalPostAjaxForm($form_path, $edit, ['op' => t("AJAX invoke command: Invoke addClass() method.")]);
$expected = new InvokeCommand('#invoke_div', 'addClass', ['error']);
$this->assertCommand($commands, $expected->render(), "'invoke' AJAX command issued with correct method and argument.");
// Tests the 'prepend' command.
$commands = $this->drupalPostAjaxForm($form_path, $edit, ['op' => t("AJAX 'prepend': Click to prepend something")]);
$expected = new PrependCommand('#prepend_div', 'prepended text');
$this->assertCommand($commands, $expected->render(), "'prepend' AJAX command issued with correct data.");
// Tests the 'remove' command.
$commands = $this->drupalPostAjaxForm($form_path, $edit, ['op' => t("AJAX 'remove': Click to remove text")]);
$expected = new RemoveCommand('#remove_text');
$this->assertCommand($commands, $expected->render(), "'remove' AJAX command issued with correct command and selector.");
// Tests the 'restripe' command.
$commands = $this->drupalPostAjaxForm($form_path, $edit, ['op' => t("AJAX 'restripe' command")]);
$expected = new RestripeCommand('#restripe_table');
$this->assertCommand($commands, $expected->render(), "'restripe' AJAX command issued with correct selector.");
// Tests the 'settings' command.
$commands = $this->drupalPostAjaxForm($form_path, $edit, ['op' => t("AJAX 'settings' command")]);
$expected = new SettingsCommand(['ajax_forms_test' => ['foo' => 42]]);
$this->assertCommand($commands, $expected->render(), "'settings' AJAX command issued with correct data.");
}
/**
* Regression test: Settings command exists regardless of JS aggregation.
*/
public function testAttachedSettings() {
$assert = function($message) {
$response = new AjaxResponse();
$response->setAttachments([
'library' => ['core/drupalSettings'],
'drupalSettings' => ['foo' => 'bar'],
]);
$ajax_response_attachments_processor = \Drupal::service('ajax_response.attachments_processor');
$subscriber = new AjaxResponseSubscriber($ajax_response_attachments_processor);
$event = new FilterResponseEvent(
\Drupal::service('http_kernel'),
new Request(),
HttpKernelInterface::MASTER_REQUEST,
$response
);
$subscriber->onResponse($event);
$expected = [
'command' => 'settings',
];
$this->assertCommand($response->getCommands(), $expected, $message);
};
$config = $this->config('system.performance');
$config->set('js.preprocess', FALSE)->save();
$assert('Settings command exists when JS aggregation is disabled.');
$config->set('js.preprocess', TRUE)->save();
$assert('Settings command exists when JS aggregation is enabled.');
}
}

View file

@ -1,214 +0,0 @@
<?php
namespace Drupal\system\Tests\Ajax;
use Drupal\ajax_test\Controller\AjaxTestController;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Url;
/**
* Performs tests on opening and manipulating dialogs via AJAX commands.
*
* @group Ajax
*/
class DialogTest extends AjaxTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['ajax_test', 'ajax_forms_test', 'contact'];
/**
* Test sending non-JS and AJAX requests to open and manipulate modals.
*/
public function testDialog() {
$this->drupalLogin($this->drupalCreateUser(['administer contact forms']));
// Ensure the elements render without notices or exceptions.
$this->drupalGet('ajax-test/dialog');
// Set up variables for this test.
$dialog_renderable = AjaxTestController::dialogContents();
$dialog_contents = \Drupal::service('renderer')->renderRoot($dialog_renderable);
$modal_expected_response = [
'command' => 'openDialog',
'selector' => '#drupal-modal',
'settings' => NULL,
'data' => $dialog_contents,
'dialogOptions' => [
'modal' => TRUE,
'title' => 'AJAX Dialog & contents',
],
];
$form_expected_response = [
'command' => 'openDialog',
'selector' => '#drupal-modal',
'settings' => NULL,
'dialogOptions' => [
'modal' => TRUE,
'title' => 'Ajax Form contents',
],
];
$entity_form_expected_response = [
'command' => 'openDialog',
'selector' => '#drupal-modal',
'settings' => NULL,
'dialogOptions' => [
'modal' => TRUE,
'title' => 'Add contact form',
],
];
$normal_expected_response = [
'command' => 'openDialog',
'selector' => '#ajax-test-dialog-wrapper-1',
'settings' => NULL,
'data' => $dialog_contents,
'dialogOptions' => [
'modal' => FALSE,
'title' => 'AJAX Dialog & contents',
],
];
$no_target_expected_response = [
'command' => 'openDialog',
'selector' => '#drupal-dialog-ajax-testdialog-contents',
'settings' => NULL,
'data' => $dialog_contents,
'dialogOptions' => [
'modal' => FALSE,
'title' => 'AJAX Dialog & contents',
],
];
$close_expected_response = [
'command' => 'closeDialog',
'selector' => '#ajax-test-dialog-wrapper-1',
'persist' => FALSE,
];
// Check that requesting a modal dialog without JS goes to a page.
$this->drupalGet('ajax-test/dialog-contents');
$this->assertRaw($dialog_contents, 'Non-JS modal dialog page present.');
// Check that requesting a modal dialog with XMLHttpRequest goes to a page.
$this->drupalGetXHR('ajax-test/dialog-contents');
$this->assertRaw($dialog_contents, 'Modal dialog page on XMLHttpRequest present.');
// Emulate going to the JS version of the page and check the JSON response.
$ajax_result = $this->drupalGetAjax('ajax-test/dialog-contents', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_modal']]);
$this->assertEqual($modal_expected_response, $ajax_result[3], 'Modal dialog JSON response matches.');
// Test the HTML escaping of & character.
$this->assertEqual($ajax_result[3]['dialogOptions']['title'], 'AJAX Dialog & contents');
$this->assertNotEqual($ajax_result[3]['dialogOptions']['title'], 'AJAX Dialog &amp; contents');
// Check that requesting a "normal" dialog without JS goes to a page.
$this->drupalGet('ajax-test/dialog-contents');
$this->assertRaw($dialog_contents, 'Non-JS normal dialog page present.');
// Emulate going to the JS version of the page and check the JSON response.
// This needs to use WebTestBase::drupalPostAjaxForm() so that the correct
// dialog options are sent.
$ajax_result = $this->drupalPostAjaxForm('ajax-test/dialog', [
// We have to mock a form element to make drupalPost submit from a link.
'textfield' => 'test',
], [], 'ajax-test/dialog-contents', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_dialog']], [], NULL, [
'submit' => [
'dialogOptions[target]' => 'ajax-test-dialog-wrapper-1',
]
]);
$this->assertEqual($normal_expected_response, $ajax_result[3], 'Normal dialog JSON response matches.');
// Emulate going to the JS version of the page and check the JSON response.
// This needs to use WebTestBase::drupalPostAjaxForm() so that the correct
// dialog options are sent.
$ajax_result = $this->drupalPostAjaxForm('ajax-test/dialog', [
// We have to mock a form element to make drupalPost submit from a link.
'textfield' => 'test',
], [], 'ajax-test/dialog-contents', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_dialog']], [], NULL, [
// Don't send a target.
'submit' => []
]);
// Make sure the selector ID starts with the right string.
$this->assert(strpos($ajax_result[3]['selector'], $no_target_expected_response['selector']) === 0, 'Selector starts with right string.');
unset($ajax_result[3]['selector']);
unset($no_target_expected_response['selector']);
$this->assertEqual($no_target_expected_response, $ajax_result[3], 'Normal dialog with no target JSON response matches.');
// Emulate closing the dialog via an AJAX request. There is no non-JS
// version of this test.
$ajax_result = $this->drupalGetAjax('ajax-test/dialog-close');
$this->assertEqual($close_expected_response, $ajax_result[0], 'Close dialog JSON response matches.');
// Test submitting via a POST request through the button for modals. This
// approach more accurately reflects the real responses by Drupal because
// all of the necessary page variables are emulated.
$ajax_result = $this->drupalPostAjaxForm('ajax-test/dialog', [], 'button1');
// Check that CSS and JavaScript are "added" to the page dynamically.
$this->assertTrue(in_array('core/drupal.dialog.ajax', explode(',', $ajax_result[0]['settings']['ajaxPageState']['libraries'])), 'core/drupal.dialog.ajax library is added to the page.');
$dialog_css_exists = strpos($ajax_result[1]['data'], 'dialog.css') !== FALSE;
$this->assertTrue($dialog_css_exists, 'jQuery UI dialog CSS added to the page.');
$dialog_js_exists = strpos($ajax_result[2]['data'], 'dialog-min.js') !== FALSE;
$this->assertTrue($dialog_js_exists, 'jQuery UI dialog JS added to the page.');
$dialog_js_exists = strpos($ajax_result[2]['data'], 'dialog.ajax.js') !== FALSE;
$this->assertTrue($dialog_js_exists, 'Drupal dialog JS added to the page.');
// Check that the response matches the expected value.
$this->assertEqual($modal_expected_response, $ajax_result[4], 'POST request modal dialog JSON response matches.');
// Test the HTML escaping of & character.
$this->assertNotEqual($ajax_result[4]['dialogOptions']['title'], 'AJAX Dialog &amp; contents');
// Abbreviated test for "normal" dialogs, testing only the difference.
$ajax_result = $this->drupalPostAjaxForm('ajax-test/dialog', [], 'button2');
$this->assertEqual($normal_expected_response, $ajax_result[4], 'POST request normal dialog JSON response matches.');
// Check that requesting a form dialog without JS goes to a page.
$this->drupalGet('ajax-test/dialog-form');
// Check we get a chunk of the code, we can't test the whole form as form
// build id and token with be different.
$form = $this->xpath("//form[@id='ajax-test-form']");
$this->assertTrue(!empty($form), 'Non-JS form page present.');
// Emulate going to the JS version of the form and check the JSON response.
$ajax_result = $this->drupalGetAjax('ajax-test/dialog-form', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_modal']]);
$expected_ajax_settings = [
'edit-preview' => [
'callback' => '::preview',
'event' => 'click',
'url' => Url::fromRoute('ajax_test.dialog_form', [], ['query' => [
MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_modal',
FormBuilderInterface::AJAX_FORM_REQUEST => TRUE,
]])->toString(),
'dialogType' => 'ajax',
'submit' => [
'_triggering_element_name' => 'op',
'_triggering_element_value' => 'Preview',
],
],
];
$this->assertEqual($expected_ajax_settings, $ajax_result[0]['settings']['ajax']);
$this->setRawContent($ajax_result[3]['data']);
// Remove the data, the form build id and token will never match.
unset($ajax_result[3]['data']);
$form = $this->xpath("//form[@id='ajax-test-form']");
$this->assertTrue(!empty($form), 'Modal dialog JSON contains form.');
$this->assertEqual($form_expected_response, $ajax_result[3]);
// Check that requesting an entity form dialog without JS goes to a page.
$this->drupalGet('admin/structure/contact/add');
// Check we get a chunk of the code, we can't test the whole form as form
// build id and token with be different.
$form = $this->xpath("//form[@id='contact-form-add-form']");
$this->assertTrue(!empty($form), 'Non-JS entity form page present.');
// Emulate going to the JS version of the form and check the JSON response.
$ajax_result = $this->drupalGetAjax('admin/structure/contact/add', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_modal']]);
$this->setRawContent($ajax_result[3]['data']);
// Remove the data, the form build id and token will never match.
unset($ajax_result[3]['data']);
$form = $this->xpath("//form[@id='contact-form-add-form']");
$this->assertTrue(!empty($form), 'Modal dialog JSON contains entity form.');
$this->assertEqual($entity_form_expected_response, $ajax_result[3]);
}
}

View file

@ -8,6 +8,7 @@ namespace Drupal\system\Tests\Ajax;
* @group Ajax
*/
class ElementValidationTest extends AjaxTestBase {
/**
* Tries to post an Ajax change to a form that has a validated element.
*

View file

@ -10,6 +10,7 @@ use Drupal\Core\Ajax\DataCommand;
* @group Ajax
*/
class FormValuesTest extends AjaxTestBase {
protected function setUp() {
parent::setUp();

View file

@ -16,6 +16,7 @@ use Drupal\Core\Asset\AttachedAssets;
* @group Ajax
*/
class FrameworkTest extends AjaxTestBase {
/**
* Verifies the Ajax rendering of a command in the settings.
*/

View file

@ -1,98 +0,0 @@
<?php
namespace Drupal\system\Tests\Ajax;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests that AJAX-enabled forms work when multiple instances of the same form
* are on a page.
*
* @group Ajax
*/
class MultiFormTest extends AjaxTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['form_test'];
protected function setUp() {
parent::setUp();
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Page']);
// Create a multi-valued field for 'page' nodes to use for Ajax testing.
$field_name = 'field_ajax_test';
FieldStorageConfig::create([
'entity_type' => 'node',
'field_name' => $field_name,
'type' => 'text',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
])->save();
FieldConfig::create([
'field_name' => $field_name,
'entity_type' => 'node',
'bundle' => 'page',
])->save();
entity_get_form_display('node', 'page', 'default')
->setComponent($field_name, ['type' => 'text_textfield'])
->save();
// Log in a user who can create 'page' nodes.
$this->drupalLogin ($this->drupalCreateUser(['create page content']));
}
/**
* Tests that pages with the 'node_page_form' included twice work correctly.
*/
public function testMultiForm() {
// HTML IDs for elements within the field are potentially modified with
// each Ajax submission, but these variables are stable and help target the
// desired elements.
$field_name = 'field_ajax_test';
$form_xpath = '//form[starts-with(@id, "node-page-form")]';
$field_xpath = '//div[contains(@class, "field--name-field-ajax-test")]';
$button_name = $field_name . '_add_more';
$button_value = t('Add another item');
$button_xpath_suffix = '//input[@name="' . $button_name . '"]';
$field_items_xpath_suffix = '//input[@type="text"]';
// Ensure the initial page contains both node forms and the correct number
// of field items and "add more" button for the multi-valued field within
// each form.
$this->drupalGet('form-test/two-instances-of-same-form');
$fields = $this->xpath($form_xpath . $field_xpath);
$this->assertEqual(count($fields), 2);
foreach ($fields as $field) {
$this->assertEqual(count($field->xpath('.' . $field_items_xpath_suffix)), 1, 'Found the correct number of field items on the initial page.');
$this->assertFieldsByValue($field->xpath('.' . $button_xpath_suffix), NULL, 'Found the "add more" button on the initial page.');
}
$this->assertNoDuplicateIds(t('Initial page contains unique IDs'), 'Other');
// Submit the "add more" button of each form twice. After each corresponding
// page update, ensure the same as above.
for ($i = 0; $i < 2; $i++) {
$forms = $this->xpath($form_xpath);
foreach ($forms as $offset => $form) {
$form_html_id = (string) $form['id'];
$this->drupalPostAjaxForm(NULL, [], [$button_name => $button_value], NULL, [], [], $form_html_id);
$form = $this->xpath($form_xpath)[$offset];
$field = $form->xpath('.' . $field_xpath);
$this->assertEqual(count($field[0]->xpath('.' . $field_items_xpath_suffix)), $i + 2, 'Found the correct number of field items after an AJAX submission.');
$this->assertFieldsByValue($field[0]->xpath('.' . $button_xpath_suffix), NULL, 'Found the "add more" button after an AJAX submission.');
$this->assertNoDuplicateIds(t('Updated page contains unique IDs'), 'Other');
}
}
}
}

View file

@ -1,27 +0,0 @@
<?php
namespace Drupal\system\Tests\Bootstrap;
use Drupal\Core\DependencyInjection\Container;
/**
* Container base class which triggers an error.
*/
class ErrorContainer extends Container {
/**
* {@inheritdoc}
*/
public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE) {
if ($id === 'http_kernel') {
// Enforce a recoverable error.
$callable = function(ErrorContainer $container) {
};
$callable(1);
}
else {
return parent::get($id, $invalidBehavior);
}
}
}

View file

@ -1,24 +0,0 @@
<?php
namespace Drupal\system\Tests\Bootstrap;
use Drupal\Core\DependencyInjection\Container;
/**
* Base container which throws an exception.
*/
class ExceptionContainer extends Container {
/**
* {@inheritdoc}
*/
public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE) {
if ($id === 'http_kernel') {
throw new \Exception('Thrown exception during Container::get');
}
else {
return parent::get($id, $invalidBehavior);
}
}
}

View file

@ -9,6 +9,12 @@ use Drupal\Core\Url;
* Provides test assertions for testing page-level cache contexts & tags.
*
* Can be used by test classes that extend \Drupal\simpletest\WebTestBase.
*
* @deprecated Scheduled for removal in Drupal 9.0.0. Use
* \Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait
* instead.
*
* @see https://www.drupal.org/node/2896632
*/
trait AssertPageCacheContextsAndTagsTrait {
@ -91,7 +97,7 @@ trait AssertPageCacheContextsAndTagsTrait {
// Assert page cache item + expected cache tags.
$cid_parts = [$url->setAbsolute()->toString(), 'html'];
$cid = implode(':', $cid_parts);
$cache_entry = \Drupal::cache('render')->get($cid);
$cache_entry = \Drupal::cache('page')->get($cid);
sort($cache_entry->tags);
$this->assertEqual($cache_entry->tags, $expected_tags);
$this->debugCacheTags($cache_entry->tags, $expected_tags);

View file

@ -69,7 +69,7 @@ abstract class GenericCacheBackendUnitTestBase extends KernelTestBase {
* @return \Drupal\Core\Cache\CacheBackendInterface
* Cache backend to test.
*/
protected abstract function createCacheBackend($bin);
abstract protected function createCacheBackend($bin);
/**
* Allows specific implementation to change the environment before a test run.
@ -303,9 +303,11 @@ abstract class GenericCacheBackendUnitTestBase extends KernelTestBase {
$reference = [
'test3',
'test7',
'test21', // Cid does not exist.
// Cid does not exist.
'test21',
'test6',
'test19', // Cid does not exist until added before second getMultiple().
// Cid does not exist until added before second getMultiple().
'test19',
'test2',
];
@ -443,13 +445,16 @@ abstract class GenericCacheBackendUnitTestBase extends KernelTestBase {
$backend->set('test7', 17);
$backend->delete('test1');
$backend->delete('test23'); // Nonexistent key should not cause an error.
// Nonexistent key should not cause an error.
$backend->delete('test23');
$backend->deleteMultiple([
'test3',
'test5',
'test7',
'test19', // Nonexistent key should not cause an error.
'test21', // Nonexistent key should not cause an error.
// Nonexistent key should not cause an error.
'test19',
// Nonexistent key should not cause an error.
'test21',
]);
// Test if expected keys have been deleted.

View file

@ -4,7 +4,7 @@ namespace Drupal\system\Tests\Cache;
use Drupal\Core\Url;
use Drupal\simpletest\WebTestBase;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Render\FormattableMarkup;
/**
* Provides helper methods for page cache tags tests.
@ -47,14 +47,14 @@ abstract class PageCacheTagsTestBase extends WebTestBase {
*/
protected function verifyPageCache(Url $url, $hit_or_miss, $tags = FALSE) {
$this->drupalGet($url);
$message = SafeMarkup::format('Page cache @hit_or_miss for %path.', ['@hit_or_miss' => $hit_or_miss, '%path' => $url->toString()]);
$message = new FormattableMarkup('Page cache @hit_or_miss for %path.', ['@hit_or_miss' => $hit_or_miss, '%path' => $url->toString()]);
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), $hit_or_miss, $message);
if ($hit_or_miss === 'HIT' && is_array($tags)) {
$absolute_url = $url->setAbsolute()->toString();
$cid_parts = [$absolute_url, 'html'];
$cid = implode(':', $cid_parts);
$cache_entry = \Drupal::cache('render')->get($cid);
$cache_entry = \Drupal::cache('page')->get($cid);
sort($cache_entry->tags);
$tags = array_unique($tags);
sort($tags);

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