Skip to content

Commit e8c07e0

Browse files
committed
Merge branch 'MDL-83216-main' of https://github.com/meirzamoodle/moodle
2 parents 31e26ba + 2f10fde commit e8c07e0

12 files changed

+553
-13
lines changed
+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
// This file is part of Moodle - http://moodle.org/
3+
//
4+
// Moodle is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// Moodle is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16+
17+
namespace core_ai\external;
18+
19+
use core_external\external_api;
20+
use core_external\external_function_parameters;
21+
use core_external\external_single_structure;
22+
use core_external\external_value;
23+
24+
/**
25+
* Web Service to control the order of a provider instance.
26+
*
27+
* @package core_ai
28+
* @category external
29+
* @copyright Meirza <[email protected]>
30+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
31+
*/
32+
class set_provider_order extends external_api {
33+
/**
34+
* Returns description of method parameters
35+
*
36+
* @return external_function_parameters
37+
*/
38+
public static function execute_parameters(): external_function_parameters {
39+
return new external_function_parameters([
40+
'plugin' => new external_value(PARAM_INT, ' The provider instance ID', VALUE_REQUIRED),
41+
'direction' => new external_value(PARAM_INT, 'The direction to move', VALUE_REQUIRED),
42+
]);
43+
}
44+
45+
/**
46+
* Set the provider instance order.
47+
*
48+
* @param int $providerid The provider instance ID
49+
* @param int $direction The direction to move the provider instance
50+
* @return array
51+
*/
52+
public static function execute(
53+
int $providerid,
54+
int $direction,
55+
): array {
56+
[
57+
'plugin' => $providerid,
58+
'direction' => $direction,
59+
] = self::validate_parameters(self::execute_parameters(), [
60+
'plugin' => $providerid,
61+
'direction' => $direction,
62+
]);
63+
64+
$context = \context_system::instance();
65+
self::validate_context($context);
66+
require_capability('moodle/site:config', $context);
67+
68+
$manager = \core\di::get(\core_ai\manager::class);
69+
$aiproviders = $manager->get_provider_instances(['id' => $providerid]);
70+
$aiprovider = reset($aiproviders);
71+
if ($aiprovider) {
72+
$manager->change_provider_order($providerid, $direction);
73+
}
74+
75+
$directionstring = $direction === \core\plugininfo\aiprovider::MOVE_UP
76+
? \core\plugininfo\aiprovider::UP
77+
: \core\plugininfo\aiprovider::DOWN;
78+
$message = get_string('providermoved' . $directionstring, 'ai', $aiprovider->name);
79+
$messagetype = \core\notification::SUCCESS;
80+
81+
\core\notification::add($message, $messagetype);
82+
83+
return [];
84+
}
85+
86+
/**
87+
* Describe the return structure of the external service.
88+
*
89+
* @return external_single_structure
90+
*/
91+
public static function execute_returns(): external_single_structure {
92+
return new external_single_structure([]);
93+
}
94+
}

ai/classes/manager.php

+147-5
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
use core\exception\coding_exception;
2020
use core_ai\aiactions\base;
2121
use core_ai\aiactions\responses;
22-
22+
use core\plugininfo\aiprovider as aiproviderplugin;
2323
/**
2424
* AI subsystem manager.
2525
*
@@ -79,7 +79,7 @@ public static function get_supported_actions(string $pluginname): array {
7979
*/
8080
public function get_providers_for_actions(array $actions, bool $enabledonly = false): array {
8181
$providers = [];
82-
$instances = $this->get_provider_instances();
82+
$instances = $this->get_sorted_providers();
8383
foreach ($actions as $action) {
8484
$providers[$action] = [];
8585
foreach ($instances as $instance) {
@@ -388,6 +388,11 @@ public function create_provider_instance(
388388

389389
$id = $this->db->insert_record('ai_providers', $provider->to_record());
390390

391+
// Ensure the provider instance order config gets updated if the provider is enabled.
392+
if ($enabled) {
393+
$this->update_provider_order($id, \core\plugininfo\aiprovider::ENABLE);
394+
}
395+
391396
return $provider->with(id: $id);
392397
}
393398

@@ -410,7 +415,7 @@ public function get_provider_record(array $filter = [], int $strictness = IGNORE
410415
* Get the provider records according to the filter.
411416
*
412417
* @param array|null $filter The filterable elements to get the records from.
413-
* @return \stdClass[]
418+
* @return array
414419
*/
415420
public function get_provider_records(?array $filter = null): array {
416421
return $this->db->get_records(
@@ -428,7 +433,6 @@ public function get_provider_records(?array $filter = null): array {
428433
*
429434
* @param null|array $filter The database filter to apply when fetching provider records.
430435
* @return array An array of instantiated provider objects.
431-
* @throws \dml_exception If there is a database error during record retrieval.
432436
*/
433437
public function get_provider_instances(?array $filter = null): array {
434438
// Filter out any null values from the array (providers that couldn't be instantiated).
@@ -469,7 +473,6 @@ function ($record): ?provider {
469473
* @param array|null $config the configuration of the provider instance to be updated.
470474
* @param array|null $actionconfig the action configuration of the provider instance to be updated.
471475
* @return provider
472-
* @throws \dml_exception
473476
*/
474477
public function update_provider_instance(
475478
provider $provider,
@@ -515,6 +518,7 @@ public function enable_provider_instance(provider $provider): provider {
515518
if (!$provider->enabled) {
516519
$provider = $provider->with(enabled: true);
517520
$this->db->update_record('ai_providers', $provider->to_record());
521+
$this->update_provider_order($provider->id, aiproviderplugin::ENABLE);
518522
}
519523

520524
return $provider;
@@ -536,8 +540,146 @@ public function disable_provider_instance(provider $provider): provider {
536540
$provider = $provider->with(enabled: false);
537541
$this->db->update_record('ai_providers', $provider->to_record());
538542
}
543+
$this->update_provider_order($provider->id, aiproviderplugin::DISABLE);
539544
}
540545

541546
return $provider;
542547
}
548+
549+
/**
550+
* Sorts provider instances by configured order.
551+
*
552+
* @param array $unsorted of provider instance objects
553+
* @return array of provider instance objects
554+
*/
555+
public static function sort_providers_by_order(array $unsorted): array {
556+
$sorted = [];
557+
$orderarray = explode(',', get_config('core_ai', 'provider_order'));
558+
559+
foreach ($orderarray as $notused => $providerid) {
560+
foreach ($unsorted as $key => $provider) {
561+
if ($provider->id == $providerid) {
562+
$sorted[] = $provider;
563+
unset($unsorted[$key]);
564+
}
565+
}
566+
}
567+
568+
return array_merge($sorted, $unsorted);
569+
}
570+
571+
/**
572+
* Get the configured ai providers from the manager.
573+
*
574+
* @return array
575+
*/
576+
public function get_sorted_providers(): array {
577+
$unsorted = $this->get_provider_instances();
578+
$orders = $this->sort_providers_by_order($unsorted);
579+
$sortedplugins = [];
580+
581+
foreach ($orders as $order) {
582+
$sortedplugins[$order->id] = $unsorted[$order->id];
583+
}
584+
585+
return $sortedplugins;
586+
}
587+
588+
/**
589+
* Change the order of the provider instance relative to other provider instances.
590+
*
591+
* When possible, the change will be stored into the config_log table, to let admins check when/who has modified it.
592+
*
593+
* @param int $providerid The provider ID.
594+
* @param int $direction The direction to move the provider instance. Negative numbers mean up, Positive mean down.
595+
* @return bool Whether the provider has been updated or not.
596+
*/
597+
public function change_provider_order(int $providerid, int $direction): bool {
598+
$activefactors = array_keys($this->get_sorted_providers());
599+
$key = array_search($providerid, $activefactors);
600+
601+
if ($key === false) {
602+
return false;
603+
}
604+
605+
$movedown = ($direction === aiproviderplugin::MOVE_DOWN && $key < count($activefactors) - 1);
606+
$moveup = ($direction === aiproviderplugin::MOVE_UP && $key >= 1);
607+
if ($movedown || $moveup) {
608+
$this->update_provider_order($providerid, $direction);
609+
return true;
610+
}
611+
612+
return false;
613+
}
614+
615+
/**
616+
* Update the provider instance order configuration.
617+
*
618+
* @param int $providerid The provider ID.
619+
* @param string|int $action
620+
*
621+
* @throws dml_exception
622+
*/
623+
public function update_provider_order(int $providerid, string|int $action): void {
624+
$order = explode(',', get_config('core_ai', 'provider_order'));
625+
$key = array_search($providerid, $order);
626+
627+
switch ($action) {
628+
case aiproviderplugin::MOVE_UP:
629+
if ($key >= 1) {
630+
$fsave = $order[$key];
631+
$order[$key] = $order[$key - 1];
632+
$order[$key - 1] = $fsave;
633+
}
634+
break;
635+
636+
case aiproviderplugin::MOVE_DOWN:
637+
if ($key < (count($order) - 1)) {
638+
$fsave = $order[$key];
639+
$order[$key] = $order[$key + 1];
640+
$order[$key + 1] = $fsave;
641+
}
642+
break;
643+
644+
case aiproviderplugin::ENABLE:
645+
if (!$key) {
646+
$order[] = $providerid;
647+
}
648+
break;
649+
650+
case aiproviderplugin::DISABLE:
651+
if ($key) {
652+
unset($order[$key]);
653+
}
654+
break;
655+
}
656+
657+
$this->set_provider_config(['provider_order' => implode(',', $order)], 'core_ai');
658+
659+
\core\session\manager::gc(); // Remove stale sessions.
660+
\core_plugin_manager::reset_caches();
661+
}
662+
663+
/**
664+
* Sets config variable for given provider instance.
665+
*
666+
* @param array $data The data to set.
667+
* @param string $plugin The plugin name.
668+
*
669+
* @return bool true or exception.
670+
* @throws dml_exception
671+
*/
672+
public function set_provider_config(array $data, string $plugin): bool|dml_exception {
673+
$providerconf = get_config($plugin);
674+
foreach ($data as $key => $newvalue) {
675+
if (empty($providerconf->$key)) {
676+
add_to_config_log($key, null, $newvalue, $plugin);
677+
set_config($key, $newvalue, $plugin);
678+
} else if ($providerconf->$key != $newvalue) {
679+
add_to_config_log($key, $providerconf->$key, $newvalue, $plugin);
680+
set_config($key, $newvalue, $plugin);
681+
}
682+
}
683+
return true;
684+
}
543685
}

0 commit comments

Comments
 (0)