moodle_local_treestudyplan/lib.php
2023-12-11 23:52:00 +01:00

516 lines
20 KiB
PHP

<?php
// This file is part of the Studyplan plugin for Moodle
//
// 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 <https://www.gnu.org/licenses/>.
/**
* Moodle hook functions and some internally used functions
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/course/modlib.php');
use local_treestudyplan\local\helpers\webservicehelper;
use \local_treestudyplan\studyplan;
use local_treestudyplan\studyplanpage;
/**
* Describe editor options
* @param context $context Context for options
* @return array Editor options
*/
function local_treestudyplan_unit_get_editor_options(context $context) {
global $CFG;
return ['subdirs' => 1,
'maxbytes' => $CFG->maxbytes,
'maxfiles' => -1,
'changeformat' => 1,
'context' => $context,
'noclean' => 1,
'trusttext' => 0];
}
/**
* Create primary navigation links for studyplan if needed
*/
function local_treestudyplan_autofill_customusermenuitems() {
if (get_config("local_treestudyplan", "primary_nav_autofill")) {
$lang = current_language();
$navitems = [
"/local/treestudyplan/myreport.php" => ["included" => false, "strkey" => "link_myreport"],
"/local/treestudyplan/view-plan.php" => ["included" => false, "strkey" => "link_viewplan"],
"/local/treestudyplan/edit-plan.php" => ["included" => false, "strkey" => "link_editplan"],
];
// Load the custom menu items from config.
$custommenuitems = get_config("core", "custommenuitems");
// Scan through all the lines to see if it is a link to one of our nav items in the current language.
$lines = explode("\n", $custommenuitems);
$links = array_keys($navitems);
foreach ($lines as $line) {
$parms = explode('|', $line);
if (count($parms) > 3) {
$link = trim($parms[1]);
if (trim($parms[3]) == $lang && in_array($link, $links)) {
// Register the link as already included if it is found.
$navitems[$link]["included"] = true;
}
}
}
// List through all the links to see if we need to add one or more.
foreach ($navitems as $link => $details) {
if (!$details["included"]) {
$line = implode("|", [
get_string($details["strkey"], "local_treestudyplan"), // Menu text.
$link, // Link.
'', // Tooltip.
$lang, // Language code.
" #Automatically added by studyplan plugin. See setting 'primary_nav_autofill' to disable this"
]);
$custommenuitems = trim($custommenuitems)."\n".$line;
}
}
// Store the modified custom menu items.
set_config("custommenuitems", $custommenuitems);
}
}
/**
* Hook to extend navigation
* @param global_navigation $navigation Navigation object
*/
function local_treestudyplan_extend_navigation(global_navigation $navigation) {
global $CFG, $PAGE, $COURSE, $USER;
$systemcontext = context_system::instance();
/* Moodle 4.0-4.2 do not yet support customizing the primary navigation bar (it is a planned feature though).
For now, go to theme settings and add the following into "Custom menu items".
[your name for my studyplan]|/local/treestudyplan/myreport.php.
[your name for studyplan viewing]|/local/treestudyplan/view-plan.php.
[your name for studyplan managing]|/local/treestudyplan/edit-plan.php.
For example:.
Mijn studieplan|/local/treestudyplan/myreport.php.
Studieplannen|/local/treestudyplan/view-plan.php.
Studieplannen beheren|/local/treestudyplan/edit-plan.php.
Using some javascript magic we'll hide the links that are not accessible.
(Since the Output API does not easily support inline style tags, adding one through Javascript is easier,.
and not much more complex than loading a separate stylesheet for each link we want to hide).
We will add all the hrefs that should be hidden to this variable below.
*/
/*
In addition, the function local_treestudyplan_autofill_customusermenuitems() called below will
automatically generate the required lines if they are missing...
*/
local_treestudyplan_autofill_customusermenuitems();
$hideprimaryhrefs = [];
if ($USER->id > 1) {
// Don't show if user is not logged in (id == 0) or is guest user (id == 1).
$userstudyplans = studyplan::find_for_user($USER->id);
if (!empty($userstudyplans)) {
// Create studyplan node.
$node = navigation_node::create(
get_string("link_myreport", "local_treestudyplan"),
new moodle_url($CFG->wwwroot . "/local/treestudyplan/myreport.php", array()),
global_navigation::TYPE_SYSTEM,
null,
"local_treestudyplan_myreport",
new pix_icon("myreport", '', 'local_treestudyplan')
);
$node->showinflatnavigation = true;
$node->showinsecondarynavigation = true;
// Create invitenode node.
$invitenode = navigation_node::create(
get_string("manage_invites", "local_treestudyplan"),
new moodle_url($CFG->wwwroot . "/local/treestudyplan/invitations.php", array()),
global_navigation::TYPE_CUSTOM ,
null,
"local_treestudyplan_invitemgmt",
new pix_icon("invitemgmt", '', 'local_treestudyplan')
);
$invitenode->showinflatnavigation = false;
$node->add_node($invitenode);
$navigation->add_node($node, 'mycourses');
} else {
$hideprimaryhrefs[] = "/local/treestudyplan/myreport.php";
}
if ( has_capability('local/treestudyplan:viewuserreports', context_system::instance())
|| webservicehelper::has_capability_in_any_category('local/treestudyplan:viewuserreports')) {
$node = navigation_node::create(
get_string("link_viewplan", "local_treestudyplan"),
new moodle_url($CFG->wwwroot . "/local/treestudyplan/view-plan.php", array()),
global_navigation::TYPE_SYSTEM ,
null,
"local_treestudyplan_viewplan",
new pix_icon("viewplans", '', 'local_treestudyplan')
);
$node->showinflatnavigation = true;
$node->showinsecondarynavigation = true;
$navigation->add_node($node, 'mycourses');
} else {
$hideprimaryhrefs[] = "/local/treestudyplan/view-plan.php";
}
if ( has_capability('local/treestudyplan:editstudyplan', context_system::instance())
|| webservicehelper::has_capability_in_any_category('local/treestudyplan:editstudyplan')
) {
$node = navigation_node::create(
get_string("link_editplan", "local_treestudyplan"),
new moodle_url($CFG->wwwroot . "/local/treestudyplan/edit-plan.php", array()),
global_navigation::TYPE_SYSTEM ,
null,
"local_treestudyplan_editplan",
new pix_icon("viewplans", '', 'local_treestudyplan')
);
$node->showinflatnavigation = true;
$node->showinsecondarynavigation = true;
$navigation->add_node($node, 'mycourses');
} else {
$hideprimaryhrefs[] = "/local/treestudyplan/edit-plan.php";
}
} else {
$hideprimaryhrefs[] = "/local/treestudyplan/myreport.php";
$hideprimaryhrefs[] = "/local/treestudyplan/edit-plan.php";
$hideprimaryhrefs[] = "/local/treestudyplan/view-plan.php";
}
// Create invitenode node.
$invitenode = navigation_node::create(
get_string("nav_invited", "local_treestudyplan"),
new moodle_url($CFG->wwwroot . "/local/treestudyplan/invited.php", array()),
global_navigation::TYPE_USER ,
null,
"local_treestudyplan_invitemgmt",
new pix_icon("nav_invited", '', 'local_treestudyplan')
);
$invitenode->showinflatnavigation = false;
$navigation->add_node($invitenode, 'mycourses');
// Now using some javascript magic, we'll hide the links that are not accessible.
$PAGE->requires->js_call_amd('local_treestudyplan/primary-nav-tools', 'hide_primary', [$hideprimaryhrefs]);
}
/**
* Hook to extend navigation in category view
* @param mixed $navigation
* @param context_coursecat $coursecategorycontext
*/
function local_treestudyplan_extend_navigation_category_settings($navigation, context_coursecat $coursecategorycontext) {
global $CFG, $PAGE;
$categoryid = $coursecategorycontext->instanceid;
if (has_capability('local/treestudyplan:editstudyplan', $coursecategorycontext)) {
$node = $navigation->add(
get_string('treestudyplan:editstudyplan', "local_treestudyplan"),
new moodle_url($CFG->wwwroot . "/local/treestudyplan/edit-plan.php", ["categoryid" => $categoryid]),
global_navigation::TYPE_CATEGORY,
null,
"local_treestudyplan_editplan",
new pix_icon("editplans", '', 'local_treestudyplan')
);
}
if (has_capability('local/treestudyplan:viewuserreports', $coursecategorycontext)) {
$node = $navigation->add(
get_string('link_viewplan', "local_treestudyplan"),
new moodle_url($CFG->wwwroot . "/local/treestudyplan/view-plan.php", ["categoryid" => $categoryid]),
global_navigation::TYPE_CATEGORY,
null,
"local_treestudyplan_viewplan",
new pix_icon("viewplans", '', 'local_treestudyplan')
);
}
}
/**
* Map fontawesome icons for use in flat navigation
* @return array Icon mapping
*
*/
function local_treestudyplan_get_fontawesome_icon_map() {
// Create the icon map with the icons which are used in any case.
$iconmapping = [
'local_treestudyplan:myreport' => 'fa-vcard',
'local_treestudyplan:editplans' => 'fa-share-alt',
'local_treestudyplan:viewplans' => 'fa-share-alt',
];
return $iconmapping;
}
/**
* Helper function to reset the icon system used as updatecallback function when saving some of the plugin's settings.
*/
function local_treestudyplan_reset_fontawesome_icon_map() {
// Reset the icon system cache.
// There is the function \core\output\icon_system::reset_caches() which does seem to be only usable in unit tests.
// Thus, we clear the icon system cache brutally.
$cache = \cache::make('core', 'fontawesomeiconmapping');
$cache->delete('mapping');
// And rebuild it brutally.
$instance = \core\output\icon_system::instance(\core\output\icon_system::FONTAWESOME);
$instance->get_icon_name_map();
}
/**
* Send invitation to invited person
* @param mixed $inviteid Database id of the invitation
*
*/
function local_treestudyplan_send_invite($inviteid) {
global $DB, $USER, $CFG;
$invite = $DB->get_record("local_treestudyplan_invit", array('id' => $inviteid));
$noreply = 'noreply@' . get_host_from_url($CFG->wwwroot);
$mailer = get_mailer();
$mailer->setFrom($noreply, "{$USER->firstname} {$USER->lastname}");
$mailer->addAddress($invite->email, $invite->name);
$mailer->addReplyTo($USER->email, "{$USER->firstname} {$USER->lastname}");
$invitehref = $CFG->wwwroot."/local/treestudyplan/invited.php?key={$invite->invitekey}";
$data = [ 'permissions' => '',
'invitee' => $invite->name,
'sender' => "{$USER->firstname} {$USER->lastname}",
'link' => $invitehref];
if ($invite->allow_details || $invite->allow_calendar || $invite->allow_badges) {
$data['permissions'] = get_string('invite_mail_permissions', 'local_treestudyplan');
$data['permissions'] .= "<ul>\n";
if ($invite->allow_details ) {
$data['permissions'] .= "<li>".get_string('invite_allow_details', 'local_treestudyplan')."</li>\n";
}
if ($invite->allow_calendar) {
$data['permissions'] .= "<li>".get_string('invite_allow_calendar', 'local_treestudyplan')."</li>\n";
}
if ($invite->allow_badges) {
$data['permissions'] .= "<li>".get_string('invite_allow_badges', 'local_treestudyplan')."</li>\n";
}
$data['permissions'] .= "</ul></p>\n";
}
$body = get_string('invite_mail_text', 'local_treestudyplan', $data);
$subject = get_string('invite_mail_subject', 'local_treestudyplan', $data);
$html = "
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>.
<html xmlns='http://www.w3.org/1999/xhtml'>.
<head>
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />
<title>{$subject}</title>
<meta name='viewport' content='width=device-width, initial-scale=1.0'/>
</head>
<body>
{$body}
</body>
</html>";
$mailer->isHTML(true);
$mailer->Subject = $subject;
$mailer->Body = $html;
$mailer->AltBody = strip_tags($body);
$mailer->send();
}
/**
* Hook to display fragment of activity/mod settings editor. Used in feature to edit name and description of activity
* @param mixed $args
* @return string Rendered form output HTML
*/
function local_treestudyplan_output_fragment_mod_edit_form($args) {
global $CFG;
global $DB;
$args = (object)$args;
$context = $args->context;
if (empty($args->cmid)) {
return "RANDOM!";
}
// Check the course module exists.
$cm = \get_coursemodule_from_id('', $args->cmid, 0, false, MUST_EXIST);
// Check the course exists.
$course = \get_course($cm->course);
// Require_login.
require_login($course, false, $cm); // Needed to setup proper $COURSE.
list($cm, $context, $module, $data, $cw) = \get_moduleinfo_data($cm, $course);
$modmoodleform = "$CFG->dirroot/mod/$module->name/mod_form.php";
if (file_exists($modmoodleform)) {
require_once($modmoodleform);
} else {
throw new \moodle_exception('noformdesc', 'local_treestudyplan');;
}
$mformclassname = 'mod_'.$module->name.'_mod_form';
$mform = new $mformclassname($data, $cw->section, $cm, $course);
$mform->set_data($data);
return $mform->render();
}
/**
* Serve the files from the myplugin file areas.
*
* @param stdClass $course the course object
* @param stdClass $cm the course module object
* @param stdClass $context the context
* @param string $filearea the name of the file area
* @param array $args extra arguments (itemid, path)
* @param bool $forcedownload whether or not force download
* @param array $options additional options affecting the file serving
* @return bool false if the file not found, just send the file otherwise and do not return anything
*/
function local_treestudyplan_pluginfile(
$course,
$cm,
$context,
string $filearea,
array $args,
bool $forcedownload,
array $options = []
): bool {
global $DB,$USER;
$studyplan_filecaps = ["local/treestudyplan:editstudyplan","local/treestudyplan:viewuserreports"];
// Check the contextlevel is as expected - the studyplan plugin only uses system context for storing files.
// This avoids headaches when moving studyplans between contexts, while the security impact is minimal...
if ($context->contextlevel != CONTEXT_SYSTEM) {
return false;
}
// Make sure the filearea is one of those used by the plugin.
if (in_array($filearea,["studyplan","icon"])) {
// The args is an array containing [itemid, path].
// Fetch the itemid from the path.
$itemid = array_shift($args);
$plan = studyplan::find_by_id($itemid);
$planctx = $plan->context();
// Studyplan icons are not secret, so don't check for access..
if ( true ) {
// Extract the filename / filepath from the $args array
$filename = array_pop($args); // The last item in the $args array.
if (empty($args)) {
// $args is empty => the path is '/'.
$filepath = '/';
} else {
// $args contains the remaining elements of the filepath.
$filepath = '/' . implode('/', $args) . '/';
}
// Retrieve the file from the Files API.
$fs = get_file_storage();
$file = $fs->get_file(\context_system::instance()->id, 'local_treestudyplan', $filearea, $itemid, $filepath, $filename);
if (!$file) {
// The file does not exist.
return false;
}
// We can now send the file back to the browser - in this case with a cache lifetime of 1 day and no filtering.
send_stored_file($file, 24*60*60, 0, $forcedownload, $options);
} else {
return false;
}
} else if (in_array($filearea,["studyplanpage"])) {
// The args is an array containing [itemid, path].
// Fetch the itemid from the path.
$itemid = array_shift($args);
$page = studyplanpage::find_by_id($itemid);
$plan = $page->studyplan();
$planctx = $plan->context();
// Check if the current user has access to this studyplan
if ( webservicehelper::has_capabilities($studyplan_filecaps,$planctx) || $plan->has_linked_user($USER)) {
// Extract the filename / filepath from the $args array
$filename = array_pop($args); // The last item in the $args array.
if (empty($args)) {
// $args is empty => the path is '/'.
$filepath = '/';
} else {
// $args contains the remaining elements of the filepath.
$filepath = '/' . implode('/', $args) . '/';
}
// Retrieve the file from the Files API.
$fs = get_file_storage();
$file = $fs->get_file(\context_system::instance()->id, 'local_treestudyplan', $filearea, $itemid, $filepath, $filename);
if (!$file) {
// The file does not exist.
return false;
}
// We can now send the file back to the browser - in this case with a cache lifetime of 1 day and no filtering.
send_stored_file($file, 24*60*60, 0, $forcedownload, $options);
} else {
return false;
}
} else if (in_array($filearea,['defaulticon'])) {
// The args is an array containing [itemid, path].
// Fetch the itemid from the path.
$itemid = array_shift($args);
// Extract the filename / filepath from the $args array
$filename = array_pop($args); // The last item in the $args array.
if (empty($args)) {
// $args is empty => the path is '/'.
$filepath = '/';
} else {
// $args contains the remaining elements of the filepath.
$filepath = '/' . implode('/', $args) . '/';
}
// Retrieve the file from the Files API.
$fs = get_file_storage();
$file = $fs->get_file(\context_system::instance()->id, 'local_treestudyplan', $filearea, $itemid, $filepath, $filename);
if (!$file) {
// The file does not exist.
return false;
}
// We can now send the file back to the browser - in this case with a cache lifetime of 1 day and no filtering.
send_stored_file($file, 24*60*60, 0, $forcedownload, $options);
} else {
return false;
}
}