diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..14bc68c7cc2ac75f3ade400869623729943f03ae --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/nbproject/private/ \ No newline at end of file diff --git a/amd/build/sembasednav.min.js b/amd/build/sembasednav.min.js new file mode 100644 index 0000000000000000000000000000000000000000..2746e1d6f2e060c807757b3633ab4f513a6bcbb0 --- /dev/null +++ b/amd/build/sembasednav.min.js @@ -0,0 +1 @@ +define(["jquery"],function(t){function a(a){t('.list-group-item[data-key="'+a+'"]').click(function(){t(this);!function(a,i){t('.list-group-item[data-parent-key="'+i+'"]').each(function(){var a=t(this),i=a.data("key");a.addClass("localboostnavigationcollapsedparent"),t('.list-group-item[data-parent-key="'+i+'"]').addClass("localboostnavigationcollapsedchild")})}(0,a)})}return{closeAllChildNodes:function(t){a(t)}}}); \ No newline at end of file diff --git a/amd/src/sembasednav.js b/amd/src/sembasednav.js new file mode 100644 index 0000000000000000000000000000000000000000..8c8c2bd0baa1c68375e91f886cb36763740206e9 --- /dev/null +++ b/amd/src/sembasednav.js @@ -0,0 +1,37 @@ +define(['jquery'], function ($) { + + /** + * Handles the actual closing of nodes + * @param {Object} $node + * @param {String} nodeName + */ + function handleClosingNodes($node, nodeName) { + $('.list-group-item[data-parent-key="' + nodeName + '"]').each(function () { + var $this = $(this); + var key = $this.data('key'); + $this.addClass('localboostnavigationcollapsedparent'); + $('.list-group-item[data-parent-key="' + key + '"]').addClass('localboostnavigationcollapsedchild'); + }); + } + + /** + * Closes all child nodes of given node name + * Fixes second level children (e.g Semester modules) not closing when mycourses gets closed + * @param {String} nodeName + */ + function closeAllChildNodes(nodeName) { + var $node = $('.list-group-item[data-key="' + nodeName + '"]'); + + $node.click(function () { + var $this = $(this); + + handleClosingNodes($this, nodeName); + }); + } + + return { + closeAllChildNodes: function (nodeName) { + closeAllChildNodes(nodeName); + } + }; +}); \ No newline at end of file diff --git a/lang/en/local_sembasednav.php b/lang/en/local_sembasednav.php index db6d42d838f5705bba8b72875eeef4d5275a4df3..28078692f45fd0ec996ee86287229fc99139bab1 100644 --- a/lang/en/local_sembasednav.php +++ b/lang/en/local_sembasednav.php @@ -19,10 +19,25 @@ * * @package local_sembasednav * @category string - * @copyright 2020 Peter Fricke <peter.fricke@hs-hannover.de> + * @copyright 2020 Julian Wendling <julian.wendling@stud.hs-hannover.de> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); -$string['pluginname'] = 'semester based navigation'; +$string['pluginname'] = 'Semester Based Navigation'; + +$string['setting_nosemestername'] = 'Kurs ohne Semester'; +$string['setting_nosemestername_desc'] = 'Name des Semesters in dem Kurse ohne Semester angezeigt werden'; + +$string['setting_nodescount'] = 'Anzahl der anzuzeigenden Semester'; +$string['setting_nodescount_desc'] = 'Semesterunabhängiger Nodes nicht mit einbegriffen'; + +$string['setting_semesterorder'] = 'Semesterreihenfolge'; +$string['setting_semesterorder_desc'] = 'Semesterreihenfolge in der Navigation'; + +$string['setting_specialNodes'] = 'Position der Special Nodes'; +$string['setting_specialNodes_desc'] = 'z.B. Semesterunabhängig'; + +$string['setting_openfirstnode'] = 'Erste Semester Node aufgeklappt darstellen'; +$string['setting_openfirstnode_desc'] = ''; diff --git a/lib.php b/lib.php new file mode 100644 index 0000000000000000000000000000000000000000..0dd94d3a6a50bba73e0d42e1278c023cdc599658 --- /dev/null +++ b/lib.php @@ -0,0 +1,309 @@ +<?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/>. + +/** + * @package local_sembasednav + * @copyright 2020 Julian Wendling, Hochschule Hannover <julian.wendling@stud.hs-hannover.de> + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +function local_sembasednav_extend_navigation(global_navigation $navigation) +{ + global $PAGE, $CFG; + + $myCoursesNode = $navigation->find('mycourses', global_navigation::TYPE_ROOTNODE); + $myCoursesChildNodes = $myCoursesNode->get_children_key_list(); + + $maxSemesterNodes = get_config('sembasednav', 'setting_nodescount'); + $openFirstSemester = get_config('sembasednav', 'setting_openfirstnode'); + $semesterDescending = get_config('sembasednav', 'setting_semesterorder'); + $specialNodesFirst = get_config('sembasednav', 'setting_specialNodes'); + + // Register JS to close all child nodes from root node (mycourse) + $PAGE->requires->js_call_amd('local_sembasednav/sembasednav', 'closeAllChildNodes', [$myCoursesNode->key]); + + try { + $courses = enrol_get_my_courses(); + } catch (Exception $e) { + echo $e->getMessage(); + $courses = []; + } + + // Hides courses that are shown by default + foreach ($myCoursesChildNodes as $cn) { + $myCoursesNode->find($cn, null)->showinflatnavigation = false; + } + + // Nodes that are excluded from max semester count - will always be shown + $noSemesterAssignedName = get_config('sembasednav', 'setting_nosemestername'); + $specialNodes = ["Semesterunabhängig", $noSemesterAssignedName]; + $specialNodesList = []; + $mySemesters = []; + + // Sort semester depending on setting + if ($semesterDescending == 0) { + // Descending + usort($courses, function ($a, $b) { + $aId = get_semester_id($a->id); + $bId = get_semester_id($b->id); + + return strcasecmp($bId, $aId); + }); + } else { + // Ascending + usort($courses, function ($a, $b) { + $aId = get_semester_id($a->id); + $bId = get_semester_id($b->id); + + return strcasecmp($aId, $bId); + }); + } + + // Assign all modules and semesters + foreach ($courses as $c) { + $semesterName = get_semester_name($c->id); + + $courseName = empty($CFG->navshowfullcoursenames) ? $c->shortname : $c->fullname; + + if (in_array($semesterName, $specialNodes, true)) { + $specialNodesList[$semesterName][] = $courseName; + } else { + if (count($mySemesters) >= $maxSemesterNodes) { + continue; + } + $mySemesters[$semesterName][] = $courseName; + } + } + + if ($specialNodesFirst == 0) + add_semester_nodes($specialNodesList, $myCoursesNode); + + // Add all semester nodes + add_semester_nodes($mySemesters, $myCoursesNode); + + if ($openFirstSemester) + open_first_semester_node($mySemesters, $myCoursesNode); + + // Add all special nodes + if ($specialNodesFirst == 1) + add_semester_nodes($specialNodesList, $myCoursesNode); + + // Sorts course node alphabetically + usort($courses, function ($a, $b) { + $aId = $a->shortname; + $bId = $b->shortname; + + return strcasecmp($aId, $bId); + }); + + // Creates all course nodes and assigns them to their semester node + foreach ($courses as $c) { + $semesterName = get_semester_name($c->id); + $semesterKey = create_node_key($semesterName); + $semesterNode = $navigation->find($semesterKey, null); + + if (!$semesterNode) { + continue; + } + + $courseName = empty($CFG->navshowfullcoursenames) ? $c->shortname : $c->fullname; + $courseKey = create_node_key($courseName); + + $courseNode = navigation_node::create($courseName, new moodle_url('/course/view.php', array('id' => $c->id)), global_navigation::TYPE_COURSE, $courseName, $courseKey, new pix_icon('i/course', 'grades')); + $courseNode->showinflatnavigation = true; + $courseNode->add_class('p-l-3'); + $courseNode->add_class('localboostnavigationcollapsiblechild'); + if (!$semesterNode->forceopen) + $courseNode->add_class('localboostnavigationcollapsedchild'); + + $semesterNode->add_node($courseNode); + } + + +} + +/** + * Adds all semester nodes to given parent node and registers + * them for collapse navigation js + * @param array $semesterList + * @param $parentNode + * @throws coding_exception + * @throws dml_exception + */ +function add_semester_nodes(array $semesterList, $parentNode) +{ + global $PAGE; + + $collapsenodesforjs = []; + + foreach ($semesterList as $key => $value) { + $semesterNode = create_semester_node($key); + + if (!in_array($semesterNode->key, $collapsenodesforjs)) { + $collapsenodesforjs[] = $semesterNode->key; + } + + $parentNode->add_node($semesterNode); + } + + // Apply collapse navigation js to every semeseter + if (!empty($collapsenodesforjs)) { + $PAGE->requires->js_call_amd('local_boostnavigation/collapsenavdrawernodes', 'init', + [$collapsenodesforjs, []]); + + foreach ($collapsenodesforjs as $node) { + user_preference_allow_ajax_update('local_boostnavigation-collapse_' . $node . 'node', PARAM_BOOL); + } + } +} + +/** + * @param array $semesterList + * @param $myCourseNode + */ +function open_first_semester_node(array $semesterList, $myCourseNode) +{ + $semesterKeys = array_keys($semesterList); + + if (empty($semesterKeys[0])) + return; + + $firstSemesterKey = create_node_key($semesterKeys[0]); + + $semesterNode = $myCourseNode->find($firstSemesterKey, global_navigation::TYPE_CUSTOM); + + $semesterNode->forceopen = true; + + $semesterNode->remove_class('localboostnavigationcollapsedparent'); +} + +/** + * Creates a semester node with needed cs classes for collapse navigation js + * @param string $semesterName + * @return navigation_node + * @throws coding_exception + * @throws dml_exception + */ +function create_semester_node(string $semesterName) +{ + $localBoostnavigationConfig = get_config('local_boostnavigation'); + $userprefmycoursesnode = get_user_preferences('local_boostnavigation-collapse_mycoursesnode', + $localBoostnavigationConfig->collapsemycoursesnodedefault); + + $semesterKey = create_node_key($semesterName); + + $semesterNode = navigation_node::create($semesterName, new moodle_url('/course/view.php', null), global_navigation::TYPE_CUSTOM, null, $semesterKey, null); + $semesterNode->showinflatnavigation = true; + + $semesterNode->add_class('localboostnavigationcollapsibleparent'); + $semesterNode->add_class('localboostnavigationcollapsedparent'); + $semesterNode->add_class('localboostnavigationcollapsiblechild'); + + if ($userprefmycoursesnode == 1) { + $semesterNode->add_class('localboostnavigationcollapsedchild'); + } + + return $semesterNode; +} + +/** + * Creates a whitespace and slash free semester key + * @param string $semesterName + * @return string + */ +function create_node_key(string $semesterName) +{ + return "node-" . str_replace([" ", "/"], "-", $semesterName); +} + +/** + * Returns semester name of given course id. + * If no semester is found, return no semester assigned name. + * @param int $id + * @return string + */ +function get_semester_name(int $id) +{ + global $DB; + + $noSemesterAssigned = get_config('sembasednav', 'setting_nosemestername'); + $semesterName = ''; + + try { + $semFieldName = $DB->get_record('customfield_field', array('type' => 'semester'), '*', MUST_EXIST); + } catch (Exception $e) { + return $noSemesterAssigned; + } + + try { + $allCustomFields = get_course_metadata($id); + $semesterField = $allCustomFields[$semFieldName->shortname]; + $semesterValue = preg_replace('/[^0-9]/', '', $semesterField); // extract semester int from string + $semesterName = \customfield_semester\data_controller::get_name_for_semester((int)$semesterValue); + + } catch (Exception $e) { + } + + return empty($semesterName) || $semesterName === '' ? $noSemesterAssigned : $semesterName; +} + +/** + * @param int $id + * @return string + */ +function get_semester_id(int $id) +{ + global $DB; + + $semesterValue = ''; + + try { + $semFieldName = $DB->get_record('customfield_field', array('type' => 'semester'), '*', MUST_EXIST); + } catch (Exception $e) { + return 0; + } + + try { + $allCustomFields = get_course_metadata($id); + $semesterField = $allCustomFields[$semFieldName->shortname]; + $semesterValue = preg_replace('/[^0-9]/', '', $semesterField); // extract semester int from string + } catch (Exception $e) { + } + + return empty($semesterValue) || $semesterValue === '' ? 0 : $semesterValue; +} + +// https://docs.moodle.org/dev/Custom_fields_API#Example_code_for_course_custom_fields +/** + * Used to access course semester field + * @param $courseid + * @return array + * @throws moodle_exception + */ +function get_course_metadata($courseid) +{ + $handler = \core_customfield\handler::get_handler('core_course', 'course'); + $data = $handler->get_instance_data($courseid); + $metadata = []; + foreach ($data as $d) { + if (empty($d->get_value())) { + continue; + } + $cat = $d->get_field()->get_category()->get('name'); + $metadata[$d->get_field()->get('shortname')] = $cat . ': ' . $d->get_value(); + } + return $metadata; +} \ No newline at end of file diff --git a/settings.php b/settings.php index 15e10f431758335561d9dda92159c052293b2e38..aa2e8b872873ef29f24a2c9b3179a24c3065d2f1 100644 --- a/settings.php +++ b/settings.php @@ -19,13 +19,61 @@ * * @package local_sembasednav * @category admin - * @copyright 2020 Peter Fricke <peter.fricke@hs-hannover.de> + * @copyright 2020 Julian Wendling <julian.wendling@stud.hs-hannover.de> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); +require_once(__DIR__ . '/lib.php'); -if ($ADMIN->fulltree) { - // TODO: Define the plugin settings page. - // https://docs.moodle.org/dev/Admin_settings +if ($hassiteconfig) { + // New settings page. + $page = new admin_settingpage('sembasednav', + get_string('pluginname', 'local_sembasednav', null, true)); + + if ($ADMIN->fulltree) { + // Default-Settings auf 0 da ein aktueller Bug besteht, der die Default-Settings nicht übernimmt. + // Name for courses without a semester + $setting = new admin_setting_configtext('sembasednav/setting_nosemestername', + get_string('setting_nosemestername', 'local_sembasednav', null, true), + get_string('setting_nosemestername_desc', 'local_sembasednav', null, true), 'Ohne Semester', PARAM_TEXT); + $page->add($setting); + + // Default-Settings auf 0 da ein aktueller Bug besteht, der die Default-Settings nicht übernimmt. + // Visible semester nodes + $setting = new admin_setting_configtext('sembasednav/setting_nodescount', + get_string('setting_nodescount', 'local_sembasednav', null, true), + get_string('setting_nodescount_desc', 'local_sembasednav', null, true), 0, PARAM_INT); + $page->add($setting); + + // Default-Settings auf 0 da ein aktueller Bug besteht, der die Default-Settings nicht übernimmt. + // Open first semester + $page->add(new admin_setting_configcheckbox('sembasednav/setting_openfirstnode', + get_string('setting_openfirstnode', 'local_sembasednav', null, true), + get_string('setting_openfirstnode_desc', 'local_sembasednav', null, true), + 0)); + + // Semester order + $name = 'sembasednav/setting_semesterorder'; + $title = get_string('setting_semesterorder', 'local_sembasednav'); + $description = get_string('setting_semesterorder_desc', 'local_sembasednav'); + $choices[0] = 'Absteigend'; + $choices[1] = 'Aufsteigend'; + $setting = new admin_setting_configselect($name, $title, $description, null, $choices); + $setting->set_updatedcallback('theme_reset_all_caches'); + $page->add($setting); + + // Position of special nodes + $name = 'sembasednav/setting_specialNodes'; + $title = get_string('setting_specialNodes', 'local_sembasednav'); + $description = get_string('setting_specialNodes_desc', 'local_sembasednav'); + $choices[0] = 'Oberhalb der Semester'; + $choices[1] = 'Unterhalb der Semester'; + $setting = new admin_setting_configselect($name, $title, $description, null, $choices); + $setting->set_updatedcallback('theme_reset_all_caches'); + $page->add($setting); + } + + // Add settings page to the appearance setting category. + $ADMIN->add('appearance', $page); } diff --git a/version.php b/version.php index 4b4d3448ac15e3f3a19c3f571bfc95b93a89b613..ef096afe2759647ba57b2b731d3ae22fac5080ac 100644 --- a/version.php +++ b/version.php @@ -31,4 +31,5 @@ $plugin->requires = 2019052000; $plugin->maturity = MATURITY_ALPHA; $plugin->dependencies = [ 'customfield_semester' => 2020041301, -]; + 'local_boostnavigation' => 2020111600 +]; \ No newline at end of file