MDL-75502 core: Added MoodleNet share progress page

This commit is contained in:
David Woloszyn
2023-09-12 15:14:39 +10:00
parent b4c6ed3650
commit 045d4368f4
10 changed files with 442 additions and 2 deletions
+1
View File
@@ -73,6 +73,7 @@ $string['cachedef_langmenu'] = 'List of available languages';
$string['cachedef_license'] = 'List of licences';
$string['cachedef_message_time_last_message_between_users'] = 'Time created for most recent message in a conversation';
$string['cachedef_modelfirstanalyses'] = 'First analysis by model and analysable';
$string['cachedef_moodlenet_usercanshare'] = 'Users can share resources to MoodleNet';
$string['cachedef_locking'] = 'Locking';
$string['cachedef_message_processors_enabled'] = "Message processors enabled status";
$string['cachedef_contextwithinsights'] = 'Context with insights';
+10
View File
@@ -1374,18 +1374,27 @@ $string['moodleversion'] = 'Moodle version';
$string['moodlerelease'] = 'Moodle release';
$string['moodleservicesandsupport'] = 'Services and support';
$string['moodlenet:cannotconnecttoserver'] = 'Cannot connect to MoodleNet server';
$string['moodlenet:columnname'] = 'Name';
$string['moodlenet:columnsenddate'] = 'Send date';
$string['moodlenet:columnsendstatus'] = 'Send status';
$string['moodlenet:columntype'] = 'Type';
$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:deletedactivity'] = 'Deleted activity';
$string['moodlenet:deletedcourse'] = 'Deleted course';
$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:nosharedresources'] = 'There are no shared resources to display at this time';
$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:shareprogress'] = 'MoodleNet share progress';
$string['moodlenet:shareprogressinfo'] = 'The list shows courses and activities you have shared to MoodleNet.<br>Click the resource name to view in MoodleNet.';
$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';
@@ -2000,6 +2009,7 @@ $string['selectcategorysort'] = 'Which categories would you like to sort?';
$string['selectcategorysortby'] = 'Select how you would like to sort categories';
$string['selectcoursesortby'] = 'Select how you would like to sort courses';
$string['senddetails'] = 'Send my details via email';
$string['sent'] = 'Sent';
$string['separate'] = 'Separate';
$string['separateandconnected'] = 'Separate and Connected ways of knowing';
$string['separateandconnectedinfo'] = 'The scale based on the theory of separate and connected knowing. This theory describes two different ways that we can evaluate and learn about the things we see and hear.<ul><li><strong>Separate knowers</strong> remain as objective as possible without including feelings and emotions. In a discussion with other people, they like to defend their own ideas, using logic to find holes in opponent\'s ideas.</li><li><strong>Connected knowers</strong> are more sensitive to other people. They are skilled at empathy and tend to listen and ask questions until they feel they can connect and "understand things from their point of view". They learn by trying to share the experiences that led to the knowledge they find in other people.</li></ul>';
@@ -0,0 +1,253 @@
<?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 share progress table.
*
* @package core
* @copyright 2023 David Woloszyn <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\moodlenet;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/tablelib.php');
use html_writer;
use moodle_url;
use stdClass;
use table_sql;
/**
* MoodleNet share progress table.
*
* @package core
* @copyright 2023 David Woloszyn <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class share_progress_table extends table_sql {
/** @var int The user id records will be displayed for. */
protected $userid;
/**
* Set up the table.
*
* @param string $uniqueid Unique id of table.
* @param moodle_url $url The base URL.
* @param int $userid The user id.
*/
public function __construct($uniqueid, $url, $userid) {
parent::__construct($uniqueid);
$this->userid = $userid;
$this->define_table_columns();
$this->define_baseurl($url);
$this->define_table_configs();
}
/**
* Define table configs.
*/
protected function define_table_configs() {
$this->collapsible(false);
$this->sortable(false);
$this->pageable(true);
$this->set_default_per_page(25);
}
/**
* Set up the columns and headers.
*/
protected function define_table_columns() {
// Define headers and columns.
$cols = [
'name' => get_string('name'),
'type' => get_string('moodlenet:columntype'),
'timecreated' => get_string('moodlenet:columnsenddate'),
'status' => get_string('moodlenet:columnsendstatus'),
];
$this->define_columns(array_keys($cols));
$this->define_headers(array_values($cols));
$this->column_class('status', 'text-center');
}
/**
* Name column.
*
* @param stdClass $row Row data.
* @return string
*/
protected function col_name(stdClass $row): string {
global $OUTPUT;
$class = '';
// Track deletion of resources on Moodle.
$deleted = false;
// Courses.
if ($row->type == share_recorder::TYPE_COURSE) {
if ($row->fullname) {
$name = $row->fullname;
} else {
$name = get_string('moodlenet:deletedcourse');
$deleted = true;
}
// Activities.
} else if ($row->type == share_recorder::TYPE_ACTIVITY) {
if ($row->modname) {
$name = get_string('modulename', $row->modname);
} else {
$name = get_string('moodlenet:deletedactivity');
$deleted = true;
}
}
if ($deleted) {
$class = 'font-italic';
}
// Add a link to the resource if it was recorded.
if (!empty($row->resourceurl)) {
// Apply bold to resource links that aren't deleted.
if (!$deleted) {
$class = 'font-weight-bold';
}
$icon = $OUTPUT->pix_icon('i/externallink', get_string('opensinnewwindow'), 'moodle', ['class' => 'ml-1']);
$text = $name . $icon;
$attributes = [
'target' => '_blank',
'rel' => 'noopener noreferrer',
];
$name = html_writer::link($row->resourceurl, $text, $attributes);
}
return html_writer::span($name, $class);
}
/**
* Type column.
*
* @param stdClass $row Row data.
* @return string
*/
protected function col_type(stdClass $row): string {
// Courses.
if ($row->type == share_recorder::TYPE_COURSE) {
$type = get_string('course');
// Activities.
} else if ($row->type == share_recorder::TYPE_ACTIVITY) {
if ($cm = get_coursemodule_from_id('', $row->cmid)) {
// If the module type is known, show it.
$type = get_string('modulename', $cm->modname);
} else {
// Alternatively, default to 'activity'.
$type = get_string('activity');
}
}
return $type;
}
/**
* Time created column (Send date).
*
* @param stdClass $row Row data.
* @return string
*/
protected function col_timecreated(stdClass $row): string {
$format = get_string('strftimedatefullshort', 'core_langconfig');
return userdate($row->timecreated, $format);
}
/**
* Status column (Send status).
*
* @param stdClass $row Row data.
* @return string
*/
protected function col_status(stdClass $row): string {
// Display a badge indicating the status of the share.
if ($row->status == share_recorder::STATUS_IN_PROGRESS) {
$status = html_writer::span(get_string('inprogress'), 'badge badge-warning');
} else if ($row->status == share_recorder::STATUS_SENT) {
$status = html_writer::span(get_string('sent'), 'badge badge-success');
} else if ($row->status == share_recorder::STATUS_ERROR) {
$status = html_writer::span(get_string('error'), 'badge badge-danger');
}
return $status;
}
/**
* Builds the SQL query.
*
* @param bool $count When true, return the count SQL.
* @return array containing sql to use and an array of params.
*/
protected function get_sql_and_params($count = false) {
if ($count) {
$select = "COUNT(1)";
} else {
$select = "msp.id, msp.type, msp.courseid, msp.cmid, msp.timecreated, " .
"msp.resourceurl, msp.status, c.fullname, md.name AS modname";
}
$sql = "SELECT $select
FROM {moodlenet_share_progress} msp
LEFT JOIN {course} c ON c.id = msp.courseid
LEFT JOIN {course_modules} cm ON cm.course = msp.courseid
AND cm.id = msp.cmid
LEFT JOIN {modules} md ON md.id = cm.module
WHERE msp.userid = :userid";
$params = ['userid' => $this->userid];
if (!$count) {
$sql .= " ORDER BY msp.status DESC, msp.timecreated DESC";
}
return [$sql, $params];
}
/**
* Query the DB.
*
* @param int $pagesize size of page for paginated displayed table.
* @param bool $useinitialsbar do you want to use the initials bar.
*/
public function query_db($pagesize, $useinitialsbar = true) {
global $DB;
list($countsql, $countparams) = $this->get_sql_and_params(true);
list($sql, $params) = $this->get_sql_and_params();
$total = $DB->count_records_sql($countsql, $countparams);
$this->pagesize($pagesize, $total);
$this->rawdata = $DB->get_records_sql($sql, $params, $this->get_page_start(), $this->get_page_size());
// Set initial bars.
if ($useinitialsbar) {
$this->initialbars($total > $pagesize);
}
}
/**
* Notification to display when there are no results.
*/
public function print_nothing_to_display() {
global $OUTPUT;
echo $OUTPUT->notification(get_string('moodlenet:nosharedresources'), 'info');
}
}
+38
View File
@@ -82,4 +82,42 @@ class utilities {
return $supporturl;
}
/**
* Check the user has a valid capability in any course they are enrolled in.
*
* @param int $userid The user id to query
* @return string Returns 'yes' if capability found, 'no' if not
*/
public static function does_user_have_capability_in_any_course(int $userid): string {
// We are checking this way because we are not always in the course context and need
// a way to retrieve the user's courses to see if any of them have the correct capability.
$capabilities = [
'moodle/moodlenet:sharecourse',
'moodle/moodlenet:shareactivity'
];
// We are using 'no' instead of false to avoid confusing a cache key
// that was not found (returns false) with a user who does not have the capablity.
$isallowed = 'no';
$cache = \cache::make('core', 'moodlenet_usercanshare');
$cachedvalue = $cache->get($userid);
if ($cachedvalue === false) {
foreach ($capabilities as $capability) {
// Find at least one course that contains a capability match.
$course = get_user_capability_course($capability, $userid, true, '', 'id', 1);
if (!empty($course)) {
// Set the cache to 'yes' and break out of the loop.
$isallowed = 'yes';
break;
}
}
$cache->set($userid, $isallowed);
} else {
$isallowed = $cachedvalue;
}
return $isallowed;
}
}
+13
View File
@@ -586,4 +586,17 @@ $definitions = array(
'canuselocalstore' => true,
'staticaccelerationsize' => 100,
],
// Cache if a user has the capability to share to MoodleNet.
'moodlenet_usercanshare' => [
'mode' => cache_store::MODE_SESSION,
'simplekeys' => true,
'simpledata' => true,
'ttl' => 1800,
'invalidationevents' => [
'changesincoursecat',
'changesincategoryenrolment',
'changesincourse',
],
],
);
+6 -1
View File
@@ -4637,7 +4637,7 @@ class settings_navigation extends navigation_node {
$coursenode->force_open();
}
// Share course to moodlenet.
// MoodleNet links.
$usercanshare = utilities::can_user_share($coursecontext, $USER->id, 'course');
$issuerid = get_config('moodlenet', 'oauthservice');
try {
@@ -4650,8 +4650,13 @@ class settings_navigation extends navigation_node {
'data-type' => 'course',
'data-sharetype' => 'resource',
]);
// Share course to MoodleNet link.
$coursenode->add(get_string('moodlenet:sharetomoodlenet', 'moodle'),
$action, self::TYPE_SETTING, null, 'exportcoursetomoodlenet')->set_force_into_more_menu(true);
// MoodleNet share progress link.
$url = new moodle_url('/moodlenet/shareprogress.php');
$coursenode->add(get_string('moodlenet:shareprogress'),
$url, self::TYPE_SETTING, null, 'moodlenetshareprogress')->set_force_into_more_menu(true);
}
} catch (dml_missing_record_exception $e) {
debugging("Invalid MoodleNet OAuth 2 service set in site administration: 'moodlenet | oauthservice'. " .
@@ -68,3 +68,27 @@ Feature: MoodleNet outbound send activity
And I press "Allow" and switch to main window
And I should see "Saved to MoodleNet drafts"
And "Go to MoodleNet drafts" "link" should exist in the "Share to MoodleNet" "dialogue"
@javascript
Scenario: User can see their shared resources on the MoodleNet share progress page
Given I am on the "C1" course page logged in as teacher1
When I navigate to "MoodleNet share progress" in current page administration
And I should see "There are no shared resources to display at this time"
And I am on "C1" course homepage
And I navigate to "Share to MoodleNet" in current page administration
And I click on "Share" "button" in the "Share to MoodleNet" "dialogue"
And I switch to "moodlenet_auth" window
And I press "Allow" and switch to main window
And I click on "Close" "button" in the "Share to MoodleNet" "dialogue"
And I am on the "Test Assignment 1" "assign activity" page
And I navigate to "Share to MoodleNet" in current page administration
And I click on "Share" "button" in the "Share to MoodleNet" "dialogue"
And I click on "Close" "button" in the "Share to MoodleNet" "dialogue"
And I am on "C1" course homepage
And I navigate to "MoodleNet share progress" in current page administration
Then "Test course 1" row "Name" column of "generaltable" table should contain "Test course 1"
And "Test course 1" row "Type" column of "generaltable" table should contain "Course"
And "Test course 1" row "Send status" column of "generaltable" table should contain "Sent"
And "Test Assignment 1" row "Name" column of "generaltable" table should contain "Test Assignment 1"
And "Test Assignment 1" row "Type" column of "generaltable" table should contain "Assignment"
And "Test Assignment 1" row "Send status" column of "generaltable" table should contain "Sent"
+40
View File
@@ -150,4 +150,44 @@ class utilities_test extends \advanced_testcase {
assign_capability('moodle/moodlenet:sharecourse', CAP_PROHIBIT, $editingteacherrole->id, $this->coursecontext);
$this->assertFalse(utilities::can_user_share($this->coursecontext, $teacher2->id, 'course'));
}
/**
* Test does_user_have_capability_in_any_course method.
*
* @covers ::does_user_have_capability_in_any_course
* @return void
*/
public function test_does_user_have_capability_in_any_course() {
global $DB;
// Prepare data.
$teacher1 = $this->generator->create_user();
$student1 = $this->generator->create_user();
$teacherrole = $DB->get_record('role', ['shortname' => 'teacher'], 'id', MUST_EXIST);
$studentrole = $DB->get_record('role', ['shortname' => 'student'], 'id', MUST_EXIST);
// Enrol users,
$this->generator->enrol_user($teacher1->id, $this->course->id, $teacherrole->id);
$this->generator->enrol_user($student1->id, $this->course->id, $studentrole->id);
// Assign a valid capability to the teacher.
assign_capability('moodle/moodlenet:shareactivity', CAP_ALLOW, $teacherrole->id, $this->coursecontext);
// Check the method's results are as expected (this is cached).
$this->assertSame('yes', utilities::does_user_have_capability_in_any_course($teacher1->id));
$this->assertSame('no', utilities::does_user_have_capability_in_any_course($student1->id));
// Compare to cache.
$teachercachedvalue = \cache::make('core', 'moodlenet_usercanshare')->get($teacher1->id);
$this->assertSame('yes', $teachercachedvalue);
$studentcachedvalue = \cache::make('core', 'moodlenet_usercanshare')->get($student1->id);
$this->assertSame('no', $studentcachedvalue);
// Change the teacher's role and check the moodlenet_usercanshare cache is invalidated for everyone.
$this->generator->role_assign($studentrole->id, $teacher1->id, $this->coursecontext->id);
$teachercachedvalue = \cache::make('core', 'moodlenet_usercanshare')->get($teacher1->id);
$this->assertFalse($teachercachedvalue);
$studentcachedvalue = \cache::make('core', 'moodlenet_usercanshare')->get($student1->id);
$this->assertFalse($studentcachedvalue);
}
}
+56
View File
@@ -0,0 +1,56 @@
<?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/>.
/**
* View the progress of MoodleNet shares.
*
* @package core
* @copyright 2023 David Woloszyn <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core\moodlenet\utilities;
require_once('../config.php');
require_login();
if (isguestuser()) {
throw new \moodle_exception('noguest');
}
// Capability was not found.
if (utilities::does_user_have_capability_in_any_course($USER->id) === 'no') {
throw new \moodle_exception('nocapabilitytousethisservice');
}
$pageurl = $CFG->wwwroot . '/moodlenet/shareprogress.php';
$PAGE->set_url($pageurl);
$PAGE->set_context(context_system::instance());
$PAGE->set_title(get_string('moodlenet:shareprogress'));
$PAGE->set_heading(get_string('moodlenet:shareprogress'));
$PAGE->set_pagelayout('standard');
echo $OUTPUT->header();
// Intro paragraph.
echo html_writer::div(get_string('moodlenet:shareprogressinfo'), 'mb-4');
// Build table.
$table = new core\moodlenet\share_progress_table('moodlenet-share-progress', $pageurl, $USER->id);
$perpage = $table->get_default_per_page();
$table->out($perpage, true);
echo $OUTPUT->footer();
+1 -1
View File
@@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2023090800.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2023090800.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '4.3dev+ (Build: 20230908)'; // Human-friendly version name