Commit 37c1ee8c authored by alexpott's avatar alexpott

Issue #2056089 by BarisW, TR, mgifford, rodvolpe, Dom., nambisolo, nod_,...

Issue #2056089 by BarisW, TR, mgifford, rodvolpe, Dom., nambisolo, nod_, katewelling, Manjit.Singh, jhodgdon, Bojhan, Wim Leers: UI problems on the Modules/Extend page
parent 1c7eeb1c
......@@ -165,12 +165,12 @@ function testCommentInstallAfterContentModule() {
// Install core content type module (book).
$edit = array();
$edit['modules[Core][book][enable]'] = 'book';
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->drupalPostForm('admin/modules', $edit, t('Install'));
// Now install the comment module.
$edit = array();
$edit['modules[Core][comment][enable]'] = 'comment';
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->drupalPostForm('admin/modules', $edit, t('Install'));
$this->rebuildContainer();
$this->assertTrue($this->container->get('module_handler')->moduleExists('comment'), 'Comment module enabled.');
......
......@@ -127,7 +127,7 @@ public function testPreExistingConfigInstall() {
// will install the config_test module first because it is a dependency of
// config_install_fail_test.
// @see \Drupal\system\Form\ModulesListForm::submitForm()
$this->drupalPostForm('admin/modules', array('modules[Testing][config_test][enable]' => TRUE, 'modules[Testing][config_install_fail_test][enable]' => TRUE), t('Save configuration'));
$this->drupalPostForm('admin/modules', array('modules[Testing][config_test][enable]' => TRUE, 'modules[Testing][config_install_fail_test][enable]' => TRUE), t('Install'));
$this->assertRaw('Unable to install Configuration install fail test, <em class="placeholder">config_test.dynamic.dotted.default</em> already exists in active configuration.');
// Uninstall the config_test module to test the confirm form.
......@@ -138,7 +138,7 @@ public function testPreExistingConfigInstall() {
// The user is shown a confirm form because the config_test module is a
// dependency.
// @see \Drupal\system\Form\ModulesListConfirmForm::submitForm()
$this->drupalPostForm('admin/modules', array('modules[Testing][config_install_fail_test][enable]' => TRUE), t('Save configuration'));
$this->drupalPostForm('admin/modules', array('modules[Testing][config_install_fail_test][enable]' => TRUE), t('Install'));
$this->drupalPostForm(NULL, array(), t('Continue'));
$this->assertRaw('Unable to install Configuration install fail test, <em class="placeholder">config_test.dynamic.dotted.default</em> already exists in active configuration.');
......@@ -152,7 +152,7 @@ public function testPreExistingConfigInstall() {
->set('label', 'Je suis Charlie')
->save();
$this->drupalPostForm('admin/modules', array('modules[Testing][config_install_fail_test][enable]' => TRUE), t('Save configuration'));
$this->drupalPostForm('admin/modules', array('modules[Testing][config_install_fail_test][enable]' => TRUE), t('Install'));
$this->assertRaw('Unable to install Configuration install fail test, <em class="placeholder">config_test.dynamic.dotted.default, language/fr/config_test.dynamic.dotted.default</em> already exist in active configuration.');
// Test installing a theme through the UI that has existing configuration.
......@@ -183,12 +183,12 @@ public function testUnmetDependenciesInstall() {
$this->drupalLogin($this->adminUser);
// We need to install separately since config_install_dependency_test does
// not depend on config_test and order is important.
$this->drupalPostForm('admin/modules', array('modules[Testing][config_test][enable]' => TRUE), t('Save configuration'));
$this->drupalPostForm('admin/modules', array('modules[Testing][config_install_dependency_test][enable]' => TRUE), t('Save configuration'));
$this->drupalPostForm('admin/modules', array('modules[Testing][config_test][enable]' => TRUE), t('Install'));
$this->drupalPostForm('admin/modules', array('modules[Testing][config_install_dependency_test][enable]' => TRUE), t('Install'));
$this->assertRaw('Unable to install Config install dependency test, <em class="placeholder">config_test.dynamic.other_module_test_with_dependency</em> has unmet dependencies.');
$this->drupalPostForm('admin/modules', array('modules[Testing][config_other_module_config_test][enable]' => TRUE), t('Save configuration'));
$this->drupalPostForm('admin/modules', array('modules[Testing][config_install_dependency_test][enable]' => TRUE), t('Save configuration'));
$this->drupalPostForm('admin/modules', array('modules[Testing][config_other_module_config_test][enable]' => TRUE), t('Install'));
$this->drupalPostForm('admin/modules', array('modules[Testing][config_install_dependency_test][enable]' => TRUE), t('Install'));
$this->rebuildContainer();
$this->assertTrue(entity_load('config_test', 'other_module_test_with_dependency'), 'The config_test.dynamic.other_module_test_with_dependency configuration has been created during install.');
}
......
......@@ -34,7 +34,7 @@ function testModuleInstallLanguageList() {
$this->drupalLogin($admin_user);
$edit = array();
$edit['modules[Multilingual][language][enable]'] = 'language';
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->drupalPostForm('admin/modules', $edit, t('Install'));
$this->assertEqual(\Drupal::state()->get('language_test.language_count_preinstall', 0), 1, 'Using LanguageManager::getLanguages() returns 1 language during Language installation.');
......
......@@ -125,7 +125,7 @@ public function testConfigTranslation() {
$this->assertFalse($string, 'Configuration strings have been created upon installation.');
// Enable the image module.
$this->drupalPostForm('admin/modules', array('modules[Field types][image][enable]' => "1"), t('Save configuration'));
$this->drupalPostForm('admin/modules', array('modules[Field types][image][enable]' => "1"), t('Install'));
$this->rebuildContainer();
$string = $this->storage->findString(array('source' => 'Medium (220×220)', 'context' => '', 'type' => 'configuration'));
......@@ -204,12 +204,12 @@ public function testConfigTranslation() {
public function testOptionalConfiguration() {
$this->assertNodeConfig(FALSE, FALSE);
// Enable the node module.
$this->drupalPostForm('admin/modules', ['modules[Core][node][enable]' => "1"], t('Save configuration'));
$this->drupalPostForm('admin/modules', ['modules[Core][node][enable]' => "1"], t('Install'));
$this->drupalPostForm(NULL, [], t('Continue'));
$this->rebuildContainer();
$this->assertNodeConfig(TRUE, FALSE);
// Enable the views module (which node provides some optional config for).
$this->drupalPostForm('admin/modules', ['modules[Core][views][enable]' => "1"], t('Save configuration'));
$this->drupalPostForm('admin/modules', ['modules[Core][views][enable]' => "1"], t('Install'));
$this->rebuildContainer();
$this->assertNodeConfig(TRUE, TRUE);
}
......
......@@ -42,7 +42,7 @@ public function setUp() {
*/
public function testCircularDependency() {
// Ensure that we can enable early_translation_test on a non-english site.
$this->drupalPostForm('admin/modules', array('modules[Testing][early_translation_test][enable]' => TRUE), t('Save configuration'));
$this->drupalPostForm('admin/modules', array('modules[Testing][early_translation_test][enable]' => TRUE), t('Install'));
$this->assertResponse(200);
}
......
......@@ -315,7 +315,7 @@ public function testEnableUninstallModule() {
$edit = array(
'modules[Testing][locale_test_translate][enable]' => 'locale_test_translate',
);
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->drupalPostForm('admin/modules', $edit, t('Install'));
// Check if translations have been imported.
$this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.',
......@@ -350,7 +350,7 @@ public function testEnableLanguage() {
$edit = array(
'modules[Testing][locale_test_translate][enable]' => 'locale_test_translate',
);
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->drupalPostForm('admin/modules', $edit, t('Install'));
// Check if there is no Dutch translation yet.
$this->assertTranslation('Extraday', '', 'nl');
......@@ -394,7 +394,7 @@ public function testEnableCustomLanguage() {
$edit = array(
'modules[Testing][locale_test_translate][enable]' => 'locale_test_translate',
);
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->drupalPostForm('admin/modules', $edit, t('Install'));
// Create a custom language with language code 'xx' and a random
// name.
......
......@@ -3,7 +3,7 @@
* Module page behaviors.
*/
(function ($, Drupal) {
(function ($, Drupal, debounce) {
"use strict";
......@@ -15,7 +15,7 @@
*
* Text search input: input.table-filter-text
* Target table: input.table-filter-text[data-table]
* Source text: .table-filter-text-source
* Source text: .table-filter-text-source, .module-name, .module-description
*
* @type {Drupal~behavior}
*/
......@@ -30,17 +30,19 @@
function hidePackageDetails(index, element) {
var $packDetails = $(element);
var $visibleRows = $packDetails.find('table:not(.sticky-header)').find('tbody tr:visible');
var $visibleRows = $packDetails.find('tbody tr:visible');
$packDetails.toggle($visibleRows.length > 0);
}
function filterModuleList(e) {
var query = $(e.target).val().toLowerCase();
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) {
var $row = $(row);
var $sources = $row.find('.table-filter-text-source');
var textMatch = $sources.text().toLowerCase().indexOf(query) !== -1;
var $sources = $row.find('.table-filter-text-source, .module-name, .module-description');
var textMatch = $sources.text().search(re) !== -1;
$row.closest('tr').toggle(textMatch);
}
// Search over all rows and packages.
......@@ -59,6 +61,13 @@
// 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;
......@@ -71,14 +80,24 @@
}
}
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', filterModuleList);
$input.on({
keyup: debounce(filterModuleList, 200),
keydown: preventEnterKey
});
}
}
};
}(jQuery, Drupal));
}(jQuery, Drupal, Drupal.debounce));
......@@ -150,16 +150,13 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
$account = $this->currentUser()->id();
$this->keyValueExpirable->delete($account);
// Gets list of modules prior to install process.
$before = $this->moduleHandler->getModuleList();
// Install the given modules.
if (!empty($this->modules['install'])) {
// Don't catch the exception that this can throw for missing dependencies:
// the form doesn't allow modules with unmet dependencies, so the only way
// this can happen is if the filesystem changed between form display and
// submit, in which case the user has bigger problems.
try {
// Install the given modules.
$this->moduleInstaller->install(array_keys($this->modules['install']));
}
catch (PreExistingConfigException $e) {
......@@ -184,12 +181,12 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
);
return;
}
}
// Gets module list after install process, flushes caches and displays a
// message if there are changes.
if ($before != $this->moduleHandler->getModuleList()) {
drupal_set_message($this->t('The configuration options have been saved.'));
$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.', array(
'%name' => $module_names[0],
'%names' => implode(', ', $module_names),
)));
}
$form_state->setRedirectUrl($this->getCancelUrl());
......
......@@ -183,14 +183,15 @@ public function buildForm(array $form, FormStateInterface $form_state) {
$form['filters']['text'] = array(
'#type' => 'search',
'#title' => $this->t('Search'),
'#title' => $this->t('Filter modules'),
'#title_display' => 'invisible',
'#size' => 30,
'#placeholder' => $this->t('Enter module name'),
'#placeholder' => $this->t('Filter by name or description'),
'#description' => $this->t('Enter a part of the module name or description'),
'#attributes' => array(
'class' => array('table-filter-text'),
'data-table' => '#system-modules',
'autocomplete' => 'off',
'title' => $this->t('Enter a part of the module name or description to filter by.'),
),
);
......@@ -238,7 +239,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Save configuration'),
'#value' => $this->t('Install'),
'#button_type' => 'primary',
);
......@@ -515,13 +516,15 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
return;
}
// Gets list of modules prior to install process.
$before = $this->moduleHandler->getModuleList();
// There seem to be no dependencies that would need approval.
// Install the given modules.
if (!empty($modules['install'])) {
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.', array(
'%name' => $module_names[0],
'%names' => implode(', ', $module_names),
)));
}
catch (PreExistingConfigException $e) {
$config_objects = $e->flattenConfigObjects($e->getConfigObjects());
......@@ -546,12 +549,6 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
return;
}
}
// Gets module list after install process, flushes caches and displays a
// message if there are changes.
if ($before != $this->moduleHandler->getModuleList()) {
drupal_set_message(t('The configuration options have been saved.'));
}
}
}
......@@ -99,14 +99,15 @@ public function buildForm(array $form, FormStateInterface $form_state) {
$form['filters']['text'] = array(
'#type' => 'search',
'#title' => $this->t('Search'),
'#title' => $this->t('Filter modules'),
'#title_display' => 'invisible',
'#size' => 30,
'#placeholder' => $this->t('Enter module name'),
'#placeholder' => $this->t('Filter by name or description'),
'#description' => $this->t('Enter a part of the module name or description'),
'#attributes' => array(
'class' => array('table-filter-text'),
'data-table' => '#system-modules-uninstall',
'autocomplete' => 'off',
'title' => $this->t('Enter a part of the module name or description to filter by.'),
),
);
......
......@@ -125,7 +125,7 @@ public function testTranslationsLoaded() {
'modules[Core][views][enable]' => TRUE,
'modules[Core][filter][enable]' => TRUE,
);
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->drupalPostForm('admin/modules', $edit, t('Install'));
// Verify the strings from the translation are still as expected.
$this->verifyImportedStringsTranslated();
......
......@@ -68,7 +68,7 @@ public function testInstaller() {
$this->assertEqual($account->language()->getId(), 'de', 'New user is German.');
// Ensure that we can enable basic_auth on a non-english site.
$this->drupalPostForm('admin/modules', array('modules[Web services][basic_auth][enable]' => TRUE), t('Save configuration'));
$this->drupalPostForm('admin/modules', array('modules[Web services][basic_auth][enable]' => TRUE), t('Install'));
$this->assertResponse(200);
// Assert that the theme CSS was added to the page.
......
......@@ -21,12 +21,12 @@ function testProjectNamespaceForDependencies() {
$edit = array(
'modules[Core][filter][enable]' => TRUE,
);
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->drupalPostForm('admin/modules', $edit, t('Install'));
// Enable module with project namespace to ensure nothing breaks.
$edit = array(
'modules[Testing][system_project_namespace_test][enable]' => TRUE,
);
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->drupalPostForm('admin/modules', $edit, t('Install'));
$this->assertModules(array('system_project_namespace_test'), TRUE);
}
......@@ -37,7 +37,7 @@ function testEnableWithoutDependency() {
// Attempt to enable Content Translation without Language enabled.
$edit = array();
$edit['modules[Multilingual][content_translation][enable]'] = 'content_translation';
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->drupalPostForm('admin/modules', $edit, t('Install'));
$this->assertText(t('Some required modules must be enabled'), 'Dependency required.');
$this->assertModules(array('content_translation', 'language'), FALSE);
......@@ -46,8 +46,7 @@ function testEnableWithoutDependency() {
$this->assertTableCount('language', FALSE);
$this->drupalPostForm(NULL, NULL, t('Continue'));
$this->assertText(t('The configuration options have been saved.'), 'Modules status has been updated.');
$this->assertText(t('2 modules have been enabled: Content Translation, Language.'), 'Modules status has been updated.');
$this->assertModules(array('content_translation', 'language'), TRUE);
// Assert that the language YAML files were created.
......@@ -109,7 +108,7 @@ function testEnableRequirementsFailureDependency() {
$edit = array();
$edit['modules[Testing][requirements1_test][enable]'] = 'requirements1_test';
$edit['modules[Testing][requirements2_test][enable]'] = 'requirements2_test';
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->drupalPostForm('admin/modules', $edit, t('Install'));
// Makes sure the modules were NOT installed.
$this->assertText(t('Requirements 1 Test failed requirements'), 'Modules status has been updated.');
......@@ -140,7 +139,7 @@ function testModuleEnableOrder() {
// is correct.
$edit = array();
$edit['modules[Core][color][enable]'] = 'color';
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->drupalPostForm('admin/modules', $edit, t('Install'));
$this->assertModules(array('color'), FALSE);
// Note that dependencies are sorted alphabetically in the confirmation
// message.
......@@ -148,7 +147,7 @@ function testModuleEnableOrder() {
$edit['modules[Core][config][enable]'] = 'config';
$edit['modules[Core][help][enable]'] = 'help';
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->drupalPostForm('admin/modules', $edit, t('Install'));
$this->assertModules(array('color', 'config', 'help'), TRUE);
// Check the actual order which is saved by module_test_modules_enabled().
......@@ -162,7 +161,7 @@ function testModuleEnableOrder() {
function testUninstallDependents() {
// Enable the forum module.
$edit = array('modules[Core][forum][enable]' => 'forum');
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->drupalPostForm('admin/modules', $edit, t('Install'));
$this->drupalPostForm(NULL, array(), t('Continue'));
$this->assertModules(array('forum'), TRUE);
......
......@@ -22,7 +22,7 @@ function testHookRequirementsFailure() {
// Attempt to install the requirements1_test module.
$edit = array();
$edit['modules[Testing][requirements1_test][enable]'] = 'requirements1_test';
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->drupalPostForm('admin/modules', $edit, t('Install'));
// Makes sure the module was NOT installed.
$this->assertText(t('Requirements 1 Test failed requirements'), 'Modules status has been updated.');
......
......@@ -77,7 +77,7 @@ public function testInstallUninstall() {
$edit = array();
$package = $module->info['package'];
$edit["modules[$package][$name][enable]"] = TRUE;
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->drupalPostForm('admin/modules', $edit, t('Install'));
// Handle the case where modules were installed along with this one and
// where we therefore hit a confirmation screen.
......@@ -85,7 +85,16 @@ public function testInstallUninstall() {
$this->drupalPostForm(NULL, array(), t('Continue'));
}
$this->assertText(t('The configuration options have been saved.'), 'Modules status has been updated.');
// List the module display names to check the confirmation message.
$module_names = array();
foreach ($modules_to_install as $module_to_install) {
$module_names[] = $all_modules[$module_to_install]->info['name'];
}
$expected_text = \Drupal::translation()->formatPlural(count($module_names), 'Module @name has been enabled.', '@count modules have been enabled: @names.', array(
'@name' => $module_names[0],
'@names' => implode(', ', $module_names),
));
$this->assertText($expected_text, 'Modules status has been updated.');
// Check that hook_modules_installed() was invoked with the expected list
// of modules, that each module's database tables now exist, and that
......@@ -138,8 +147,8 @@ public function testInstallUninstall() {
foreach ($all_modules as $name => $module) {
$edit['modules[' . $module->info['package'] . '][' . $name . '][enable]'] = TRUE;
}
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->assertText(t('The configuration options have been saved.'), 'Modules status has been updated.');
$this->drupalPostForm('admin/modules', $edit, t('Install'));
$this->assertText(t('@count modules have been enabled: ', array('@count' => count($all_modules))), 'Modules status has been updated.');
}
/**
......
......@@ -70,8 +70,8 @@ function testMainContentFallback() {
$this->drupalLogin($this->adminUser);
$edit = array();
$edit['modules[Core][block][enable]'] = 'block';
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->assertText(t('The configuration options have been saved.'), 'Modules status has been updated.');
$this->drupalPostForm('admin/modules', $edit, t('Install'));
$this->assertText(t('Module Block has been enabled.'), 'Modules status has been updated.');
$this->rebuildContainer();
$this->assertTrue(\Drupal::moduleHandler()->moduleExists('block'), 'Block module re-enabled.');
}
......
......@@ -259,7 +259,7 @@ function theme_system_modules_details($variables) {
}
$details = array(
'#type' => 'details',
'#title' => SafeMarkup::set('<span class="text"> ' . drupal_render($module['description']) . '</span>'),
'#title' => SafeMarkup::set('<span class="text module-description">' . drupal_render($module['description']) . '</span>'),
'#attributes' => array('id' => $module['enable']['#id'] . '-description'),
'#description' => $description,
);
......@@ -332,7 +332,7 @@ function theme_system_modules_uninstall($variables) {
array(
'data' => array(
'#type' => 'inline_template',
'#template' => '{{ module_description }} {% if disabled_header is not empty %} <div class="admin-requirements">{{ disabled_header }}{{ disabled_reasons }}</div> {% endif %}',
'#template' => '<span class="text module-description">{{ module_description }}</span>{% if disabled_header is not empty %}<div class="admin-requirements">{{ disabled_header }}{{ disabled_reasons }}</div>{% endif %}',
'#context' => array(
'module_description' => drupal_render($form['modules'][$module]['description']),
'disabled_header' => $disabled_header,
......
......@@ -41,6 +41,7 @@ drupal.system.modules:
dependencies:
- core/jquery
- core/drupal
- core/drupal.debounce
- core/jquery.once
diff:
......
......@@ -125,7 +125,7 @@ function system_help($route_name, RouteMatchInterface $route_match) {
}
}
else {
$output .= '<p>' . t('Regularly review available updates to maintain a secure and current site. Always run the <a href="!update-php">update script</a> each time a module is updated. Enable the Update Manager module to update and install modules and themes.', array('!update-php' => \Drupal::url('system.db_update'))) . '</p>';
$output .= '<p>' . t('Regularly review available updates to maintain a secure and current site. Always run the <a href="!update-php">update script</a> each time a module is updated. Enable the <a href="!update-manager">Update Manager module</a> to update and install modules and themes.', array('!update-php' => \Drupal::url('system.db_update'), '!update-manager' => \Drupal::url('system.modules_list', [], ['fragment' => 'module-update']))) . '</p>';
}
return $output;
......
......@@ -120,7 +120,7 @@ function testModuleStatusChangeSubtreesHashCacheClear() {
// Enable a module.
$edit = array();
$edit['modules[Core][taxonomy][enable]'] = TRUE;
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->drupalPostForm('admin/modules', $edit, t('Install'));
$this->rebuildContainer();
// Assert that the subtrees hash has been altered because the subtrees
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment