. /** * 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\premium; 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"], ]; if ((premium::enabled() && \get_config("local_treestudyplan", "enablecoach"))) { // Also include the coach role if enabled. $navitems["/local/treestudyplan/coach.php"] = ["included" => false, "strkey" => "link_coach"]; } // 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, $DB; $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", []), 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", []), 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", []), 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", []), 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"; } $coachsql = "SELECT COUNT('id') FROM {local_treestudyplan_coach} c INNER JOIN {local_treestudyplan} t ON c.studyplan_id = t.id WHERE c.user_id = :user_id"; if ((premium::enabled() && \get_config("local_treestudyplan", "enablecoach")) && (has_capability('local/treestudyplan:coach', context_system::instance()) || webservicehelper::has_capability_in_any_category('local/treestudyplan:coach') ) && $DB->count_records_sql($coachsql, ["user_id" => $USER->id]) > 0 ) { $node = navigation_node::create( get_string("link_coach", "local_treestudyplan"), new moodle_url($CFG->wwwroot . "/local/treestudyplan/coach.php", []), global_navigation::TYPE_SYSTEM , null, "local_treestudyplan_coach", new pix_icon("viewplans", '', 'local_treestudyplan') ); $node->showinflatnavigation = true; $node->showinsecondarynavigation = true; $navigation->add_node($node, 'mycourses'); } else { $hideprimaryhrefs[] = "/local/treestudyplan/coach.php"; } } else { $hideprimaryhrefs[] = "/local/treestudyplan/myreport.php"; $hideprimaryhrefs[] = "/local/treestudyplan/edit-plan.php"; $hideprimaryhrefs[] = "/local/treestudyplan/view-plan.php"; $hideprimaryhrefs[] = "/local/treestudyplan/coach.php"; } // Create invitenode node. $invitenode = navigation_node::create( get_string("nav_invited", "local_treestudyplan"), new moodle_url($CFG->wwwroot . "/local/treestudyplan/invited.php", []), 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', 'hidePrimary', [$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", ['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'] .= "

\n"; } $body = get_string('invite_mail_text', 'local_treestudyplan', $data); $subject = get_string('invite_mail_subject', 'local_treestudyplan', $data); $html = " . . {$subject} {$body} "; $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; $studyplanfilecaps = ["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", "studyplanpage"])) { // The args is an array containing [itemid, path]. // Fetch the itemid from the path. $itemid = array_shift($args); // Studyplan icons and description images are not secret, so don't overdo it on access control... if (true) { // Extract the filename / filepath from the $args array. $filename = array_pop($args); // The last item in the $args array. if (empty($args)) { // Var $args is empty => the path is '/'. $filepath = '/'; } else { // Var $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)) { // Var $args is empty => the path is '/'. $filepath = '/'; } else { // Var $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; } }