Skip to content

Commit

Permalink
MDL-76867 core_admin: Add support for editor sorting via ajax
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewnicols committed Mar 14, 2023
1 parent f54cc61 commit 6d978dd
Show file tree
Hide file tree
Showing 13 changed files with 719 additions and 25 deletions.
2 changes: 1 addition & 1 deletion admin/amd/build/plugin_management_table.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion admin/amd/build/plugin_management_table.min.js.map

Large diffs are not rendered by default.

46 changes: 46 additions & 0 deletions admin/amd/src/plugin_management_table.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default class {

constructor() {
this.addClickHandler(this.handleStateToggle);
this.addClickHandler(this.handleMoveUpDown);
this.registerEventListeners();
}

Expand Down Expand Up @@ -102,6 +103,16 @@ export default class {
}])[0];
}

setPluginOrder(methodname, plugin, direction) {
return fetchMany([{
methodname,
args: {
plugin,
direction,
},
}])[0];
}

/**
* Handle state toggling.
*
Expand Down Expand Up @@ -130,4 +141,39 @@ export default class {
pendingPromise.resolve();
}
}

async handleMoveUpDown(tableRoot, e) {
const actionLink = e.target.closest('[data-action="move"][data-method][data-direction]');
if (!actionLink) {
return;
}

e.preventDefault();

const pendingPromise = new Pending('core_table/dynamic:processAction');

await this.setPluginOrder(
actionLink.dataset.method,
actionLink.dataset.plugin,
actionLink.dataset.direction === 'up' ? -1 : 1,
);

const [updatedRoot] = await Promise.all([
refreshTableContent(tableRoot),
fetchNotifications(),
]);

// Refocus on the link that as pressed in the first place.
const exactMatch = updatedRoot.querySelector(
`[data-action="move"][data-plugin="${actionLink.dataset.plugin}"][data-direction="${actionLink.dataset.direction}"]`
);
if (exactMatch) {
exactMatch.focus();
} else {
// The move link is not present anymore, so we need to focus on the other one.
updatedRoot.querySelector(`[data-action="move"][data-plugin="${actionLink.dataset.plugin}"]`)?.focus();
}

pendingPromise.resolve();
}
}
84 changes: 84 additions & 0 deletions admin/classes/external/set_plugin_order.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

namespace core_admin\external;

use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;

/**
* Web Service to control the order of a plugin.
*
* @package core_admin
* @category external
* @copyright 2023 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class set_plugin_order extends external_api {
/**
* Returns description of method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'plugin' => new external_value(PARAM_PLUGIN, 'The name of the plugin', VALUE_REQUIRED),
'direction' => new external_value(PARAM_INT, 'The direction to move', VALUE_REQUIRED),
]);
}

/**
* Set the plugin state.
*
* @param string $plugin The name of the plugin
* @param int $direction The direction to move the plugin
* @return array
*/
public static function execute(
string $plugin,
int $direction,
): array {
[
'plugin' => $plugin,
'direction' => $direction,
] = self::validate_parameters(self::execute_parameters(), [
'plugin' => $plugin,
'direction' => $direction,
]);

$context = \context_system::instance();
self::validate_context($context);
require_capability('moodle/site:config', $context);

[$plugintype, $pluginname] = explode('_', \core_component::normalize_componentname($plugin), 2);

$manager = \core_plugin_manager::resolve_plugininfo_class($plugintype);
$manager::change_plugin_order($pluginname, $direction);

return [];
}

/**
* Describe the return structure of the external service.
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([]);
}
}
4 changes: 0 additions & 4 deletions admin/classes/table/editor_management_table.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,6 @@ protected function get_action_url(array $params = []): moodle_url {
return new moodle_url('/admin/editors.php', $params);
}

protected function supports_ordering(): bool {
return true;
}

protected function order_plugins(array $plugins): array {
global $CFG;

Expand Down
18 changes: 9 additions & 9 deletions admin/classes/table/plugin_management_table.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,6 @@ public function __construct() {
return $plugin->is_enabled();
}));

$this->enabledplugincount = count(array_filter($this->plugins, function ($plugin) {
return $plugin->is_enabled();
}));

$this->setup_column_configuration();
$this->set_filterset(new plugin_management_table_filterset());
$this->setup();
Expand All @@ -80,8 +76,12 @@ public function __construct() {
* @return \core\plugininfo\base[]
*/
protected function get_sorted_plugins(): array {
$plugins = $this->pluginmanager->get_plugins_of_type($this->get_plugintype());
return self::sort_plugins($plugins);
if ($this->plugininfoclass::plugintype_supports_ordering()) {
return $this->plugininfoclass::get_sorted_plugins();
} else {
$plugins = $this->pluginmanager->get_plugins_of_type($this->get_plugintype());
return self::sort_plugins($plugins);
}
}

/**
Expand Down Expand Up @@ -176,7 +176,7 @@ protected function get_toggle_service(): ?string {
* @return null|string
*/
protected function get_sortorder_service(): ?string {
return null;
return 'core_admin_set_plugin_order';
}

/**
Expand Down Expand Up @@ -348,7 +348,7 @@ protected function col_order(stdClass $row): string {
$hasdown = false;
}

if ($this->get_sortorder_service()) {
if ($this->supports_ordering()) {
$dataattributes = [
'data-method' => $this->get_sortorder_service(),
'data-action' => 'move',
Expand Down Expand Up @@ -490,6 +490,6 @@ protected function supports_disabling(): bool {
* @return bool
*/
protected function supports_ordering(): bool {
return false;
return $this->plugininfoclass::plugintype_supports_ordering();
}
}
170 changes: 170 additions & 0 deletions admin/tests/external/set_plugin_order_test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

declare(strict_types=1);

namespace core_admin\external;

defined('MOODLE_INTERNAL') || die();

global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');

/**
* Unit tests to configure the plugin order.
*
* Note: Not all plugins can be ordered, so this test is limited to those which support it.
*
* @package core
* @covers \core_admin\external\set_plugin_state
* @copyright 2023 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class set_plugin_order_test extends \externallib_advanced_testcase {
/**
* Text execute method for editor plugins, which support ordering.
*
* @dataProvider execute_editor_provider
* @param string $initialstate The initial state of the plugintype
* @param string $plugin The name of the plugin
* @param int $direction
* @param array $neworder
* @param string $newstate
*/
public function test_execute_editors(
string $initialstate,
string $plugin,
int $direction,
array $neworder,
string $newstate,
): void {
global $CFG;

$this->resetAfterTest();
$this->setAdminUser();

$CFG->texteditors = $initialstate;

set_plugin_order::execute($plugin, $direction);

$this->assertSame(
$neworder,
array_keys(\core\plugininfo\editor::get_sorted_plugins()),
);
$this->assertSame($newstate, $CFG->texteditors);
}

/**
* Data provider for base tests of the execute method.
*
* @return array
*/
public function execute_editor_provider(): array {
return [
[
'initialstate' => 'textarea,tiny',
'pluginname' => 'editor_textarea',
1, // DOWN.
'expected' => [
'tiny',
'textarea',
'atto',
'tinymce',
],
'newtexteditors' => 'tiny,textarea',
],
[
'initialstate' => 'textarea,tiny',
'pluginname' => 'editor_textarea',
-1, // UP.
'expected' => [
'textarea',
'tiny',
'atto',
'tinymce',
],
'newtexteditors' => 'textarea,tiny',
],
[
'initialstate' => 'textarea,tiny',
'pluginname' => 'editor_tiny',
1, // DOWN.
// Tiny is already at the bottom of the list of enabled plugins.
'expected' => [
'textarea',
'tiny',
'atto',
'tinymce',
],
'newtexteditors' => 'textarea,tiny',
],
[
'initialstate' => 'textarea,tiny',
'pluginname' => 'editor_atto',
1, // DOWN.
// Atto is not enabled. Disabled editors are listed lexically after enabled editors.
'expected' => [
'textarea',
'tiny',
'atto',
'tinymce',
],
'newtexteditors' => 'textarea,tiny',
],
];
}

/**
* Text execute method for plugins which do not support ordering.
*
* @dataProvider execute_non_orderable_provider
* @param string $plugin
*/
public function test_execute_editors_non_orderable(string $plugin): void {
$this->resetAfterTest();
$this->setAdminUser();

$this->assertIsArray(set_plugin_order::execute($plugin, 1));
}

public function execute_non_orderable_provider(): array {
return [
// Activities do not support ordering.
['mod_assign'],
// Nor to blocks.
['block_login'],
];
}

/**
* Test execute method with no login.
*/
public function test_execute_no_login(): void {
$this->expectException(\require_login_exception::class);
set_plugin_order::execute('editor_tiny', 1);
}

/**
* Test execute method with no login.
*/
public function test_execute_no_capability(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(\required_capability_exception::class);
set_plugin_order::execute('editor_tiny', 1);
}
}

0 comments on commit 6d978dd

Please sign in to comment.