Merge branch 'MDL-83276-main' of https://github.com/HuongNV13/moodle

This commit is contained in:
Sara Arjona 2024-09-30 11:20:30 +02:00
commit 0cde4a8842
No known key found for this signature in database
17 changed files with 497 additions and 39 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -325,6 +325,7 @@ const AICourseAssist = class {
}
} catch (error) {
window.console.log(error);
this.displayError();
}
}
}

View File

@ -16,6 +16,7 @@
namespace aiplacement_courseassist\external;
use aiplacement_courseassist\utils;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_value;
@ -77,7 +78,9 @@ class summarise_text extends external_api {
// Check the user has permission to use the AI service.
self::validate_context($context);
require_capability('aiplacement/courseassist:summarise_text', $context);
if (!utils::is_course_assist_available($context)) {
throw new \moodle_exception('nocourseassist', 'aiplacement_courseassist');
}
// Prepare the action.
$action = new \core_ai\aiactions\summarise_text(

View File

@ -16,10 +16,9 @@
namespace aiplacement_courseassist\output;
use aiplacement_courseassist\utils;
use core\hook\output\after_http_headers;
use core\hook\output\before_footer_html_generation;
use core_ai\aiactions\summarise_text;
use core_ai\manager;
/**
* Output handler for the course assist AI Placement.
@ -90,18 +89,8 @@ class assist_ui {
if ($PAGE->context->contextlevel != CONTEXT_MODULE) {
return false;
}
[$plugintype, $pluginname] = explode('_', \core_component::normalize_componentname('aiplacement_courseassist'), 2);
$manager = \core_plugin_manager::resolve_plugininfo_class($plugintype);
if (!$manager::is_plugin_enabled($pluginname)) {
return false;
}
$providers = manager::get_providers_for_actions([summarise_text::class], true);
if (!has_capability('aiplacement/courseassist:summarise_text', $PAGE->context)
|| !manager::is_action_enabled('aiplacement_courseassist', summarise_text::class)
|| empty($providers[summarise_text::class])
) {
return false;
}
return true;
// Check if the user has permission to use the AI service.
return utils::is_course_assist_available($PAGE->context);
}
}

View File

@ -0,0 +1,54 @@
<?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 aiplacement_courseassist;
use core_ai\aiactions\summarise_text;
use core_ai\manager;
/**
* AI Placement course assist utils.
*
* @package aiplacement_courseassist
* @copyright 2024 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class utils {
/**
* Check if AI Placement course assist is available for the context.
*
* @param \context $context The context.
* @return bool True if AI Placement course assist is available, false otherwise.
*/
public static function is_course_assist_available(\context $context): bool {
[$plugintype, $pluginname] = explode('_', \core_component::normalize_componentname('aiplacement_courseassist'), 2);
$manager = \core_plugin_manager::resolve_plugininfo_class($plugintype);
if (!$manager::is_plugin_enabled($pluginname)) {
return false;
}
$providers = manager::get_providers_for_actions([summarise_text::class], true);
if (!has_capability('aiplacement/courseassist:summarise_text', $context)
|| !manager::is_action_available(summarise_text::class)
|| !manager::is_action_enabled('aiplacement_courseassist', summarise_text::class)
|| empty($providers[summarise_text::class])
) {
return false;
}
return true;
}
}

View File

@ -27,6 +27,7 @@ $string['courseassist:summarise_text'] = 'Summarise text';
$string['copy'] = 'Copy';
$string['generatefailtitle'] = 'Something went wrong';
$string['generating'] = 'Generating your response';
$string['nocourseassist'] = 'AI summary is not available for this context';
$string['pluginname'] = 'Course Assistance Placement';
$string['privacy:metadata'] = 'The Course Assistance placement plugin does not store any personal data.';
$string['regenerate'] = 'Regenerate';

View File

@ -46,6 +46,44 @@ Feature: AI Course assist summarise
And I am on the "PageName1" "page activity" page logged in as teacher1
And "Summarise" "button" should exist
@javascript
Scenario: Summarise text using AI is not available if provider action is not enabled
Given the following config values are set as admin:
| summarise_text | | aiprovider_openai |
When I am on the "PageName1" "page activity" page logged in as teacher1
Then "Summarise" "button" should not exist
And the following config values are set as admin:
| summarise_text | 1 | aiprovider_openai |
And I am on the "PageName1" "page activity" page logged in as teacher1
And "Summarise" "button" should exist
@javascript
Scenario: Summarise text using AI is not available if placement action is not enabled
Given the following config values are set as admin:
| summarise_text | | aiplacement_courseassist |
When I am on the "PageName1" "page activity" page logged in as teacher1
Then "Summarise" "button" should not exist
And the following config values are set as admin:
| summarise_text | 1 | aiplacement_courseassist |
And I am on the "PageName1" "page activity" page logged in as teacher1
And "Summarise" "button" should exist
@javascript
Scenario: Summarise text using AI is not available if provider action is not enabled and placement action is enabled
Given the following config values are set as admin:
| summarise_text | | aiplacement_courseassist |
| summarise_text | | aiprovider_openai |
When I am on the "PageName1" "page activity" page logged in as teacher1
Then "Summarise" "button" should not exist
And the following config values are set as admin:
| summarise_text | 1 | aiplacement_courseassist |
And I am on the "PageName1" "page activity" page logged in as teacher1
And "Summarise" "button" should not exist
And the following config values are set as admin:
| summarise_text | 1 | aiprovider_openai |
And I am on the "PageName1" "page activity" page logged in as teacher1
And "Summarise" "button" should exist
@javascript
Scenario: Summarise text using AI is not available if the user does not have permission
When I am on the "PageName1" "page activity" page logged in as teacher2

View File

@ -0,0 +1,79 @@
<?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 aiplacement_courseassist;
/**
* AI Placement course assist utils test.
*
* @package aiplacement_courseassist
* @copyright 2024 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \aiplacement_courseassist\utils
*/
final class utils_test extends \advanced_testcase {
/**
* Test is_course_assist_available method.
*/
public function test_is_course_assist_available(): void {
global $DB;
$this->resetAfterTest();
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$context = \context_course::instance($course->id);
$teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']);
$this->getDataGenerator()->enrol_user($user1->id, $course->id, 'manager');
$this->getDataGenerator()->enrol_user($user2->id, $course->id, 'editingteacher');
// Provider is not enabled.
$this->setUser($user1);
set_config('enabled', 0, 'aiprovider_openai');
$this->assertFalse(utils::is_course_assist_available($context));
set_config('enabled', 1, 'aiprovider_openai');
// Plugin is not enabled.
$this->setUser($user1);
set_config('enabled', 0, 'aiplacement_courseassist');
$this->assertFalse(utils::is_course_assist_available($context));
// Plugin is enabled but user does not have capability.
assign_capability('aiplacement/courseassist:summarise_text', CAP_PROHIBIT, $teacherrole->id, $context);
$this->setUser($user2);
set_config('enabled', 1, 'aiprovider_openai');
set_config('enabled', 1, 'aiplacement_courseassist');
$this->assertFalse(utils::is_course_assist_available($context));
// Plugin is enabled, user has capability and placement action is not available.
$this->setUser($user1);
set_config('summarise_text', 0, 'aiplacement_courseassist');
$this->assertFalse(utils::is_course_assist_available($context));
// Plugin is enabled, user has capability and provider action is not available.
$this->setUser($user1);
set_config('summarise_text', 0, 'aiprovider_openai');
set_config('summarise_text', 1, 'aiplacement_courseassist');
$this->assertFalse(utils::is_course_assist_available($context));
// Plugin is enabled, user has capability, placement action is available and provider action is available.
$this->setUser($user1);
set_config('summarise_text', 1, 'aiprovider_openai');
set_config('summarise_text', 1, 'aiplacement_courseassist');
$this->assertTrue(utils::is_course_assist_available($context));
}
}

View File

@ -16,6 +16,7 @@
namespace aiplacement_editor\external;
use aiplacement_editor\utils;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_value;
@ -114,7 +115,10 @@ class generate_image extends external_api {
// Check the user has permission to use the AI service.
self::validate_context($context);
require_capability('aiplacement/editor:generate_image', $context);
if (!utils::is_html_editor_placement_action_available($context, 'generate_text',
\core_ai\aiactions\generate_image::class)) {
throw new \moodle_exception('noeditor', 'aiplacement_editor');
}
// Prepare the action.
$action = new \core_ai\aiactions\generate_image(

View File

@ -16,6 +16,7 @@
namespace aiplacement_editor\external;
use aiplacement_editor\utils;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_value;
@ -76,7 +77,9 @@ class generate_text extends external_api {
// Check the user has permission to use the AI service.
self::validate_context($context);
require_capability('aiplacement/editor:generate_text', $context);
if (!utils::is_html_editor_placement_action_available($context, 'generate_text', \core_ai\aiactions\generate_text::class)) {
throw new \moodle_exception('noeditor', 'aiplacement_editor');
}
// Prepare the action.
$action = new \core_ai\aiactions\generate_text(

View File

@ -0,0 +1,58 @@
<?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 aiplacement_editor;
use core_ai\manager;
/**
* AI Placement HTML editor utils.
*
* @package aiplacement_editor
* @copyright 2024 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class utils {
/**
* Check if AI Placement HTML editor action is available for the context.
*
* @param \context $context The context.
* @param string $actionname The name of the action.
* @param string $actionclass The class name of the action.
* @return bool True if the action is available, false otherwise.
*/
public static function is_html_editor_placement_action_available(
\context $context,
string $actionname,
string $actionclass
): bool {
[$plugintype, $pluginname] = explode('_', \core_component::normalize_componentname('aiplacement_editor'), 2);
$manager = \core_plugin_manager::resolve_plugininfo_class($plugintype);
if ($manager::is_plugin_enabled($pluginname)) {
if (
has_capability("aiplacement/editor:{$actionname}", $context)
&& manager::is_action_available($actionclass)
&& manager::is_action_enabled('aiplacement_editor', $actionclass)
) {
return true;
}
}
return false;
}
}

View File

@ -29,6 +29,7 @@ $string['generateimagesetting_desc'] = 'Enable or disable the generation of imag
$string['generatetext'] = 'AI generate text';
$string['generatetextsetting'] = 'Enable generate text';
$string['generatetextsetting_desc'] = 'Enable or disable the generation of text from a text prompt.';
$string['noeditor'] = 'HTML Text Editor Placement is not available for this context';
$string['pluginname'] = 'HTML Text Editor Placement';
$string['privacy:metadata'] = 'The HTML text editor placement plugin does not store any personal data.';
$string['editor:generate_image'] = 'Generate AI Images in HTML Text Editor';

View File

@ -0,0 +1,143 @@
<?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 aiplacement_editor;
use core_ai\aiactions\generate_image;
use core_ai\aiactions\generate_text;
/**
* HTML Text Editor Placement utils test.
*
* @package aiplacement_editor
* @copyright 2024 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \aiplacement_courseassist\utils
*/
final class utils_test extends \advanced_testcase {
/** @var array List of users. */
private array $users;
/** @var \stdClass Course object. */
private \stdClass $course;
/** @var \context_course Course context. */
private \context_course $context;
/** @var \stdClass Teacher role. */
private \stdClass $teacherrole;
public function setUp(): void {
global $DB;
parent::setUp();
$this->resetAfterTest();
$this->users[1] = $this->getDataGenerator()->create_user();
$this->users[2] = $this->getDataGenerator()->create_user();
$this->course = $this->getDataGenerator()->create_course();
$this->context = \context_course::instance($this->course->id);
$this->teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']);
$this->getDataGenerator()->enrol_user($this->users[1]->id, $this->course->id, 'manager');
$this->getDataGenerator()->enrol_user($this->users[2]->id, $this->course->id, 'editingteacher');
}
/**
* Test is_html_editor_placement_action_available method.
*
* @param string $actionname Action name.
* @param string $actionclass Action class.
* @dataProvider html_editor_placement_action_available_provider
*/
public function test_is_html_editor_placement_action_available(
string $actionname,
string $actionclass,
): void {
// Provider is not enabled.
$this->setUser($this->users[1]);
set_config('enabled', 0, 'aiprovider_openai');
$this->assertFalse(utils::is_html_editor_placement_action_available(
context: $this->context,
actionname: $actionname,
actionclass: $actionclass
));
set_config('enabled', 1, 'aiprovider_openai');
// Plugin is not enabled.
$this->setUser($this->users[1]);
set_config('enabled', 0, 'aiplacement_editor');
$this->assertFalse(utils::is_html_editor_placement_action_available(
context: $this->context,
actionname: $actionname,
actionclass: $actionclass
));
// Plugin is enabled but user does not have capability.
assign_capability("aiplacement/editor:{$actionname}", CAP_PROHIBIT, $this->teacherrole->id, $this->context);
$this->setUser($this->users[2]);
set_config('enabled', 1, 'aiprovider_openai');
set_config('enabled', 1, 'aiplacement_editor');
$this->assertFalse(utils::is_html_editor_placement_action_available(
context: $this->context,
actionname: $actionname,
actionclass: $actionclass
));
// Plugin is enabled, user has capability and placement action is not available.
$this->setUser($this->users[1]);
set_config($actionname, 0, 'aiplacement_editor');
$this->assertFalse(utils::is_html_editor_placement_action_available(
context: $this->context,
actionname: $actionname,
actionclass: $actionclass
));
// Plugin is enabled, user has capability and provider action is not available.
$this->setUser($this->users[1]);
set_config($actionname, 0, 'aiprovider_openai');
set_config($actionname, 1, 'aiplacement_editor');
$this->assertFalse(utils::is_html_editor_placement_action_available(
context: $this->context,
actionname: $actionname,
actionclass: $actionclass
));
// Plugin is enabled, user has capability, placement action is available and provider action is available.
$this->setUser($this->users[1]);
set_config($actionname, 1, 'aiprovider_openai');
set_config($actionname, 1, 'aiplacement_editor');
$this->assertTrue(utils::is_html_editor_placement_action_available(
context: $this->context,
actionname: $actionname,
actionclass: $actionclass
));
}
/**
* Data provider for {@see test_is_html_editor_placement_action_available}
*
* @return array
*/
public static function html_editor_placement_action_available_provider(): array {
return [
'Text generation' => [
'generate_text',
generate_text::class,
],
'Image generation' => [
'generate_image',
generate_image::class,
],
];
}
}

View File

@ -16,6 +16,7 @@
namespace tiny_aiplacement;
use aiplacement_editor\utils;
use core\context;
use core_ai\aiactions\generate_image;
use core_ai\aiactions\generate_text;
@ -89,26 +90,19 @@ class plugininfo extends plugin implements plugin_with_buttons, plugin_with_menu
* @return array The allowed actions.
*/
private static function get_allowed_actions(context $context, array $options): array {
[$plugintype, $pluginname] = explode('_', \core_component::normalize_componentname('aiplacement_editor'), 2);
$manager = \core_plugin_manager::resolve_plugininfo_class($plugintype);
$allowedactions = [];
if ($manager::is_plugin_enabled($pluginname)) {
foreach (self::$possibleactions as $action => $actionclass) {
if (
has_capability("aiplacement/editor:{$action}", $context)
&& manager::is_action_available($actionclass)
) {
if ($action == 'generate_image') {
// For generate image, we need to check if the user has the capability to upload files.
$canhavefiles = !empty($options['maxfiles']);
$canhaveexternalfiles = !empty($options['return_types']) && ($options['return_types'] & FILE_EXTERNAL);
$allowedactions[$action] = $canhavefiles || $canhaveexternalfiles;
} else {
$allowedactions[$action] = true;
}
} else {
$allowedactions[$action] = false;
}
foreach (self::$possibleactions as $action => $actionclass) {
$allowedactions[$action] = utils::is_html_editor_placement_action_available(
context: $context,
actionname: $action,
actionclass: $actionclass,
);
if ($allowedactions[$action] && $action == 'generate_image') {
// For generate image, we need to check if the user has the capability to upload files.
$canhavefiles = !empty($options['maxfiles']);
$canhaveexternalfiles = !empty($options['return_types']) && ($options['return_types'] & FILE_EXTERNAL);
$allowedactions[$action] = $canhavefiles || $canhaveexternalfiles;
}
}
return $allowedactions;

View File

@ -53,6 +53,51 @@ Feature: Generate image using AI
And I navigate to "Settings" in current page administration
And "AI generate image" button should exist in the "Description" TinyMCE editor
@javascript
Scenario: Image generation using AI is not available if provider action is not enabled
Given the following config values are set as admin:
| generate_image | | aiprovider_openai |
When I am on the "PageName2" "page activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
Then "AI generate image" button should not exist in the "Description" TinyMCE editor
And the following config values are set as admin:
| generate_image | 1 | aiprovider_openai |
And I am on the "PageName2" "page activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
And "AI generate image" button should exist in the "Description" TinyMCE editor
@javascript
Scenario: Image generation using AI is not available if placement action is not enabled
Given the following config values are set as admin:
| generate_image | | aiplacement_editor |
When I am on the "PageName2" "page activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
Then "AI generate image" button should not exist in the "Description" TinyMCE editor
And the following config values are set as admin:
| generate_image | 1 | aiplacement_editor |
And I am on the "PageName2" "page activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
And "AI generate image" button should exist in the "Description" TinyMCE editor
@javascript
Scenario: Image generation using AI is not available if provider action is not enabled and placement action is enabled
Given the following config values are set as admin:
| generate_image | | aiplacement_editor |
| generate_image | | aiprovider_openai |
When I am on the "PageName2" "page activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
Then "AI generate image" button should not exist in the "Description" TinyMCE editor
And the following config values are set as admin:
| generate_image | 1 | aiplacement_editor |
And I am on the "PageName2" "page activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
And "AI generate image" button should not exist in the "Description" TinyMCE editor
And the following config values are set as admin:
| generate_image | 1 | aiprovider_openai |
And I am on the "PageName2" "page activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
And "AI generate image" button should exist in the "Description" TinyMCE editor
@javascript
Scenario: Image generation using AI is not available if the user does not have permission
Given I am on the "PageName1" "page activity" page logged in as teacher2

View File

@ -54,6 +54,51 @@ Feature: Generate text using AI
And I navigate to "Settings" in current page administration
And "AI generate text" button should exist in the "Description" TinyMCE editor
@javascript
Scenario: Text generation using AI is not available if provider action is not enabled
Given the following config values are set as admin:
| generate_text | | aiprovider_openai |
When I am on the "PageName2" "page activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
Then "AI generate text" button should not exist in the "Description" TinyMCE editor
And the following config values are set as admin:
| generate_text | 1 | aiprovider_openai |
And I am on the "PageName2" "page activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
And "AI generate text" button should exist in the "Description" TinyMCE editor
@javascript
Scenario: Text generation using AI is not available if placement action is not enabled
Given the following config values are set as admin:
| generate_text | | aiplacement_editor |
When I am on the "PageName2" "page activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
Then "AI generate text" button should not exist in the "Description" TinyMCE editor
And the following config values are set as admin:
| generate_text | 1 | aiplacement_editor |
And I am on the "PageName2" "page activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
And "AI generate text" button should exist in the "Description" TinyMCE editor
@javascript
Scenario: Text generation using AI is not available if provider action is not enabled and placement action is enabled
Given the following config values are set as admin:
| generate_text | | aiplacement_editor |
| generate_text | | aiprovider_openai |
When I am on the "PageName2" "page activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
Then "AI generate text" button should not exist in the "Description" TinyMCE editor
And the following config values are set as admin:
| generate_text | 1 | aiplacement_editor |
And I am on the "PageName2" "page activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
And "AI generate text" button should not exist in the "Description" TinyMCE editor
And the following config values are set as admin:
| generate_text | 1 | aiprovider_openai |
And I am on the "PageName2" "page activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
And "AI generate text" button should exist in the "Description" TinyMCE editor
@javascript
Scenario: Text generation using AI is not available if the user does not have permission
When I am on the "PageName1" "page activity" page logged in as teacher2