Compare commits

...

53 Commits

Author SHA1 Message Date
Andrew Nicols 7beda01cae Moodle release 4.2rc1 2023-04-18 22:29:14 +08:00
Andrew Nicols 5124b6747c NOBUG: Fixed SVG browser compatibility 2023-04-18 22:29:11 +08:00
Andrew Nicols 09912bfefa Merge branch 'install_master' of https://git.in.moodle.com/amosbot/moodle-install 2023-04-18 22:29:10 +08:00
Jake Dallimore c9e8aa0406 Merge branch 'MDL-75316-master-4' of https://github.com/mickhawkins/moodle 2023-04-18 17:24:55 +08:00
Michael Hawkins 2e024f304a MDL-75316 core: MoodleNet integration final improvements 2023-04-18 17:00:54 +08:00
Huong Nguyen 58ced4add0 MDL-75316 core_moodlenet: Share to MoodleNet modal and activity menu
Originally implemented as MDL-75785
2023-04-18 17:05:07 +08:00
Huong Nguyen 6a877d438b MDL-75316 core: Webservice for sending activity to MoodleNet
Originally implemented as MDL-75933
2023-04-18 17:04:46 +08:00
Michael Hawkins 42ba0492e2 MDL-75316 core: Corrected oauth2_client::get_accesstoken() docblock
Originally implemented as MDL-75932
2023-04-18 17:04:30 +08:00
Michael Hawkins 77f85ed287 MDL-75316 core: Add activity sender & moodlenet_client to MoodleNet API
This makes it possible to share a single activity to a MoodleNet
instance (which has been enabled for sharing in site admin).
This utilises the core\moodlenet\activity_packager to create an
activity backup, then sends it using the MoodleNet create resource
API specification.

Originally implemented as MDL-75932
2023-04-18 17:04:14 +08:00
raortegar 9bc769a610 MDL-75316 core: Add activity packager to MoodleNet API
This makes it possible to package a single activity as a Moodle backup
file, excluding user data so it is ready to be shared as a MoodleNet
resource.

Co-authored-by: Michael Hawkins <michaelh@moodle.com>

Originally implemented as MDL-75932
2023-04-18 17:03:45 +08:00
Michael Hawkins d6c4c0fbbd MDL-75316 core: Set up MoodleNet API structure and utilities helper
Initial folder structure and declaring API.
Created utilities class.

Originally implemented as MDL-75932
2023-04-18 17:03:18 +08:00
Huong Nguyen cd097f117b MDL-75316 core_moodlenet: Rename settings
Originally implemented as MDL-75319
2023-04-18 17:03:02 +08:00
Huong Nguyen 7727745bf7 MDL-75316 core_moodlenet: Create MoodleNet capability
Originally implemented as MDL-75319
2023-04-18 17:02:47 +08:00
Huong Nguyen 852e31dbc4 MDL-75316 tool_moodlenet: Rename setting section
Rename the existing section from "MoodleNet settings" to "MoodleNet inbound settings"

Originally implemented as MDL-75319
2023-04-18 17:02:19 +08:00
Huong Nguyen ef2a1934a0 MDL-75316 core_moodlenet: Add setting to control sharing
Originally implemented as MDL-75319
2023-04-18 17:01:38 +08:00
Huong Nguyen d9a6577410 MDL-75316 admin: Create Share to MoodleNet experimental setting
Originally implemented as MDL-75319
2023-04-18 17:00:54 +08:00
Ilya Tregubov 34d7edf079 Merge branch 'MDL-77916' of https://github.com/paulholden/moodle 2023-04-18 14:27:49 +08:00
Andrew Nicols 797f03228b Merge branch 'MDL-77893-master' of https://github.com/jleyva/moodle 2023-04-18 13:28:12 +08:00
Andrew Nicols 825e621264 Merge branch 'MDL-77735-master' of https://github.com/sarjona/moodle 2023-04-18 12:11:08 +08:00
Ilya Tregubov aac60d73a1 Merge branch 'MDL-77770' of https://github.com/paulholden/moodle 2023-04-18 12:01:28 +08:00
Ilya Tregubov 43c2c25645 Merge branch 'MDL-76855' of https://github.com/Chocolate-lightning/moodle 2023-04-18 11:17:43 +08:00
Andrew Nicols 5e4559c85a Merge branch 'MDL-77355' of https://github.com/paulholden/moodle 2023-04-18 10:32:49 +08:00
Ilya Tregubov 6255f92d66 Merge branch 'MDL-77943' of https://github.com/paulholden/moodle 2023-04-18 10:17:45 +08:00
Andrew Nicols df2e6c1cd4 Merge branch 'MDL-77902' of https://github.com/Chocolate-lightning/moodle 2023-04-18 10:10:39 +08:00
Mathew May ae6826953e MDL-77902 gradereport_grader: Clear btn & user differentiation 2023-04-18 09:59:13 +08:00
Ilya Tregubov 688c9b13d4 Merge branch 'MDL-77827-master' of https://github.com/HuongNV13/moodle 2023-04-18 08:58:33 +08:00
AMOS bot e93fd454a2 Automatically generated installer lang files 2023-04-18 00:07:41 +00:00
Paul Holden ba64c15814 MDL-77943 group: use shorter names for visibility report elements. 2023-04-17 19:36:55 +01:00
Paul Holden 6a5a787844 Merge branch 'MDL-77878-master-enfix' of https://github.com/vmdef/moodle 2023-04-17 19:12:36 +01:00
Juan Leyva cf76d1bf9f MDL-77893 airnotifier: Apply payload size optimisation to all push 2023-04-17 15:36:28 +02:00
Juan Leyva 862a9fb24c MDL-77893 airnotifier: Allow configuring how to process encrypted notifs 2023-04-17 15:36:28 +02:00
Paul Holden 71cdfae287 Merge branch 'MDL-77842-master' of https://github.com/sarjona/moodle 2023-04-17 13:49:58 +01:00
Juan Leyva bdf525a43e MDL-77893 airnotifier: Leave only message data processed by Airnotifier 2023-04-17 13:34:55 +02:00
Paul Holden f2c1e6c114 Merge branch 'MDL-76212-master-nav' of https://github.com/kevpercy/moodle 2023-04-17 11:49:23 +01:00
Paul Holden 44e3cb05e5 Merge branch 'MDL-77324-master-2' of https://github.com/ilyatregubov/moodle 2023-04-17 11:05:55 +01:00
Juan Leyva 964ad9d264 MDL-77893 airnotifier: Allow to set publickey when creating new device 2023-04-17 11:03:15 +02:00
Víctor Déniz b576b068bd MDL-77878 lang: Use fixed strings in tests 2023-04-17 08:49:59 +01:00
Mathew May fbc51cd591 MDL-77902 gradebook: Ensure dyn search works with pagination 2023-04-17 13:10:56 +08:00
Mathew May 53675693b4 MDL-76855 gradereport_user: Prevent parent access errors 2023-04-17 11:24:51 +08:00
Andrew Nicols 35118869c7 Merge branch 'MDL-77874' of https://github.com/paulholden/moodle 2023-04-17 11:20:53 +08:00
Helen Foster ee8408c4d5 MDL-77878 lang: Import fixed English strings (en_fix) 2023-04-14 13:08:38 +01:00
Sara Arjona d2e27ec8bb Merge branch 'MDL-77913' of https://github.com/paulholden/moodle 2023-04-14 14:02:08 +02:00
Sara Arjona ed53d81dc7 MDL-77842 badges: Improve debugging information for Badgr
In MDL-77086 some extra information was added when a badge can't be
sent to a backpack. However, as the Badgr is not following the
specification, it's still hard to debug it when it fails (as
raised in MDL-75552, Badgr is not working because, in some cases,
it's returning the "Method Not Allowed / Request method 'POST'
not supported" error.
2023-04-14 13:47:23 +02:00
Sara Arjona 440edc5e42 MDL-77735 core: Check $CFG->lang isset
In some cases, $CFG->lang might not be set, and this is causing a
Notice to be displayed when, for instance, database connection fails.
This patch should fix this case.
2023-04-14 13:46:18 +02:00
Huong Nguyen f5867c078d MDL-77827 events: Changed JSON comparison to be less strict 2023-04-14 12:28:55 +07:00
Paul Holden 62d48a1d47 MDL-77916 h5p: register autoloader in helper testcase. 2023-04-13 23:12:13 +01:00
Paul Holden dcdeb47f39 MDL-77913 qbank_previewquestion: deterministic ordering of versions.
Ensure the ordering of loaded question versions is consistent, avoids
random Oracle failures.
2023-04-13 21:13:41 +01:00
Ilya Tregubov 0fdd514f11 MDL-77324 gradereport_singleview: Make action menus consistent 2023-04-13 16:57:48 +08:00
Kevin Percy b9ad54bad8 MDL-76212 gradebook_nav: Fixed tertiary nav for smaller screens 2023-04-12 13:29:13 +08:00
Paul Holden aaf231551a MDL-77874 tiny_link: define help link text language string. 2023-04-06 19:08:03 +01:00
Paul Holden e80192f606 MDL-77770 gradereport_user: fix errors when no users to navigate. 2023-04-06 14:40:58 +01:00
Paul Holden 997713f43e MDL-77355 webservice: Behat scenario for adding service functions. 2023-04-06 11:58:53 +01:00
Matt Porritt 6abfde47dc MDL-77355 Web services: add function class not found
Fixes a regression caused by MDL-76583 That prevents functions
being added to new or existing external services.
2023-04-06 11:46:35 +01:00
142 changed files with 3903 additions and 431 deletions
+57
View File
@@ -0,0 +1,57 @@
<?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/>.
/**
* MoodleNet callback.
*
* @package core
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core\moodlenet\moodlenet_client;
use core\oauth2\api;
require_once(__DIR__ . '/../config.php');
require_login();
// Parameters.
$issuerid = required_param('issuerid', PARAM_INT);
$error = optional_param('error', '', PARAM_RAW);
$message = optional_param('error_description', null, PARAM_RAW);
// Headers to make it not cacheable.
header('Cache-Control: no-cache, must-revalidate');
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
$PAGE->set_context(context_system::instance());
$PAGE->set_url('/admin/moodlenet_oauth2_callback.php');
$PAGE->set_pagelayout('popup');
// Wait as long as it takes for this script to finish.
core_php_time_limit::raise();
$issuer = api::get_issuer($issuerid);
$returnurl = new moodle_url('/admin/moodlenet_oauth2_callback.php');
$returnurl->param('issuerid', $issuerid);
$returnurl->param('callback', 'yes');
$returnurl->param('sesskey', sesskey());
$oauthclient = api::get_user_oauth_client($issuer, $returnurl, moodlenet_client::API_SCOPE_CREATE_RESOURCE, true);
$oauthclient->is_logged_in(); // Will upgrade the auth code to a token.
echo $OUTPUT->header();
$PAGE->requires->js_call_amd('core/moodlenet/oauth2callback', 'init', [$error, $message]);
echo $OUTPUT->footer();
+5
View File
@@ -33,6 +33,11 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
new lang_string('enablecourserelativedates', 'core_admin'),
new lang_string('enablecourserelativedates_desc', 'core_admin'), 0));
// Sharing to MoodleNet setting.
$temp->add(new admin_setting_configcheckbox('enablesharingtomoodlenet',
new lang_string('enablesharingtomoodlenet', 'core_admin'),
new lang_string('enablesharingtomoodlenet_desc', 'core_admin'), 0));
$ADMIN->add('experimental', $temp);
// "debugging" settingpage
+55
View File
@@ -0,0 +1,55 @@
<?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/>.
/**
* This file gives information about MoodleNet.
*
* @package core
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
if ($hassiteconfig) {
if (!empty($CFG->enablesharingtomoodlenet)) {
if (!$ADMIN->locate('moodlenet')) {
$ADMIN->add('root', new admin_category('moodlenet', get_string('pluginname', 'tool_moodlenet')));
}
// Outbound settings page.
$settings = new admin_settingpage('moodlenetoutbound', new lang_string('moodlenet:outboundsettings', 'moodle'));
$ADMIN->add('moodlenet', $settings);
// Get all the issuers.
$issuers = \core\oauth2\api::get_all_issuers();
$oauth2services = [
'' => new lang_string('none', 'admin'),
];
foreach ($issuers as $issuer) {
// Get the enabled issuer with the service type is MoodleNet only.
if ($issuer->get('servicetype') == 'moodlenet' && $issuer->get('enabled')) {
$oauth2services[$issuer->get('id')] = s($issuer->get('name'));
}
}
$url = new \moodle_url('/admin/tool/oauth2/issuers.php');
$settings->add(new admin_setting_configselect('moodlenet/oauthservice', new lang_string('issuer', 'auth_oauth2'),
new lang_string('moodlenet:configoauthservice', 'moodle', $url->out()), '', $oauth2services));
}
}
+36 -36
View File
@@ -8,52 +8,52 @@ Feature: An administrator can manage Block plugins
Scenario: An administrator can control the enabled state of block plugins using JavaScript
Given I am logged in as "admin"
And I navigate to "Plugins > Blocks > Manage blocks" in site administration
When I click on "Disable the Latest badges plugin" "link"
Then I should see "The Latest badges plugin has been disabled"
And "Disable the Latest badges plugin" "link" should not exist
But "Enable the Latest badges plugin" "link" should exist
When I click on "Enable the Latest badges plugin" "link"
Then I should see "The Latest badges plugin has been enabled"
And "Enable the Latest badges plugin" "link" should not exist
But "Disable the Latest badges plugin" "link" should exist
When I click on "Disable Latest badges" "link"
Then I should see "Latest badges disabled."
And "Disable Latest badges" "link" should not exist
But "Enable Latest badges" "link" should exist
When I click on "Enable Latest badges" "link"
Then I should see "Latest badges enabled."
And "Enable Latest badges" "link" should not exist
But "Disable Latest badges" "link" should exist
Scenario: An administrator can control the enabled state of block plugins without JavaScript
Given I am logged in as "admin"
And I navigate to "Plugins > Blocks > Manage blocks" in site administration
When I click on "Disable the Latest badges plugin" "link"
Then I should see "The Latest badges plugin has been disabled"
And "Disable the Latest badges plugin" "link" should not exist
But "Enable the Latest badges plugin" "link" should exist
When I click on "Enable the Latest badges plugin" "link"
Then I should see "The Latest badges plugin has been enabled"
And "Enable the Latest badges plugin" "link" should not exist
But "Disable the Latest badges plugin" "link" should exist
When I click on "Disable Latest badges" "link"
Then I should see "Latest badges disabled."
And "Disable Latest badges" "link" should not exist
But "Enable Latest badges" "link" should exist
When I click on "Enable Latest badges" "link"
Then I should see "Latest badges enabled."
And "Enable Latest badges" "link" should not exist
But "Disable Latest badges" "link" should exist
@javascript
Scenario: An administrator can control the protected state of block plugins using JavaScript
Given I am logged in as "admin"
And I navigate to "Plugins > Blocks > Manage blocks" in site administration
When I click on "Protect instances of the Latest badges block" "link"
Then I should see "The Latest badges block is now protected"
And "Protect instances of the Latest badges block" "link" should not exist
But "Unprotect instances of the Latest badges block" "link" should exist
And "Protect instances of the Activities block" "link" should exist
When I click on "Unprotect instances of the Latest badges block" "link"
Then I should see "The Latest badges block is no longer protected"
And "Unprotect instances of the Activities block" "link" should not exist
But "Protect instances of the Latest badges block" "link" should exist
And "Protect instances of the Activities block" "link" should exist
When I click on "Protect instances of Latest badges" "link"
Then I should see "Latest badges block instances are protected."
And "Protect instances of Latest badges" "link" should not exist
But "Unprotect instances of Latest badges" "link" should exist
And "Protect instances of Activities" "link" should exist
When I click on "Unprotect instances of Latest badges" "link"
Then I should see "Latest badges block instances are unprotected."
And "Unprotect instances of Activities" "link" should not exist
But "Protect instances of Latest badges" "link" should exist
And "Protect instances of Activities" "link" should exist
Scenario: An administrator can control the protected state of block plugins without JavaScript
Given I am logged in as "admin"
And I navigate to "Plugins > Blocks > Manage blocks" in site administration
When I click on "Protect instances of the Latest badges block" "link"
Then I should see "The Latest badges block is now protected"
And "Protect instances of the Latest badges block" "link" should not exist
But "Unprotect instances of the Latest badges block" "link" should exist
And "Protect instances of the Activities block" "link" should exist
When I click on "Unprotect instances of the Latest badges block" "link"
Then I should see "The Latest badges block is no longer protected"
And "Unprotect instances of the Activities block" "link" should not exist
But "Protect instances of the Latest badges block" "link" should exist
And "Protect instances of the Activities block" "link" should exist
When I click on "Protect instances of Latest badges" "link"
Then I should see "Latest badges block instances are protected."
And "Protect instances of Latest badges" "link" should not exist
But "Unprotect instances of Latest badges" "link" should exist
And "Protect instances of Activities" "link" should exist
When I click on "Unprotect instances of Latest badges" "link"
Then I should see "Latest badges block instances are unprotected."
And "Unprotect instances of Activities" "link" should not exist
But "Protect instances of Latest badges" "link" should exist
And "Protect instances of Activities" "link" should exist
+16 -16
View File
@@ -8,23 +8,23 @@ Feature: An administrator can manage Media plugins
Scenario: An administrator can control the enabled state of media plugins using JavaScript
Given I am logged in as "admin"
And I navigate to "Plugins > Media players > Manage media players" in site administration
When I click on "Disable the YouTube plugin" "link"
Then I should see "The YouTube plugin has been disabled"
And "Disable the YouTube plugin" "link" should not exist
But "Enable the YouTube plugin" "link" should exist
When I click on "Enable the YouTube plugin" "link"
Then I should see "The YouTube plugin has been enabled"
And "Enable the YouTube plugin" "link" should not exist
But "Disable the YouTube plugin" "link" should exist
When I click on "Disable YouTube" "link"
Then I should see "YouTube disabled."
And "Disable YouTube" "link" should not exist
But "Enable YouTube" "link" should exist
When I click on "Enable YouTube" "link"
Then I should see "YouTube enabled."
And "Enable YouTube" "link" should not exist
But "Disable YouTube" "link" should exist
Scenario: An administrator can control the enabled state of media plugins without JavaScript
Given I am logged in as "admin"
And I navigate to "Plugins > Media players > Manage media players" in site administration
When I click on "Disable the YouTube plugin" "link"
Then I should see "The YouTube plugin has been disabled"
And "Disable the YouTube plugin" "link" should not exist
But "Enable the YouTube plugin" "link" should exist
When I click on "Enable the YouTube plugin" "link"
Then I should see "The YouTube plugin has been enabled"
And "Enable the YouTube plugin" "link" should not exist
But "Disable the YouTube plugin" "link" should exist
When I click on "Disable YouTube" "link"
Then I should see "YouTube disabled."
And "Disable YouTube" "link" should not exist
But "Enable YouTube" "link" should exist
When I click on "Enable YouTube" "link"
Then I should see "YouTube enabled."
And "Enable YouTube" "link" should not exist
But "Disable YouTube" "link" should exist
@@ -0,0 +1,52 @@
@core @core_admin
Feature: MoodleNet outbound configuration
In order to send activity/resource to MoodleNet
As a Moodle administrator
I need to set outbound configuration
Background:
Given I log in as "admin"
Scenario: Share to MoodleNet experimental flag
Given I navigate to "Development > Experimental" in site administration
Then "Enable sharing to MoodleNet" "field" should exist
And the field "Enable sharing to MoodleNet" matches value "0"
Scenario: Outbound configuration without experimental flag enable yet
Given I navigate to "MoodleNet" in site administration
Then I should not see "MoodleNet outbound settings"
Scenario: Outbound configuration without OAuth 2 service setup yet
Given the following config values are set as admin:
| enablesharingtomoodlenet | 1 |
When I navigate to "MoodleNet" in site administration
Then I should see "MoodleNet outbound settings"
And I click on "MoodleNet outbound settings" "link"
And the field "OAuth 2 service" matches value "None"
And I should see "Select a MoodleNet OAuth 2 service to enable sharing to that MoodleNet site. If the service doesn't exist yet, you will need to create it."
And I click on "create" "link"
And I should see "OAuth 2 services"
Scenario: Outbound configuration with OAuth 2 service setup
Given a MoodleNet mock server is configured
And the following config values are set as admin:
| enablesharingtomoodlenet | 1 |
And I navigate to "Server > OAuth 2 services" in site administration
And I press "Custom"
And I should see "Create new service: Custom"
And I set the following fields to these values:
| Name | Testing custom service |
| Client ID | thisistheclientid |
| Client secret | supersecret |
And I press "Save changes"
When I navigate to "MoodleNet > MoodleNet outbound settings" in site administration
Then the field "OAuth 2 service" matches value "None"
And I navigate to "Server > OAuth 2 services" in site administration
And I press "MoodleNet"
And I should see "Create new service: MoodleNet"
And I change the MoodleNet field "Service base URL" to mock server
And I press "Save changes"
And I navigate to "MoodleNet > MoodleNet outbound settings" in site administration
And the "OAuth 2 service" "field" should be enabled
And I should see "MoodleNet" in the "OAuth 2 service" "select"
And I should not see "Testing custom service" in the "OAuth 2 service" "select"
+11 -1
View File
@@ -1,4 +1,4 @@
@core @core_admin
@core @core_admin @core_webservice
Feature: Web service user settings
In order to configure authorised users for a web service
As an admin
@@ -29,3 +29,13 @@ Feature: Web service user settings
Then I should see "User One" in the ".alloweduserlist" "css_element"
And I should see "1@example.org" in the ".alloweduserlist" "css_element"
And I should see "Kermit" in the ".alloweduserlist" "css_element"
@javascript
Scenario: Add a function to a web service
When I log in as "admin"
And I navigate to "Server > Web services > External services" in site administration
And I click on "Functions" "link" in the "Silly service" "table_row"
And I follow "Add functions"
And I set the field "Name" to "core_blog_get_entries"
And I click on "Add functions" "button" in the "#fgroup_id_buttonar" "css_element"
Then I should see "core_blog_get_entries" in the "generaltable" "table"
@@ -14,11 +14,11 @@ Feature: I can apply presets
Scenario: Applying Starter Moodle preset changes status and settings
# Checking the settings before applying Full Moodle preset (we're only testing one of each type).
Given I navigate to "Plugins > Activity modules > Manage activities" in site administration
And "Disable the Chat plugin" "icon" should exist in the "Chat" "table_row"
And "Disable Chat" "icon" should exist in the "Chat" "table_row"
And I navigate to "Plugins > Availability restrictions > Manage restrictions" in site administration
And "Hide" "icon" should exist in the "Restriction by grouping" "table_row"
And I navigate to "Plugins > Blocks > Manage blocks" in site administration
And "Disable the Logged in user plugin" "icon" should exist in the "Logged in user" "table_row"
And "Disable Logged in user" "icon" should exist in the "Logged in user" "table_row"
And I navigate to "Plugins > Course formats > Manage course formats" in site administration
And "Disable" "icon" should exist in the "Social format" "table_row"
And I navigate to "Plugins > Question behaviours > Manage question behaviours" in site administration
@@ -109,11 +109,11 @@ Feature: I can apply presets
And the field "Enable badges" matches value "0"
And the field "Enable competencies" matches value "0"
And I navigate to "Plugins > Activity modules > Manage activities" in site administration
And "Disable the Chat plugin" "icon" should not exist in the "Chat" "table_row"
And "Disable Chat" "icon" should not exist in the "Chat" "table_row"
And I navigate to "Plugins > Availability restrictions > Manage restrictions" in site administration
And "Hide" "icon" should not exist in the "Restriction by grouping" "table_row"
And I navigate to "Plugins > Blocks > Manage blocks" in site administration
And "Disable the Logged in user plugin" "icon" should not exist in the "Logged in user" "table_row"
And "Disable Logged in user" "icon" should not exist in the "Logged in user" "table_row"
And I navigate to "Plugins > Course formats > Manage course formats" in site administration
And "Disable" "icon" should not exist in the "Social format" "table_row"
And I navigate to "Plugins > Question behaviours > Manage question behaviours" in site administration
@@ -20,11 +20,11 @@ Feature: I can revert changes after a load
And the field "Enable badges" matches value "0"
And the field "Enable competencies" matches value "0"
And I navigate to "Plugins > Activity modules > Manage activities" in site administration
And "Disable the Chat plugin" "icon" should not exist in the "Chat" "table_row"
And "Disable Chat" "icon" should not exist in the "Chat" "table_row"
And I navigate to "Plugins > Availability restrictions > Manage restrictions" in site administration
And "Hide" "icon" should not exist in the "Restriction by grouping" "table_row"
And I navigate to "Plugins > Blocks > Manage blocks" in site administration
And "Disable the Logged in user plugin" "icon" should not exist in the "Logged in user" "table_row"
And "Disable Logged in user" "icon" should not exist in the "Logged in user" "table_row"
And I navigate to "Plugins > Course formats > Manage course formats" in site administration
And "Disable" "icon" should not exist in the "Social format" "table_row"
And I navigate to "Plugins > Question behaviours > Manage question behaviours" in site administration
@@ -39,11 +39,11 @@ Feature: I can revert changes after a load
Then the field "Enable badges" matches value "1"
And the field "Enable competencies" matches value "1"
And I navigate to "Plugins > Activity modules > Manage activities" in site administration
And "Disable the Chat plugin" "icon" should exist in the "Chat" "table_row"
And "Disable Chat" "icon" should exist in the "Chat" "table_row"
And I navigate to "Plugins > Availability restrictions > Manage restrictions" in site administration
And "Hide" "icon" should exist in the "Restriction by grouping" "table_row"
And I navigate to "Plugins > Blocks > Manage blocks" in site administration
And "Disable the Logged in user plugin" "icon" should exist in the "Logged in user" "table_row"
And "Disable Logged in user" "icon" should exist in the "Logged in user" "table_row"
And I navigate to "Plugins > Course formats > Manage course formats" in site administration
And "Disable" "icon" should exist in the "Social format" "table_row"
And I navigate to "Plugins > Question behaviours > Manage question behaviours" in site administration
@@ -47,9 +47,8 @@ $string['coursesize_2'] = 'M (~100MB; create in ~2 minutes)';
$string['coursesize_3'] = 'L (~1GB; create in ~30 minutes)';
$string['coursesize_4'] = 'XL (~10GB; create in ~2 hours)';
$string['coursesize_5'] = 'XXL (~20GB; create in ~4 hours)';
$string['additionalmodules'] = 'Additional modules';
$string['additionalmodules_help'] = 'We normally create standard modules in the course (like forum, page or label),
but you can select any other module that implements the course_backend_generator_create_activity function.';
$string['additionalmodules'] = 'Additional activities';
$string['additionalmodules_help'] = 'Select more activities that implement the course_backend_generator_create_activity function to include in the test course.';
$string['coursewithoutusers'] = 'The selected course has no users';
$string['createcourse'] = 'Create course';
$string['createtestplan'] = 'Create test plan';
@@ -25,7 +25,7 @@
defined('MOODLE_INTERNAL') || die();
$string['autoenablenotification'] = '<p>In Moodle 4.0 onwards, the <a href="https://moodle.net/">MoodleNet</a> integration is enabled by default in Advanced features. Users with the capability to create and manage activities can browse MoodleNet via the activity chooser and import MoodleNet resources into their courses.</p><p>If desired, an alternative MoodleNet instance may be specified in the <a href="{$a->settingslink}">MoodleNet settings</a>.</p>';
$string['autoenablenotification'] = '<p>In Moodle 4.0 onwards, the <a href="https://moodle.net/">MoodleNet</a> integration is enabled by default in Advanced features. Users with the capability to create and manage activities can browse MoodleNet via the activity chooser and import MoodleNet resources into their courses.</p><p>If desired, an alternative MoodleNet instance may be specified in the <a href="{$a->settingslink}">MoodleNet inbound settings</a>.</p>';
$string['autoenablenotification_subject'] = 'Default MoodleNet setting changed.';
$string['addingaresource'] = 'Adding content from MoodleNet';
$string['aria:enterprofile'] = "Enter your MoodleNet profile ID";
@@ -38,7 +38,7 @@ $string['defaultmoodlenet_desc'] = 'The URL of the MoodleNet instance available
$string['defaultmoodlenetname'] = "MoodleNet instance name";
$string['defaultmoodlenetnamevalue'] = 'MoodleNet Central';
$string['defaultmoodlenetname_desc'] = 'The name of the MoodleNet instance available via the activity chooser.';
$string['enablemoodlenet'] = 'Enable MoodleNet integration';
$string['enablemoodlenet'] = 'Enable MoodleNet integration (inbound)';
$string['enablemoodlenet_desc'] = 'If enabled, a user with the capability to create and manage activities can browse MoodleNet via the activity chooser and import MoodleNet resources into their course. In addition, a user with the capability to restore backups can select a backup file on MoodleNet and restore it into Moodle.';
$string['errorduringdownload'] = 'An error occurred while downloading the file: {$a}';
$string['forminfo'] = 'Your MoodleNet profile ID will be automatically saved in your profile on this site.';
@@ -55,7 +55,7 @@ $string['missinginvalidpostdata'] = 'The resource information from MoodleNet is
If this happens repeatedly, please contact the site administrator.';
$string['mnetprofile'] = 'MoodleNet profile';
$string['mnetprofiledesc'] = '<p>Enter your MoodleNet profile details here to be redirected to your profile while visiting MoodleNet.</p>';
$string['moodlenetsettings'] = 'MoodleNet settings';
$string['moodlenetsettings'] = 'MoodleNet inbound settings';
$string['moodlenetnotenabled'] = 'The MoodleNet integration must be enabled in Site administration / MoodleNet before resource imports can be processed.';
$string['notification'] = 'You are about to import the content "{$a->name} ({$a->type})" into your site. Select the course in which it should be added, or <a href="{$a->cancellink}">cancel</a>.';
$string['removedmnetprofilenotification'] = 'Due to recent changes on the MoodleNet platform, any users who previously saved their MoodleNet profile ID on the site will need to enter a MoodleNet profile ID in the new format in order to authenticate on the MoodleNet platform.';
+3 -1
View File
@@ -35,7 +35,9 @@ if ($hassiteconfig) {
// Create a MoodleNet category.
if (get_config('tool_moodlenet', 'enablemoodlenet')) {
$ADMIN->add('root', new admin_category('moodlenet', get_string('pluginname', 'tool_moodlenet')));
if (!$ADMIN->locate('moodlenet')) {
$ADMIN->add('root', new admin_category('moodlenet', get_string('pluginname', 'tool_moodlenet')));
}
// Our settings page.
$settings = new admin_settingpage('tool_moodlenet', get_string('moodlenetsettings', 'tool_moodlenet'));
$ADMIN->add('moodlenet', $settings);
+1 -1
View File
@@ -196,7 +196,7 @@ class external_service_functions_form extends moodleform {
//we add the descriptions to the functions
foreach ($functions as $functionid => $functionname) {
//retrieve full function information (including the description)
$function = external_api::external_function_info($functionname);
$function = \core_external\external_api::external_function_info($functionname);
if (empty($function->deprecated)) {
$functions[$functionid] = $function->name . ':' . $function->description;
} else {
+19 -4
View File
@@ -228,11 +228,26 @@ class backpack_api2p1 {
$msg['status'] = \core\output\notification::NOTIFY_SUCCESS;
$msg['message'] = get_string('addedtobackpack', 'badges');
} else {
$statuserror = $response->status->error;
if (is_array($statuserror)) {
if ($response) {
// Although the specification defines that status error is a string, some providers, like Badgr, are wrongly
// returning an array. It has been reported, but adding this extra check doesn't hurt, just in case.
$statuserror = implode($statuserror);
// returning an array. It has been reported, but adding these extra checks doesn't hurt, just in case.
if (
property_exists($response, 'status') &&
is_object($response->status) &&
property_exists($response->status, 'error')
) {
$statuserror = $response->status->error;
if (is_array($statuserror)) {
$statuserror = implode($statuserror);
}
} else if (property_exists($response, 'error')) {
$statuserror = $response->error;
if (property_exists($response, 'message')) {
$statuserror .= '. Message: ' . $response->message;
}
}
} else {
$statuserror = 'Empty response';
}
$data = [
'badgename' => $data['assertion']['badge']['name'],
@@ -17,7 +17,7 @@ Feature: Add the glossary random block when main feature is enabled
Scenario: The glossary random block cannot be added when glossary module is disabled
Given I navigate to "Plugins > Activity modules > Manage activities" in site administration
And I click on "Disable the Glossary plugin" "icon" in the "Glossary" "table_row"
And I click on "Disable Glossary" "icon" in the "Glossary" "table_row"
And I am on "Course 1" course homepage with editing mode on
When I click on "Add a block" "link"
Then I should not see "Random glossary entry"
@@ -15,7 +15,7 @@ Feature: Add the glossary random block when main feature is disabled
And I add the "Random glossary entry" block to the default region with:
| Title | Random glossary entry |
When I navigate to "Plugins > Activity modules > Manage activities" in site administration
And I click on "Disable the Glossary plugin" "icon" in the "Glossary" "table_row"
And I click on "Disable Glossary" "icon" in the "Glossary" "table_row"
And I am on "Course 1" course homepage with editing mode on
Then I should see "Random glossary entry"
@@ -29,7 +29,7 @@ Feature: Add the glossary random block when main feature is disabled
And I click on "Cancel" "button" in the "Delete block?" "dialogue"
And I should see "Random glossary entry"
When I navigate to "Plugins > Activity modules > Manage activities" in site administration
And I click on "Disable the Glossary plugin" "icon" in the "Glossary" "table_row"
And I click on "Disable Glossary" "icon" in the "Glossary" "table_row"
And I am on "Course 1" course homepage with editing mode on
And I open the "Random glossary entry" blocks action menu
And I click on "Delete Random glossary entry block" "link" in the "Random glossary entry" "block"
@@ -94,7 +94,7 @@ $string['zero_request_title'] = 'Request your first course';
$string['zero_request_intro'] = 'Need help getting started? Check out the <a href="{$a->dochref}" title="{$a->doctitle}" target="{$a->doctarget}">Moodle documentation</a> or take your first steps with our <a href="{$a->quickhref}" title="{$a->quicktitle}" target="{$a->quicktarget}">Quickstart guide</a>.';
$string['zero_nocourses_title'] = 'Create your first course';
$string['zero_nocourses_intro'] = 'Need help getting started? Check out the <a href="{$a->dochref}" title="{$a->doctitle}" target="{$a->doctarget}">Moodle documentation</a> or take your first steps with our Quickstart guide.';
$string['zero_createcourses_intro'] = 'Once you enrol in a course, it will appear here. To view all courses on this site, go to Manage courses.';
$string['zero_createcourses_intro'] = 'Once you enrol in a course, it will appear here.';
$string['zero_nomanagecourses_intro'] = 'Once you enrol in a course, it will appear here.';
// Deprecated since Moodle 4.0.
@@ -47,7 +47,7 @@ Feature: Zero state on my overview block
| shortname | C1 |
When I am on the "My courses" page logged in as "manager"
Then I should see "You're not enrolled in any course"
Then I should see "To view all courses on this site, go to Manage courses."
Then I should see "Once you enrol in a course, it will appear here."
And "Manage courses" "button" should exist
And "Create course" "button" should exist
And I click on "Create course" "button"
@@ -7,7 +7,7 @@ Feature: Adding and configuring YouTube block
Background:
Given I log in as "admin"
And I navigate to "Plugins > Blocks > Manage blocks" in site administration
And I click on "Enable the YouTube plugin" "icon" in the "YouTube" "table_row"
And I click on "Enable YouTube" "icon" in the "YouTube" "table_row"
@javascript
Scenario: Category options are not available (except default) in the block settings if the YouTube API key is not set.
+1 -1
View File
@@ -40,7 +40,7 @@ $string['indentation_help'] = 'Allow teachers, and other users with the manage a
$string['section0name'] = 'General';
$string['sectionavailability_title'] = 'Week availability';
$string['sectiondelete_title'] = 'Delete week?';
$string['sectionmove_title'] = 'Move section';
$string['sectionmove_title'] = 'Move week';
$string['sectionname'] = 'Week';
$string['sectionsavailability'] = 'Weeks availability';
$string['sectionsavailability_title'] = 'Weeks availability';
+2 -2
View File
@@ -1764,8 +1764,8 @@ class grade_structure {
if ($menuitems) {
$menu = new action_menu($menuitems);
$icon = $OUTPUT->pix_icon('i/dropdown', get_string('actions'));
$extraclasses = 'btn btn-icon icon-size-2 bg-secondary d-flex align-items-center justify-content-center';
$icon = $OUTPUT->pix_icon('i/moremenu', get_string('actions'));
$extraclasses = 'btn btn-link btn-icon icon-size-3 d-flex align-items-center justify-content-center';
$menu->set_menu_trigger($icon, $extraclasses);
$menu->set_menu_left();
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+18 -6
View File
@@ -31,6 +31,7 @@ import {renderForPromise, replaceNodeContents} from 'core/templates';
const selectors = {
component: '.user-search',
courseid: '[data-region="courseid"]',
resetPageButton: '[data-action="resetpage"]',
};
const component = document.querySelector(selectors.component);
const courseID = component.querySelector(selectors.courseid).dataset.courseid;
@@ -81,10 +82,10 @@ export default class UserSearch extends GradebookSearchClass {
*/
async renderDropdown() {
const {html, js} = await renderForPromise('gradereport_grader/search/resultset', {
users: this.getMatchedResults(),
users: this.getMatchedResults().slice(0, 5),
hasusers: this.getMatchedResults().length > 0,
total: this.getDatasetSize(),
found: this.getMatchedResults().length,
matches: this.getMatchedResults().length,
showing: this.getMatchedResults().slice(0, 5).length,
searchterm: this.getSearchTerm(),
selectall: this.selectAllResultsLink(),
});
@@ -135,6 +136,7 @@ export default class UserSearch extends GradebookSearchClass {
this.getPreppedSearchTerm(),
`<span class="font-weight-bold">${this.getSearchTerm()}</span>`
);
user.matchingField = `${user.matchingField} (${user.email})`;
user.link = this.selectOneLink(user.id);
break;
}
@@ -153,6 +155,9 @@ export default class UserSearch extends GradebookSearchClass {
if (e.target === this.getHTMLElements().currentViewAll && e.button === 0) {
window.location = this.selectAllResultsLink();
}
if (e.target.closest(selectors.resetPageButton)) {
window.location = e.target.closest(selectors.resetPageButton).href;
}
}
/**
@@ -180,11 +185,18 @@ export default class UserSearch extends GradebookSearchClass {
}
}
if (document.activeElement === this.getHTMLElements().clearSearchButton) {
this.closeSearch();
this.closeSearch(true);
break;
}
if (e.target.closest(selectors.resetPageButton)) {
window.location = e.target.closest(selectors.resetPageButton).href;
break;
}
if (e.target.closest('.dropdown-item')) {
e.preventDefault();
window.location = e.target.closest('.dropdown-item').href;
break;
}
e.preventDefault();
window.location = e.target.closest('.dropdown-item').href;
break;
}
}
@@ -222,14 +222,18 @@ export default class {
/**
* When called, close the dropdown and reset the input field attributes.
*
* @param {Boolean} clear Conditionality clear the input box.
*/
closeSearch() {
closeSearch(clear = false) {
this.toggleDropdown();
// Hide the "clear" search button search bar.
this.clearSearchButton.classList.add('d-none');
// Clear the entered search query in the search bar and hide the search results container.
this.setSearchTerms('');
this.searchInput.value = "";
if (clear) {
// Clear the entered search query in the search bar and hide the search results container.
this.setSearchTerms('');
this.searchInput.value = "";
}
}
/**
@@ -379,7 +383,7 @@ export default class {
}
// The "clear search" button is triggered.
if (e.target.closest(this.selectors.clearSearch) && e.button === 0) {
this.closeSearch();
this.closeSearch(true);
this.searchInput.focus({preventScroll: true});
}
// User may have accidentally clicked off the dropdown and wants to reopen it.
@@ -418,7 +422,7 @@ export default class {
case 'Tab':
// If the current focus is on clear search, then check if viewall exists then around tab to it.
if (e.target.closest(this.selectors.clearSearch)) {
if (this.currentViewAll) {
if (this.currentViewAll && !e.shiftKey) {
e.preventDefault();
this.currentViewAll.focus({preventScroll: true});
} else {
@@ -101,7 +101,7 @@ class get_users_in_report extends external_api {
$userpicture->size = 0; // Size f2.
$user->profileimageurlsmall = $userpicture->get_url($PAGE)->out(false);
return $user;
}, $report->load_users());
}, $report->load_users(true));
sort($users);
return [
@@ -92,9 +92,11 @@ class action_bar extends \core_grades\output\action_bar {
$data['initialselector'] = $initialselector->export_for_template($output);
$data['groupselector'] = $gradesrenderer->group_selector($course);
$resetlink = new moodle_url('/grade/report/grader/index.php', ['id' => $courseid]);
$searchinput = $OUTPUT->render_from_template('gradereport_grader/search/searchinput', [
'currentvalue' => $this->usersearch,
'courseid' => $courseid,
'resetlink' => $resetlink->out(false),
]);
$searchdropdown = new gradebook_dropdown(
true,
@@ -23,6 +23,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['clearsearch'] = 'Clear searched users';
$string['collapsedcolumns'] = 'Collapsed columns <span class="badge badge-pill badge-primary ml-1" data-collapse="count">{$a}</span>';
$string['eventgradereportviewed'] = 'Grader report viewed';
$string['grader:manage'] = 'Manage the grader report';
@@ -46,12 +47,11 @@ $string['privacy:metadata:preference:grade_report_studentsperpage'] = 'The numbe
$string['privacy:request:preference:grade_report_grader_collapsed_categories'] = 'You have some gradebook categories collapsed in the "{$a->name}" course';
$string['reopencolumn'] = 'Reopen {$a} column';
$string['summarygrader'] = 'A table with the names of students in the first column, with assessable activities grouped by course and category across the top.';
$string['showingxofy'] = 'Showing {$a->found} of {$a->total}';
$string['useractivitygrade'] = '{$a} grade';
$string['overriddengrade'] = 'Overridden grade';
$string['advancedgrading'] = 'View {$a} results';
$string['cellactions'] = 'Cell actions';
$string['viewallresults'] = 'View all results for "{$a}"';
$string['viewallresults'] = 'View all results ({$a})';
$string['viewresultsuser'] = 'View results for {$a}';
// Deprecated since Moodle 4.2.
+5 -2
View File
@@ -389,8 +389,10 @@ class grade_report_grader extends grade_report {
/**
* pulls out the userids of the users to be display, and sorts them
*
* @param bool $allusers If we are getting the users within the report, we want them all irrespective of paging.
*/
public function load_users() {
public function load_users(bool $allusers = false) {
global $CFG, $DB;
if (!empty($this->users)) {
@@ -465,7 +467,8 @@ class grade_report_grader extends grade_report {
$this->groupwheresql
ORDER BY $sort";
// We never work with unlimited result. Limit the number of records by MAX_STUDENTS_PER_PAGE if no other limit is specified.
$studentsperpage = $this->get_students_per_page() ?: static::MAX_STUDENTS_PER_PAGE;
$studentsperpage = ($this->get_students_per_page() && !$allusers) ?
$this->get_students_per_page() : static::MAX_STUDENTS_PER_PAGE;
$this->users = $DB->get_records_sql($sql, $params, $studentsperpage * $this->page, $studentsperpage);
if (empty($this->users)) {
+2 -1
View File
@@ -234,7 +234,8 @@
pointer-events: initial;
z-index: 1;
}
.path-grade-report-grader .usersearchwidget button {
.path-grade-report-grader .usersearchwidget button,
.path-grade-report-grader .usersearchwidget a {
pointer-events: initial;
}
.path-grade-report-grader .usersearchdropdown {
@@ -45,10 +45,10 @@
{{/profileimageurl}}
</div>
<div class="d-block pr-3 w-75">
<span class="d-flex w-100 p-0 font-weight-bold">
<span class="d-block w-100 p-0 text-truncate font-weight-bold">
{{fullname}}
</span>
<span class="d-flex w-100 pull-left text-truncate small" aria-hidden="true">
<span class="d-block w-100 pull-left text-truncate small" aria-hidden="true">
{{{matchingField}}}
</span>
<span class="sr-only" aria-label="{{#str}}usermatchedon, core_grades{{/str}}">{{matchingFieldName}}</span>
@@ -44,8 +44,7 @@
"link": "http://foo.bar/grade/report/grader/index.php?id=42&userid=3"
}
],
"found": 2,
"total": 20,
"matches": 20,
"selectall": "https://foo.bar/grade/report/grader/index.php?id=2&searchvalue=abe",
"searchterm": "Foo",
"hasusers": true
@@ -53,17 +52,16 @@
}}
<div class="d-flex flex-column mh-100 h-100">
{{#hasusers}}
<span class="small d-block px-4 my-2">{{#str}}showingxofy, gradereport_grader, {"found": "{{found}}", "total": "{{total}}"}{{/str}}</span>
<ul class="searchresultitemscontainer d-flex flex-column mw-100 position-relative py-2 list-group h-100 mh-100 mx-0" role="listbox" data-region="search-result-items-container" tabindex="-1">
{{#users}}
{{>gradereport_grader/search/resultitem}}
{{/users}}
<li class="w-100 result-row p-1 border-top bottom-0 position-sticky" role="none" id="result-row-{{id}}">
<a role="option" class="dropdown-item d-flex small p-3" id="select-all" href="{{{selectall}}}" tabindex="-1">
{{#str}}viewallresults, gradereport_grader, {{matches}}{{/str}}
</a>
</li>
</ul>
<div class="unsearchablecontentcontainer p-1">
<a role="button" id="select-all" href="{{{selectall}}}" class="small d-block p-4" tabindex="-1">
{{#str}}viewallresults, gradereport_grader, {{searchterm}}{{/str}}
</a>
</div>
{{/hasusers}}
{{^hasusers}}
<span class="small d-block px-4 my-4">{{#str}} noresultsfor, core_grades, {{searchterm}}{{/str}}</span>
@@ -23,7 +23,8 @@
Example context (json):
{
"currentvalue": "bar",
"courseid": 2
"courseid": 2,
"resetlink": "grade/report/grader/index.php?id=2"
}
}}
<span class="d-none" data-region="courseid" data-courseid="{{courseid}}" aria-hidden="true"></span>
@@ -34,6 +35,9 @@
{{/str}}{{/label}}
{{$value}}{{{currentvalue}}}{{/value}}
{{/ core/search_input_auto }}
<a class="ml-2 btn btn-link border-0" href="{{resetlink}}" data-action="resetpage" role="link" aria-label="{{#str}}clearsearch, gradereport_grader{{/str}}">
{{#str}}clear{{/str}}
</a>
{{/currentvalue}}
{{^currentvalue}}
{{< core/search_input_auto }}
@@ -49,7 +49,7 @@ Feature: Within the grader report, test that we can search for users
| -1- |
| Teacher 1 |
When I set the field "Search users" to "Turtle"
And I wait until "View all results for \"Turtle\"" "button" exists
And I wait until "View all results (1)" "option_role" exists
And "Turtle Manatee" "list_item" should exist in the ".user-search" "css_element"
And "User Example" "list_item" should not exist in the ".user-search" "css_element"
And I click on "Turtle Manatee" "list_item"
@@ -66,14 +66,14 @@ Feature: Within the grader report, test that we can search for users
| User Test |
| Dummy User |
And I set the field "Search users" to "Turt"
And I wait until "View all results for \"Turt\"" "button" exists
And I wait until "View all results (1)" "option_role" exists
And I click on "Clear search input" "button" in the ".user-search" "css_element"
And "View all results for \"Turt\"" "button" should not be visible
And "View all results (1)" "option_role" should not be visible
Scenario: A teacher can search the grader report to find specified users
# Case: Standard search.
Given I set the field "Search users" to "Dummy"
And I wait until "View all results for \"Dummy\"" "button" exists
And I wait until "View all results (1)" "option_role" exists
And I click on "Dummy User" "option_role"
And I wait until the page is ready
And the following should exist in the "user-grades" table:
@@ -104,11 +104,15 @@ Feature: Within the grader report, test that we can search for users
# Case: Multiple users found and select only one result.
Then I set the field "Search users" to "User"
And I wait until "View all results for \"User\"" "button" exists
And I wait until "View all results (3)" "option_role" exists
And "Dummy User" "list_item" should exist in the ".user-search" "css_element"
And "User Example" "list_item" should exist in the ".user-search" "css_element"
And "User Test" "list_item" should exist in the ".user-search" "css_element"
And "Turtle Manatee" "list_item" should not exist in the ".user-search" "css_element"
# Check if the matched field names (by lines) includes some identifiable info to help differentiate similar users.
And "User (student2@example.com)" "list_item" should exist in the ".user-search" "css_element"
And "User (student3@example.com)" "list_item" should exist in the ".user-search" "css_element"
And "User (student4@example.com)" "list_item" should exist in the ".user-search" "css_element"
And I click on "Dummy User" "list_item"
And I wait until the page is ready
And the following should exist in the "user-grades" table:
@@ -125,12 +129,11 @@ Feature: Within the grader report, test that we can search for users
# Business case: When searching with multiple partial matches, show the matches in the dropdown + a "View all results for (Bob)"
# Business case cont. When pressing enter with multiple partial matches, behave like when you select the "View all results for (Bob)"
# Case: Multiple users found and select all partial matches.
# TODO: Need to look at maybe adding the users email or something in the case multiple matches exist?
And I set the field "Search users" to "User"
And I wait until "View all results for \"User\"" "button" exists
And I wait until "View all results (3)" "option_role" exists
# Dont need to check if all users are in the dropdown, we checked that earlier in this test.
And "View all results for \"User\"" "button" should exist
And I click on "View all results for \"User\"" "button"
And "View all results (3)" "option_role" should exist
And I click on "View all results (3)" "option_role"
And I wait until the page is ready
And the following should exist in the "user-grades" table:
| -1- |
@@ -142,15 +145,24 @@ Feature: Within the grader report, test that we can search for users
| Teacher 1 |
| Student 1 |
| Turtle Manatee |
And I click on "Clear" "link" in the ".user-search" "css_element"
And I wait until the page is ready
And the following should exist in the "user-grades" table:
| -1- |
| Turtle Manatee |
| Student 1 |
| User Example |
| User Test |
| Dummy User |
Scenario: A teacher can quickly tell that a search is active on the current table
Given I set the field "Search users" to "Turtle"
And I wait until "View all results for \"Turtle\"" "button" exists
And I wait until "View all results (1)" "option_role" exists
And I click on "Turtle Manatee" "list_item"
And I wait until the page is ready
# The search input remains in the field on reload this is in keeping with other search implementations.
When the field "Search users" matches value "Turtle"
And "View all results for \"Turtle\"" "link" should not exist
And "View all results (1)" "link" should not exist
# Test if we can then further retain the turtle result set and further filter from there.
Then I set the field "Search users" to "Turtle plagiarism"
And "Turtle Manatee" "list_item" should not exist
@@ -158,10 +170,10 @@ Feature: Within the grader report, test that we can search for users
Scenario: A teacher can search for values besides the users' name
Given I set the field "Search users" to "student5@example.com"
And I wait until "View all results for \"student5@example.com\"" "button" exists
And I wait until "View all results (1)" "option_role" exists
And "Turtle Manatee" "list_item" should exist
And I set the field "Search users" to "@example.com"
And I wait until "View all results for \"@example.com\"" "button" exists
And I wait until "View all results (5)" "option_role" exists
# Note: All learners match this email & showing emails is current default.
And "Dummy User" "list_item" should exist in the ".user-search" "css_element"
And "User Example" "list_item" should exist in the ".user-search" "css_element"
@@ -238,11 +250,11 @@ Feature: Within the grader report, test that we can search for users
And I press the down key
And the focused element is "Student 1" "option_role"
And I press the end key
And the focused element is "Turtle Manatee" "option_role"
And the focused element is "View all results (5)" "option_role"
And I press the home key
And the focused element is "Student 1" "option_role"
And I press the up key
And the focused element is "Turtle Manatee" "option_role"
And the focused element is "View all results (5)" "option_role"
And I press the down key
And the focused element is "Student 1" "option_role"
And I press the escape key
@@ -250,14 +262,20 @@ Feature: Within the grader report, test that we can search for users
Then I set the field "Search users" to "Goodmeme"
And I press the down key
And the focused element is "Search users" "field"
And I navigate to "View > Grader report" in the course gradebook
And I set the field "Search users" to "ABC"
And I wait until "Turtle Manatee" "option_role" exists
And I press the down key
And the focused element is "Student 1" "option_role"
# Lets check the tabbing order.
And I set the field "Search users" to "ABC"
And I wait "3" seconds
And I wait until "View all results for \"ABC\"" "button" exists
And I wait until "View all results (5)" "option_role" exists
And I press the tab key
And the focused element is "Clear search input" "button"
And I press the tab key
And the focused element is "View all results for \"ABC\"" "button"
And the focused element is "View all results (5)" "option_role"
And I press the tab key
And the focused element is ".search-widget[data-searchtype='group'] [data-toggle='dropdown']" "css_element"
# Ensure we can interact with the input & clear search options with the keyboard.
@@ -307,3 +325,20 @@ Feature: Within the grader report, test that we can search for users
# Begin the search checking if we are adhering the filters.
When I set the field "Search users" to "Turtle"
Then "Turtle Manatee" "list_item" should not exist in the ".user-search" "css_element"
Scenario: As a teacher I can dynamically find users whilst ignoring pagination
Given "42" "users" exist with the following data:
| username | students[count] |
| firstname | Student |
| lastname | s[count] |
| email | students[count]@example.com |
And "42" "course enrolments" exist with the following data:
| user | students[count] |
| course | C1 |
| role |student |
And I reload the page
And the field "perpage" matches value "20"
When I set the field "Search users" to "42"
# One of the users' phone numbers also matches.
And I wait until "View all results (2)" "link" exists
Then "Student s42" "list_item" should exist in the ".user-search" "css_element"
@@ -408,7 +408,8 @@ class grade extends tablelike implements selectable_items, filterable_items {
$menuitems[] = new \action_menu_link_secondary($url, null, $title);
$menu = new \action_menu($menuitems);
$icon = $OUTPUT->pix_icon('i/moremenu', get_string('actions'));
$menu->set_menu_trigger($icon);
$extraclasses = 'btn btn-link btn-icon icon-size-3 d-flex align-items-center justify-content-center';
$menu->set_menu_trigger($icon, $extraclasses);
$menu->set_menu_left();
return $OUTPUT->render($menu);
@@ -261,7 +261,8 @@ class user extends tablelike implements selectable_items {
$menuitems[] = new \action_menu_link_secondary($url, null, $title);
$menu = new \action_menu($menuitems);
$icon = $OUTPUT->pix_icon('i/moremenu', get_string('actions'));
$menu->set_menu_trigger($icon);
$extraclasses = 'btn btn-link btn-icon icon-size-3 d-flex align-items-center justify-content-center';
$menu->set_menu_trigger($icon, $extraclasses);
$menu->set_menu_left();
return $OUTPUT->render($menu);
+1 -1
View File
@@ -121,7 +121,7 @@ if ($itemtype == 'user') {
true, null, null, $report->screen->item, $actionbar);
} else {
print_grade_page_head($course->id, 'report', 'singleview', $reportname, false, $button,
true, null, null, null, $actionbar, false);
true, null, null, null, $actionbar);
}
if ($data = data_submitted()) {
@@ -73,36 +73,35 @@
<div class="container-fluid tertiary-navigation full-width-bottom-border">
<div class="row">
{{#generalnavselector}}
<div class="navitem">
<div class="navitem order-1">
{{>core/tertiary_navigation_selector}}
</div>
<div class="navitem-divider"></div>
<div class="navitem-divider d-none d-sm-flex order-1"></div>
{{/generalnavselector}}
{{#groupselector}}
<div class="navitem">
<div class="navitem order-2">
{{{.}}}
</div>
<div class="navitem-divider"></div>
<div class="navitem-divider d-none d-sm-flex order-2"></div>
{{/groupselector}}
{{#itemselector}}
<div class="navitem">
<div class="navitem order-3">
{{{.}}}
</div>
<div class="navitem-divider"></div>
<div class="navitem-divider d-none d-sm-flex order-3"></div>
{{/itemselector}}
{{#pagetoggler}}
<div class="d-flex ml-auto">
<div class="d-flex row ml-0 ml-sm-auto order-4 px-0">
{{#bulkactions}}
<div class="navitem-divider"></div>
<div class="navitem">{{{bulkactions}}}</div>
{{#js}}
require(['gradereport_singleview/bulkactions'], function(bulkactions) {
bulkactions.init();
});
{{/js}}
<div class="d-flex navitem ml-0 ml-sm-auto mr-3">{{{bulkactions}}}</div>
{{#js}}
require(['gradereport_singleview/bulkactions'], function(bulkactions) {
bulkactions.init();
});
{{/js}}
<div class="navitem-divider d-none d-sm-flex"></div>
{{/bulkactions}}
<div class="navitem-divider"></div>
<div class="navitem">
<div class="d-flex navitem ml-0 mr-auto mr-sm-0 ml-sm-auto">
{{>gradereport_singleview/page_toggler}}
</div>
</div>
@@ -48,7 +48,7 @@
>
<div class="align-items-center d-flex">
{{#selectedoption}}
<div class="d-block pr-3">
<div class="d-block pr-3 text-truncate">
<span class="d-block small">
{{#str}} selectagrade, gradereport_singleview {{/str}}
</span>
@@ -28,7 +28,7 @@
"gradeselectactive": "true"
}
}}
<div class="page-toggler d-flex">
<div class="page-toggler d-block d-sm-flex text-nowrap">
{{#displaylabel}}
<p class="my-auto mr-3 text-uppercase">{{#str}}viewby, gradereport_singleview{{/str}}</p>
{{/displaylabel}}
+6 -4
View File
@@ -840,15 +840,17 @@ class user extends grade_report {
}
$this->gradeitemsdata[] = $gradeitemdata;
}
$parent = $gradeobject->load_parent_category();
if ($gradeobject->is_category_item()) {
$parent = $parent->load_parent_category();
}
// We collect the aggregation hints whether they are hidden or not.
if ($this->showcontributiontocoursetotal) {
$hint['grademax'] = $gradegrade->grade_item->grademax;
$hint['grademin'] = $gradegrade->grade_item->grademin;
$hint['grade'] = $gradeval;
$parent = $gradeobject->load_parent_category();
if ($gradeobject->is_category_item()) {
$parent = $parent->load_parent_category();
}
$hint['parent'] = $parent->load_grade_item()->id;
$this->aggregationhints[$gradegrade->itemid] = $hint;
}
+6
View File
@@ -145,6 +145,7 @@ class gradereport_user_renderer extends plugin_renderer_base {
$navigationdata = [];
$users = [];
while ($userdata = $gui->next_user()) {
$users[$userdata->user->id] = $userdata->user;
}
@@ -153,6 +154,11 @@ class gradereport_user_renderer extends plugin_renderer_base {
$arraykeys = array_keys($users);
$keynumber = array_search($userid, $arraykeys);
// Without a valid user or users list, there's nothing to render.
if ($keynumber === false) {
return '';
}
// Determine directionality so that icons can be modified to suit language.
$previousarrow = right_to_left() ? 'right' : 'left';
$nextarrow = right_to_left() ? 'left' : 'right';
+1 -1
View File
@@ -66,7 +66,7 @@
<span class="userinitials"></span>
{{/image}}
</div>
<div class="selected-option-info d-block pr-3">
<div class="selected-option-info d-block pr-3 text-truncate">
<span class="selected-option-text p-0 font-weight-bold">
{{text}}
</span>
@@ -178,7 +178,7 @@ class group extends base {
// Visibility column.
$columns[] = (new column(
'visibility',
new lang_string('visibility', 'core_group'),
new lang_string('visibilityshort', 'core_group'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
@@ -205,7 +205,7 @@ class group extends base {
// Participation column.
$columns[] = (new column(
'participation',
new lang_string('participation', 'core_group'),
new lang_string('participationshort', 'core_group'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
@@ -296,7 +296,7 @@ class group extends base {
$filters[] = (new filter(
select::class,
'visibility',
new lang_string('visibility', 'core_group'),
new lang_string('visibilityshort', 'core_group'),
$this->get_entity_name(),
"{$groupsalias}.visibility"
))
@@ -312,7 +312,7 @@ class group extends base {
$filters[] = (new filter(
boolean_select::class,
'participation',
new lang_string('participation', 'core_group'),
new lang_string('participationshort', 'core_group'),
$this->get_entity_name(),
"{$groupsalias}.participation"
))
+16 -16
View File
@@ -15,13 +15,13 @@ Feature: Backup and restore a course containing groups
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "groups" exist:
| name | course | idnumber | visibility | participation |
| Visible to all/Participation | C1 | VP | 0 | 1 |
| Visible to members/Participation | C1 | MP | 1 | 1 |
| See own membership | C1 | O | 2 | 0 |
| Not visible | C1 | N | 3 | 0 |
| Visible to all/Non-Participation | C1 | VN | 0 | 0 |
| Visible to members/Non-Participation | C1 | MN | 1 | 0 |
| name | course | idnumber | visibility | participation |
| Visible to everyone/Participation | C1 | VP | 0 | 1 |
| Only visible to members/Participation | C1 | MP | 1 | 1 |
| Only see own membership | C1 | O | 2 | 0 |
| Not visible | C1 | N | 3 | 0 |
| Visible to everyone/Non-Participation | C1 | VN | 0 | 0 |
| Only visible to members/Non-Participation | C1 | MN | 1 | 0 |
And I log in as "admin"
And I backup "Course 1" course using this options:
| Confirmation | Filename | test_backup.mbz |
@@ -35,14 +35,14 @@ Feature: Backup and restore a course containing groups
And I press "Edit group settings"
Then the following fields match these values:
| Group ID number | <idnumber> |
| Group visibility | <visibility> |
| Allow activity participation | <participation> |
| Group membership visibility | <visibility> |
| Show group in dropdown menu for activities in group mode | <participation> |
Examples:
| group | idnumber | visibility | participation |
| Visible to all/Participation | VP | 0 | 1 |
| Visible to members/Participation | MP | 1 | 1 |
| See own membership | O | 2 | 0 |
| Not visible | N | 3 | 0 |
| Visible to all/Non-Participation | VN | 0 | 0 |
| Visible to members/Non-Participation | MN | 1 | 0 |
| group | idnumber | visibility | participation |
| Visible to everyone/Participation | VP | 0 | 1 |
| Only visible to members/Participation | MP | 1 | 1 |
| Only see own membership | O | 2 | 0 |
| Not visible | N | 3 | 0 |
| Visible to everyone/Non-Participation | VN | 0 | 0 |
| Only visible to members/Non-Participation | MN | 1 | 0 |
+28 -28
View File
@@ -31,13 +31,13 @@ Feature: Private groups
| student7 | C1 | student |
| student8 | C1 | student |
And the following "groups" exist:
| name | course | idnumber | visibility | participation |
| Visible to all/Participation | C1 | VP | 0 | 1 |
| Visible to members/Participation | C1 | MP | 1 | 1 |
| See own membership | C1 | O | 2 | 0 |
| Not visible | C1 | N | 3 | 0 |
| Visible to all/Non-Participation | C1 | VN | 0 | 0 |
| Visible to members/Non-Participation | C1 | MN | 1 | 0 |
| name | course | idnumber | visibility | participation |
| Visible to everyone/Participation | C1 | VP | 0 | 1 |
| Only visible to members/Participation | C1 | MP | 1 | 1 |
| Only see own membership | C1 | O | 2 | 0 |
| Not visible | C1 | N | 3 | 0 |
| Visible to everyone/Non-Participation | C1 | VN | 0 | 0 |
| Only visible to members/Non-Participation | C1 | MN | 1 | 0 |
And the following "group members" exist:
| user | group |
| student1 | VP |
@@ -53,62 +53,62 @@ Feature: Private groups
| student7 | O |
| student8 | N |
Scenario: Participants in "Visible to all" groups see their membership and other members:
Scenario: Participants in "Visible to everyone" groups see their membership and other members:
Given I log in as "student1"
And I am on "Course 1" course homepage
When I follow "Participants"
Then the following should exist in the "participants" table:
| First name / Surname | Groups |
| Student 1 | Visible to all/Non-Participation, Visible to all/Participation |
| Student 1 | Visible to everyone/Non-Participation, Visible to everyone/Participation |
| Student 2 | No groups |
| Student 3 | No groups |
| Student 4 | No groups |
| Student 5 | Visible to all/Non-Participation, Visible to all/Participation |
| Student 5 | Visible to everyone/Non-Participation, Visible to everyone/Participation |
| Student 6 | No groups |
| Student 7 | No groups |
| Student 8 | No groups |
Scenario: Participants in "Visible to members" groups see their membership and other members, plus "Visible to all"
Scenario: Participants in "Only visible to members" groups see their membership and other members, plus "Visible to everyone"
Given I log in as "student2"
And I am on "Course 1" course homepage
When I follow "Participants"
Then the following should exist in the "participants" table:
| First name / Surname | Groups |
| Student 1 | Visible to all/Non-Participation, Visible to all/Participation |
| Student 2 | Visible to members/Non-Participation, Visible to members/Participation |
| Student 1 | Visible to everyone/Non-Participation, Visible to everyone/Participation |
| Student 2 | Only visible to members/Non-Participation, Only visible to members/Participation |
| Student 3 | No groups |
| Student 4 | No groups |
| Student 5 | Visible to all/Non-Participation, Visible to all/Participation |
| Student 6 | Visible to members/Non-Participation, Visible to members/Participation |
| Student 5 | Visible to everyone/Non-Participation, Visible to everyone/Participation |
| Student 6 | Only visible to members/Non-Participation, Only visible to members/Participation |
| Student 7 | No groups |
| Student 8 | No groups |
Scenario: Participants in "See own membership" groups see their membership but not other members, plus "Visible to all"
Scenario: Participants in "Only see own membership" groups see their membership but not other members, plus "Visible to everyone"
Given I log in as "student3"
And I am on "Course 1" course homepage
When I follow "Participants"
Then the following should exist in the "participants" table:
| First name / Surname | Groups |
| Student 1 | Visible to all/Non-Participation, Visible to all/Participation |
| Student 1 | Visible to everyone/Non-Participation, Visible to everyone/Participation |
| Student 2 | No groups |
| Student 3 | See own membership |
| Student 3 | Only see own membership |
| Student 4 | No groups |
| Student 5 | Visible to all/Non-Participation, Visible to all/Participation |
| Student 5 | Visible to everyone/Non-Participation, Visible to everyone/Participation |
| Student 6 | No groups |
| Student 7 | No groups |
| Student 8 | No groups |
Scenario: Participants in "Not visible" groups do not see that group, do see "Visible to all"
Scenario: Participants in "Not visible" groups do not see that group, do see "Visible to everyone"
Given I log in as "student4"
And I am on "Course 1" course homepage
When I follow "Participants"
Then the following should exist in the "participants" table:
| First name / Surname | Groups |
| Student 1 | Visible to all/Non-Participation, Visible to all/Participation |
| Student 1 | Visible to everyone/Non-Participation, Visible to everyone/Participation |
| Student 2 | No groups |
| Student 3 | No groups |
| Student 4 | No groups |
| Student 5 | Visible to all/Non-Participation, Visible to all/Participation |
| Student 5 | Visible to everyone/Non-Participation, Visible to everyone/Participation |
| Student 6 | No groups |
| Student 7 | No groups |
| Student 8 | No groups |
@@ -119,11 +119,11 @@ Feature: Private groups
When I follow "Participants"
Then the following should exist in the "participants" table:
| First name / Surname | Groups |
| Student 1 | Visible to all/Non-Participation, Visible to all/Participation |
| Student 2 | Visible to members/Non-Participation, Visible to members/Participation |
| Student 3 | See own membership |
| Student 1 | Visible to everyone/Non-Participation, Visible to everyone/Participation |
| Student 2 | Only visible to members/Non-Participation, Only visible to members/Participation |
| Student 3 | Only see own membership |
| Student 4 | Not visible |
| Student 5 | Visible to all/Non-Participation, Visible to all/Participation |
| Student 6 | Visible to members/Non-Participation, Visible to members/Participation |
| Student 7 | See own membership |
| Student 5 | Visible to everyone/Non-Participation, Visible to everyone/Participation |
| Student 6 | Only visible to members/Non-Participation, Only visible to members/Participation |
| Student 7 | Only see own membership |
| Student 8 | Not visible |
+4 -4
View File
@@ -148,14 +148,14 @@ Feature: Automatic updating of groups and groupings
Given I set the field "groups" to "Group (with ID)"
And I press "Edit group settings"
And "visibility" "select" should exist
And the field "Group visibility" matches value "Visible to all"
And the field "Group membership visibility" matches value "Visible to everyone"
And the "participation" "checkbox" should be enabled
And the field "Allow activity participation" matches value "1"
And the field "Show group in dropdown menu for activities in group mode" matches value "1"
When the following "group members" exist:
| user | group |
| teacher1 | An ID |
And I reload the page
Then "visibility" "select" should not exist
And "Visible to all" "text" should exist
And "Visible to everyone" "text" should exist
And the "participation" "checkbox" should be disabled
And the field "Allow activity participation" matches value "1"
And the field "Show group in dropdown menu for activities in group mode" matches value "1"
@@ -180,7 +180,7 @@ class groups_test extends core_reportbuilder_testcase {
$this->assertEquals('G101', $groupidnumber);
$this->assertEquals(format_text($group->description), $groupdescription);
$this->assertEquals('S', $groupenrolmentkey);
$this->assertEquals('Visible to all', $groupvisibility);
$this->assertEquals('Visible to everyone', $groupvisibility);
$this->assertEquals('Yes', $groupparticipation);
$this->assertEmpty($grouppicture);
$this->assertNotEmpty($grouptimecreated);
@@ -231,7 +231,7 @@ class groups_test extends core_reportbuilder_testcase {
], false],
'Filter group visibility' => ['group:visibility', [
'group:visibility_operator' => select::EQUAL_TO,
'group:visibility_value' => 0, // Visible to all.
'group:visibility_value' => 0, // Visible to everyone.
], true],
'Filter group visibility (no match)' => ['group:visibility', [
'group:visibility_operator' => select::EQUAL_TO,
+10 -10
View File
@@ -14,30 +14,30 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Testing the H5P helper.
*
* @package core_h5p
* @category test
* @copyright 2019 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types = 1);
namespace core_h5p;
use advanced_testcase;
use core_h5p\local\library\autoloader;
/**
* Test class covering the H5P helper.
*
* @package core_h5p
* @category test
* @copyright 2019 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \core_h5p\helper
*/
class helper_test extends \advanced_testcase {
/**
* Register the H5P autoloader
*/
protected function setUp(): void {
autoloader::register();
}
/**
* Test the behaviour of get_display_options().
*
+1 -1
View File
@@ -73,7 +73,7 @@ $string['pathssubdataroot'] = '<p>Um diretório em que Moodle armazenará todo o
<p>Este diretório deve ser legível e gravável pelo usuário do servidor web (geralmente "www-data \',\' nobody \', ou\' apache\'). </p>
<p>Não deve ser diretamente acessível através da web. </p>
<p>Se o diretório não existir atualmente, o processo de instalação tentará criá-lo. </p>';
$string['pathssubdirroot'] = '<p>Caminho completo do diretório para instalação do Moddle.</p>';
$string['pathssubdirroot'] = '<p>Caminho completo do diretório para instalação do Moodle.</p>';
$string['pathssubwwwroot'] = '<p>O endereço completo em que Moodle pode ser acessado ou seja, o endereço que os usuários vão digitar na barra de endereços do seu navegador para acessar Moodle. </p>
<p>Não é possível acessar Moodle usando múltiplos endereços. Se o seu site é acessível através de múltiplos endereços, em seguida, escolher o mais fácil e configurar um redirecionamento permanente para cada um dos outros endereços. </p>
<p>Se o seu site é acessível tanto a partir da Internet e, a partir de uma rede interna (às vezes chamado de Intranet), em seguida, use o endereço público aqui. </p>
+17 -15
View File
@@ -95,13 +95,13 @@ $string['blockediplist'] = 'Blocked IP List';
$string['blockinstances'] = 'Instances';
$string['blockmultiple'] = 'Multiple';
$string['blockprotect'] = 'Protect instances';
$string['blockprotectblock'] = 'Protect instances of the {$a} block';
$string['blockprotected'] = 'The {$a} block is now protected';
$string['blockprotectblock'] = 'Protect instances of {$a}';
$string['blockprotected'] = '{$a} block instances are protected.';
$string['blockprotect_help'] = 'If you lock a particular type of block, then no-one will be able to add or delete instances. (You can, of course, unlock again if you need to edit instances.)
This is intended to protect blocks like the navigation and settings which are very hard to get back if accidentally deleted.';
$string['blockunprotectblock'] = 'Unprotect instances of the {$a} block';
$string['blockunprotected'] = 'The {$a} block is no longer protected';
$string['blockunprotectblock'] = 'Unprotect instances of {$a}';
$string['blockunprotected'] = '{$a} block instances are unprotected.';
$string['blocksettings'] = 'Manage blocks';
$string['bloglevel'] = 'Blog visibility';
$string['bookmarkadded'] = 'Bookmark added.';
@@ -145,7 +145,7 @@ $string['cliupgradecompletenomaintenanceupgrade'] = 'To purge remaining caches a
php admin/cli/purge_caches.php --filter
php admin/cli/purge_caches.php --other
It is recommended to perform these purges in isolation, with a gap between commands, to reduce load spikes on the web server.';
You should perform these purges in isolation, with a gap between commands, to reduce load spikes on the web server.';
$string['cliupgradedefault'] = 'New setting: {$a}';
$string['cliupgradedefaultheading'] = 'Setting new default values';
$string['cliupgradedefaultverbose'] = 'New setting: {$a->name}, Default value: {$a->defaultsetting}';
@@ -366,7 +366,7 @@ $string['configsectionsecurity'] = 'Security';
$string['configsectionstats'] = 'Statistics';
$string['configsectionuser'] = 'User';
$string['configsecureforms'] = 'Moodle can use an additional level of security when accepting data from web forms. If this is enabled, then the browser\'s HTTP_REFERER variable is checked against the current form address. In a very few cases this can cause problems if the user is using a firewall (eg Zonealarm) configured to strip HTTP_REFERER from their web traffic. Symptoms are getting \'stuck\' on a form. If your users are having problems with the login page (for example) you might want to disable this setting, although it might leave your site more open to brute-force password attacks. If in doubt, leave this set to \'Yes\'.';
$string['configservicespage'] = 'Users will be sent to this URL when clicking on \'Services and support\'. If the field is left blank the user will be redirect to the \'Find your Moodle partner\' official page.';
$string['configservicespage'] = 'Enter the URL of a services and support page or leave empty to link to Moodle services on moodle.com.';
$string['configsessioncookie'] = 'This setting customises the name of the cookie used for Moodle sessions. This is optional, and only useful to avoid cookies being confused when there is more than one copy of Moodle running within the same web site.';
$string['configsessioncookiedomain'] = 'This allows you to change the domain that the Moodle cookies are available from. This is useful for Moodle customisations (e.g. authentication or enrolment plugins) that need to share Moodle session information with a web application on another subdomain. <strong>WARNING: it is strongly recommended to leave this setting at the default (empty) - an incorrect value will prevent all logins to the site.</strong>';
$string['configsessioncookiepath'] = 'If you need to change where browsers send the Moodle cookies, you can change this setting to specify a subdirectory of your web site. Otherwise the default \'/\' should be fine.';
@@ -402,7 +402,7 @@ $string['configstripalltitletags'] = 'Uncheck this setting to allow HTML tags in
$string['configsupportavailability'] = 'Determines who has access to contact site support from the footer.';
$string['configsupportemail'] = 'If SMTP is configured on this site and a support page is not set, this email address will receive messages submitted through the support form. If sending fails, the email address will be displayed to logged-in users.';
$string['configsupportname'] = 'The name of the person or other entity providing support via the support form or support page.';
$string['configsupportpage'] = 'A link to this page will be provided for users to contact the site support. If the field is left blank then a link to a support form will be provided instead.';
$string['configsupportpage'] = 'Enter the URL of a support page or leave empty to link to a contact form.';
$string['configtempdatafoldercleanup'] = 'Remove temporary data files from the data folder that are older than the selected time.';
$string['configthemedesignermode'] = 'Normally all theme images and style sheets are cached in browsers and on the server for a very long time, for performance. If you are designing themes or developing code then you probably want to turn this mode on so that you are not served cached versions. Warning: this will make your site slower for all users! Alternatively, you can also reset the theme caches manually from the Theme selection page.';
$string['configthemelist'] = 'Leave this blank to allow any valid theme to be used. If you want to shorten the theme menu, you can specify a comma-separated list of names here (Don\'t use spaces!).
@@ -458,7 +458,7 @@ $string['cron_enabled'] = 'Enable cron';
$string['cron_enabled_desc'] = 'Cron should normally be enabled, however this setting allows it to be disabled temporarily, for example before a server restart. If disabled, the system is prevented from starting new background tasks. Note that the cron should not be disabled for a long time, as this will prevent important functionality from working.';
$string['cron_help'] = 'The cron.php script runs a number of tasks at different scheduled intervals, such as sending forum post notification emails. The script should be run regularly - ideally every minute.';
$string['cron_keepalive'] = 'Keep alive';
$string['cron_keepalive_desc'] = 'The amount of time to keep polling for additional tasks. This setting is useful for ensuring that cron is always running.<br><br>If you use dedicated task runners then you should set this value to an 0, otherwise we recommend setting this to around a value similar to your adhoc task concurrency limit. Longer values should be avoided and the maximum value is 15 minutes.';
$string['cron_keepalive_desc'] = 'The length of time to keep polling for additional tasks. This setting is for ensuring that cron is always running. If you use dedicated task runners, set it to 0. Otherwise, set it to a value similar to the adhoc task concurrency limit. Avoid longer times. The maximum time is 15 minutes.';
$string['cron_link'] = 'admin/cron';
$string['cronclionly'] = 'Cron execution via command line only';
$string['cronerrorclionly'] = 'Sorry, internet access to this page has been disabled by the administrator.';
@@ -528,7 +528,7 @@ $string['devicedetectregexexpression'] = 'Regular expression';
$string['devicedetectregexvalue'] = 'Return value';
$string['devicetype'] = 'Device type';
$string['disabled'] = 'Disabled';
$string['disableplugin'] = 'Disable the {$a} plugin';
$string['disableplugin'] = 'Disable {$a}';
$string['disableuserimages'] = 'Disable user profile images';
$string['displayerrorswarning'] = 'Enabling the PHP setting <em>display_errors</em> is not recommended on production sites because some error messages may reveal sensitive information about your server.';
$string['displayloginfailures'] = 'Display login failures';
@@ -603,10 +603,12 @@ $string['enablegravatar_help'] = 'When enabled Moodle will attempt to fetch a us
$string['enablemobilewebservice'] = 'Enable web services for mobile devices';
$string['enablepdfexportfont'] = 'Enable PDF fonts';
$string['enablepdfexportfont_desc'] = 'If your site has courses in different languages which need other fonts in generated PDF files, you can provide the option to set the font in the course settings. You need to specify available fonts in $CFG->pdfexportfont in config.php.';
$string['enableplugin'] = 'Enable the {$a} plugin';
$string['enableplugin'] = 'Enable {$a}';
$string['enablerecordcache'] = 'Enable record cache';
$string['enablerssfeeds'] = 'Enable RSS feeds';
$string['enablesearchareas'] = 'Enable search areas';
$string['enablesharingtomoodlenet'] = 'Enable sharing to MoodleNet (outbound)';
$string['enablesharingtomoodlenet_desc'] = 'Enable users to share course content to a configured MoodleNet instance if they have the relevant capability.';
$string['enablestats'] = 'Enable statistics';
$string['enabletrusttext'] = 'Enable trusted content';
$string['enableuserfeedback'] = 'Enable feedback about this software';
@@ -1014,8 +1016,8 @@ $string['pleaserefreshregistration'] = 'Your site is registered. Registration la
$string['pleaserefreshregistrationunknown'] = 'Your site has been registered but the registration date is unknown. Please update your registration using the \'Update registration\' button or ensure that the \'Site registration\' scheduled task is enabled so your registration is automatically updated.';
$string['pleaserefreshregistrationnewdata'] = 'Registration information has been changed. Please confirm it using the \'Update registration\' button.';
$string['plugin'] = 'Plugin';
$string['plugin_disabled'] = 'The {$a} plugin has been disabled';
$string['plugin_enabled'] = 'The {$a} plugin has been enabled';
$string['plugin_disabled'] = '{$a} disabled.';
$string['plugin_enabled'] = '{$a} enabled.';
$string['plugins'] = 'Plugins';
$string['pluginscheck'] = 'Plugin dependencies check';
$string['pluginscheckfailed'] = 'Dependencies check failed for {$a->pluginslist}';
@@ -1250,7 +1252,7 @@ $string['selecttheme'] = 'Select theme for {$a} device';
$string['server'] = 'Server';
$string['serverchecks'] = 'Server checks';
$string['serverlimit'] = 'Server limit';
$string['servicespage'] = 'Link to \'Services and support\'';
$string['servicespage'] = 'Services and support link';
$string['sessionautostartwarning'] = '<p>Serious configuration error detected, please notify server administrator.</p><p> To operate properly, Moodle requires that administrator changes PHP settings.</p><p><code>session.auto_start</code> must be set to <code>off</code>.</p><p>This setting is controlled by editing <code>php.ini</code>, Apache/IIS <br />configuration or <code>.htaccess</code> file on the server.</p>';
$string['sessioncookie'] = 'Cookie prefix';
$string['sessioncookiedomain'] = 'Cookie domain';
@@ -1332,7 +1334,7 @@ $string['supportemail'] = 'Support email';
$string['supportemailsubject'] = 'Site support request - {$a}';
$string['supportavailability'] = 'Support availability';
$string['supportname'] = 'Support name';
$string['supportpage'] = 'Support page';
$string['supportpage'] = 'Contact site support link';
$string['suspenduser'] = 'Suspend user account';
$string['switchlang'] = 'Switch lang directory';
$string['systempaths'] = 'System paths';
@@ -1583,7 +1585,7 @@ $string['xmlrpcwebserviceenabled'] = 'It has been detected that the XML-RPC Web
$string['yuicomboloading'] = 'YUI combo loading';
$string['ziprequired'] = 'The Zip PHP extension is now required by Moodle, info-ZIP binaries or PclZip library are not used anymore.';
$string['manageqbanks'] = 'Manage question bank plugins';
$string['modassignmentinuse'] = 'It has been detected that your site is still using the Assignment 2.2 plugin. You may resolve this before upgrading by 1) Backing up your Assignment 2.2 activities and restoring them as new Assignment activities; or 2) Deleting the data from the assignment tables in the database.';
$string['modassignmentinuse'] = 'Your site is still using the Assignment 2.2 plugin. Before upgrading you must 1) backup any Assignment 2.2 activities that you want to keep and restore them as Assignment activities, and 2) delete all Assignment 2.2 data from the database.';
$string['caching'] = 'Caching';
+1 -1
View File
@@ -42,7 +42,7 @@ $string['defaultweight_help'] = 'The default weight allows you to choose roughly
$string['deletecheck'] = 'Delete {$a} block?';
$string['deletecheck_modal'] = 'Delete block?';
$string['deleteblock'] = 'Delete {$a} block';
$string['deleteblockcheck'] = 'Are you sure that you want to delete this block titled {$a}?';
$string['deleteblockcheck'] = 'This will delete the block {$a}.';
$string['deleteblockinprogress'] = 'Block {$a} removal in progress...';
$string['deleteblockwarning'] = '<p>You are about to delete a block that appears elsewhere.</p><p>Original block location: {$a->location}<br />Display on page types: {$a->pagetype}</p><p>Are you sure you want to continue?</p>';
$string['hideblock'] = 'Hide {$a} block';
+5 -5
View File
@@ -33,15 +33,15 @@ $string['bulkcancel'] = 'Close bulk edit';
$string['bulkselection'] = '{$a} selected';
$string['bulkselection_plural'] = '{$a} selected';
$string['cmavailability'] = 'Activity availability';
$string['cmdelete_info'] = 'This will delete "{$a->name}" and any user data it contains';
$string['cmdelete_info'] = 'This will delete {$a->name} and any user data it contains.';
$string['cmdelete_title'] = 'Delete activity?';
$string['cmsdelete'] = 'Delete activities';
$string['cmsdelete_info'] = 'This will delete {$a->count} activities and any user data they contain';
$string['cmsdelete_info'] = 'This will delete {$a->count} activities and any user data they contain.';
$string['cmsdelete_title'] = 'Delete selected activities?';
$string['cmsduplicate'] = 'Duplicate activities';
$string['cmsmove'] = 'Move activities';
$string['cmmove_title'] = 'Move activity';
$string['cmmove_info'] = 'Move "{$a}" activity after';
$string['cmmove_info'] = 'Move {$a} after';
$string['cmsmove_title'] = 'Move selected activities';
$string['cmsmove_info'] = 'Move {$a} activities after';
$string['courseindex'] = 'Course index';
@@ -49,12 +49,12 @@ $string['nobulkaction'] = 'No bulk actions available';
$string['preference:coursesectionspreferences'] = 'Section user preferences for course {$a}';
$string['privacy:metadata:preference:coursesectionspreferences'] = 'Section user preferences like collapsed and expanded.';
$string['sectionavailability_title'] = 'Section availability';
$string['sectiondelete_info'] = 'This will delete "{$a->name}" and all the activities it contains.';
$string['sectiondelete_info'] = 'This will delete {$a->name} and all the activities it contains.';
$string['sectiondelete_title'] = 'Delete section?';
$string['sectionsavailability'] = 'Sections availability';
$string['sectionsavailability_title'] = 'Sections availability';
$string['sectionsdelete'] = 'Delete sections';
$string['sectionmove_info'] = 'Move "{$a}" after';
$string['sectionmove_info'] = 'Move {$a} after';
$string['sectionmove_title'] = 'Move section';
$string['sectionsdelete_info'] = 'This will delete {$a->count} sections and all the activities they contain.';
$string['sectionsdelete_title'] = 'Delete selected sections?';
+1 -1
View File
@@ -84,7 +84,7 @@ $string['errorthresholdlow'] = 'Notification threshold must be at least 1 day.';
$string['errorwithbulkoperation'] = 'There was an error while processing your bulk enrolment change.';
$string['eventuserenrolmentcreated'] = 'User enrolled in course';
$string['eventuserenrolmentdeleted'] = 'User unenrolled from course';
$string['eventuserenrolmentupdated'] = 'User unenrolment updated';
$string['eventuserenrolmentupdated'] = 'User enrolment updated';
$string['expirynotify'] = 'Notify before enrolment expires';
$string['expirynotify_help'] = 'This setting determines whether enrolment expiry notification messages are sent.';
$string['expirynotifyall'] = 'Enroller and enrolled user';
+3 -1
View File
@@ -240,7 +240,7 @@ $string['duplicateparaminsql'] = 'ERROR: duplicate parameter name in query';
$string['duplicaterolename'] = 'There is already a role with this name!';
$string['duplicateroleshortname'] = 'There is already a role with this short name!';
$string['duplicateusername'] = 'Duplicate username - skipping record';
$string['editedpagenotfound'] = 'Could not determine the current page. Please try refreshing the page and repeating the operation.';
$string['editedpagenotfound'] = 'The system couldn\'t determine the page you are on. Please refresh the page and try again.';
$string['emailfail'] = 'Emailing failed';
$string['encryption_encryptfailed'] = 'Encryption failed';
$string['encryption_decryptfailed'] = 'Decryption failed';
@@ -426,6 +426,8 @@ $string['moduledisable'] = 'This module ({$a}) has been disabled for this partic
$string['moduledoesnotexist'] = 'This module does not exist';
$string['moduleinstancedoesnotexist'] = 'The instance of this module does not exist';
$string['modulemissingcode'] = 'Module {$a} is missing the code needed to perform this function';
$string['moodlenet:invalidshareformat'] = 'Invalid MoodleNet share format';
$string['moodlenet:usernotconfigured'] = 'You do not have permission to share content to MoodleNet, or your account is incorrectly configured.';
$string['movecatcontentstoroot'] = 'Moving the category content to root is not allowed. You must move the contents to an existing category!';
$string['movecatcontentstoselected'] = 'Some category content cannot be moved into the selected category.';
$string['movecategorynotpossible'] = 'You cannot move category \'{$a}\' into the selected category.';
+15 -25
View File
@@ -174,14 +174,9 @@ $string['nummembers'] = 'Members per group';
$string['mygroups'] = 'My groups';
$string['othergroups'] = 'Other groups';
$string['overview'] = 'Overview';
$string['participation'] = 'Allow activity participation';
$string['participation_help'] = 'If enabled, members can select this group when participating in an activity using Separate Groups
or Visible Groups mode.
This setting is only applicable if the Group visibility is set to "Visible to all" or "Visible to members". Participation is
disabled otherwise.
This setting cannot be edited once a group has members.';
$string['participation'] = 'Show group in dropdown menu for activities in group mode';
$string['participation_help'] = 'Should group members be able to select this group for activities in separate or visible groups mode? (Only applicable if group membership is visible to everyone or only visible to members.)';
$string['participationshort'] = 'Participation';
$string['potentialmembers'] = 'Potential members: {$a}';
$string['potentialmembs'] = 'Potential members';
$string['printerfriendly'] = 'Printer-friendly display';
@@ -206,25 +201,20 @@ $string['toomanygroups'] = 'Insufficient users to populate this number of groups
$string['usercount'] = 'User count';
$string['usercounttotal'] = 'User count ({$a})';
$string['usergroupmembership'] = 'Selected user\'s membership:';
$string['visibility'] = 'Group visibility';
$string['visibility_help'] = 'Controls the visibility of membership to this group.
$string['visibility'] = 'Group membership visibility';
$string['visibility_help'] = '* Visible to everyone - all course participants can view who is in the group
* Only visible to members - course participants not in the group cant view the group or its members
* Only see own membership - a user can see they are in the group but cant view other group members
* Hidden - only teachers can view the group and its members
If "Visible to all" is set, all users can see when a user is a member of this group (default).
Users with the view hidden groups capability can always view group membership.
If "Visible to members" is set, only members of this group can see when another user is a member.
If "See own membership" is set, users can see that they are in this group, but cannot see that other users are members of the group.
If "Membership is hidden" is set, users cannot see that they or anyone else are members of the group.
Users with moodle/course:viewhiddengroups will always be able to see group membership.
This setting cannot be edited once a group has members.
';
$string['visibilityall'] = 'Visible to all';
$string['visibilitymembers'] = 'Visible to members';
$string['visibilityown'] = 'See own membership';
$string['visibilitynone'] = 'Membership is hidden';
Note that you can\'t change this setting if the group has members.';
$string['visibilityshort'] = 'Visibility';
$string['visibilityall'] = 'Visible to everyone';
$string['visibilitymembers'] = 'Only visible to members';
$string['visibilityown'] = 'Only see own membership';
$string['visibilitynone'] = 'Hidden';
$string['memberofgroup'] = 'Group member of: {$a}';
// Deprecated since Moodle 3.11.
+22
View File
@@ -1365,6 +1365,27 @@ $string['moodledocslink'] = 'Help and documentation';
$string['moodleversion'] = 'Moodle version';
$string['moodlerelease'] = 'Moodle release';
$string['moodleservicesandsupport'] = 'Services and support';
$string['moodlenet:cannotconnecttoserver'] = 'Cannot connect to MoodleNet server';
$string['moodlenet:configoauthservice'] = 'Select a MoodleNet OAuth 2 service to enable sharing to that MoodleNet site. If the service doesn\'t exist yet, you will need to <a href="{$a}">create</a> it.';
$string['moodlenet:eventresourceexported'] = 'Resource exported';
$string['moodlenet:gotomoodlenet'] = 'Go to MoodleNet drafts';
$string['moodlenet:issuerisnotauthorized'] = 'MoodleNet issuer is not authorized';
$string['moodlenet:issuerisnotenabled'] = 'MoodleNet issuer is not enabled';
$string['moodlenet:issuerisnotset'] = 'MoodleNet issuer is not set at site administration';
$string['moodlenet:outboundsettings'] = 'MoodleNet outbound settings';
$string['moodlenet:sharenotice'] = 'You are sharing this to MoodleNet as a {$a}';
$string['moodlenet:sharefailtitle'] = 'Something went wrong';
$string['moodlenet:sharefailtext'] = 'There was an error sharing your content to MoodleNet.<br>Please try again later.';
$string['moodlenet:sharefailtextwithsitesupport'] = 'There was an error sharing your content to MoodleNet.<br>Please try again later or <a href="{$a}">contact site support</a>.';
$string['moodlenet:sharefilesizelimitexceeded'] = 'The size of the resource being shared ({$a->filesize} bytes) exceeds the limit of {$a->filesizelimit} bytes.';
$string['moodlenet:sharesuccesstitle'] = 'Saved to MoodleNet drafts';
$string['moodlenet:sharesuccesstext'] = "Almost done! Visit your drafts in MoodleNet to finish sharing your content.";
$string['moodlenet:sharetomoodlenet'] = 'Share to MoodleNet';
$string['moodlenet:sharetyperesource'] = 'resource';
$string['moodlenet:sharingstatus'] = 'Sharing to MoodleNet';
$string['moodlenet:sharinglargefile'] = "Large files can take some time.";
$string['moodlenet:sharingto'] = 'Sharing to: ';
$string['moodlenet:packagingandsending'] = 'Packaging your file and sending to MoodleNet...';
$string['more'] = 'more';
$string['morehelp'] = 'More help';
$string['morehelpaboutmodule'] = 'More help about the {$a} activity';
@@ -1970,6 +1991,7 @@ $string['setmode'] = 'Set mode';
$string['setpassword'] = 'Set password';
$string['setpasswordinstructions'] = 'Please enter your new password below, then save changes.';
$string['settings'] = 'Settings';
$string['share'] = 'Share';
$string['shortname'] = 'Short name'; /* @deprecated MDL-34652 - Use shortnamecourse or shortnameuser or some own context specific string. */
$string['shortnamecollisionwarning'] = '[*] = This shortname is already in use by a course and will need to be changed upon approval';
$string['shortnamecourse'] = 'Course short name';
+3 -3
View File
@@ -460,10 +460,10 @@ $string['settingsformultipletries'] = 'Multiple tries';
$string['showhidden'] = 'Also show old questions';
$string['showmarkandmax'] = 'Show mark and max';
$string['showmaxmarkonly'] = 'Show max mark only';
$string['showquestiontext'] = 'Show question text in the question list';
$string['showquestiontext_full'] = 'Full display';
$string['showquestiontext'] = 'Show question text in the question list?';
$string['showquestiontext_full'] = 'Yes, with images, media, etc.';
$string['showquestiontext_off'] = 'No';
$string['showquestiontext_plain'] = 'Text only';
$string['showquestiontext_plain'] = 'Yes, text only';
$string['shown'] = 'Shown';
$string['shownumpartscorrect'] = 'Show the number of correct responses';
$string['shownumpartscorrectwhenfinished'] = 'Show the number of correct responses once the question has finished';
+1
View File
@@ -309,6 +309,7 @@ $string['manager'] = 'Manager';
$string['managerdescription'] = 'Managers can access courses and modify them, but usually do not participate in them.';
$string['manageroles'] = 'Manage roles';
$string['maybeassignedin'] = 'Context types where this role may be assigned';
$string['moodlenet:shareactivity'] = 'Share activities to MoodleNet';
$string['morethan'] = 'More than {$a}';
$string['multipleroles'] = 'Multiple roles';
$string['my:manageblocks'] = 'Manage Dashboard page blocks';
+1 -1
View File
@@ -27,7 +27,7 @@ $string['privacy:metadata:component'] = 'The component name in frankenstyle';
$string['privacy:metadata:itemid'] = 'The item ID of the state';
$string['privacy:metadata:registration'] = 'The xAPI registration UUID';
$string['privacy:metadata:statedata'] = 'JSON object with the state data';
$string['privacy:metadata:stateid'] = 'The xAPI state id.';
$string['privacy:metadata:stateid'] = 'The xAPI state ID';
$string['privacy:metadata:timecreated'] = 'The time when the state element was created';
$string['privacy:metadata:timemodified'] = 'The last time state was updated';
$string['privacy:metadata:userid'] = 'The ID of the user who belongs the state ';
+11
View File
@@ -0,0 +1,11 @@
define("core/moodlenet/oauth2callback",["exports","core/prefetch","core/notification","core/str"],(function(_exports,_prefetch,_notification,_str){var obj;
/**
* A module to handle the OAuth2 callback for MoodleNet.
*
* @module core/moodlenet/oauth2callback
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.2
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_prefetch=(obj=_prefetch)&&obj.__esModule?obj:{default:obj};_exports.init=(error,errorDescription)=>{_prefetch.default.prefetchStrings("moodle",["moodlenet:sharefailtitle","error"]),((error,errorDescription)=>{window.opener?(window.opener.moodleNetAuthorize(error,errorDescription),setTimeout((()=>{window.close()}),300)):(0,_notification.alert)((0,_str.get_string)("error","moodle"),(0,_str.get_string)("moodlenet:sharefailtitle","moodle"))})(error,errorDescription)}}));
//# sourceMappingURL=oauth2callback.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"oauth2callback.min.js","sources":["../../src/moodlenet/oauth2callback.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * A module to handle the OAuth2 callback for MoodleNet.\n *\n * @module core/moodlenet/oauth2callback\n * @copyright 2023 Huong Nguyen <huongnv13@gmail.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.2\n */\n\nimport Prefetch from \"core/prefetch\";\nimport {alert} from 'core/notification';\nimport {get_string as getString} from 'core/str';\n\n/**\n * Handle the OAuth2 callback for MoodleNet.\n *\n * @param {String} error Error\n * @param {String} errorDescription Error description\n */\nconst handleCallback = (error, errorDescription) => {\n if (window.opener) {\n // Call the MoodleNet Authorization again in the opener window.\n window.opener.moodleNetAuthorize(error, errorDescription);\n // Close the authorization popup.\n // We need to use setTimeout here because the Behat 'I press \"x\" and switch to main window' step expects the popup to still\n // be visible after clicking the button. Otherwise, it will throw a webdriver error.\n setTimeout(() => {\n // Close the authorization popup.\n window.close();\n }, 300);\n } else {\n alert(getString('error', 'moodle'), getString('moodlenet:sharefailtitle', 'moodle'));\n }\n};\n\n/**\n * Initialize.\n *\n * @param {String} error Error\n * @param {String} errorDescription Error description\n */\nexport const init = (error, errorDescription) => {\n Prefetch.prefetchStrings('moodle', ['moodlenet:sharefailtitle', 'error']);\n handleCallback(error, errorDescription);\n};\n"],"names":["error","errorDescription","prefetchStrings","window","opener","moodleNetAuthorize","setTimeout","close","handleCallback"],"mappings":";;;;;;;;4JAwDoB,CAACA,MAAOC,sCACfC,gBAAgB,SAAU,CAAC,2BAA4B,UAvB7C,EAACF,MAAOC,oBACvBE,OAAOC,QAEPD,OAAOC,OAAOC,mBAAmBL,MAAOC,kBAIxCK,YAAW,KAEPH,OAAOI,UACR,+BAEG,mBAAU,QAAS,WAAW,mBAAU,2BAA4B,YAY9EC,CAAeR,MAAOC"}
+3
View File
@@ -0,0 +1,3 @@
define("core/moodlenet/send_activity_modal",["exports","core/modal","core/modal_registry"],(function(_exports,_modal,_modal_registry){var _class;function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=_interopRequireDefault(_modal),_modal_registry=_interopRequireDefault(_modal_registry);const SendActivityModal=(_defineProperty(_class=class extends _modal.default{registerEventListeners(){super.registerEventListeners(),this.registerCloseOnSave(),this.registerCloseOnCancel()}},"TYPE","core/moodlenet/send_activity_modal"),_defineProperty(_class,"TEMPLATE","core/moodlenet/send_activity_modal_base"),_class);_modal_registry.default.register(SendActivityModal.TYPE,SendActivityModal,SendActivityModal.TEMPLATE);var _default=SendActivityModal;return _exports.default=_default,_exports.default}));
//# sourceMappingURL=send_activity_modal.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"send_activity_modal.min.js","sources":["../../src/moodlenet/send_activity_modal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Send activity modal for MoodleNet.\n *\n * @module core/moodlenet/send_activity_modal\n * @copyright 2023 Huong Nguyen <huongnv13@gmail.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.2\n */\n\nimport Modal from 'core/modal';\nimport ModalRegistry from 'core/modal_registry';\n\nconst SendActivityModal = class extends Modal {\n static TYPE = 'core/moodlenet/send_activity_modal';\n static TEMPLATE = 'core/moodlenet/send_activity_modal_base';\n\n registerEventListeners() {\n // Call the parent registration.\n super.registerEventListeners();\n\n // Register to close on save/cancel.\n this.registerCloseOnSave();\n this.registerCloseOnCancel();\n }\n};\n\nModalRegistry.register(SendActivityModal.TYPE, SendActivityModal, SendActivityModal.TEMPLATE);\n\nexport default SendActivityModal;\n"],"names":["SendActivityModal","Modal","registerEventListeners","registerCloseOnSave","registerCloseOnCancel","register","TYPE","TEMPLATE"],"mappings":"2jBA2BMA,0CAAoB,cAAcC,eAIpCC,+BAEUA,8BAGDC,2BACAC,iCATK,wEACI,2EAYRC,SAASL,kBAAkBM,KAAMN,kBAAmBA,kBAAkBO,uBAErEP"}
+11
View File
@@ -0,0 +1,11 @@
define("core/moodlenet/send_resource",["exports","core/config","core/modal_factory","core/notification","core/str","core/prefetch","core/templates","core/moodlenet/service","core/moodlenet/send_activity_modal"],(function(_exports,_config,_modal_factory,_notification,_str,_prefetch,Templates,MoodleNetService,_send_activity_modal){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* A module to handle Share operations of the MoodleNet.
*
* @module core/moodlenet/send_resource
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.2
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_config=_interopRequireDefault(_config),_modal_factory=_interopRequireDefault(_modal_factory),_prefetch=_interopRequireDefault(_prefetch),Templates=_interopRequireWildcard(Templates),MoodleNetService=_interopRequireWildcard(MoodleNetService),_send_activity_modal=_interopRequireDefault(_send_activity_modal);let currentModal,siteSupportUrl,issuerId,courseId,cmId,shareFormat;const responseFromMoodleNet=function(status){let resourceUrl=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";const $modal=currentModal.getModal(),modal=$modal[0];modal.querySelector(".modal-header").classList.add("no-border"),currentModal.setBody(Templates.render("core/moodlenet/send_activity_modal_done",{success:status,sitesupporturl:siteSupportUrl})),status&&(currentModal.setFooter(Templates.render("core/moodlenet/send_activity_modal_footer_view",{resourseurl:resourceUrl})),currentModal.showFooter())},handleAuthorization=issuerId=>MoodleNetService.authorizationCheck(issuerId,courseId).then((async data=>data.status?((issuerId,cmId,shareFormat)=>{const modal=currentModal.getModal()[0];modal.querySelector(".modal-header").classList.remove("no-border"),modal.querySelector(".modal-header").classList.add("no-header-text"),currentModal.setBody(Templates.render("core/moodlenet/send_activity_modal_packaging",{})),currentModal.hideFooter(),MoodleNetService.sendActivity(issuerId,cmId,shareFormat).then((async data=>{const status=data.status,resourceUrl=data.resourceurl;return responseFromMoodleNet(status,resourceUrl)})).catch(_notification.exception)})(issuerId,cmId,shareFormat):(window.moodleNetAuthorize=(error,errorDescription)=>{""==error?handleAuthorization(issuerId):"access_denied"!==error&&(0,_notification.alert)("Authorization error","Error: "+error+"<br><br>Error description: "+errorDescription,"Cancel")},window.open(data.loginurl,"moodlenet_auth","location=0,status=0,width=".concat(550,",height=").concat(550,",scrollbars=yes"))))).catch(_notification.exception);_exports.init=()=>{_prefetch.default.prefetchTemplates(["core/moodlenet/send_activity_modal_base","core/moodlenet/send_activity_modal_packaging","core/moodlenet/send_activity_modal_done","core/moodlenet/send_activity_modal_footer_view"]),document.addEventListener("click",(e=>{const shareAction=e.target.closest('[data-action="sendtomoodlenet"]'),sendAction=e.target.closest('.moodlenet-action-buttons [data-action="share"]');if(shareAction){e.preventDefault();const type=shareAction.getAttribute("data-type"),shareType=shareAction.getAttribute("data-sharetype"),cmId=_config.default.contextInstanceId;"activity"==type&&MoodleNetService.getActivityInformation(cmId).then((async data=>data.status?(siteSupportUrl=data.supportpageurl,issuerId=data.issuerid,_modal_factory.default.create({type:_send_activity_modal.default.TYPE,large:!0,templateContext:{activitytype:data.type,activityname:data.name,sharetype:await(0,_str.get_string)("moodlenet:sharetype"+shareType,"moodle"),server:data.server}}).then((modal=>(currentModal=modal,modal.show(),modal))).catch(_notification.exception)):(0,_notification.addNotification)({message:data.warnings[0].message,type:"error"}))).catch(_notification.exception)}sendAction&&(e.preventDefault(),courseId=_config.default.courseId,cmId=_config.default.contextInstanceId,shareFormat=0,handleAuthorization(issuerId))}))}}));
//# sourceMappingURL=send_resource.min.js.map
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
define("core/moodlenet/service",["exports","core/ajax"],(function(_exports,_ajax){var obj;
/**
* A javascript module to handle MoodleNet ajax actions.
*
* @module core/moodlenet/service
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.2
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.sendActivity=_exports.getActivityInformation=_exports.authorizationCheck=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.getActivityInformation=cmId=>{const request={methodname:"core_moodlenet_get_share_info_activity",args:{cmid:cmId}};return _ajax.default.call([request])[0]};_exports.sendActivity=(issuerId,cmId,shareFormat)=>{const request={methodname:"core_moodlenet_send_activity",args:{issuerid:issuerId,cmid:cmId,shareformat:shareFormat}};return _ajax.default.call([request])[0]};_exports.authorizationCheck=(issuerId,courseId)=>{const request={methodname:"core_moodlenet_auth_check",args:{issuerid:issuerId,courseid:courseId}};return _ajax.default.call([request])[0]}}));
//# sourceMappingURL=service.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"service.min.js","sources":["../../src/moodlenet/service.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * A javascript module to handle MoodleNet ajax actions.\n *\n * @module core/moodlenet/service\n * @copyright 2023 Huong Nguyen <huongnv13@gmail.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.2\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Get the activity information by course module id.\n *\n * @param {Integer} cmId The course module id.\n * @return {promise}\n */\nexport const getActivityInformation = (cmId) => {\n const request = {\n methodname: 'core_moodlenet_get_share_info_activity',\n args: {\n cmid: cmId\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Send the activity to Moodlenet.\n *\n * @param {Integer} issuerId The OAuth 2 issuer ID.\n * @param {Integer} cmId The course module ID.\n * @param {Integer} shareFormat The share format.\n * @return {promise}\n */\nexport const sendActivity = (issuerId, cmId, shareFormat) => {\n const request = {\n methodname: 'core_moodlenet_send_activity',\n args: {\n issuerid: issuerId,\n cmid: cmId,\n shareformat: shareFormat,\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Check if the user is already authorized with MoodleNet.\n *\n * @param {Integer} issuerId The OAuth 2 issuer ID.\n * @param {Integer} courseId The course ID.\n * @return {promise}\n */\nexport const authorizationCheck = (issuerId, courseId) => {\n const request = {\n methodname: 'core_moodlenet_auth_check',\n args: {\n issuerid: issuerId,\n courseid: courseId,\n }\n };\n\n return Ajax.call([request])[0];\n};\n"],"names":["cmId","request","methodname","args","cmid","Ajax","call","issuerId","shareFormat","issuerid","shareformat","courseId","courseid"],"mappings":";;;;;;;;0OAgCuCA,aAC7BC,QAAU,CACZC,WAAY,yCACZC,KAAM,CACFC,KAAMJ,cAIPK,cAAKC,KAAK,CAACL,UAAU,0BAWJ,CAACM,SAAUP,KAAMQ,qBACnCP,QAAU,CACZC,WAAY,+BACZC,KAAM,CACFM,SAAUF,SACVH,KAAMJ,KACNU,YAAaF,qBAIdH,cAAKC,KAAK,CAACL,UAAU,gCAUE,CAACM,SAAUI,kBACnCV,QAAU,CACZC,WAAY,4BACZC,KAAM,CACFM,SAAUF,SACVK,SAAUD,kBAIXN,cAAKC,KAAK,CAACL,UAAU"}
+60
View File
@@ -0,0 +1,60 @@
// 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/>.
/**
* A module to handle the OAuth2 callback for MoodleNet.
*
* @module core/moodlenet/oauth2callback
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.2
*/
import Prefetch from "core/prefetch";
import {alert} from 'core/notification';
import {get_string as getString} from 'core/str';
/**
* Handle the OAuth2 callback for MoodleNet.
*
* @param {String} error Error
* @param {String} errorDescription Error description
*/
const handleCallback = (error, errorDescription) => {
if (window.opener) {
// Call the MoodleNet Authorization again in the opener window.
window.opener.moodleNetAuthorize(error, errorDescription);
// Close the authorization popup.
// We need to use setTimeout here because the Behat 'I press "x" and switch to main window' step expects the popup to still
// be visible after clicking the button. Otherwise, it will throw a webdriver error.
setTimeout(() => {
// Close the authorization popup.
window.close();
}, 300);
} else {
alert(getString('error', 'moodle'), getString('moodlenet:sharefailtitle', 'moodle'));
}
};
/**
* Initialize.
*
* @param {String} error Error
* @param {String} errorDescription Error description
*/
export const init = (error, errorDescription) => {
Prefetch.prefetchStrings('moodle', ['moodlenet:sharefailtitle', 'error']);
handleCallback(error, errorDescription);
};
@@ -0,0 +1,44 @@
// 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/>.
/**
* Send activity modal for MoodleNet.
*
* @module core/moodlenet/send_activity_modal
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.2
*/
import Modal from 'core/modal';
import ModalRegistry from 'core/modal_registry';
const SendActivityModal = class extends Modal {
static TYPE = 'core/moodlenet/send_activity_modal';
static TEMPLATE = 'core/moodlenet/send_activity_modal_base';
registerEventListeners() {
// Call the parent registration.
super.registerEventListeners();
// Register to close on save/cancel.
this.registerCloseOnSave();
this.registerCloseOnCancel();
}
};
ModalRegistry.register(SendActivityModal.TYPE, SendActivityModal, SendActivityModal.TEMPLATE);
export default SendActivityModal;
+189
View File
@@ -0,0 +1,189 @@
// 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/>.
/**
* A module to handle Share operations of the MoodleNet.
*
* @module core/moodlenet/send_resource
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.2
*/
import Config from 'core/config';
import ModalFactory from 'core/modal_factory';
import {alert as displayAlert, addNotification, exception as displayException} from 'core/notification';
import {get_string as getString} from 'core/str';
import Prefetch from "core/prefetch";
import * as Templates from 'core/templates';
import * as MoodleNetService from 'core/moodlenet/service';
import SendActivityModal from 'core/moodlenet/send_activity_modal';
const TYPE_ACTIVITY = "activity";
let currentModal;
let siteSupportUrl;
let issuerId;
let courseId;
let cmId;
let shareFormat;
/**
* Handle send to MoodleNet.
*
* @param {int} issuerId The OAuth 2 issuer ID.
* @param {int} cmId The course module ID.
* @param {int} shareFormat The share format.
*/
const sendToMoodleNet = (issuerId, cmId, shareFormat) => {
const $modal = currentModal.getModal();
const modal = $modal[0];
modal.querySelector('.modal-header').classList.remove('no-border');
modal.querySelector('.modal-header').classList.add('no-header-text');
currentModal.setBody(Templates.render('core/moodlenet/send_activity_modal_packaging', {}));
currentModal.hideFooter();
MoodleNetService.sendActivity(issuerId, cmId, shareFormat).then(async(data) => {
const status = data.status;
const resourceUrl = data.resourceurl;
return responseFromMoodleNet(status, resourceUrl);
}).catch(displayException);
};
/**
* Handle response from MoodleNet.
*
* @param {boolean} status Response status. True if successful.
* @param {String} resourceUrl Resource URL.
*/
const responseFromMoodleNet = (status, resourceUrl = '') => {
const $modal = currentModal.getModal();
const modal = $modal[0];
modal.querySelector('.modal-header').classList.add('no-border');
currentModal.setBody(Templates.render('core/moodlenet/send_activity_modal_done', {
success: status,
sitesupporturl: siteSupportUrl,
}));
if (status) {
currentModal.setFooter(Templates.render('core/moodlenet/send_activity_modal_footer_view', {
resourseurl: resourceUrl,
}));
currentModal.showFooter();
}
};
/**
* Handle authorization with MoodleNet server.
*
* @param {int} issuerId The OAuth 2 issuer ID.
* @return {promise}
*/
const handleAuthorization = (issuerId) => {
const windowsizewidth = 550;
const windowsizeheight = 550;
// Check if the user is authorized with MoodleNet or not.
return MoodleNetService.authorizationCheck(issuerId, courseId).then(async(data) => {
if (!data.status) {
// Not yet authorized.
// Declare moodleNetAuthorize variable, so we can call it later in the callback.
window.moodleNetAuthorize = (error, errorDescription) => {
// This will be called by the callback after the authorization is successful.
if (error == '') {
handleAuthorization(issuerId);
} else if (error !== 'access_denied') {
displayAlert(
'Authorization error',
'Error: ' + error + '<br><br>Error description: ' + errorDescription,
'Cancel'
);
}
};
// Open the login url of the OAuth 2 issuer for user to login into MoodleNet and authorize.
return window.open(data.loginurl, 'moodlenet_auth',
`location=0,status=0,width=${windowsizewidth},height=${windowsizeheight},scrollbars=yes`);
} else {
// Already authorized.
return sendToMoodleNet(issuerId, cmId, shareFormat);
}
}).catch(displayException);
};
/**
* Register events.
*/
const registerEventListeners = () => {
document.addEventListener('click', e => {
const shareAction = e.target.closest('[data-action="sendtomoodlenet"]');
const sendAction = e.target.closest('.moodlenet-action-buttons [data-action="share"]');
if (shareAction) {
e.preventDefault();
const type = shareAction.getAttribute('data-type');
const shareType = shareAction.getAttribute('data-sharetype');
const cmId = Config.contextInstanceId;
if (type == TYPE_ACTIVITY) {
MoodleNetService.getActivityInformation(cmId).then(async(data) => {
if (data.status) {
siteSupportUrl = data.supportpageurl;
issuerId = data.issuerid;
const modalPromise = ModalFactory.create({
type: SendActivityModal.TYPE,
large: true,
templateContext: {
'activitytype': data.type,
'activityname': data.name,
'sharetype': await getString('moodlenet:sharetype' + shareType, 'moodle'),
'server': data.server,
}
});
return modalPromise.then(modal => {
currentModal = modal;
modal.show();
return modal;
}).catch(displayException);
} else {
return addNotification({
message: data.warnings[0].message,
type: 'error'
});
}
}).catch(displayException);
}
}
if (sendAction) {
e.preventDefault();
courseId = Config.courseId;
cmId = Config.contextInstanceId;
shareFormat = 0;
handleAuthorization(issuerId);
}
});
};
/**
* Initialize.
*/
export const init = () => {
Prefetch.prefetchTemplates([
'core/moodlenet/send_activity_modal_base',
'core/moodlenet/send_activity_modal_packaging',
'core/moodlenet/send_activity_modal_done',
'core/moodlenet/send_activity_modal_footer_view',
]);
registerEventListeners();
};
+82
View File
@@ -0,0 +1,82 @@
// 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/>.
/**
* A javascript module to handle MoodleNet ajax actions.
*
* @module core/moodlenet/service
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.2
*/
import Ajax from 'core/ajax';
/**
* Get the activity information by course module id.
*
* @param {Integer} cmId The course module id.
* @return {promise}
*/
export const getActivityInformation = (cmId) => {
const request = {
methodname: 'core_moodlenet_get_share_info_activity',
args: {
cmid: cmId
}
};
return Ajax.call([request])[0];
};
/**
* Send the activity to Moodlenet.
*
* @param {Integer} issuerId The OAuth 2 issuer ID.
* @param {Integer} cmId The course module ID.
* @param {Integer} shareFormat The share format.
* @return {promise}
*/
export const sendActivity = (issuerId, cmId, shareFormat) => {
const request = {
methodname: 'core_moodlenet_send_activity',
args: {
issuerid: issuerId,
cmid: cmId,
shareformat: shareFormat,
}
};
return Ajax.call([request])[0];
};
/**
* Check if the user is already authorized with MoodleNet.
*
* @param {Integer} issuerId The OAuth 2 issuer ID.
* @param {Integer} courseId The course ID.
* @return {promise}
*/
export const authorizationCheck = (issuerId, courseId) => {
const request = {
methodname: 'core_moodlenet_auth_check',
args: {
issuerid: issuerId,
courseid: courseId,
}
};
return Ajax.call([request])[0];
};
+5
View File
@@ -154,6 +154,11 @@
"allowedlevel2": true,
"allowedspread": true
},
"moodlenet": {
"component": "core",
"allowedlevel2": false,
"allowedspread": false
},
"navigation": {
"component": "core",
"allowedlevel2": true,
+4 -3
View File
@@ -740,9 +740,10 @@ abstract class base implements \IteratorAggregate {
debugging('Number of event data fields must not be changed in event classes', DEBUG_DEVELOPER);
}
$encoded = json_encode($this->data['other']);
// The comparison here is not set to strict as whole float numbers will be converted to integers through JSON encoding /
// decoding and send an unwanted debugging message.
if ($encoded === false or $this->data['other'] != json_decode($encoded, true)) {
// The comparison here is not set to strict. We just need to check if the data is compatible with the JSON encoding
// or not and we don't need to worry about how the data is encoded. Because in some cases, the data can contain
// objects, and objects can be converted to a different format during encoding and decoding.
if ($encoded === false) {
debugging('other event data must be compatible with json encoding', DEBUG_DEVELOPER);
}
if ($this->data['userid'] and !is_number($this->data['userid'])) {
@@ -0,0 +1,72 @@
<?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\event;
/**
* MoodleNet send attempt event.
*
* @package core
* @copyright 2023 Michael Hawkins <michaelh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class moodlenet_resource_exported extends \core\event\base {
/**
* Set basic properties for the event.
*
* @return void
*/
protected function init(): void {
$this->data['crud'] = 'c';
// Used by teachers, but not for direct educational value to their students.
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Fetch the localised general event name.
*
* @return string
*/
public static function get_name(): string {
return get_string('moodlenet:eventresourceexported');
}
/**
* Fetch the non-localised event description.
* This description format is designed to work for both single activity and course sharing.
*
* @return string
*/
public function get_description() {
$outcome = $this->other['success'] ? 'successfully shared' : 'failed to share';
$cmids = implode("', '", $this->other['cmids']);
$description = "The user with id '{$this->userid}' {$outcome} activities to MoodleNet with the " .
"following course module ids, from context with id '{$this->data['contextid']}': '{$cmids}'.";
return rtrim($description, ", '");
}
/**
* Returns relevant URL.
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url($this->other['resourceurl']);
}
}
+147
View File
@@ -0,0 +1,147 @@
<?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\external;
use context_course;
use core\moodlenet\moodlenet_client;
use core\moodlenet\utilities;
use core\oauth2\api;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use moodle_url;
/**
* The external API to check whether a user has authorized for a given MoodleNet OAuth 2 issuer.
*
* @package core
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class moodlenet_auth_check extends external_api {
/**
* Returns description of parameters.
*
* @return external_function_parameters
* @since Moodle 4.2
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'issuerid' => new external_value(PARAM_INT, 'OAuth 2 issuer ID', VALUE_REQUIRED),
'courseid' => new external_value(PARAM_INT, 'Course ID', VALUE_REQUIRED),
]);
}
/**
* External function to check if the user is already authorized with MoodleNet.
*
* @param int $issuerid Issuer Id.
* @param int $courseid The course ID that contains the activity which being shared
* @return array
* @since Moodle 4.2
*/
public static function execute(int $issuerid, int $courseid): array {
global $USER;
[
'issuerid' => $issuerid,
'courseid' => $courseid,
] = self::validate_parameters(self::execute_parameters(), [
'issuerid' => $issuerid,
'courseid' => $courseid,
]);
// Check capability.
$coursecontext = context_course::instance($courseid);
$usercanshare = utilities::can_user_share($coursecontext, $USER->id);
if (!$usercanshare) {
return self::return_errors($courseid, 'errorpermission',
get_string('nopermissions', 'error', get_string('moodlenet:sharetomoodlenet', 'moodle')));
}
// Get the issuer.
$issuer = api::get_issuer($issuerid);
// Validate the issuer and check if it is enabled or not.
if (!utilities::is_valid_instance($issuer)) {
return self::return_errors($issuerid, 'errorissuernotenabled', get_string('invalidparameter', 'debug'));
}
$returnurl = new moodle_url('/admin/moodlenet_oauth2_callback.php');
$returnurl->param('issuerid', $issuerid);
$returnurl->param('callback', 'yes');
$returnurl->param('sesskey', sesskey());
// Get the OAuth Client.
if (!$oauthclient = api::get_user_oauth_client($issuer, $returnurl, moodlenet_client::API_SCOPE_CREATE_RESOURCE, true)) {
return self::return_errors($issuerid, 'erroroauthclient', get_string('invalidparameter', 'debug'));
}
$status = false;
$warnings = [];
$loginurl = '';
if (!$oauthclient->is_logged_in()) {
$loginurl = $oauthclient->get_login_url()->out(false);
} else {
$status = true;
}
return [
'status' => $status,
'loginurl' => $loginurl,
'warnings' => $warnings,
];
}
/**
* Describes the data returned from the external function.
*
* @return external_single_structure
* @since Moodle 4.2
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'loginurl' => new external_value(PARAM_RAW, 'Login url'),
'status' => new external_value(PARAM_BOOL, 'status: true if success'),
'warnings' => new external_warnings(),
]);
}
/**
* Handle return error.
*
* @param int $itemid Item id
* @param string $warningcode Warning code
* @param string $message Message
* @return array
*/
protected static function return_errors(int $itemid, string $warningcode, string $message): array {
$warnings[] = [
'item' => $itemid,
'warningcode' => $warningcode,
'message' => $message,
];
return [
'status' => false,
'loginurl' => '',
'warnings' => $warnings,
];
}
}
@@ -0,0 +1,160 @@
<?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\external;
use context_course;
use core\moodlenet\utilities;
use core\oauth2\api;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
/**
* The external API to het the activity information for MoodleNet sharing.
*
* @package core
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class moodlenet_get_share_info_activity extends external_api {
/**
* Returns description of parameters.
*
* @return external_function_parameters
* @since Moodle 4.2
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'cmid' => new external_value(PARAM_INT, 'The cmid of the activity', VALUE_REQUIRED),
]);
}
/**
* External function to get the activity information.
*
* @param int $cmid The course module id.
* @return array
* @since Moodle 4.2
*/
public static function execute(int $cmid): array {
global $CFG, $USER;
[
'cmid' => $cmid
] = self::validate_parameters(self::execute_parameters(), [
'cmid' => $cmid
]);
// Get course module.
$coursemodule = get_coursemodule_from_id(false, $cmid);
if (!$coursemodule) {
return self::return_errors($cmid, 'errorgettingactivityinformation', get_string('invalidcoursemodule', 'error'));
}
// Get course.
$course = get_course($coursemodule->course);
// Check capability.
$coursecontext = context_course::instance($course->id);
$usercanshare = utilities::can_user_share($coursecontext, $USER->id);
if (!$usercanshare) {
return self::return_errors($cmid, 'errorpermission',
get_string('nopermissions', 'error', get_string('moodlenet:sharetomoodlenet', 'moodle')));
}
$warnings = [];
$supporturl = '';
$issuerid = get_config('moodlenet', 'oauthservice');
if (empty($issuerid)) {
return self::return_errors(0, 'errorissuernotset', get_string('moodlenet:issuerisnotset', 'moodle'));
}
if ($CFG->supportavailability && $CFG->supportavailability != CONTACT_SUPPORT_DISABLED) {
if (!empty($CFG->supportpage)) {
$supporturl = $CFG->supportpage;
} else {
$supporturl = $CFG->wwwroot . '/user/contactsitesupport.php';
}
}
// Get the issuer.
$issuer = api::get_issuer($issuerid);
// Validate the issuer and check if it is enabled or not.
if (!utilities::is_valid_instance($issuer)) {
return self::return_errors($issuerid, 'errorissuernotenabled', get_string('moodlenet:issuerisnotenabled', 'moodle'));
}
return [
'status' => true,
'name' => $coursemodule->name,
'type' => get_string('modulename', $coursemodule->modname),
'server' => $issuer->get_display_name(),
'supportpageurl' => $supporturl,
'issuerid' => $issuerid,
'warnings' => $warnings
];
}
/**
* Describes the data returned from the external function.
*
* @return external_single_structure
* @since Moodle 4.2
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'name' => new external_value(PARAM_TEXT, 'Activity name'),
'type' => new external_value(PARAM_TEXT, 'Activity type'),
'server' => new external_value(PARAM_TEXT, 'MoodleNet server'),
'supportpageurl' => new external_value(PARAM_URL, 'Support page URL'),
'issuerid' => new external_value(PARAM_INT, 'MoodleNet issuer id'),
'status' => new external_value(PARAM_BOOL, 'status: true if success'),
'warnings' => new external_warnings()
]);
}
/**
* Handle return error.
*
* @param int $itemid Item id.
* @param string $warningcode Warning code.
* @param string $message Message.
* @param int $issuerid Issuer id.
* @return array
*/
protected static function return_errors(int $itemid, string $warningcode, string $message, int $issuerid = 0): array {
$warnings[] = [
'item' => $itemid,
'warningcode' => $warningcode,
'message' => $message
];
return [
'status' => false,
'name' => '',
'type' => '',
'server' => '',
'supportpageurl' => '',
'issuerid' => $issuerid,
'warnings' => $warnings
];
}
}
+170
View File
@@ -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/>.
namespace core\external;
use context_course;
use core\http_client;
use core\moodlenet\activity_sender;
use core\moodlenet\moodlenet_client;
use core\moodlenet\utilities;
use core\oauth2\api;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use moodle_url;
/**
* The external API to send activity to MoodleNet.
*
* @package core
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class moodlenet_send_activity extends external_api {
/**
* Describes the parameters for sending the activity.
*
* @return external_function_parameters
* @since Moodle 4.2
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'issuerid' => new external_value(PARAM_INT, 'OAuth 2 issuer ID', VALUE_REQUIRED),
'cmid' => new external_value(PARAM_INT, 'Course module ID', VALUE_REQUIRED),
'shareformat' => new external_value(PARAM_INT, 'Share format', VALUE_REQUIRED),
]);
}
/**
* External function to send the activity to MoodleNet.
*
* @param int $issuerid The MoodleNet OAuth 2 issuer ID
* @param int $cmid The course module ID of the activity that being shared
* @param int $shareformat The share format being used, as defined by \core\moodlenet\activity_sender
* @return array
* @since Moodle 4.2
*/
public static function execute(int $issuerid, int $cmid, int $shareformat): array {
global $CFG, $USER;
[
'issuerid' => $issuerid,
'cmid' => $cmid,
'shareformat' => $shareformat,
] = self::validate_parameters(self::execute_parameters(), [
'issuerid' => $issuerid,
'cmid' => $cmid,
'shareformat' => $shareformat,
]);
// Check capability.
[$course] = get_course_and_cm_from_cmid($cmid);
$coursecontext = context_course::instance($course->id);
$usercanshare = utilities::can_user_share($coursecontext, $USER->id);
if (!$usercanshare) {
return self::return_errors($cmid, 'errorpermission',
get_string('nopermissions', 'error', get_string('moodlenet:sharetomoodlenet', 'moodle')));
}
// Check format.
if (!in_array($shareformat, [activity_sender::SHARE_FORMAT_BACKUP])) {
return self::return_errors($shareformat, 'errorinvalidformat', get_string('invalidparameter', 'debug'));
}
// Get the issuer.
$issuer = api::get_issuer($issuerid);
// Validate the issuer and check if it is enabled or not.
if (!utilities::is_valid_instance($issuer)) {
return self::return_errors($issuerid, 'errorissuernotenabled', get_string('invalidparameter', 'debug'));
}
// Get the OAuth Client.
if (!$oauthclient = api::get_user_oauth_client(
$issuer,
new moodle_url($CFG->wwwroot),
moodlenet_client::API_SCOPE_CREATE_RESOURCE
)) {
return self::return_errors($issuerid, 'erroroauthclient', get_string('invalidparameter', 'debug'));
}
// Check login state.
if (!$oauthclient->is_logged_in()) {
return self::return_errors($issuerid, 'erroroauthclient', get_string('moodlenet:issuerisnotauthorized', 'moodle'));
}
// Get the HTTP Client.
$client = new http_client();
// Share activity.
try {
$moodlenetclient = new moodlenet_client($client, $oauthclient);
$activitysender = new activity_sender($cmid, $USER->id, $moodlenetclient, $oauthclient, $shareformat);
$result = $activitysender->share_activity();
if (empty($result['drafturl'])) {
return self::return_errors($result['responsecode'], 'errorsendingactivity',
get_string('moodlenet:cannotconnecttoserver', 'moodle'));
}
} catch (\moodle_exception $e) {
return self::return_errors(0, 'errorsendingactivity', $e->getMessage());
}
return [
'status' => true,
'resourceurl' => $result['drafturl'],
'warnings' => [],
];
}
/**
* Describes the data returned from the external function.
*
* @return external_single_structure
* @since Moodle 4.2
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'status' => new external_value(PARAM_BOOL, 'Status: true if success'),
'resourceurl' => new external_value(PARAM_URL, 'Resource URL from MoodleNet'),
'warnings' => new external_warnings(),
]);
}
/**
* Handle return error.
*
* @param int $itemid Item id
* @param string $warningcode Warning code
* @param string $message Message
* @return array
*/
protected static function return_errors(int $itemid, string $warningcode, string $message): array {
$warnings[] = [
'item' => $itemid,
'warningcode' => $warningcode,
'message' => $message,
];
return [
'status' => false,
'resourceurl' => '',
'warnings' => $warnings,
];
}
}
+182
View File
@@ -0,0 +1,182 @@
<?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\moodlenet;
use backup;
use backup_controller;
use backup_root_task;
use cm_info;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
/**
* Packager to prepare appropriate backup of an activity to share to MoodleNet.
*
* @package core
* @copyright 2023 Raquel Ortega <raquel.ortega@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class activity_packager {
/** @var backup_controller $controller */
protected $controller;
/**
* Constructor.
*
* @param cm_info $cminfo context module information about the resource being packaged.
* @param int $userid The ID of the user performing the packaging.
*/
public function __construct(
protected cm_info $cminfo,
protected int $userid
) {
// Check backup/restore support.
if (!plugin_supports('mod', $cminfo->modname , FEATURE_BACKUP_MOODLE2)) {
throw new \coding_exception("Cannot backup module $cminfo->modname. This module doesn't support the backup feature.");
}
$this->cminfo = $cminfo;
$this->controller = new backup_controller (
backup::TYPE_1ACTIVITY,
$cminfo->id,
backup::FORMAT_MOODLE,
backup::INTERACTIVE_NO,
backup::MODE_GENERAL,
$userid
);
}
/**
* Prepare the backup file using appropriate setting overrides and return relevant information.
*
* @return array Array containing packaged file and stored_file object describing it.
* Array in the format [storedfile => stored_file object, filecontents => raw file].
*/
public function get_package(): array {
$alltasksettings = $this->get_all_task_settings();
// Override relevant settings to remove user data when packaging to share to MoodleNet.
$this->override_task_setting($alltasksettings, 'setting_root_users', 0);
$this->override_task_setting($alltasksettings, 'setting_root_role_assignments', 0);
$this->override_task_setting($alltasksettings, 'setting_root_blocks', 0);
$this->override_task_setting($alltasksettings, 'setting_root_comments', 0);
$this->override_task_setting($alltasksettings, 'setting_root_badges', 0);
$this->override_task_setting($alltasksettings, 'setting_root_userscompletion', 0);
$this->override_task_setting($alltasksettings, 'setting_root_logs', 0);
$this->override_task_setting($alltasksettings, 'setting_root_grade_histories', 0);
$this->override_task_setting($alltasksettings, 'setting_root_groups', 0);
return $this->package();
}
/**
* Get all backup settings available for override.
*
* @return array the associative array of taskclass => settings instances.
*/
protected function get_all_task_settings(): array {
$tasksettings = [];
foreach ($this->controller->get_plan()->get_tasks() as $task) {
$taskclass = get_class($task);
$tasksettings[$taskclass] = $task->get_settings();
}
return $tasksettings;
}
/**
* Override a backup task setting with a given value.
*
* @param array $alltasksettings All task settings.
* @param string $settingname The name of the setting to be overridden (task class name format).
* @param int $settingvalue Value to be given to the setting.
* @return void
*/
protected function override_task_setting(array $alltasksettings, string $settingname, int $settingvalue): void {
if (empty($rootsettings = $alltasksettings[backup_root_task::class])) {
return;
}
foreach ($rootsettings as $setting) {
$name = $setting->get_ui_name();
if ($name == $settingname && $settingvalue != $setting->get_value()) {
$setting->set_value($settingvalue);
return;
}
}
}
/**
* Package the activity identified by CMID.
*
* @return array Array containing packaged file and stored_file object describing it.
* Array in the format [storedfile => stored_file object, filecontents => raw file].
* @throws \moodle_exception.
*/
protected function package(): array {
// Execute the backup and fetch the result.
$this->controller->execute_plan();
$result = $this->controller->get_results();
// Controller no longer required.
$this->controller->destroy();
if (!isset($result['backup_destination'])) {
throw new \moodle_exception('Failed to package activity.');
}
$backupfile = $result['backup_destination'];
if (!$backupfile->get_contenthash()) {
throw new \moodle_exception('Failed to package activity (invalid file).');
}
// Create the location we want to copy this file to.
$time = time();
$fr = [
'contextid' => \context_course::instance($this->cminfo->course)->id,
'component' => 'core',
'filearea' => 'moodlenet_resource',
'filename' => $this->cminfo->modname . '_backup.mbz',
// Add timestamp to itemid to make it unique, to avoid any collisions.
'itemid' => $this->cminfo->id . $time,
'timemodified' => $time,
];
// Create the local file based on the backup.
$fs = get_file_storage();
$packagedfiledata = [
'storedfile' => $fs->create_file_from_storedfile($fr, $backupfile),
];
// Delete the backup now it has been created in the file area.
$backupfile->delete();
if (!$packagedfiledata['storedfile']) {
throw new \moodle_exception("Failed to copy backup file to moodlenet_activity area.");
}
// Ensure we can handle files at the upper end of the limit supported by MoodleNet.
raise_memory_limit(activity_sender::MAX_FILESIZE);
// Get the actual file content.
$packagedfiledata['filecontents'] = $packagedfiledata['storedfile']->get_content();
return $packagedfiledata;
}
}
+211
View File
@@ -0,0 +1,211 @@
<?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\moodlenet;
use cm_info;
use core\event\moodlenet_resource_exported;
use core\oauth2\client;
use moodle_exception;
use stdClass;
/**
* API for sharing Moodle LMS activities to MoodleNet instances.
*
* @package core
* @copyright 2023 Michael Hawkins <michaelh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class activity_sender {
/**
* @var int Backup share format - the content is being shared as a Moodle backup file.
*/
public const SHARE_FORMAT_BACKUP = 0;
/**
* @var int Maximum upload file size (1.07 GB).
*/
public const MAX_FILESIZE = 1070000000;
/**
* @var cm_info The context module info object for the activity being shared.
*/
protected cm_info $cminfo;
/**
* @var stdClass The course where the activity is located.
*/
protected stdClass $course;
/**
* Class constructor.
*
* @param int $cmid The course module ID of the activity being shared.
* @param int $userid The user ID who is sharing the activity.
* @param moodlenet_client $moodlenetclient The moodlenet_client object used to perform the share.
* @param client $oauthclient The OAuth 2 client for the MoodleNet instance.
* @param int $shareformat The data format to share in. Defaults to a Moodle backup (SHARE_FORMAT_BACKUP).
* @throws moodle_exception
*/
public function __construct(
int $cmid,
protected int $userid,
protected moodlenet_client $moodlenetclient,
protected client $oauthclient,
protected int $shareformat = self::SHARE_FORMAT_BACKUP
) {
[$this->course, $this->cminfo] = get_course_and_cm_from_cmid($cmid);
if (!in_array($shareformat, $this->get_allowed_share_formats())) {
throw new moodle_exception('moodlenet:invalidshareformat');
}
}
/**
* Share an activity/resource to MoodleNet.
*
* @return array The HTTP response code from MoodleNet and the MoodleNet draft resource URL (URL empty string on fail).
* Format: ['responsecode' => 201, 'drafturl' => 'https://draft.mnurl/here']
* @throws moodle_exception
*/
public function share_activity(): array {
global $DB;
$accesstoken = '';
$resourceurl = '';
$issuer = $this->oauthclient->get_issuer();
// Check user can share to the requested MoodleNet instance.
$coursecontext = \context_course::instance($this->course->id);
$usercanshare = utilities::can_user_share($coursecontext, $this->userid);
if ($usercanshare && utilities::is_valid_instance($issuer) && $this->oauthclient->is_logged_in()) {
$accesstoken = $this->oauthclient->get_accesstoken()->token;
}
// Throw an exception if the user is not currently set up to be able to share to MoodleNet.
if (!$accesstoken) {
throw new moodle_exception('moodlenet:usernotconfigured');
}
// Attempt to prepare and send the resource if validation has passed and we have an OAuth 2 token.
// Prepare file in requested format.
$filedata = $this->prepare_share_contents();
// If we have successfully prepared a file to share of permitted size, share it to MoodleNet.
if (!empty($filedata['storedfile'])) {
// Avoid sending a file larger than the defined limit.
$filesize = $filedata['storedfile']->get_filesize();
if ($filesize > self::MAX_FILESIZE) {
$filedata['storedfile']->delete();
throw new moodle_exception('moodlenet:sharefilesizelimitexceeded', 'core', '',
[
'filesize' => $filesize,
'filesizelimit' => self::MAX_FILESIZE,
]);
}
// MoodleNet only accept plaintext descriptions.
$resourcedescription = $DB->get_field($this->cminfo->modname, 'intro', ['id' => $this->cminfo->instance]);
$resourcedescription = strip_tags($resourcedescription);
$resourcedescription = format_text(
$resourcedescription,
FORMAT_PLAIN,
['context' => $coursecontext]
);
$response = $this->moodlenetclient->create_resource_from_file($filedata, $this->cminfo->name, $resourcedescription);
$responsecode = $response->getStatusCode();
$responsebody = json_decode($response->getBody());
$resourceurl = $responsebody->homepage ?? '';
// TODO: Store consumable information about completed share - to be completed in MDL-77296.
// Delete the generated file now it is no longer required.
// (It has either been sent, or failed - retries not currently supported).
$filedata['storedfile']->delete();
}
// Log every attempt to share (and whether or not it was successful).
$this->log_event($coursecontext, $this->cminfo->id, $resourceurl, $responsecode);
return [
'responsecode' => $responsecode,
'drafturl' => $resourceurl,
];
}
/**
* Prepare the data for sharing, in the format specified.
*
* @return array Array of metadata about the file, as well as a stored_file object for the file.
*/
protected function prepare_share_contents(): array {
switch ($this->shareformat) {
case self::SHARE_FORMAT_BACKUP:
default:
// If sharing the activity as a backup, prepare the packaged backup.
$packager = new activity_packager($this->cminfo, $this->userid);
$filedata = $packager->get_package();
break;
};
return $filedata;
}
/**
* Log an event to the admin logs for an outbound share attempt.
*
* @param \context $coursecontext The course context being shared from.
* @param int $cmid The CMID of the activity being shared.
* @param string $resourceurl The URL of the draft resource if it was created.
* @param int $responsecode The HTTP response code describing the outcome of the attempt.
* @return void
*/
protected function log_event(
\context $coursecontext,
int $cmid,
string $resourceurl,
int $responsecode
): void {
$event = moodlenet_resource_exported::create([
'context' => $coursecontext,
'other' => [
'cmids' => [$cmid],
'resourceurl' => $resourceurl,
'success' => ($responsecode == 201),
],
]);
$event->trigger();
}
/**
* Return the list of supported share formats.
*
* @return array Array of supported share format values.
*/
protected function get_allowed_share_formats(): array {
return [
self::SHARE_FORMAT_BACKUP,
];
}
}
+113
View File
@@ -0,0 +1,113 @@
<?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\moodlenet;
use core\http_client;
use core\oauth2\client;
/**
* MoodleNet client which handles direct outbound communication with MoodleNet instances.
*
* @package core
* @copyright 2023 Michael Hawkins <michaelh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class moodlenet_client {
/**
* @var string MoodleNet resource creation endpoint URI.
*/
protected const API_CREATE_RESOURCE_URI = '/.pkg/@moodlenet/ed-resource/basic/v1/create';
/**
* @var string MoodleNet scope for creating resources.
*/
public const API_SCOPE_CREATE_RESOURCE = '@moodlenet/ed-resource:write.own';
/**
* Constructor.
*
* @param http_client $httpclient The httpclient object being used to perform the share.
* @param client $oauthclient The OAuth 2 client for the MoodleNet site being shared to.
*/
public function __construct(
protected http_client $httpclient,
protected client $oauthclient,
) {
// All properties promoted, nothing further required.
}
/**
* Create a resource on MoodleNet which includes a file.
*
* @param array $filedata The file data in the format [storedfile => stored_file object, filecontents => raw file].
* @param string $resourcename The name of the resource being shared.
* @param string $resourcedescription A description of the resource being shared.
* @return Psr\Http\Message\ResponseInterface The HTTP client response from MoodleNet.
*/
public function create_resource_from_file(array $filedata, string $resourcename, $resourcedescription) {
// This may take a long time if a lot of data is being shared.
\core_php_time_limit::raise();
$moodleneturl = $this->oauthclient->get_issuer()->get('baseurl');
$apiurl = rtrim($moodleneturl, '/') . self::API_CREATE_RESOURCE_URI;
$requestdata = $this->prepare_file_share_request_data($filedata, $resourcename, $resourcedescription);
return $this->httpclient->request('POST', $apiurl, $requestdata);
}
/**
* Prepare the request data required for sharing a file to MoodleNet.
* This creates an array in the format used by \core\httpclient options to send a multipart request.
*
* @param array $filedata An array of data relating to the file being shared (as prepared by ::prepare_share_contents).
* @param string $resourcename The name of the resource being shared.
* @param string $resourcedescription A description of the resource being shared.
* @return array Data in the format required to send a file to MoodleNet using \core\httpclient.
*/
protected function prepare_file_share_request_data(array $filedata, string $resourcename, $resourcedescription): array {
return [
'headers' => [
'Authorization' => 'Bearer ' . $this->oauthclient->get_accesstoken()->token,
],
'multipart' => [
[
'name' => 'metadata',
'contents' => json_encode([
'name' => $resourcename,
'description' => $resourcedescription,
]),
'headers' => [
'Content-Disposition' => 'form-data; name="."',
],
],
[
'name' => 'filecontents',
'contents' => $filedata['filecontents'],
'headers' => [
'Content-Disposition' => 'form-data; name=".resource"; filename="' .
$filedata['storedfile']->get_filename() . '"',
'Content-Type' => $filedata['storedfile']->get_mimetype(),
'Content-Transfer-Encoding' => 'binary',
],
],
],
];
}
}
+57
View File
@@ -0,0 +1,57 @@
<?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\moodlenet;
use context_course;
use core\oauth2\issuer;
/**
* Class containing static utilities (such as various checks) required by the MoodleNet API.
*
* @package core
* @copyright 2023 Michael Hawkins <michaelh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class utilities {
/**
* Check whether the specified issuer is configured as a MoodleNet instance that can be shared to.
*
* @param issuer $issuer The OAuth 2 issuer being validated.
* @return bool true if the issuer is enabled and available to share to.
*/
public static function is_valid_instance(issuer $issuer): bool {
global $CFG;
$issuerid = $issuer->get('id');
$allowedissuer = get_config('moodlenet', 'oauthservice');
return ($CFG->enablesharingtomoodlenet && $issuerid == $allowedissuer && $issuer->get('enabled') &&
$issuer->get('servicetype') == 'moodlenet');
}
/**
* Check whether a user has the capabilities required to share activities from a given course to MoodleNet.
*
* @param context_course $coursecontext Course context where the activity would be shared from.
* @param int $userid The user ID being checked.
* @return boolean
*/
public static function can_user_share(context_course $coursecontext, int $userid): bool {
return (has_capability('moodle/moodlenet:shareactivity', $coursecontext, $userid) &&
has_capability('moodle/backup:backupactivity', $coursecontext, $userid));
}
}
@@ -122,6 +122,7 @@ class secondary extends view {
'backup' => 9,
'restore' => 10,
'competencybreakdown' => 11,
'sendtomoodlenet' => 16,
],
self::TYPE_CUSTOM => [
'advgrading' => 2,
+6 -2
View File
@@ -76,8 +76,8 @@ class api {
/**
* Create one of the standard issuers.
*
* @param string $type One of google, facebook, microsoft, nextcloud or imsobv2p1
* @param string|false $baseurl Baseurl (only required for nextcloud and imsobv2p1)
* @param string $type One of google, facebook, microsoft, MoodleNet, nextcloud or imsobv2p1
* @param string|false $baseurl Baseurl (only required for nextcloud, imsobv2p1 and moodlenet)
* @return \core\oauth2\issuer
*/
public static function create_standard_issuer($type, $baseurl = false) {
@@ -92,6 +92,10 @@ class api {
if (!$baseurl) {
throw new moodle_exception('Nextcloud service type requires the baseurl parameter.');
}
case 'moodlenet':
if (!$baseurl) {
throw new moodle_exception('MoodleNet service type requires the baseurl parameter.');
}
case 'google':
case 'facebook':
case 'microsoft':
+10 -2
View File
@@ -76,7 +76,11 @@ class moodlenet implements issuer_interface {
}
$endpointscreated = 0;
$configreader = new auth_server_config_reader(new http_client());
$config = [];
if (defined('BEHAT_SITE_RUNNING')) {
$config['verify'] = false;
}
$configreader = new auth_server_config_reader(new http_client($config));
try {
$config = $configreader->read_configuration(new \moodle_url($baseurl));
@@ -150,7 +154,11 @@ class moodlenet implements issuer_interface {
'scope' => $scopes
];
$client = new http_client();
$config = [];
if (defined('BEHAT_SITE_RUNNING')) {
$config['verify'] = false;
}
$client = new http_client($config);
$request = new Request(
'POST',
$url,
@@ -43,6 +43,9 @@ class user_field_mapping extends persistent {
* @return array
*/
private static function get_user_fields() {
global $CFG;
require_once($CFG->dirroot . '/user/profile/lib.php');
return array_merge(\core_user::AUTHSYNCFIELDS, ['picture', 'username'], get_profile_field_names());
}
+10
View File
@@ -2681,4 +2681,14 @@ $capabilities = array(
'contextlevel' => CONTEXT_SYSTEM,
'archetypes' => [],
],
// Allow users to share activities to MoodleNet.
'moodle/moodlenet:shareactivity' => [
'captype' => 'read',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => [
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW,
]
],
);
+18
View File
@@ -3042,6 +3042,24 @@ $functions = array(
'type' => 'write',
'ajax' => true,
],
'core_moodlenet_send_activity' => [
'classname' => 'core\external\moodlenet_send_activity',
'description' => 'Send activity to MoodleNet',
'type' => 'read',
'ajax' => true,
],
'core_moodlenet_get_share_info_activity' => [
'classname' => 'core\external\moodlenet_get_share_info_activity',
'description' => 'Get information about an activity being shared',
'type' => 'read',
'ajax' => true,
],
'core_moodlenet_auth_check' => [
'classname' => 'core\external\moodlenet_auth_check',
'description' => 'Check a user has authorized for a given MoodleNet site',
'type' => 'write',
'ajax' => true,
],
);
$services = array(
+2 -2
View File
@@ -28,8 +28,8 @@ $string['settings'] = 'General settings';
$string['privacy:reason'] = 'The TinyMCE editor does not store any preferences or user data.';
$string['branding'] = 'TinyMCE branding';
$string['branding_desc'] = 'Support TinyMCE by displaying the logo in the bottom corner of the text editor. The logo links to the TinyMCE website.';
$string['plugin_enabled'] = 'The {$a} plugin has been enabled.';
$string['plugin_disabled'] = 'The {$a} plugin has been disabled.';
$string['plugin_enabled'] = '{$a} enabled.';
$string['plugin_disabled'] = '{$a} disabled.';
$string['tiny:hash'] = '#';
$string['tiny:accessibility'] = 'Accessibility';
$string['tiny:action'] = 'Action';
@@ -25,6 +25,7 @@
$string['browserepositories'] = 'Browse repositories...';
$string['createlink'] = 'Create link';
$string['enterurl'] = 'Enter a URL';
$string['helplinktext'] = 'Link';
$string['openinnewwindow'] = 'Open in new window';
$string['pluginname'] = 'Tiny link';
$string['link'] = 'Link';
@@ -8,23 +8,23 @@ Feature: An administrator can manage TinyMCE subplugins
Scenario: An administrator can control the enabled state of TinyMCE subplugins using JavaScript
Given I am logged in as "admin"
And I navigate to "Plugins > Text editors > TinyMCE editor > General settings" in site administration
When I click on "Disable the Tiny equation editor plugin" "link"
Then I should see "The Tiny equation editor plugin has been disabled"
And "Disable the Tiny equation editor plugin" "link" should not exist
But "Enable the Tiny equation editor plugin" "link" should exist
When I click on "Enable the Tiny equation editor plugin" "link"
Then I should see "The Tiny equation editor plugin has been enabled"
And "Enable the Tiny equation editor plugin" "link" should not exist
But "Disable the Tiny equation editor plugin" "link" should exist
When I click on "Disable Tiny equation editor" "link"
Then I should see "Tiny equation editor disabled."
And "Disable Tiny equation editor" "link" should not exist
But "Enable Tiny equation editor" "link" should exist
When I click on "Enable Tiny equation editor" "link"
Then I should see "Tiny equation editor enabled."
And "Enable Tiny equation editor" "link" should not exist
But "Disable Tiny equation editor" "link" should exist
Scenario: An administrator can control the enabled state of TinyMCE subplugins without JavaScript
Given I am logged in as "admin"
And I navigate to "Plugins > Text editors > TinyMCE editor > General settings" in site administration
When I click on "Disable the Tiny equation editor plugin" "link"
Then I should see "The Tiny equation editor plugin has been disabled"
And "Disable the Tiny equation editor plugin" "link" should not exist
But "Enable the Tiny equation editor plugin" "link" should exist
When I click on "Enable the Tiny equation editor plugin" "link"
Then I should see "The Tiny equation editor plugin has been enabled"
And "Enable the Tiny equation editor plugin" "link" should not exist
But "Disable the Tiny equation editor plugin" "link" should exist
When I click on "Disable Tiny equation editor" "link"
Then I should see "Tiny equation editor disabled."
And "Disable Tiny equation editor" "link" should not exist
But "Enable Tiny equation editor" "link" should exist
When I click on "Enable Tiny equation editor" "link"
Then I should see "Tiny equation editor enabled."
And "Enable Tiny equation editor" "link" should not exist
But "Disable Tiny equation editor" "link" should exist
+18 -1
View File
@@ -23,6 +23,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core\moodlenet\utilities;
use core_contentbank\contentbank;
defined('MOODLE_INTERNAL') || die();
@@ -4799,7 +4800,7 @@ class settings_navigation extends navigation_node {
* @return navigation_node|false
*/
protected function load_module_settings() {
global $CFG;
global $CFG, $USER;
if (!$this->page->cm && $this->context->contextlevel == CONTEXT_MODULE && $this->context->instanceid) {
$cm = get_coursemodule_from_id(false, $this->context->instanceid, 0, false, MUST_EXIST);
@@ -4880,6 +4881,22 @@ class settings_navigation extends navigation_node {
$function($this, $modulenode);
}
// Send to MoodleNet.
$usercanshare = utilities::can_user_share($this->context->get_course_context(), $USER->id);
$issuerid = get_config('moodlenet', 'oauthservice');
$issuer = \core\oauth2\api::get_issuer($issuerid);
$isvalidinstance = utilities::is_valid_instance($issuer);
if ($usercanshare && $isvalidinstance) {
$this->page->requires->js_call_amd('core/moodlenet/send_resource', 'init');
$action = new action_link(new moodle_url(''), '', null, [
'data-action' => 'sendtomoodlenet',
'data-type' => 'activity',
'data-sharetype' => 'resource',
]);
$modulenode->add(get_string('moodlenet:sharetomoodlenet', 'moodle'),
$action, self::TYPE_SETTING, null, 'exportmoodlenet')->set_force_into_more_menu(true);
}
// Remove the module node if there are no children.
if ($modulenode->children->count() <= 0) {
$modulenode->remove();
+2 -2
View File
@@ -726,11 +726,11 @@ abstract class oauth2_client extends curl {
}
/**
* Get access token.
* Get access token object.
*
* This is just a getter to read the private property.
*
* @return string
* @return stdClass
*/
public function get_accesstoken() {
return $this->accesstoken;
@@ -0,0 +1,57 @@
{{!
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/>.
}}
{{!
@template core/moodlenet/send_activity_modal_base
Modal to send activity to MoodleNet
Example context (json):
{
"uniqid": 123456,
"activitytype": "assignment",
"activityname": "Test assignment",
"sharetype": "resource"
}
}}
<div class="modal moodle-has-zindex" data-region="modal-container" aria-hidden="true" role="dialog">
<div class="modal-dialog moodlenet-share-dialog" role="document" data-region="modal" aria-labelledby="{{uniqid}}-moodlenet-modal-title" tabindex="0">
<div class="modal-content">
<div class="modal-header no-border" data-region="header">
<div id="{{uniqid}}-moodlenet-modal-title" class="modal-title" data-region="title">
<h5 class="sr-only modal-title">{{#str}} moodlenet:sharetomoodlenet, moodle {{/str}}</h5>
<div class="d-flex moodlenet-share-moodlenetinfo">
<span class="moodlenet-logo">{{#pix}} moodlenet, moodle, {{#str}} moodlenet:sharetomoodlenet, moodle {{/str}} {{/pix}}</span>
<span class="moodlenet-title">{{#str}} moodlenet:sharetomoodlenet, moodle {{/str}}</span>
</div>
</div>
<button type="button" class="close" data-action="hide" aria-label="{{#cleanstr}}closebuttontitle{{/cleanstr}}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" data-region="body">
<div class="moodlenet-share-modal-content">
<div class="moodlenet-share-activity-info">
<span class="moodlenet-activity-type text-uppercase">{{activitytype}}</span>
<span class="moodlenet-activity-name">{{activityname}}</span>
</div>
<hr class="moodlenet-share-activity-info-hr">
<div class="moodlenet-share-notice">
{{#pix}} e/help, core, {{#str}} moodlenet:sharenotice, moodle, {{sharetype}} {{/str}}{{/pix}} {{#str}} moodlenet:sharenotice, moodle, {{sharetype}} {{/str}}
</div>
</div>
</div>
<div class="modal-footer" data-region="footer">
{{> core/moodlenet/send_activity_modal_footer_share }}
</div>
</div>
</div>
</div>
@@ -0,0 +1,57 @@
{{!
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/>.
}}
{{!
@template core/moodlenet/send_activity_modal_done
This template renders done state for share modal.
Example context (json):
{
"success": true,
"sitesupporturl": "https://moodle.test/contactsitesupport.php"
}
}}
<div class="moodlenet-share-modal-content text-center">
<div class="moodlenet-circle-status{{#success}} success{{/success}}{{^success}} fail{{/success}}">
<span class="status-icon pt-5{{#success}} text-success{{/success}}{{^success}} text-danger{{/success}}">
{{#success}}
{{#pix}} t/check, core {{/pix}}
{{/success}}
{{^success}}
{{#pix}} fp/cross, theme {{/pix}}
{{/success}}
</span>
<span class="status-text font-weight-bold h5 pt-5">
{{#success}}
{{#str}} moodlenet:sharesuccesstitle, moodle {{/str}}
{{/success}}
{{^success}}
{{#str}} moodlenet:sharefailtitle, moodle {{/str}}
{{/success}}
</span>
</div>
<div class="result pt-2">
<span class="d-block h6 font-weight-normal pt-1 result-text">
{{#success}}
{{#str}} moodlenet:sharesuccesstext, moodle {{/str}}
{{/success}}
{{^success}}
{{#sitesupporturl}}
{{#str}} moodlenet:sharefailtextwithsitesupport, moodle, {{sitesupporturl}} {{/str}}
{{/sitesupporturl}}
{{^sitesupporturl}}
{{#str}} moodlenet:sharefailtext, moodle {{/str}}
{{/sitesupporturl}}
{{/success}}
</span>
</div>
</div>
@@ -0,0 +1,28 @@
{{!
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/>.
}}
{{!
@template core/moodlenet/send_activity_modal_footer_share
Render the footer for sharing activity
Example context (json):
{
"server": "MoodleNet Local"
}
}}
<div class="moodlenet-share-to">
{{#str}} moodlenet:sharingto, moodle {{/str}}<span class="font-weight-bold">{{server}}</span>
</div>
<div class="moodlenet-action-buttons">
<button type="button" class="btn btn-secondary" data-action="cancel">{{#str}} cancel, moodle {{/str}}</button>
<button type="button" class="btn btn-primary" data-action="share">{{#str}} share, moodle {{/str}}</button>
</div>
@@ -0,0 +1,26 @@
{{!
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/>.
}}
{{!
@template core/moodlenet/send_activity_modal_footer_view
Render the footer for sharing activity
Example context (json):
{
"resourseurl": "https://moodlenet.test/draft/activity.mbz"
}
}}
<div class="moodlenet-action-buttons">
<a class="btn btn-primary" href="{{resourseurl}}" target="_blank">
{{#str}} moodlenet:gotomoodlenet, moodle {{/str}}
</a>
</div>
@@ -0,0 +1,28 @@
{{!
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/>.
}}
{{!
@template core/moodlenet/send_activity_modal_packaging
This template renders the packaging stage for MoodleNet.
Example context (json):
{
}
}}
<div class="moodlenet-share-modal-content text-center p-5">
<div class="font-weight-bold h3 p-2">{{#str}} moodlenet:sharingstatus, moodle {{/str}}</div>
<div class="h5 font-weight-normal p-2 test-share-large">{{#str}} moodlenet:sharinglargefile, moodle {{/str}}</div>
<div class="p-5">
{{> core/loading }}
</div>
<div class="h5 font-weight-normal p-2">{{#str}} moodlenet:packagingandsending, moodle {{/str}}</div>
</div>

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