From 6970e584bfbcc7f9e9965fd26069d3730dcdc0a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Storhaug?= Date: Wed, 24 Jun 2020 18:01:27 +0200 Subject: [PATCH 01/83] Add initial version --- .editorconfig | 17 ++++ .gitattributes | 1 + .gitignore | 3 + README.md | 2 +- classes/output/issue_registration_block.php | 100 ++++++++++++++++++++ classes/output/renderer.php | 46 +++++++++ db/access.php | 27 ++++++ db/install.xml | 20 ++++ db/upgrade.php | 33 +++++++ edit_form.php | 16 ++++ lang/en/local_qtracker.php | 35 +++++++ lib.php | 39 ++++++++ locallib.php | 22 +++++ settings.php | 35 +++++++ templates/issue_registration_block.mustache | 58 ++++++++++++ version.php | 31 ++++++ 16 files changed, 484 insertions(+), 1 deletion(-) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 classes/output/issue_registration_block.php create mode 100644 classes/output/renderer.php create mode 100644 db/access.php create mode 100755 db/install.xml create mode 100755 db/upgrade.php create mode 100644 edit_form.php create mode 100755 lang/en/local_qtracker.php create mode 100644 lib.php create mode 100644 locallib.php create mode 100644 settings.php create mode 100644 templates/issue_registration_block.mustache create mode 100755 version.php diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..dfe57bd --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.yml] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false +insert_final_newline = true diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a367c48 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vscode + +*.code-workspace diff --git a/README.md b/README.md index b071092..d5f3f33 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# moodle-local_questiontracker +# moodle-local_qtracker :bug: Local Moodle plugin providing issue tracking for Moodle questions. diff --git a/classes/output/issue_registration_block.php b/classes/output/issue_registration_block.php new file mode 100644 index 0000000..984a8fb --- /dev/null +++ b/classes/output/issue_registration_block.php @@ -0,0 +1,100 @@ +. + +/** + * Renderable for block + * + * @package block_studiosity + * @author Andrew Madden + * @copyright 2019 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_qtracker\output; + +defined('MOODLE_INTERNAL') || die; + +use mod_quiz\local\structure\slot_random; +use renderable; +use renderer_base; +use templatable; +use stdClass; + + +/** + * Studiosity block class. + * + * @package block_studiosity + * @author Andrew Madden + * @copyright 2019 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class issue_registration_block implements renderable, templatable { + + /** @var \question_definition[] Array of {@link \question_definition} */ + public $questions = array(); + + /** @var int User ID */ + public $userid; + + /** @var array of stdclass strings to display */ + public $slots = array(); + + /** + * Construct the contents of the block + * @param \question_definition[] $questions The questions that can be filed issues for. + * @param int $userid The id of the user. + * @throws \coding_exception If called at incorrect times + */ + public function __construct(array $questions, $userid, $slots=null) { + $this->questions = $questions; + $this->userid = $userid; + $this->slots = $slots; + if (!is_null($slots)) { + if (count($questions) != count($slots)) { + throw new \coding_exception('The number of questions and slots does not match.'); + } + } + } + + /** + * Export the data. + * + * @param renderer_base $output + * @return stdClass Data to be used for the template + */ + public function export_for_template(renderer_base $output) { + + $data = new stdClass(); + $data->userid = $this->userid; + $data->lol = "lol this is a test"; + $data->name = "test"; + $data->id = "test0"; + $data->size = 3; + + $value = 22; + $name = "OK"; + + foreach ($this->questions as $key => $question) { + $questions[] = [ + 'name' => $question->name, + 'selected' => true + ]; + } + $data->questions = $questions; + return $data; + } +} \ No newline at end of file diff --git a/classes/output/renderer.php b/classes/output/renderer.php new file mode 100644 index 0000000..523f88d --- /dev/null +++ b/classes/output/renderer.php @@ -0,0 +1,46 @@ +. + +namespace local_qtracker\output; + +use plugin_renderer_base; +use templatable; + +defined('MOODLE_INTERNAL') || die(); + +/** + * @package local_qtracker + * @author Aleksander Skrede + * @author Sebastian S. Gundersen + * @copyright 2019 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class renderer extends plugin_renderer_base +{ + + /** + * Renders a block. + * + * @param templatable $block Renderable of block content. + * @return string + * @throws \moodle_exception + */ + public function render_block(templatable $block) + { + $data = $block->export_for_template($this); + return $this->render_from_template('local_qtracker/issue_creation_block', $data); + } +} diff --git a/db/access.php b/db/access.php new file mode 100644 index 0000000..5f2c0d7 --- /dev/null +++ b/db/access.php @@ -0,0 +1,27 @@ + array( + 'captype' => 'write', + 'contextlevel' => CONTEXT_SYSTEM, + 'archetypes' => array( + 'user' => CAP_ALLOW + ), + + 'clonepermissionsfrom' => 'moodle/my:manageblocks' + ), + + 'local/qtracker:addinstance' => array( + 'riskbitmask' => RISK_SPAM | RISK_XSS, + + 'captype' => 'write', + 'contextlevel' => CONTEXT_BLOCK, + 'archetypes' => array( + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ), + + 'clonepermissionsfrom' => 'moodle/site:manageblocks' + ), + +); \ No newline at end of file diff --git a/db/install.xml b/db/install.xml new file mode 100755 index 0000000..f3f95e7 --- /dev/null +++ b/db/install.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + +
+
+
diff --git a/db/upgrade.php b/db/upgrade.php new file mode 100755 index 0000000..37959ce --- /dev/null +++ b/db/upgrade.php @@ -0,0 +1,33 @@ +. + +/** + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +function xmldb_local_qtracker_upgrade($oldversion) { + global $DB; + $dbman = $DB->get_manager(); + + //TODO perform upgrades here... + + return true; +} diff --git a/edit_form.php b/edit_form.php new file mode 100644 index 0000000..6dd1ce6 --- /dev/null +++ b/edit_form.php @@ -0,0 +1,16 @@ +addElement('header', 'configheader', get_string('blocksettings', 'block')); + + // A sample string variable with a default value. + $mform->addElement('text', 'config_text', get_string('blockstring', 'local_qtracker')); + $mform->setDefault('config_text', 'default value'); + $mform->setType('config_text', PARAM_TEXT); + + } +} \ No newline at end of file diff --git a/lang/en/local_qtracker.php b/lang/en/local_qtracker.php new file mode 100755 index 0000000..0da445d --- /dev/null +++ b/lang/en/local_qtracker.php @@ -0,0 +1,35 @@ +. + +/** + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['pluginname'] = 'Question Tracker'; +$string['qtracker'] = 'Question Tracker'; +$string['qtracker:addinstance'] = 'Add a new Question Tracker block'; +$string['qtracker:myaddinstance'] = 'Add a new Question Tracker block to the My Moodle page'; + +$string['blockstring'] = 'Block string'; +$string['descconfig'] = 'Description of the config section'; +$string['descfoo'] = 'Config description'; +$string['headerconfig'] = 'Config section header'; +$string['labelfoo'] = 'Config label'; diff --git a/lib.php b/lib.php new file mode 100644 index 0000000..d201b44 --- /dev/null +++ b/lib.php @@ -0,0 +1,39 @@ +. + +/** + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + + /** + * Adds module specific settings to the settings block + * + * @param settings_navigation $settings The settings navigation object + * @param navigation_node $choicenode The node to add module settings to + */ +function local_qtracker_extend_navigation($navigation){ + $pluginname = 'I\'m in navigation!'; + $url = new moodle_url('/local/localplugin/index.php'); + //$navigation->add($pluginname, $url, navigation_node::TYPE_SETTING); + } + +function qtracker_get_view($calendar, $view, $includenavigation = true, bool $skipevents = false) { + +} \ No newline at end of file diff --git a/locallib.php b/locallib.php new file mode 100644 index 0000000..a9fc15a --- /dev/null +++ b/locallib.php @@ -0,0 +1,22 @@ +. + +/** + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ \ No newline at end of file diff --git a/settings.php b/settings.php new file mode 100644 index 0000000..4fc3508 --- /dev/null +++ b/settings.php @@ -0,0 +1,35 @@ +. + +/** + * Main interface to Question Tracker + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); +/* +$settings->add(new admin_setting_heading('sampleheader', + get_string('headerconfig', 'local_qtracker'), + get_string('descconfig', 'local_qtracker'))); + +$settings->add(new admin_setting_configcheckbox('qtracker/foo', + get_string('labelfoo', 'local_qtracker'), + get_string('descfoo', 'local_qtracker'), + '0')); */ \ No newline at end of file diff --git a/templates/issue_registration_block.mustache b/templates/issue_registration_block.mustache new file mode 100644 index 0000000..22bd91d --- /dev/null +++ b/templates/issue_registration_block.mustache @@ -0,0 +1,58 @@ +{{! + 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 . +}} +{{! + @template core_admin/setting_configmultiselect + + Admin multiselect setting template. + + Context variables required for this template: + * name - form element name + * id - element id + * size - element size + * options - list of options containing name, value, selected + + Example context (json): + { + "name": "test", + "id": "test0", + "size": "3", + "options": [ { "name": "Option 1", "value": "V", "selected": true }, + { "name": "Option 2", "value": "V", "selected": true } ] + } +}} + + +
+Userid: {{userid}} +
+
+{{name}} +
+ +
+ {{#questions}} +
+ {{name}} + +
+ {{/questions}} +
+ + +
+{{lol}} +
diff --git a/version.php b/version.php new file mode 100755 index 0000000..208cc3b --- /dev/null +++ b/version.php @@ -0,0 +1,31 @@ +. + +/** + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$plugin->version = 2020062400; +$plugin->requires = 2016120500; +$plugin->cron = 0; +$plugin->component = 'local_qtracker'; +$plugin->maturity = MATURITY_ALPHA; +$plugin->release = '0.0.1'; From fcd2de43d06df3db9c56623806a6a63e5db0c21f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Storhaug?= Date: Mon, 6 Jul 2020 12:16:53 +0200 Subject: [PATCH 02/83] Working base setup --- amd/src/issue.js | 50 ++++++ amd/src/newissue.js | 166 ++++++++++++++++++ classes/form/view/issue_registration_form.php | 75 ++++++++ classes/issue.php | 28 +++ classes/output/issue_registration_block.php | 61 +++++-- classes/output/renderer.php | 27 ++- db/access.php | 24 +-- db/services.php | 57 ++++++ externallib.php | 77 ++++++++ lang/en/local_qtracker.php | 3 + edit_form.php => qtracker_form.php | 0 templates/button.mustache | 31 ++++ templates/channel_image.mustache | 50 ++++++ templates/feed.mustache | 79 +++++++++ templates/footer.mustache | 42 +++++ templates/issue_registration_block.mustache | 52 ++++-- templates/item.mustache | 63 +++++++ version.php | 2 +- view.php | 39 ++++ 19 files changed, 867 insertions(+), 59 deletions(-) create mode 100644 amd/src/issue.js create mode 100644 amd/src/newissue.js create mode 100644 classes/form/view/issue_registration_form.php create mode 100644 classes/issue.php create mode 100644 db/services.php create mode 100644 externallib.php rename edit_form.php => qtracker_form.php (100%) create mode 100644 templates/button.mustache create mode 100644 templates/channel_image.mustache create mode 100644 templates/feed.mustache create mode 100644 templates/footer.mustache create mode 100644 templates/item.mustache create mode 100644 view.php diff --git a/amd/src/issue.js b/amd/src/issue.js new file mode 100644 index 0000000..8c76b0f --- /dev/null +++ b/amd/src/issue.js @@ -0,0 +1,50 @@ +/** + * Add a create new group modal to the page. + * + * @module core_group/NewIssue + * @class NewIssue + * @package core_group + * @copyright 2017 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/fragment', 'core/ajax', 'core/yui'], + function($, Str, ModalFactory, ModalEvents, Fragment, Ajax, Y) { + + /** + * Constructor + * + * @param {String} selector used to find triggers for the new group modal. + * @param {int} contextid + * + * Each call to init gets it's own instance of this class. + */ + var NewIssue = function() { + console.log("issue created") + this.init(selector); + }; + + /** + * @var {Modal} modal + * @private + */ + NewIssue.prototype.modal = null; + + /** + * @var {int} contextid + * @private + */ + NewIssue.prototype.contextid = -1; + + /** + * Initialise the class. + * + * @param {String} selector used to find triggers for the new group modal. + * @private + * @return {Promise} + */ + NewIssue.prototype.init = function(selector) { + console.log("issue initiated"); + }; + + return new Issue(selector); +}); diff --git a/amd/src/newissue.js b/amd/src/newissue.js new file mode 100644 index 0000000..da2bcfe --- /dev/null +++ b/amd/src/newissue.js @@ -0,0 +1,166 @@ +/** + * Add a create new group modal to the page. + * + * @module core_group/NewIssue + * @class NewIssue + * @package core_group + * @copyright 2017 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/fragment', 'core/ajax', 'core/yui'], + function($, Str, ModalFactory, ModalEvents, Fragment, Ajax, Y) { + + /** + * Constructor + * + * @param {String} selector used to find triggers for the new group modal. + * @param {int} contextid + * + * Each call to init gets it's own instance of this class. + */ + var NewIssue = function(selector) { + this.init(selector); + }; + + /** + * @var {Form} form + * @private + */ + NewIssue.prototype.form = null; + + /** + * @var {int} contextid + * @private + */ + NewIssue.prototype.contextid = -1; + + /** + * Initialise the class. + * + * @param {String} selector used to find triggers for the new question issue. + * @private + * @return {Promise} + */ + NewIssue.prototype.init = function(selector) { + var trigger = $(selector); + this.form = trigger; + + // Fetch the title string. + return Str.get_string('creategroup', 'core_group').then(function(title) { + console.log(title); + this.contextid = 2 + }.bind(this)).then(()=> { + + console.log(trigger) + // We catch the modal save event, and use it to submit the form inside the modal. + // Triggering a form submission will give JS validation scripts a chance to check for errors. + //this.form.getRoot().on(ModalEvents.save, this.submitForm.bind(this)); + // We also catch the form submit event and use it to submit the form with ajax. + //this.modal.getRoot().on('submit', 'form', this.submitFormAjax.bind(this)); + trigger.on('submit', this.submitFormAjax.bind(this)); + }); + + }; + + /** + * @method handleFormSubmissionResponse + * @private + * @return {Promise} + */ + NewIssue.prototype.handleFormSubmissionResponse = function(formData, responce) { + console.log("Success!", formData, responce) + // We could trigger an event instead. + // Yuk. + Y.use('moodle-core-formchangechecker', function() { + M.core_formchangechecker.reset_form_dirty_state(); + }); + //document.location.reload(); + }; + + /** + * @method handleFormSubmissionFailure + * @private + * @return {Promise} + */ + NewIssue.prototype.handleFormSubmissionFailure = function(data) { + // Oh noes! Epic fail :( + // Ah wait - this is normal. We need to re-display the form with errors! + console.error("An error occured"); + console.error(data); + }; + + /** + * Private method + * + * @method submitFormAjax + * @private + * @param {Event} e Form submission event. + */ + NewIssue.prototype.submitFormAjax = function(e) { + // We don't want to do a real form submission. + e.preventDefault(); + + + var changeEvent = document.createEvent('HTMLEvents'); + changeEvent.initEvent('change', true, true); + + // Prompt all inputs to run their validation functions. + // Normally this would happen when the form is submitted, but + // since we aren't submitting the form normally we need to run client side + // validation. + this.form.find(':input').each(function(index, element) { + element.dispatchEvent(changeEvent); + }); + + // Now the change events have run, see if there are any "invalid" form fields. + var invalid = $.merge( + this.form.find('[aria-invalid="true"]'), + this.form.find('.error') + ); + + // If we found invalid fields, focus on the first one and do not submit via ajax. + if (invalid.length) { + invalid.first().focus(); + return; + } + + // Convert all the form elements values to a serialised string. + var formData = this.form.serialize(); + + // Now we can continue... + Ajax.call([{ + methodname: 'local_qtracker_new_issue', + //args: {jsonformdata: JSON.stringify(formData)}, + args: {}, + done: this.handleFormSubmissionResponse.bind(this, formData), + fail: this.handleFormSubmissionFailure.bind(this, formData) + }]); + }; + + /** + * This triggers a form submission, so that any mform elements can do final tricks before the form submission is processed. + * + * @method submitForm + * @param {Event} e Form submission event. + * @private + */ + NewIssue.prototype.submitForm = function(e) { + e.preventDefault(); + this.form.submit(); + }; + + return /** @alias module:core_group/NewIssue */ { + // Public variables and functions. + /** + * Attach event listeners to initialise this module. + * + * @method init + * @param {string} selector The CSS selector used to find nodes that will trigger this module. + * @param {int} contextid The contextid for the course. + * @return {Promise} + */ + init: function(selector) { + return new NewIssue(selector); + } + }; +}); diff --git a/classes/form/view/issue_registration_form.php b/classes/form/view/issue_registration_form.php new file mode 100644 index 0000000..32bf47b --- /dev/null +++ b/classes/form/view/issue_registration_form.php @@ -0,0 +1,75 @@ +. + +namespace local_qtracker\form\view; + + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . '/formslib.php'); + +/** + * @package mod_capquiz + * @author Aleksander Skrede + * @copyright 2018 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class issue_registration_form extends \moodleform { + + + // TODO: FIX EVERYTHING IN THIS FILE AND REPLACE THE CURRENT JS SETUP. + + + public function __construct($questions, \moodle_url $url) { + $this->questions = $questions; + parent::__construct($url); + } + + public function definition() { + $form = $this->_form; + + $options = []; + $i=0; + foreach ($this->questions as $key => $question) { + $options[$i] = $question->name; + $i++; + } + + $form->addElement('select', 'question', null, $options); + //$mform->addHelpButton('question', 'question', 'block_community'); + //$mform->setDefault('question', $question); + + + $form->addElement('text', 'default_user_rating', get_string('default_user_rating', 'capquiz')); + $form->setType('default_user_rating', PARAM_INT); + $form->setDefault('default_user_rating', 'lol'); + $form->addRule('default_user_rating', get_string('default_user_rating_required', 'capquiz'), 'required', null, 'client'); + + $form->addElement('submit', 'submitbutton', get_string('savechanges')); + } + + public function validations($data, $files) { + $errors = []; + if (empty($data['default_user_rating'])) { + $errors['default_user_rating'] = get_string('default_user_rating_required', 'capquiz'); + } + if (empty($data['starstopass']) || $data['starstopass'] < 0 || $data['starstopass'] > 5) { + $errors['starstopass'] = get_string('stars_to_pass_required', 'capquiz'); + } + return $errors; + } + +} diff --git a/classes/issue.php b/classes/issue.php new file mode 100644 index 0000000..6d3f82e --- /dev/null +++ b/classes/issue.php @@ -0,0 +1,28 @@ +. + +/** + * External Web Service Template + * + * @package localwstemplate + * @copyright 2011 Moodle Pty Ltd (http://moodle.com) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once($CFG->libdir . "/externallib.php"); + +class issue { + + // TODO create issue class here... See the db fields... +} diff --git a/classes/output/issue_registration_block.php b/classes/output/issue_registration_block.php index 984a8fb..9fda17e 100644 --- a/classes/output/issue_registration_block.php +++ b/classes/output/issue_registration_block.php @@ -27,7 +27,8 @@ defined('MOODLE_INTERNAL') || die; -use mod_quiz\local\structure\slot_random; +use local_qtracker\form\view\issue_registration_form; +use moodle_url; use renderable; use renderer_base; use templatable; @@ -43,13 +44,13 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class issue_registration_block implements renderable, templatable { - + /** @var \question_definition[] Array of {@link \question_definition} */ public $questions = array(); /** @var int User ID */ public $userid; - + /** @var array of stdclass strings to display */ public $slots = array(); @@ -77,24 +78,46 @@ public function __construct(array $questions, $userid, $slots=null) { * @return stdClass Data to be used for the template */ public function export_for_template(renderer_base $output) { - + global $PAGE; + $url = $PAGE->url; $data = new stdClass(); $data->userid = $this->userid; - $data->lol = "lol this is a test"; - $data->name = "test"; - $data->id = "test0"; - $data->size = 3; - - $value = 22; - $name = "OK"; - - foreach ($this->questions as $key => $question) { - $questions[] = [ - 'name' => $question->name, - 'selected' => true - ]; + + + if (count($this->questions) > 1) { + $data->hasmultiple = true; + + $select = new stdClass(); + $options = array(); + $select->name = "questionid"; + $select->formid = "fjndfsndf"; + $select->label = "fmdifj"; + foreach ($this->questions as $question) { + $questions[] = [ + 'name' => $question->name, + 'selected' => true + ]; + + $option = new stdClass(); + $option->value = $question->id; + $option->name = $question->name; + array_push($options, $option); + } + $select->options = $options; + $data->select = $select; + + } else { + $data->hasmultiple = false; + $data->questionid = $this->questions[0]->id; } - $data->questions = $questions; + + $data->action = $url; + $data->tooltip = "This is a tooltip"; + + // TODO: Fix this as both the button and the select gets this. Wrap in separate mustashe templates. + $data->label = "Send feedback"; + + //$data->questions = $questions; return $data; } -} \ No newline at end of file +} diff --git a/classes/output/renderer.php b/classes/output/renderer.php index 523f88d..d94ade8 100644 --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -16,6 +16,8 @@ namespace local_qtracker\output; +use Error; +use moodle_url; use plugin_renderer_base; use templatable; @@ -31,6 +33,10 @@ class renderer extends plugin_renderer_base { + public function render_issue_table(templatable $block) { + + } + /** * Renders a block. * @@ -38,9 +44,24 @@ class renderer extends plugin_renderer_base * @return string * @throws \moodle_exception */ - public function render_block(templatable $block) - { + public function render_block(templatable $block) { + + $content = ''; + //$editor = \editors_get_preferred_editor(); // This gets the default editor for your site + //$editor->use_editor("someidhere"); // This is used to set the id of the html element + // This creates the html element + //$content.= \html_writer::tag('textarea', 'somedefaultvalue', + //array('id' => "someidhere", 'name' => 'somenamehere', 'rows' => 5, 'cols' => 10)); + /*$formdata = $form->get_data(); + if ($formdata) { + $this->process_rating_configuration($formdata); + } + */ + + $content.= \html_writer::tag('div', 'oYESSSSSSkokok'); $data = $block->export_for_template($this); - return $this->render_from_template('local_qtracker/issue_creation_block', $data); + + $content .= $this->render_from_template('local_qtracker/issue_registration_block', $data); + return $content; } } diff --git a/db/access.php b/db/access.php index 5f2c0d7..ff0ec2c 100644 --- a/db/access.php +++ b/db/access.php @@ -1,27 +1,13 @@ array( + + 'local/qtracker:createissue' => array( + 'riskbitmask' => RISK_SPAM, 'captype' => 'write', - 'contextlevel' => CONTEXT_SYSTEM, + 'contextlevel' => CONTEXT_COURSE, 'archetypes' => array( 'user' => CAP_ALLOW ), - - 'clonepermissionsfrom' => 'moodle/my:manageblocks' - ), - - 'local/qtracker:addinstance' => array( - 'riskbitmask' => RISK_SPAM | RISK_XSS, - - 'captype' => 'write', - 'contextlevel' => CONTEXT_BLOCK, - 'archetypes' => array( - 'editingteacher' => CAP_ALLOW, - 'manager' => CAP_ALLOW - ), - - 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), -); \ No newline at end of file +); diff --git a/db/services.php b/db/services.php new file mode 100644 index 0000000..78a1d2d --- /dev/null +++ b/db/services.php @@ -0,0 +1,57 @@ +. + +/** + * This file contains the library of functions and constants for the lti module + * + * @package mod_lti + * @copyright 2009 Marc Alier, Jordi Piguillem, Nikolas Galanis + * marc.alier@upc.edu + * @copyright 2009 Universitat Politecnica de Catalunya http://www.upc.edu + * @author Marc Alier + * @author Jordi Piguillem + * @author Nikolas Galanis + * @author Chris Scribner + * @copyright 2015 Vital Source Technologies http://vitalsource.com + * @author Stephen Vickers + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die; + +// We defined the web service functions to install. +$functions = array( + 'local_qtracker_new_issue' => array( + 'classname' => 'local_qtracker_external', + 'methodname' => 'new_issue', + 'classpath' => 'local/qtracker/externallib.php', + 'description' => 'Register a new question issue.', + 'type' => 'write', + 'ajax' => true, + //'capabilities' => 'moodle/course:managegroups', + 'capabilities' => array(), // capabilities required by the function. + + ) +); + +// We define the services to install as pre-build services. A pre-build service is not editable by administrator. +$services = array( + 'Question tracker service' => array( + 'functions' => array ('local_qtracker_new_issue'), + 'restrictedusers' => 0, + 'enabled'=>1, + ) +); diff --git a/externallib.php b/externallib.php new file mode 100644 index 0000000..4324140 --- /dev/null +++ b/externallib.php @@ -0,0 +1,77 @@ +. + +/** + * External Web Service Template + * + * @package localwstemplate + * @copyright 2011 Moodle Pty Ltd (http://moodle.com) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once($CFG->libdir . "/externallib.php"); + +class local_qtracker_external extends external_api { + + /** + * Returns description of method parameters + * @return external_function_parameters + */ + public static function new_issue_parameters() { + return new external_function_parameters( + array( + 'questionid' => new external_value(PARAM_INT, 'question id', VALUE_REQUIRED), + 'issuetitle' => new external_value(PARAM_TEXT, 'issue title', VALUE_REQUIRED), + 'issuedescription' => new external_value(PARAM_TEXT, 'issue description', VALUE_REQUIRED), + ) + ); + } + + /** + * Returns welcome message + * @return string welcome message + */ + public static function new_issue($questionid, $issuetitle, $issuedescription) { + global $USER; + + //Parameter validation + $params = self::validate_parameters(self::new_issue_parameters(), + array( + 'questionid' => $questionid, + 'issuetitle' => $issuetitle, + 'issuedescription' => $issuedescription, + ) + ); + + //Context validation + $context = \context_user::instance($USER->id); + self::validate_context($context); + + //Capability checking + //OPTIONAL but in most web service it should present + if (!has_capability('local/qtracker:createissue', $context)) { + throw new moodle_exception('cannotcreateissue', 'local_qtracker'); + } + + return $params; + } + + /** + * Returns description of method result value + * @return external_description + */ + public static function new_issue_returns() { + return new external_value(PARAM_TEXT, 'The welcome message + user first name'); + } +} diff --git a/lang/en/local_qtracker.php b/lang/en/local_qtracker.php index 0da445d..8fc1386 100755 --- a/lang/en/local_qtracker.php +++ b/lang/en/local_qtracker.php @@ -33,3 +33,6 @@ $string['descfoo'] = 'Config description'; $string['headerconfig'] = 'Config section header'; $string['labelfoo'] = 'Config label'; + +$string['question_problem_details'] = 'If you have feedback for this question, please type it below.'; +$string['cannotcreateissue'] = 'You cannot create a new question issue.'; diff --git a/edit_form.php b/qtracker_form.php similarity index 100% rename from edit_form.php rename to qtracker_form.php diff --git a/templates/button.mustache b/templates/button.mustache new file mode 100644 index 0000000..eacf901 --- /dev/null +++ b/templates/button.mustache @@ -0,0 +1,31 @@ +{{! + 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 . +}} +{{! + @template local_qtracker/button + + Example context (json): + { + "method": "post", + "url": "", + "primary": true, + "tooltip": "A button.", + "label": "Button" + } +}} +{{#button}} + {{>core/single_button}} +{{/button}} diff --git a/templates/channel_image.mustache b/templates/channel_image.mustache new file mode 100644 index 0000000..f20166e --- /dev/null +++ b/templates/channel_image.mustache @@ -0,0 +1,50 @@ +{{! + 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 . +}} +{{! + @template block_rss_client/channel_image + + Template which defines an item in an RSS Feed + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * url - string: The escaped URL of the image. + * title - string: The title of the image. + * link - string: Optionally, a URL to link the image to. Must be escaped. + + Example context (json): + { + "url": "http://www.example.com/images/catpic.jpg", + "title": "A picture of my cat", + "link": "http://www.example.com/cat-news/" + } +}} +
+ {{#link}} + + {{/link}} + + {{title}} + + {{#link}} + + {{/link}} +
diff --git a/templates/feed.mustache b/templates/feed.mustache new file mode 100644 index 0000000..a69f3e8 --- /dev/null +++ b/templates/feed.mustache @@ -0,0 +1,79 @@ +{{! + 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 . +}} +{{! + @template block_rss_client/feed + + Template which defines an item in an RSS Feed + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * channel_image - object: URL, title and link for the channel image. + * title - string: The title of the feed. + * items - array: An array of feed items. + + Example context (json): + { + "title": "News from around my living room", + "image": { + "url": "https://www.example.com/feeds/news/poster.jpg", + "title": "Example News Logo", + "link": "https://www.example.com/feeds/news/" + }, + "feeditems": [ + { + "id": "https://www.example.com/node/12", + "link": "https://www.example.com/my-turtle-story.html", + "title": "My Turtle Story", + "description": "This is a story about my turtle.", + "permalink": "https://www.example.com/my-turtle-story.html", + "datepublished": "11 January 2016, 7:11 pm" + }, + { + "id": "https://www.example.com/node/12", + "link": "https://www.example.com/my-cat-story.html", + "title": "My Story", + "description": "This is a story about my cats.", + "permalink": "https://www.example.com/my-cat-story.html", + "datepublished": "12 January 2016, 9:12 pm" + } + ] + } +}} +{{$image}} + {{#image}} + {{> block_rss_client/channel_image}} + {{/image}} +{{/image}} + +{{$title}} + {{#title}} +
{{title}}
+ {{/title}} +{{/title}} + +{{$items}} +
    + {{#items}} + {{> block_rss_client/item}} + {{/items}} +
+{{/items}} diff --git a/templates/footer.mustache b/templates/footer.mustache new file mode 100644 index 0000000..dd5d0fe --- /dev/null +++ b/templates/footer.mustache @@ -0,0 +1,42 @@ +{{! + 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 . +}} +{{! + @template block_rss_client/footer + + Template which defines an item in an RSS Feed + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * channellink - string: The channel URL. Must be escaped. + + Example context (json): + { + "channellink": "https://www.example.com/feeds/rss" + } +}} +{{#channellink}} + {{#str}} clientchannellink, block_rss_client {{/str}} + {{#hasfailedfeeds}}
{{/hasfailedfeeds}} +{{/channellink}} +{{#hasfailedfeeds}} + {{#str}} failedfeeds, block_rss_client {{/str}} +{{/hasfailedfeeds}} \ No newline at end of file diff --git a/templates/issue_registration_block.mustache b/templates/issue_registration_block.mustache index 22bd91d..879abd5 100644 --- a/templates/issue_registration_block.mustache +++ b/templates/issue_registration_block.mustache @@ -30,29 +30,47 @@ "name": "test", "id": "test0", "size": "3", - "options": [ { "name": "Option 1", "value": "V", "selected": true }, - { "name": "Option 2", "value": "V", "selected": true } ] + "hasmultiple": true, + "select": { + "name": "questionid", + "options": [ + { "name": "Option 1", "value": "V", "selected": true }, + { "name": "Option 2", "value": "V", "selected": true } + ] + } } }} +{{! + @template mod_capquiz/configure_badge_rating - + Example context (json): + { + "form": "raw html for the form" + } +}}
Userid: {{userid}}
-
-{{name}} -
+

{{#str}} question_problem_details, local_qtracker {{/str}}

- {{#questions}} -
- {{name}} - -
- {{/questions}} -
- - -
-{{lol}} +
+ {{#hasmultiple}} + {{#select}} + {{disabled}} + {{> core/single_select }} + {{/select}} + {{/hasmultiple}} + {{^hasmultiple}} + + {{/hasmultiple}} +
+ + {{>core/single_button}} +
+{{#js}} +require(['jquery', 'local_qtracker/newissue'], function($, NewIssue) { + NewIssue.init('[data-action=newissue]'); +}); +{{/js}} diff --git a/templates/item.mustache b/templates/item.mustache new file mode 100644 index 0000000..1d700ba --- /dev/null +++ b/templates/item.mustache @@ -0,0 +1,63 @@ +{{! + 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 . +}} +{{! + @template block_rss_client/item + + Template which defines an item in an RSS Feed + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * id - string: A unique id for the feed item. + * link - string: The URL of the feed item. Must already be escaped. + * title - string: The title of the feed item. + * description - string: The text description of the feed item. + * permalink - string: The permalink of the feed item. Must already be escaped. + * datepublished - string: The date the feed item was published. + + Example context (json): + { + "id": "https://www.example.com/node", + "link": "https://www.example.com/my-cat-story.html", + "title": "My Story", + "description": "This is a story about my cats.", + "permalink": "https://www.example.com/my-cat-story.html", + "datepublished": "12 January 2016, 9:12 pm" + } +}} +
  • + {{$title}} + + {{/title}} + + {{$content}} + {{#description}} +
    + {{{datepublished}}} +
    +
    + {{{description}}} +
    + {{/description}} + {{/content}} +
  • diff --git a/version.php b/version.php index 208cc3b..02990bd 100755 --- a/version.php +++ b/version.php @@ -23,7 +23,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2020062400; +$plugin->version = 2020062410; $plugin->requires = 2016120500; $plugin->cron = 0; $plugin->component = 'local_qtracker'; diff --git a/view.php b/view.php new file mode 100644 index 0000000..846af14 --- /dev/null +++ b/view.php @@ -0,0 +1,39 @@ +get_record('course', array('id' => $courseid))) { + print_error('invalidcourse', 'block_simplehtml', $courseid); +} + + +require_login($course); +$PAGE->set_url('/local/qtracker/view.php', array('id' => $courseid)); +$PAGE->set_pagelayout('standard'); +$PAGE->set_heading(get_string('pluginname', 'local_qtracker')); + +//$simplehtml = new simplehtml_form(); +$settingsnode = $PAGE->settingsnav->add(get_string('frontpagesettings'), null, navigation_node::TYPE_SETTING, null); +$editurl = new moodle_url('/blocks/simplehtml/view.php', array('id' => $id, 'courseid' => $courseid, 'blockid' => $blockid)); +$editnode = $settingsnode->add(get_string('resetpage', 'my'), $editurl); +$editnode->make_active(); + +echo $OUTPUT->header(); +//$simplehtml->display(); + + +echo "omg it works"; +// Get table renderer and display table here.... + +echo $OUTPUT->footer(); + +?> From 609f1e7170a7c4552f9ab1d6a7c0de5a97c5c33c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Storhaug?= Date: Mon, 6 Jul 2020 15:29:42 +0200 Subject: [PATCH 03/83] Add basic setup for table --- classes/output/question_issues_page.php | 80 ++++++++++ classes/output/question_issues_table.php | 138 ++++++++++++++++++ classes/output/renderer.php | 12 +- db/install.xml | 5 +- templates/channel_image.mustache | 50 ------- templates/feed.mustache | 79 ---------- templates/item.mustache | 63 -------- ...oter.mustache => question_issues.mustache} | 25 +--- version.php | 2 +- view.php | 16 +- 10 files changed, 245 insertions(+), 225 deletions(-) create mode 100644 classes/output/question_issues_page.php create mode 100644 classes/output/question_issues_table.php delete mode 100644 templates/channel_image.mustache delete mode 100644 templates/feed.mustache delete mode 100644 templates/item.mustache rename templates/{footer.mustache => question_issues.mustache} (53%) diff --git a/classes/output/question_issues_page.php b/classes/output/question_issues_page.php new file mode 100644 index 0000000..1f518af --- /dev/null +++ b/classes/output/question_issues_page.php @@ -0,0 +1,80 @@ +. + +/** + * Class containing data for question issues. + * + * @package local_qtracker + * @copyright 2020 André Storhaug + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace local_qtracker\output; +defined('MOODLE_INTERNAL') || die(); + +use coding_exception; +use dml_exception; +use moodle_exception; +use moodle_url; +use renderable; +use renderer_base; +use single_select; +use stdClass; +use templatable; + + +/** + * Class containing data for question issues. + * + * @copyright 2020 André Storhaug + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class question_issues_page implements renderable, templatable { + + /** The default number of results to be shown per page. */ + const DEFAULT_PAGE_SIZE = 20; + + protected $questionissuestable = []; + + /** + * Construct this renderable. + * + * @param \local_qtracker\question_issues_table $questionissuestable + */ + public function __construct(question_issues_table $questionissuestable) { + $this->questionissuestable = $questionissuestable; + } + + /** + * Export this data so it can be used as the context for a mustache template. + * + * @param renderer_base $output + * @return stdClass + * @throws coding_exception + * @throws dml_exception + * @throws moodle_exception + */ + public function export_for_template(renderer_base $output) { + $data = new stdClass(); + + ob_start(); + $this->questionissuestable->out(self::DEFAULT_PAGE_SIZE, true); + $questionissues = ob_get_contents(); + ob_end_clean(); + $data->questionissues = $questionissues; + + return $data; + } +} diff --git a/classes/output/question_issues_table.php b/classes/output/question_issues_table.php new file mode 100644 index 0000000..a343c05 --- /dev/null +++ b/classes/output/question_issues_table.php @@ -0,0 +1,138 @@ +. + +/** + * Class containing data for question issues. + * + * @package local_qtracker + * @copyright 2020 André Storhaug + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_qtracker\output; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . '/tablelib.php'); + +use context_system; +use moodle_url; +use table_sql; + +/** + * Question issues table. + * + * @package local_qtracker + * @copyright 2020 André Storhaug + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class question_issues_table extends table_sql { + + /** + * Sets up the table. + * + * @param string $uniqueid Unique id of table. + * @param moodle_url $url The base URL. + */ + public function __construct($uniqueid, $url) { + global $CFG; + parent::__construct($uniqueid); + + // TODO: determine which context to use... + $context = context_system::instance(); + $this->context = $context; + + // This object should not be used without the right permissions. + require_capability('moodle/role:manage', $context); // DO WE NEED THIS? + + // Define columns in the table. + $this->define_table_columns(); + // Set the baseurl + $this->define_baseurl($url); + // Define configs. + $this->define_table_configs(); + // Define SQL. + $this->setup_sql_queries(); + } + + /** + * Something name column. + * + * @param object $data Row data. + * @return string + */ + protected function col_something($data) { + // TODO: implement one of these for questionid, title, description, and user. + return $data->something; + } + + /** + * The timecreated column. + * + * @param stdClass $data The row data. + * @return string + */ + public function col_timecreated($data) { + return userdate($data->timecreated); + } + + /** + * TODO: touch up this + * Setup the headers for the table. + */ + protected function define_table_columns() { + + // Define headers and columns. + //TODO: define strings in lang file. + $cols = array( + 'questionid' => get_string('questionid', 'local_qtracker'), + 'title' => get_string('issuetitle', 'local_qtracker'), + 'description' => get_string('issuedescription', 'local_qtracker'), + 'datecreated' => get_string('datecreated', 'local_qtracker'), + ); + + // Add remaining headers. + $cols = array_merge($cols, array('actions' => get_string('actions'))); + + $this->define_columns(array_keys($cols)); + $this->define_headers(array_values($cols)); + } + + /** + * Define table configs. + */ + protected function define_table_configs() { + $this->collapsible(false); + $this->sortable(true); + $this->pageable(true); + } + + /** + * Builds the SQL query. + * + * @return array containing sql to use and an array of params. + */ + public function setup_sql_queries() { + + // TODO: Write SQL to retrieve all rows... + $fields = ''; + $from = ''; + $where = ''; + $params = array(); // TODO: find a way to only get the correct contexts.. For now just get everything (keep this empty)... + + $this->set_sql($fields, $from, $where, $params); + } +} diff --git a/classes/output/renderer.php b/classes/output/renderer.php index d94ade8..3dd0e71 100644 --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -33,8 +33,16 @@ class renderer extends plugin_renderer_base { - public function render_issue_table(templatable $block) { - + /** + * Render the review page for the deletion of expired contexts. + * + * @param question_issues_page $page + * @return string html for the page + * @throws moodle_exception + */ + public function render_question_issues_page(question_issues_page $page) { + $data = $page->export_for_template($this); + return parent::render_from_template('local_qtracker/question_issues', $data); } /** diff --git a/db/install.xml b/db/install.xml index f3f95e7..602f7ee 100755 --- a/db/install.xml +++ b/db/install.xml @@ -9,11 +9,12 @@ - + + - + diff --git a/templates/channel_image.mustache b/templates/channel_image.mustache deleted file mode 100644 index f20166e..0000000 --- a/templates/channel_image.mustache +++ /dev/null @@ -1,50 +0,0 @@ -{{! - 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 . -}} -{{! - @template block_rss_client/channel_image - - Template which defines an item in an RSS Feed - - Classes required for JS: - * none - - Data attributes required for JS: - * none - - Context variables required for this template: - * url - string: The escaped URL of the image. - * title - string: The title of the image. - * link - string: Optionally, a URL to link the image to. Must be escaped. - - Example context (json): - { - "url": "http://www.example.com/images/catpic.jpg", - "title": "A picture of my cat", - "link": "http://www.example.com/cat-news/" - } -}} -
    - {{#link}} - - {{/link}} - - {{title}} - - {{#link}} - - {{/link}} -
    diff --git a/templates/feed.mustache b/templates/feed.mustache deleted file mode 100644 index a69f3e8..0000000 --- a/templates/feed.mustache +++ /dev/null @@ -1,79 +0,0 @@ -{{! - 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 . -}} -{{! - @template block_rss_client/feed - - Template which defines an item in an RSS Feed - - Classes required for JS: - * none - - Data attributes required for JS: - * none - - Context variables required for this template: - * channel_image - object: URL, title and link for the channel image. - * title - string: The title of the feed. - * items - array: An array of feed items. - - Example context (json): - { - "title": "News from around my living room", - "image": { - "url": "https://www.example.com/feeds/news/poster.jpg", - "title": "Example News Logo", - "link": "https://www.example.com/feeds/news/" - }, - "feeditems": [ - { - "id": "https://www.example.com/node/12", - "link": "https://www.example.com/my-turtle-story.html", - "title": "My Turtle Story", - "description": "This is a story about my turtle.", - "permalink": "https://www.example.com/my-turtle-story.html", - "datepublished": "11 January 2016, 7:11 pm" - }, - { - "id": "https://www.example.com/node/12", - "link": "https://www.example.com/my-cat-story.html", - "title": "My Story", - "description": "This is a story about my cats.", - "permalink": "https://www.example.com/my-cat-story.html", - "datepublished": "12 January 2016, 9:12 pm" - } - ] - } -}} -{{$image}} - {{#image}} - {{> block_rss_client/channel_image}} - {{/image}} -{{/image}} - -{{$title}} - {{#title}} -
    {{title}}
    - {{/title}} -{{/title}} - -{{$items}} -
      - {{#items}} - {{> block_rss_client/item}} - {{/items}} -
    -{{/items}} diff --git a/templates/item.mustache b/templates/item.mustache deleted file mode 100644 index 1d700ba..0000000 --- a/templates/item.mustache +++ /dev/null @@ -1,63 +0,0 @@ -{{! - 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 . -}} -{{! - @template block_rss_client/item - - Template which defines an item in an RSS Feed - - Classes required for JS: - * none - - Data attributes required for JS: - * none - - Context variables required for this template: - * id - string: A unique id for the feed item. - * link - string: The URL of the feed item. Must already be escaped. - * title - string: The title of the feed item. - * description - string: The text description of the feed item. - * permalink - string: The permalink of the feed item. Must already be escaped. - * datepublished - string: The date the feed item was published. - - Example context (json): - { - "id": "https://www.example.com/node", - "link": "https://www.example.com/my-cat-story.html", - "title": "My Story", - "description": "This is a story about my cats.", - "permalink": "https://www.example.com/my-cat-story.html", - "datepublished": "12 January 2016, 9:12 pm" - } -}} -
  • - {{$title}} - - {{/title}} - - {{$content}} - {{#description}} -
    - {{{datepublished}}} -
    -
    - {{{description}}} -
    - {{/description}} - {{/content}} -
  • diff --git a/templates/footer.mustache b/templates/question_issues.mustache similarity index 53% rename from templates/footer.mustache rename to templates/question_issues.mustache index dd5d0fe..388ab45 100644 --- a/templates/footer.mustache +++ b/templates/question_issues.mustache @@ -15,28 +15,13 @@ along with Moodle. If not, see . }} {{! - @template block_rss_client/footer - - Template which defines an item in an RSS Feed - - Classes required for JS: - * none - - Data attributes required for JS: - * none - - Context variables required for this template: - * channellink - string: The channel URL. Must be escaped. + @template local_qtracker/question_issues Example context (json): { - "channellink": "https://www.example.com/feeds/rss" + "questionissues": "raw html" } }} -{{#channellink}} - {{#str}} clientchannellink, block_rss_client {{/str}} - {{#hasfailedfeeds}}
    {{/hasfailedfeeds}} -{{/channellink}} -{{#hasfailedfeeds}} - {{#str}} failedfeeds, block_rss_client {{/str}} -{{/hasfailedfeeds}} \ No newline at end of file +
    + {{{questionissues}}} +
    diff --git a/version.php b/version.php index 02990bd..fbf8932 100755 --- a/version.php +++ b/version.php @@ -23,7 +23,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2020062410; +$plugin->version = 2020070600; $plugin->requires = 2016120500; $plugin->cron = 0; $plugin->component = 'local_qtracker'; diff --git a/view.php b/view.php index 846af14..3781fe7 100644 --- a/view.php +++ b/view.php @@ -6,7 +6,6 @@ // Check for all required variables. $courseid = required_param('courseid', PARAM_INT); -$blockid = required_param('blockid', PARAM_INT); // Next look for optional variables. $id = optional_param('id', 0, PARAM_INT); @@ -15,24 +14,25 @@ print_error('invalidcourse', 'block_simplehtml', $courseid); } - require_login($course); -$PAGE->set_url('/local/qtracker/view.php', array('id' => $courseid)); +$url = new moodle_url('/local/qtracker/view.php', array('id' => $courseid)); + +$PAGE->set_url($url); $PAGE->set_pagelayout('standard'); $PAGE->set_heading(get_string('pluginname', 'local_qtracker')); -//$simplehtml = new simplehtml_form(); $settingsnode = $PAGE->settingsnav->add(get_string('frontpagesettings'), null, navigation_node::TYPE_SETTING, null); -$editurl = new moodle_url('/blocks/simplehtml/view.php', array('id' => $id, 'courseid' => $courseid, 'blockid' => $blockid)); +$editurl = new moodle_url('/blocks/simplehtml/view.php', array('id' => $id, 'courseid' => $courseid)); $editnode = $settingsnode->add(get_string('resetpage', 'my'), $editurl); $editnode->make_active(); echo $OUTPUT->header(); -//$simplehtml->display(); - -echo "omg it works"; // Get table renderer and display table here.... +$table = new \local_qtracker\output\question_issues_table(uniqid(), $url); +$renderer = $PAGE->get_renderer('local_qtracker'); +$questionissuespage = new \local_qtracker\output\question_issues_page($table); +echo $renderer->render($questionissuespage); echo $OUTPUT->footer(); From c76a305f49351848699d817853bbd24985a3d29a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Storhaug?= Date: Wed, 8 Jul 2020 03:54:40 +0200 Subject: [PATCH 04/83] Add working webservice for blocks --- amd/src/newissue.js | 25 +++++---- classes/output/issue_registration_block.php | 19 +++---- classes/output/renderer.php | 14 ----- db/install.xml | 2 + db/services.php | 1 + db/upgrade.php | 16 +++++- externallib.php | 48 ++++++++++++++--- lang/en/local_qtracker.php | 6 +++ qtracker_form.php | 16 ------ templates/button.mustache | 11 ++-- templates/issue_registration_block.mustache | 26 +++++---- templates/select.mustache | 60 +++++++++++++++++++++ version.php | 2 +- 13 files changed, 172 insertions(+), 74 deletions(-) delete mode 100644 qtracker_form.php create mode 100644 templates/select.mustache diff --git a/amd/src/newissue.js b/amd/src/newissue.js index da2bcfe..0c68da3 100644 --- a/amd/src/newissue.js +++ b/amd/src/newissue.js @@ -7,8 +7,8 @@ * @copyright 2017 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/fragment', 'core/ajax', 'core/yui'], - function($, Str, ModalFactory, ModalEvents, Fragment, Ajax, Y) { +define(['jquery', 'core/str', 'core/templates', 'core/modal_events', 'core/fragment', 'core/ajax', 'core/yui'], + function($, Str, Templates, ModalEvents, Fragment, Ajax, Y, ) { /** * Constructor @@ -67,8 +67,8 @@ define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/f * @private * @return {Promise} */ - NewIssue.prototype.handleFormSubmissionResponse = function(formData, responce) { - console.log("Success!", formData, responce) + NewIssue.prototype.handleFormSubmissionResponse = function(formData, response) { + console.log("Success!", formData, response) // We could trigger an event instead. // Yuk. Y.use('moodle-core-formchangechecker', function() { @@ -82,11 +82,11 @@ define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/f * @private * @return {Promise} */ - NewIssue.prototype.handleFormSubmissionFailure = function(data) { + NewIssue.prototype.handleFormSubmissionFailure = function(data, response) { // Oh noes! Epic fail :( // Ah wait - this is normal. We need to re-display the form with errors! console.error("An error occured"); - console.error(data); + console.error(response); }; /** @@ -101,6 +101,8 @@ define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/f e.preventDefault(); + + var changeEvent = document.createEvent('HTMLEvents'); changeEvent.initEvent('change', true, true); @@ -125,13 +127,16 @@ define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/f } // Convert all the form elements values to a serialised string. - var formData = this.form.serialize(); - + //var formData = this.form.serialize(); + var formData = new FormData(this.form[0]); // Now we can continue... Ajax.call([{ methodname: 'local_qtracker_new_issue', - //args: {jsonformdata: JSON.stringify(formData)}, - args: {}, + args: { + questionid: formData.get('questionid'), + issuetitle: formData.get('issuetitle'), + issuedescription: formData.get('issuedescription'), + }, done: this.handleFormSubmissionResponse.bind(this, formData), fail: this.handleFormSubmissionFailure.bind(this, formData) }]); diff --git a/classes/output/issue_registration_block.php b/classes/output/issue_registration_block.php index 9fda17e..904312a 100644 --- a/classes/output/issue_registration_block.php +++ b/classes/output/issue_registration_block.php @@ -33,6 +33,7 @@ use renderer_base; use templatable; use stdClass; +use help_icon; /** @@ -54,6 +55,9 @@ class issue_registration_block implements renderable, templatable { /** @var array of stdclass strings to display */ public $slots = array(); + /** @var help_icon The help icon. */ + protected $helpicon; + /** * Construct the contents of the block * @param \question_definition[] $questions The questions that can be filed issues for. @@ -69,6 +73,7 @@ public function __construct(array $questions, $userid, $slots=null) { throw new \coding_exception('The number of questions and slots does not match.'); } } + $this->helpicon = new help_icon('question', 'local_qtracker'); } /** @@ -90,17 +95,13 @@ public function export_for_template(renderer_base $output) { $select = new stdClass(); $options = array(); $select->name = "questionid"; - $select->formid = "fjndfsndf"; - $select->label = "fmdifj"; - foreach ($this->questions as $question) { - $questions[] = [ - 'name' => $question->name, - 'selected' => true - ]; + $select->label = "Question"; + $select->helpicon = $this->helpicon->export_for_template($output); + foreach ($this->questions as $key => $question) { $option = new stdClass(); $option->value = $question->id; - $option->name = $question->name; + $option->name = $this->slots[$key]; array_push($options, $option); } $select->options = $options; @@ -115,7 +116,7 @@ public function export_for_template(renderer_base $output) { $data->tooltip = "This is a tooltip"; // TODO: Fix this as both the button and the select gets this. Wrap in separate mustashe templates. - $data->label = "Send feedback"; + $data->label = "Submit new issue"; //$data->questions = $questions; return $data; diff --git a/classes/output/renderer.php b/classes/output/renderer.php index 3dd0e71..2b446af 100644 --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -53,22 +53,8 @@ public function render_question_issues_page(question_issues_page $page) { * @throws \moodle_exception */ public function render_block(templatable $block) { - $content = ''; - //$editor = \editors_get_preferred_editor(); // This gets the default editor for your site - //$editor->use_editor("someidhere"); // This is used to set the id of the html element - // This creates the html element - //$content.= \html_writer::tag('textarea', 'somedefaultvalue', - //array('id' => "someidhere", 'name' => 'somenamehere', 'rows' => 5, 'cols' => 10)); - /*$formdata = $form->get_data(); - if ($formdata) { - $this->process_rating_configuration($formdata); - } - */ - - $content.= \html_writer::tag('div', 'oYESSSSSSkokok'); $data = $block->export_for_template($this); - $content .= $this->render_from_template('local_qtracker/issue_registration_block', $data); return $content; } diff --git a/db/install.xml b/db/install.xml index 602f7ee..072e0d9 100755 --- a/db/install.xml +++ b/db/install.xml @@ -10,11 +10,13 @@ + + diff --git a/db/services.php b/db/services.php index 78a1d2d..468658c 100644 --- a/db/services.php +++ b/db/services.php @@ -43,6 +43,7 @@ 'ajax' => true, //'capabilities' => 'moodle/course:managegroups', 'capabilities' => array(), // capabilities required by the function. + 'loginrequired' => true, ) ); diff --git a/db/upgrade.php b/db/upgrade.php index 37959ce..2d9f706 100755 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -26,8 +26,22 @@ function xmldb_local_qtracker_upgrade($oldversion) { global $DB; $dbman = $DB->get_manager(); - + //TODO perform upgrades here... + if ($oldversion < 2020070800) { + // Define table capquiz_user_rating to be created. + $table = new xmldb_table('qtracker_issue'); + + $field = new xmldb_field( + 'userid', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, null, -1); + $key = new xmldb_key( + 'userid', XMLDB_KEY_FOREIGN, array('userid'), 'user', array('id')); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + $dbman->add_key($table, $key); + } + upgrade_mod_savepoint(true, 2020070800, 'capquiz'); + } return true; } diff --git a/externallib.php b/externallib.php index 4324140..2bf68a3 100644 --- a/externallib.php +++ b/externallib.php @@ -31,9 +31,9 @@ class local_qtracker_external extends external_api { public static function new_issue_parameters() { return new external_function_parameters( array( - 'questionid' => new external_value(PARAM_INT, 'question id', VALUE_REQUIRED), - 'issuetitle' => new external_value(PARAM_TEXT, 'issue title', VALUE_REQUIRED), - 'issuedescription' => new external_value(PARAM_TEXT, 'issue description', VALUE_REQUIRED), + 'questionid' => new external_value(PARAM_INT, 'question id'), + 'issuetitle' => new external_value(PARAM_TEXT, 'issue title'), + 'issuedescription' => new external_value(PARAM_TEXT, 'issue description'), ) ); } @@ -43,18 +43,22 @@ public static function new_issue_parameters() { * @return string welcome message */ public static function new_issue($questionid, $issuetitle, $issuedescription) { - global $USER; + global $USER, $DB; + + $added = false; + $warnings = array(); //Parameter validation $params = self::validate_parameters(self::new_issue_parameters(), array( - 'questionid' => $questionid, + 'questionid' => (int) $questionid, 'issuetitle' => $issuetitle, 'issuedescription' => $issuedescription, ) ); //Context validation + // TODO: ensure proper validation.... $context = \context_user::instance($USER->id); self::validate_context($context); @@ -64,7 +68,32 @@ public static function new_issue($questionid, $issuetitle, $issuedescription) { throw new moodle_exception('cannotcreateissue', 'local_qtracker'); } - return $params; + // Check if question exists. + $question = $DB->get_record('question', array('id' => $questionid)); + if ($question === false) { + $warnings[] = array( + 'item' => 'question', + 'itemid' => $questionid, + 'warningcode' => 'unknownquestionidnumber', + 'message' => 'Unknown question ID ' . $questionid + ); + } else { // Insert new issue + $dataobject = new \stdClass; + $dataobject->questionid = $questionid; + $dataobject->title = $issuetitle; + $dataobject->description = $issuedescription; + $dataobject->userid = $USER->id; + $time = time(); + $dataobject->timecreated = $time; + $DB->insert_record('qtracker_issue', $dataobject); + $added = true; + } + + $result = array(); + $result['status'] = $added; + $result['warnings'] = $warnings; + + return $result; } /** @@ -72,6 +101,11 @@ public static function new_issue($questionid, $issuetitle, $issuedescription) { * @return external_description */ public static function new_issue_returns() { - return new external_value(PARAM_TEXT, 'The welcome message + user first name'); + return new external_single_structure( + array( + 'status' => new external_value(PARAM_BOOL, 'status: true if success'), + 'warnings' => new external_warnings() + ) + ); } } diff --git a/lang/en/local_qtracker.php b/lang/en/local_qtracker.php index 8fc1386..133d32b 100755 --- a/lang/en/local_qtracker.php +++ b/lang/en/local_qtracker.php @@ -36,3 +36,9 @@ $string['question_problem_details'] = 'If you have feedback for this question, please type it below.'; $string['cannotcreateissue'] = 'You cannot create a new question issue.'; +$string['question'] = 'Question'; +$string['question_help'] = 'Select the question you want to register a new issue for.'; + +$string['unknownquestionidnumber'] = 'Unknown question ID "{$a}"'; +$string['title'] = 'Title'; +$string['leavecomment'] = 'Leave a comment'; diff --git a/qtracker_form.php b/qtracker_form.php deleted file mode 100644 index 6dd1ce6..0000000 --- a/qtracker_form.php +++ /dev/null @@ -1,16 +0,0 @@ -addElement('header', 'configheader', get_string('blocksettings', 'block')); - - // A sample string variable with a default value. - $mform->addElement('text', 'config_text', get_string('blockstring', 'local_qtracker')); - $mform->setDefault('config_text', 'default value'); - $mform->setType('config_text', PARAM_TEXT); - - } -} \ No newline at end of file diff --git a/templates/button.mustache b/templates/button.mustache index eacf901..fee19c9 100644 --- a/templates/button.mustache +++ b/templates/button.mustache @@ -19,13 +19,14 @@ Example context (json): { - "method": "post", - "url": "", "primary": true, "tooltip": "A button.", "label": "Button" } }} -{{#button}} - {{>core/single_button}} -{{/button}} +
    + +
    diff --git a/templates/issue_registration_block.mustache b/templates/issue_registration_block.mustache index 879abd5..249ce32 100644 --- a/templates/issue_registration_block.mustache +++ b/templates/issue_registration_block.mustache @@ -48,25 +48,29 @@ "form": "raw html for the form" } }} -
    -Userid: {{userid}} -
    - -

    {{#str}} question_problem_details, local_qtracker {{/str}}

    {{#hasmultiple}} {{#select}} - {{disabled}} - {{> core/single_select }} +
    + {{> local_qtracker/select }} +
    {{/select}} {{/hasmultiple}} {{^hasmultiple}} - +
    + +
    {{/hasmultiple}} -
    - - {{>core/single_button}} +
    + +
    +
    + +
    +
    + {{>local_qtracker/button}} +
    {{#js}} diff --git a/templates/select.mustache b/templates/select.mustache new file mode 100644 index 0000000..6c072cc --- /dev/null +++ b/templates/select.mustache @@ -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 . +}} +{{! + @template core_admin/setting_configselect + + Admin select setting template. + + Context variables required for this template: + * name - form element name + * id - element id + * options - list of options containing name, value, selected + + Example context (json): + { + "name": "test", + "id": "test0", + "options": [ + { "name": "Option 1", "value": "V", "selected": true }, + { "name": "Option 2", "value": "V", "selected": true } + ] + } +}} +
    + {{#label}} + + {{/label}} + {{#helpicon}} + {{>core/help_icon}} + {{/helpicon}} + +
    diff --git a/version.php b/version.php index fbf8932..67baadf 100755 --- a/version.php +++ b/version.php @@ -23,7 +23,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2020070600; +$plugin->version = 2020070800; $plugin->requires = 2016120500; $plugin->cron = 0; $plugin->component = 'local_qtracker'; From f396fb7f62e276e8ee61cb421d08a8022e6d6350 Mon Sep 17 00:00:00 2001 From: HomKham <46867728+HomKham@users.noreply.github.com> Date: Thu, 16 Jul 2020 17:40:57 +0200 Subject: [PATCH 05/83] Create table for question issues --- classes/output/question_issues_table.php | 89 ++++++++++++++++++++---- lang/en/local_qtracker.php | 7 ++ 2 files changed, 81 insertions(+), 15 deletions(-) diff --git a/classes/output/question_issues_table.php b/classes/output/question_issues_table.php index a343c05..d1d7395 100644 --- a/classes/output/question_issues_table.php +++ b/classes/output/question_issues_table.php @@ -69,19 +69,59 @@ public function __construct($uniqueid, $url) { } /** - * Something name column. - * - * @param object $data Row data. - * @return string + * Generate the display of the id column. + * @param object $data the table row being output. + * @return string HTML content to go inside the td. + */ + public function col_id($data) { + if ($data->id) { + return $data->id; + } else { + return '-'; + } + } + + /** + * Generate the display of the question name column. + * @param object $data the table row being output. + * @return string HTML content to go inside the td. */ - protected function col_something($data) { - // TODO: implement one of these for questionid, title, description, and user. - return $data->something; + protected function col_questionid($data) { + if ($data->questionid) { + return $data->questionid; + } else { + return '-'; + } + } + + /** + * Generate the display of the title. + * @param object $data the table row being output. + * @return string HTML content to go inside the td. + */ + protected function col_title($data) { + if ($data->title) { + return $data->title; + } else { + return '-'; + } + } + + /** + * Generate the display of the description. + * @param object $data the table row being output. + * @return string HTML content to go inside the td. + */ + protected function col_description($data) { + if ($data->description) { + return $data->description; + } else { + return '-'; + } } /** * The timecreated column. - * * @param stdClass $data The row data. * @return string */ @@ -98,10 +138,11 @@ protected function define_table_columns() { // Define headers and columns. //TODO: define strings in lang file. $cols = array( + 'id' => get_string('id', 'local_qtracker'), 'questionid' => get_string('questionid', 'local_qtracker'), - 'title' => get_string('issuetitle', 'local_qtracker'), - 'description' => get_string('issuedescription', 'local_qtracker'), - 'datecreated' => get_string('datecreated', 'local_qtracker'), + 'title' => get_string('title', 'local_qtracker'), + 'description' => get_string('description', 'local_qtracker'), + 'datecreated' => get_string('datecreated', 'local_qtracker') ); // Add remaining headers. @@ -126,13 +167,31 @@ protected function define_table_configs() { * @return array containing sql to use and an array of params. */ public function setup_sql_queries() { - // TODO: Write SQL to retrieve all rows... - $fields = ''; - $from = ''; - $where = ''; + $fields = 'DISTINCT'; + $fields .= '*'; + $from = '{qtracker_issue} qs'; + $where = '1=1'; $params = array(); // TODO: find a way to only get the correct contexts.. For now just get everything (keep this empty)... + // The WHERE clause is vital here, because some parts of tablelib.php will expect to + // add bits like ' AND x = 1' on the end, and that needs to leave to valid SQL. + $this->set_count_sql("SELECT COUNT(1) FROM (SELECT $fields FROM $from WHERE $where) temp WHERE 1 = 1", $params); + + list($fields, $from, $where, $params) = $this->update_sql_after_count($fields, $from, $where, $params); $this->set_sql($fields, $from, $where, $params); } + + /** + * A chance for subclasses to modify the SQL after the count query has been generated, + * and before the full query is constructed. + * @param string $fields SELECT list. + * @param string $from JOINs part of the SQL. + * @param string $where WHERE clauses. + * @param array $params Query params. + * @return array with 4 elements ($fields, $from, $where, $params) as from base_sql. + */ + protected function update_sql_after_count($fields, $from, $where, $params) { + return [$fields, $from, $where, $params]; + } } diff --git a/lang/en/local_qtracker.php b/lang/en/local_qtracker.php index 8fc1386..2e9db30 100755 --- a/lang/en/local_qtracker.php +++ b/lang/en/local_qtracker.php @@ -34,5 +34,12 @@ $string['headerconfig'] = 'Config section header'; $string['labelfoo'] = 'Config label'; +// question issues table +$string['id'] = 'ID'; +$string['questionid'] = 'Question ID'; +$string['title'] = 'Title'; +$string['description'] = 'Description'; +$string['datecreated'] = 'Created Time'; + $string['question_problem_details'] = 'If you have feedback for this question, please type it below.'; $string['cannotcreateissue'] = 'You cannot create a new question issue.'; From acf0d993bf0f36f8b65b16640a5f79fc00df3273 Mon Sep 17 00:00:00 2001 From: HomKham <46867728+homkham@users.noreply.github.com> Date: Thu, 16 Jul 2020 17:57:02 +0200 Subject: [PATCH 06/83] Create table for question issues --- classes/output/question_issues_table.php | 1 - lang/en/local_qtracker.php | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/output/question_issues_table.php b/classes/output/question_issues_table.php index d1d7395..11fc475 100644 --- a/classes/output/question_issues_table.php +++ b/classes/output/question_issues_table.php @@ -53,7 +53,6 @@ public function __construct($uniqueid, $url) { // TODO: determine which context to use... $context = context_system::instance(); - $this->context = $context; // This object should not be used without the right permissions. require_capability('moodle/role:manage', $context); // DO WE NEED THIS? diff --git a/lang/en/local_qtracker.php b/lang/en/local_qtracker.php index 2e9db30..195b223 100755 --- a/lang/en/local_qtracker.php +++ b/lang/en/local_qtracker.php @@ -35,6 +35,7 @@ $string['labelfoo'] = 'Config label'; // question issues table + $string['id'] = 'ID'; $string['questionid'] = 'Question ID'; $string['title'] = 'Title'; From c5d616ac4ea5185874489b09dd436c586176a744 Mon Sep 17 00:00:00 2001 From: HomKham <46867728+HomKham@users.noreply.github.com> Date: Thu, 16 Jul 2020 18:17:21 +0200 Subject: [PATCH 07/83] Create table for question issues --- classes/output/question_issues_table.php | 1 - lang/en/local_qtracker.php | 1 - 2 files changed, 2 deletions(-) diff --git a/classes/output/question_issues_table.php b/classes/output/question_issues_table.php index 11fc475..c10cb67 100644 --- a/classes/output/question_issues_table.php +++ b/classes/output/question_issues_table.php @@ -50,7 +50,6 @@ class question_issues_table extends table_sql { public function __construct($uniqueid, $url) { global $CFG; parent::__construct($uniqueid); - // TODO: determine which context to use... $context = context_system::instance(); diff --git a/lang/en/local_qtracker.php b/lang/en/local_qtracker.php index 195b223..2e9db30 100755 --- a/lang/en/local_qtracker.php +++ b/lang/en/local_qtracker.php @@ -35,7 +35,6 @@ $string['labelfoo'] = 'Config label'; // question issues table - $string['id'] = 'ID'; $string['questionid'] = 'Question ID'; $string['title'] = 'Title'; From 5bcb137bd6abd3602170b2476c0ec1d26b9ba639 Mon Sep 17 00:00:00 2001 From: HomKham <46867728+HomKham@users.noreply.github.com> Date: Thu, 16 Jul 2020 18:20:26 +0200 Subject: [PATCH 08/83] Create table for question issues --- classes/output/question_issues_table.php | 1 + lang/en/local_qtracker.php | 1 + 2 files changed, 2 insertions(+) diff --git a/classes/output/question_issues_table.php b/classes/output/question_issues_table.php index c10cb67..280eb26 100644 --- a/classes/output/question_issues_table.php +++ b/classes/output/question_issues_table.php @@ -165,6 +165,7 @@ protected function define_table_configs() { * @return array containing sql to use and an array of params. */ public function setup_sql_queries() { + // TODO: Write SQL to retrieve all rows... $fields = 'DISTINCT'; $fields .= '*'; diff --git a/lang/en/local_qtracker.php b/lang/en/local_qtracker.php index 2e9db30..195b223 100755 --- a/lang/en/local_qtracker.php +++ b/lang/en/local_qtracker.php @@ -35,6 +35,7 @@ $string['labelfoo'] = 'Config label'; // question issues table + $string['id'] = 'ID'; $string['questionid'] = 'Question ID'; $string['title'] = 'Title'; From e4c053bb0f94de648a5daf04ced30951fb8634be Mon Sep 17 00:00:00 2001 From: HomKham <46867728+HomKham@users.noreply.github.com> Date: Thu, 16 Jul 2020 18:23:50 +0200 Subject: [PATCH 09/83] Create table for question issues --- classes/output/question_issues_table.php | 1 - lang/en/local_qtracker.php | 1 - 2 files changed, 2 deletions(-) diff --git a/classes/output/question_issues_table.php b/classes/output/question_issues_table.php index 280eb26..c10cb67 100644 --- a/classes/output/question_issues_table.php +++ b/classes/output/question_issues_table.php @@ -165,7 +165,6 @@ protected function define_table_configs() { * @return array containing sql to use and an array of params. */ public function setup_sql_queries() { - // TODO: Write SQL to retrieve all rows... $fields = 'DISTINCT'; $fields .= '*'; diff --git a/lang/en/local_qtracker.php b/lang/en/local_qtracker.php index 195b223..2e9db30 100755 --- a/lang/en/local_qtracker.php +++ b/lang/en/local_qtracker.php @@ -35,7 +35,6 @@ $string['labelfoo'] = 'Config label'; // question issues table - $string['id'] = 'ID'; $string['questionid'] = 'Question ID'; $string['title'] = 'Title'; From 785d73ef53bc24343b544a2ca7e4819de6d239a7 Mon Sep 17 00:00:00 2001 From: HomKham <46867728+HomKham@users.noreply.github.com> Date: Thu, 16 Jul 2020 18:24:57 +0200 Subject: [PATCH 10/83] Create table for question issues --- classes/output/question_issues_table.php | 1 + lang/en/local_qtracker.php | 1 + 2 files changed, 2 insertions(+) diff --git a/classes/output/question_issues_table.php b/classes/output/question_issues_table.php index c10cb67..280eb26 100644 --- a/classes/output/question_issues_table.php +++ b/classes/output/question_issues_table.php @@ -165,6 +165,7 @@ protected function define_table_configs() { * @return array containing sql to use and an array of params. */ public function setup_sql_queries() { + // TODO: Write SQL to retrieve all rows... $fields = 'DISTINCT'; $fields .= '*'; diff --git a/lang/en/local_qtracker.php b/lang/en/local_qtracker.php index 2e9db30..195b223 100755 --- a/lang/en/local_qtracker.php +++ b/lang/en/local_qtracker.php @@ -35,6 +35,7 @@ $string['labelfoo'] = 'Config label'; // question issues table + $string['id'] = 'ID'; $string['questionid'] = 'Question ID'; $string['title'] = 'Title'; From 02650855aaea67afe5912ef9719d5272ea4ddd27 Mon Sep 17 00:00:00 2001 From: HomKham <46867728+HomKham@users.noreply.github.com> Date: Thu, 16 Jul 2020 18:30:39 +0200 Subject: [PATCH 11/83] Create table for question issues --- classes/output/question_issues_table.php | 1 - lang/en/local_qtracker.php | 1 - 2 files changed, 2 deletions(-) diff --git a/classes/output/question_issues_table.php b/classes/output/question_issues_table.php index 280eb26..c10cb67 100644 --- a/classes/output/question_issues_table.php +++ b/classes/output/question_issues_table.php @@ -165,7 +165,6 @@ protected function define_table_configs() { * @return array containing sql to use and an array of params. */ public function setup_sql_queries() { - // TODO: Write SQL to retrieve all rows... $fields = 'DISTINCT'; $fields .= '*'; diff --git a/lang/en/local_qtracker.php b/lang/en/local_qtracker.php index 195b223..2e9db30 100755 --- a/lang/en/local_qtracker.php +++ b/lang/en/local_qtracker.php @@ -35,7 +35,6 @@ $string['labelfoo'] = 'Config label'; // question issues table - $string['id'] = 'ID'; $string['questionid'] = 'Question ID'; $string['title'] = 'Title'; From 36ac051e7f4d2c59b746e26c69ea19ad163181d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Storhaug?= Date: Fri, 24 Jul 2020 04:29:22 +0200 Subject: [PATCH 12/83] Add support for blocks --- amd/src/block_form_manager.js | 483 ++++++++++++++++++ amd/src/issue.js | 120 ++++- amd/src/issue_manager.js | 105 ++++ amd/src/newissue.js | 171 ------- classes/external/deleteissue.php | 116 +++++ classes/external/editissue.php | 139 +++++ classes/external/getissue.php | 138 +++++ .../external/newissue.php | 82 ++- classes/issue.php | 181 ++++++- classes/output/issue_registration_block.php | 68 ++- classes/output/question_issues_page.php | 12 +- classes/output/question_issues_table.php | 9 +- classes/output/renderer.php | 26 +- db/access.php | 19 +- db/install.xml | 3 + db/services.php | 49 +- db/upgrade.php | 24 +- lang/en/local_qtracker.php | 20 +- lib.php | 43 +- locallib.php | 22 - templates/button.mustache | 2 +- templates/issue_registration_block.mustache | 27 +- version.php | 2 +- view.php | 4 +- 24 files changed, 1542 insertions(+), 323 deletions(-) create mode 100644 amd/src/block_form_manager.js create mode 100644 amd/src/issue_manager.js delete mode 100644 amd/src/newissue.js create mode 100644 classes/external/deleteissue.php create mode 100644 classes/external/editissue.php create mode 100644 classes/external/getissue.php rename externallib.php => classes/external/newissue.php (57%) delete mode 100644 locallib.php diff --git a/amd/src/block_form_manager.js b/amd/src/block_form_manager.js new file mode 100644 index 0000000..dc61f64 --- /dev/null +++ b/amd/src/block_form_manager.js @@ -0,0 +1,483 @@ +// 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 . + +/** + * Manager for a Question Tracker Block form. + * + * @module local_qtracker/BlockFormManager + * @class BlockFormManager + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/issue', 'local_qtracker/issue_manager'], + function ($, Str, Templates, Ajax, Issue, IssueManager) { + var SELECTORS = { + SLOT: '[name="slot"]', + SLOT_SELECT_OPTION: '[name="slot"] option', + TITLE: '[name="issuetitle"]', + DESCRIPTION: '[name="issuedescription"]', + SUBMIT_BUTTON: 'button[type="submit"]', + DELETE_BUTTON: '#qtracker-delete', + } + + let VALIDATION_ELEMENTS = [ + SELECTORS.TITLE, + SELECTORS.DESCRIPTION, + ]; + + var NOTIFICATION_DURATION = 7500; + var notificationTimeoutHandle = null; + + /** + * Constructor + * + * @param {String} selector used to find triggers for the new group modal. + * @param {int} contextid + * + * Each call to init gets it's own instance of this class. + */ + var BlockFormManager = function (selector, issueids) { + this.form = $(selector); + this.form.closest('.card-text').prepend(''); + this.issueManager = new IssueManager(); + this.init(selector, JSON.parse(issueids)); + }; + + /** + * @var {Form} form + * @private + */ + BlockFormManager.prototype.form = null; + + /** + * @var {int} contextid + * @private + */ + BlockFormManager.prototype.contextid = -1; + + /** + * @var {int} issueid + * @private + */ + BlockFormManager.prototype.issueid = null; + + /** + * @var {int} issueid + * @private + */ + BlockFormManager.prototype.issues = []; + + /** + * @var {int} issueid + * @private + */ + BlockFormManager.prototype.issueManager = null; + + /** + * Initialise the class. + * + * @param {String} selector used to find triggers for the new question issue. + * @private + * @return {Promise} + */ + BlockFormManager.prototype.init = function (selector, issueids) { + // Init all slots + let slots = $(SELECTORS.SLOT_SELECT_OPTION); + if (slots.length == 0) slots = $(SELECTORS.SLOT); + slots.map((index, option) => { + let issue = new Issue(null, parseInt(option.value)); + issue.changeState(Issue.STATES.NEW); + this.issueManager.addIssue(issue); + }); + + this.issueManager.loadIssues(issueids).then(() => { + var formData = new FormData(this.form[0]); + this.issueManager.setActiveIssue(parseInt(formData.get('slot'))); + + this.reflectFormState() + + // Issue title event listener. + let titleElement = this.form.find(SELECTORS.TITLE) + titleElement.change((event) => { + this.issueManager.getActiveIssue().setTitle(event.target.value); + }); + /* $(document).on(qtrackerEvents.CHANGED_SLOT_BLOCK_FORM, (event, value) => { + titleElement.val(value); + }); */ + + // Issue description event listener. + let descriptionElement = this.form.find(SELECTORS.DESCRIPTION) + descriptionElement.change((event) => { + this.issueManager.getActiveIssue().setDescription(event.target.value); + }); + /* $(document).on(qtrackerEvents.CHANGED_SLOT_BLOCK_FORM, (event, value) => { + descriptionElement.val(value) + }); */ + + // + + + // Load existing issues. + var slotElement = this.form.find(SELECTORS.SLOT); + slotElement.change(this.handleSlotChange.bind(this)); + + this.form.on('submit', this.submitFormAjax.bind(this)); + + }).catch((error) => { + console.error(error); + }); + }; + + BlockFormManager.prototype.handleSlotChange = function (e) { + this.issueManager.setActiveIssue(parseInt(e.target.value)) + this.reflectFormState(); + this.resetValidation() + } + + BlockFormManager.prototype.reflectFormState = function () { + let issue = this.issueManager.getActiveIssue(); + var state = issue.getState(); + if (state === Issue.STATES.EXISTING) { + this.toggleDeleteButton(true); + this.toggleUpdateButton(true); + } else if (state === Issue.STATES.NEW) { + this.clearForm(); + } + + this.restoreForm(); + } + + /** + * @method handleFormSubmissionResponse + * @private + * @return {Promise} + */ + BlockFormManager.prototype.handleFormSubmissionResponse = function (response) { + + // TODO: handle response.status === false + // TODO: handle response.warning ... + + // We could trigger an event instead. + // Yuk. + Y.use('moodle-core-formchangechecker', function () { + M.core_formchangechecker.reset_form_dirty_state(); + }); + //document.location.reload(); + + this.issueManager.getActiveIssue().setId(response.issueid); + }; + + /** + * @method handleFormSubmissionFailure + * @private + * @return {Promise} + */ + BlockFormManager.prototype.handleFormSubmissionFailure = function (response) { + // Oh noes! Epic fail :( + // Ah wait - this is normal. We need to re-display the form with errors! + console.error("An error occured"); + console.error(response); + }; + + BlockFormManager.prototype.clearForm = function () { + // Remove delete button. + this.form.find('#qtracker-delete').remove(); + this.resetValidation(); + Str.get_string('submitnewissue', 'local_qtracker').then(function (string) { + this.form.find('button[type="submit"]').html(string); + }.bind(this)); + } + + BlockFormManager.prototype.restoreForm = function () { + let issue = this.issueManager.getActiveIssue(); + this.form.find('[name="issuetitle"]').val(issue.getTitle()); + this.form.find('[name="issuedescription"]').val(issue.getDescription()); + + }; + + /** + * @method editIssue + * @private + * @return {Promise} + */ + BlockFormManager.prototype.editIssue = function () { + var formData = new FormData(this.form[0]); + Ajax.call([{ + methodname: 'local_qtracker_edit_issue', + args: { + issueid: this.issueManager.getActiveIssue().getId(), + issuetitle: formData.get('issuetitle'), + issuedescription: formData.get('issuedescription'), + }, + done: function (response) { + Str.get_string('issueupdated', 'local_qtracker').then(function (string) { + let notification = { + message: string, + announce: true, + type: "success", + }; + this.notify(notification); + }.bind(this)); + this.handleFormSubmissionResponse(response); + }.bind(this), + fail: this.handleFormSubmissionFailure.bind(this) + }]); + }; + /** + * @method editIssue + * @private + * @return {Promise} + */ + BlockFormManager.prototype.deleteIssue = function () { + Ajax.call([{ + methodname: 'local_qtracker_delete_issue', + args: { + issueid: this.issueManager.getActiveIssue().getId(), + }, + done: function (response) { + Str.get_string('issuedeleted', 'local_qtracker').then(function (string) { + let notification = { + message: string, + announce: true, + type: "success", + }; + this.notify(notification); + }.bind(this)); + this.issueManager.getActiveIssue().changeState(Issue.STATES.NEW);; + this.clearForm(); + }.bind(this), + fail: this.handleFormSubmissionFailure.bind(this) + }]); + }; + + /** + * @method handleFormSubmissionFailure + * @private + * @return {Promise} + */ + BlockFormManager.prototype.createIssue = function () { + var formData = new FormData(this.form[0]); + // Now we can continue... + Ajax.call([{ + methodname: 'local_qtracker_new_issue', + args: { + qubaid: formData.get('qubaid'), + slot: formData.get('slot'), + issuetitle: formData.get('issuetitle'), + issuedescription: formData.get('issuedescription'), + }, + done: function (response) { + Str.get_string('issuecreated', 'local_qtracker').then(function (string) { + let notification = { + message: string, + announce: true, + type: "success", + }; + this.notify(notification); + }.bind(this)); + this.issueManager.getActiveIssue().changeState(Issue.STATES.EXISTING) + //this.setAction(ACTION.EDITISSUE); + // TODO: add delete button. + this.toggleUpdateButton(true); + this.toggleDeleteButton(true); + + this.handleFormSubmissionResponse(response); + }.bind(this), + fail: this.handleFormSubmissionFailure.bind(this) + }]); + }; + + /** + * Cancel any typing pause timer. + */ + BlockFormManager.prototype.cancelNotificationTimer = function () { + if (notificationTimeoutHandle) { + clearTimeout(notificationTimeoutHandle); + } + notificationTimeoutHandle = null; + } + + BlockFormManager.prototype.notify = function (notification) { + notification = $.extend({ + closebutton: true, + announce: true, + type: 'error', + extraclasses: "show", + }, notification); + + let types = { + 'success': 'core/notification_success', + 'info': 'core/notification_info', + 'warning': 'core/notification_warning', + 'error': 'core/notification_error', + }; + + this.cancelNotificationTimer(); + + let template = types[notification.type]; + Templates.render(template, notification) + .then((html, js) => { + $('#qtracker-notifications').html(html); + Templates.runTemplateJS(js); + + notificationTimeoutHandle = setTimeout(() => { + $('#qtracker-notifications').find('.alert').alert('close'); + }, NOTIFICATION_DURATION); + }) + .catch((error) => { + console.error(error) + }); + } + /** + * @method handleFormSubmissionFailure + * @private + * @return {Promise} + */ + BlockFormManager.prototype.toggleUpdateButton = function (show) { + if (show) { + Str.get_string('update', 'core').then(function (updateStr) { + this.form.find(SELECTORS.SUBMIT_BUTTON).html(updateStr); + }.bind(this)); + } else { + Str.get_string('submitnewissue', 'local_qtracker').then(function (updateStr) { + this.form.find(SELECTORS.SUBMIT_BUTTON).html(updateStr); + }.bind(this)); + } + } + /** + * @method handleFormSubmissionFailure + * @private + * @return {Promise} + */ + BlockFormManager.prototype.toggleDeleteButton = function (show) { + const context = { + type: "button", + classes: "col-auto", + label: "Delete", + id: "qtracker-delete", + }; + + let deleteButton = this.form.find(SELECTORS.DELETE_BUTTON); + if (deleteButton.length == 0 && show) { + Templates.render('local_qtracker/button', context) + .then(function (html, js) { + var container = this.form.find('button').closest(".form-row"); + Templates.appendNodeContents(container, html, js); + this.form.find('#qtracker-delete').on('click', function () { + this.deleteIssue() + }.bind(this)); + }.bind(this)); + } else { + if (show) { + deleteButton.show(); + } else { + deleteButton.hide(); + } + } + } + + /** + * @method handleFormSubmissionFailure + * @private + * @return {Promise} + */ + BlockFormManager.prototype.setAction = function (newaction) { + + this.form.data('action', newaction); + }; + + /** + * Private method + * + * @method submitFormAjax + * @private + * @param {Event} e Form submission event. + */ + BlockFormManager.prototype.submitFormAjax = function (e) { + // We don't want to do a real form submission. + e.preventDefault(); + e.stopPropagation(); + + if (!this.validateForm()) { + return; + } + + var state = this.issueManager.getActiveIssue().getState(); + switch (state) { + case Issue.STATES.NEW: + this.createIssue(); + break; + case Issue.STATES.EXISTING: + this.editIssue(); + break; + case Issue.STATES.DELETED: + this.issueManager.getActiveIssue().changeState(Issue.STATES.NEW) + this.createIssue(); + break; + default: + break; + } + }; + + BlockFormManager.prototype.validateForm = function () { + let valid = true; + VALIDATION_ELEMENTS.forEach(selector => { + let element = this.form.find(selector) + if (element.val() != "" && element.prop("validity").valid) { + element.removeClass("is-invalid").addClass("is-valid"); + } else { + element.removeClass("is-valid").addClass("is-invalid"); + valid = false; + } + }); + return valid; + }; + + BlockFormManager.prototype.resetValidation = function () { + VALIDATION_ELEMENTS.forEach(selector => { + let element = this.form.find(selector) + element.removeClass("is-invalid").removeClass("is-valid") + }); + }; + + /** + * This triggers a form submission, so that any mform elements can do final tricks before the form submission is processed. + * + * @method submitForm + * @param {Event} e Form submission event. + * @private + */ + BlockFormManager.prototype.submitForm = function (e) { + e.preventDefault(); + this.form.submit(); + }; + + return /** @alias module:local_qtracker/BlockFormManager */ { + + /** + * Initialise the module. + * + * @method init + * @param {string} selector The selector used to find the form for to use for this module. + * @param {string} issueids The ids of existing issues to load. + * @return {BlockFormManager} + */ + init: function (selector, issueids) { + return new BlockFormManager(selector, issueids); + } + }; + }); diff --git a/amd/src/issue.js b/amd/src/issue.js index 8c76b0f..26fdfea 100644 --- a/amd/src/issue.js +++ b/amd/src/issue.js @@ -1,14 +1,29 @@ +// 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 . + /** - * Add a create new group modal to the page. + * Module for representing a question issue. * - * @module core_group/NewIssue - * @class NewIssue - * @package core_group - * @copyright 2017 Damyon Wiese + * @module local_qtracker/Issue + * @class Issue + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/fragment', 'core/ajax', 'core/yui'], - function($, Str, ModalFactory, ModalEvents, Fragment, Ajax, Y) { +define(['jquery', 'core/str', 'core/ajax'], function ($, Str, Ajax) { /** * Constructor @@ -18,22 +33,55 @@ define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/f * * Each call to init gets it's own instance of this class. */ - var NewIssue = function() { - console.log("issue created") - this.init(selector); + var Issue = function (id = null, slot = null) { + this.id = id; + this.slot = slot; }; + Issue.STATES = { + NEW: "new", + EXISTING: "existing", + DELETED: "deleted", + } + + /** + * @var {int} id The id of this issue + * @private + */ + Issue.prototype.id = null; + + /** + * @var {int} id The slot for this issue + * @private + */ + Issue.prototype.slot = null; + /** - * @var {Modal} modal + * @var {string} title The title for this issue * @private */ - NewIssue.prototype.modal = null; + Issue.prototype.title = ""; /** - * @var {int} contextid + * @var {string} title The description for this issue * @private */ - NewIssue.prototype.contextid = -1; + Issue.prototype.description = ""; + + Issue.prototype.state = Issue.STATES.NEW; + + Issue.prototype.inDB = false; + + /** + * Initialise the class. + * + * @param {String} selector used to find triggers for the new group modal. + * @private + * @return {Promise} + */ + Issue.prototype.setId = function (id) { + this.id = id; + }; /** * Initialise the class. @@ -42,9 +90,47 @@ define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/f * @private * @return {Promise} */ - NewIssue.prototype.init = function(selector) { - console.log("issue initiated"); + Issue.prototype.getId = function () { + return this.id; + }; + + Issue.prototype.getSlot = function () { + return this.slot; + }; + + Issue.prototype.getTitle = function () { + return this.title; + }; + + Issue.prototype.setTitle = function (title) { + this.title = title; + }; + + Issue.prototype.getDescription = function () { + return this.description; + }; + + + Issue.prototype.setDescription = function (description) { + this.description = description; + }; + + Issue.prototype.changeState = function (state) { + this.state = state; + }; + + Issue.prototype.getState = function () { + return this.state; + }; + + /** + * return {Promise} + */ + Issue.load = function (id) { + return Ajax.call([ + { methodname: 'local_qtracker_get_issue', args: { issueid: id} } + ])[0]; }; - return new Issue(selector); + return Issue; }); diff --git a/amd/src/issue_manager.js b/amd/src/issue_manager.js new file mode 100644 index 0000000..5216a68 --- /dev/null +++ b/amd/src/issue_manager.js @@ -0,0 +1,105 @@ +// 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 . + +/** + * Manager for managing question issues. + * + * @module local_qtracker/IssueManager + * @class IssueManager + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define(['jquery', 'local_qtracker/events', 'local_qtracker/issue'], function ($, qtrackerEvents, Issue) { + + /** + * Constructor + * @constructor + * @param {String} selector used to find triggers for the new group modal. + * @param {int} contextid + * + * Each call to init gets it's own instance of this class. + */ + var IssueManager = function () { }; + + /** + * @var {Form} form + * @private + */ + IssueManager.prototype.issues = new Map(); + + IssueManager.prototype.activeIssue = null; + + + IssueManager.prototype.getActiveIssue = function () { + return this.activeIssue; + }; + + IssueManager.prototype.setActiveIssue = function (slot) { + let newIssue = this.getIssueBySlot(slot); + this.activeIssue = newIssue; + /* if (newIssue) { + $(document).trigger(qtrackerEvents.ISSUEMANAGER_ACTIVEISSUE_CHANGED) + } */ + return newIssue; + }; + + /** + * This triggers a form submission, so that any mform elements can do final tricks before the form submission is processed. + * + * @method submitForm + * @param {Event} e Form submission event. + * @private + */ + IssueManager.prototype.getIssueBySlot = function (slot) { + return this.issues.get(slot); + }; + + IssueManager.prototype.getIssueById = function (id) { + for (const [slot, issue] of this.issues) { + if (issue.getId() !== null && issue.getId() === id) { + return issue; + } + }; + return false; + }; + + IssueManager.prototype.addIssue = function (issue) { + this.issues.set(issue.getSlot(), issue); + }; + + IssueManager.prototype.loadIssues = function (issueids) { + var promises = []; + for (const id of issueids) { + let promise = Issue.load(id).then((response) => { + let issue = this.getIssueBySlot(response.issue.slot) + if (!issue) { + issue = new Issue(response.issue.id, response.issue.slot); + } + issue.setId(response.issue.id); + issue.setTitle(response.issue.title); + issue.setDescription(response.issue.description); + issue.changeState(Issue.STATES.EXISTING); + this.addIssue(issue); + }); + promises.push(promise); + } + + return Promise.all(promises); + }; + + return IssueManager; +}); diff --git a/amd/src/newissue.js b/amd/src/newissue.js deleted file mode 100644 index 0c68da3..0000000 --- a/amd/src/newissue.js +++ /dev/null @@ -1,171 +0,0 @@ -/** - * Add a create new group modal to the page. - * - * @module core_group/NewIssue - * @class NewIssue - * @package core_group - * @copyright 2017 Damyon Wiese - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -define(['jquery', 'core/str', 'core/templates', 'core/modal_events', 'core/fragment', 'core/ajax', 'core/yui'], - function($, Str, Templates, ModalEvents, Fragment, Ajax, Y, ) { - - /** - * Constructor - * - * @param {String} selector used to find triggers for the new group modal. - * @param {int} contextid - * - * Each call to init gets it's own instance of this class. - */ - var NewIssue = function(selector) { - this.init(selector); - }; - - /** - * @var {Form} form - * @private - */ - NewIssue.prototype.form = null; - - /** - * @var {int} contextid - * @private - */ - NewIssue.prototype.contextid = -1; - - /** - * Initialise the class. - * - * @param {String} selector used to find triggers for the new question issue. - * @private - * @return {Promise} - */ - NewIssue.prototype.init = function(selector) { - var trigger = $(selector); - this.form = trigger; - - // Fetch the title string. - return Str.get_string('creategroup', 'core_group').then(function(title) { - console.log(title); - this.contextid = 2 - }.bind(this)).then(()=> { - - console.log(trigger) - // We catch the modal save event, and use it to submit the form inside the modal. - // Triggering a form submission will give JS validation scripts a chance to check for errors. - //this.form.getRoot().on(ModalEvents.save, this.submitForm.bind(this)); - // We also catch the form submit event and use it to submit the form with ajax. - //this.modal.getRoot().on('submit', 'form', this.submitFormAjax.bind(this)); - trigger.on('submit', this.submitFormAjax.bind(this)); - }); - - }; - - /** - * @method handleFormSubmissionResponse - * @private - * @return {Promise} - */ - NewIssue.prototype.handleFormSubmissionResponse = function(formData, response) { - console.log("Success!", formData, response) - // We could trigger an event instead. - // Yuk. - Y.use('moodle-core-formchangechecker', function() { - M.core_formchangechecker.reset_form_dirty_state(); - }); - //document.location.reload(); - }; - - /** - * @method handleFormSubmissionFailure - * @private - * @return {Promise} - */ - NewIssue.prototype.handleFormSubmissionFailure = function(data, response) { - // Oh noes! Epic fail :( - // Ah wait - this is normal. We need to re-display the form with errors! - console.error("An error occured"); - console.error(response); - }; - - /** - * Private method - * - * @method submitFormAjax - * @private - * @param {Event} e Form submission event. - */ - NewIssue.prototype.submitFormAjax = function(e) { - // We don't want to do a real form submission. - e.preventDefault(); - - - - - var changeEvent = document.createEvent('HTMLEvents'); - changeEvent.initEvent('change', true, true); - - // Prompt all inputs to run their validation functions. - // Normally this would happen when the form is submitted, but - // since we aren't submitting the form normally we need to run client side - // validation. - this.form.find(':input').each(function(index, element) { - element.dispatchEvent(changeEvent); - }); - - // Now the change events have run, see if there are any "invalid" form fields. - var invalid = $.merge( - this.form.find('[aria-invalid="true"]'), - this.form.find('.error') - ); - - // If we found invalid fields, focus on the first one and do not submit via ajax. - if (invalid.length) { - invalid.first().focus(); - return; - } - - // Convert all the form elements values to a serialised string. - //var formData = this.form.serialize(); - var formData = new FormData(this.form[0]); - // Now we can continue... - Ajax.call([{ - methodname: 'local_qtracker_new_issue', - args: { - questionid: formData.get('questionid'), - issuetitle: formData.get('issuetitle'), - issuedescription: formData.get('issuedescription'), - }, - done: this.handleFormSubmissionResponse.bind(this, formData), - fail: this.handleFormSubmissionFailure.bind(this, formData) - }]); - }; - - /** - * This triggers a form submission, so that any mform elements can do final tricks before the form submission is processed. - * - * @method submitForm - * @param {Event} e Form submission event. - * @private - */ - NewIssue.prototype.submitForm = function(e) { - e.preventDefault(); - this.form.submit(); - }; - - return /** @alias module:core_group/NewIssue */ { - // Public variables and functions. - /** - * Attach event listeners to initialise this module. - * - * @method init - * @param {string} selector The CSS selector used to find nodes that will trigger this module. - * @param {int} contextid The contextid for the course. - * @return {Promise} - */ - init: function(selector) { - return new NewIssue(selector); - } - }; -}); diff --git a/classes/external/deleteissue.php b/classes/external/deleteissue.php new file mode 100644 index 0000000..a08874a --- /dev/null +++ b/classes/external/deleteissue.php @@ -0,0 +1,116 @@ +. + +/** + * External (web service) function calls for deleting a question issue. + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_qtracker\external; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . "/externallib.php"); +require_once($CFG ->libdir . '/questionlib.php'); + +use external_value; +use external_function_parameters; +use moodle_exception; +use external_single_structure; +use external_warnings; +use local_qtracker\issue; + + +class deleteissue extends \external_api { + + /** + * Returns description of method parameters + * @return external_function_parameters + */ + public static function delete_issue_parameters() { + return new external_function_parameters( + array( + 'issueid' => new external_value(PARAM_INT, 'issue id'), + ) + ); + } + + /** + * Returns welcome message + * @return string welcome message + */ + public static function delete_issue($issueid) { + global $USER, $DB; + + $deleted = false; + $warnings = array(); + + //Parameter validation + $params = self::validate_parameters(self::delete_issue_parameters(), + array( + 'issueid' => (int) $issueid, + ) + ); + + //Context validation + // TODO: ensure proper validation.... + $context = \context_user::instance($USER->id); + self::validate_context($context); + + //Capability checking + if (!has_capability('local/qtracker:createissue', $context)) { + throw new \moodle_exception('cannotdeleteissue', 'local_qtracker'); + } + + if (!$DB->record_exists_select('qtracker_issue', 'id = :issueid AND userid = :userid', + array( + 'issueid' => $params['issueid'], + 'userid' => $USER->id + ) + )) { + throw new \moodle_exception('cannotdeleteissue', 'local_qtracker', '', $params['issueid']); + } + + if (empty($warnings)) { + $issue = issue::load($params['issueid']); + $deleted = $issue->delete(); + } + + $result = array(); + $result['status'] = $deleted; + $result['issueid'] = $params['issueid']; + $result['warnings'] = $warnings; + + return $result; + } + + /** + * Returns description of method result value + * @return external_description + */ + public static function delete_issue_returns() { + return new external_single_structure( + array( + 'status' => new external_value(PARAM_BOOL, 'status: true if success'), + 'issueid' => new external_value(PARAM_INT, 'The id of the new issue'), + 'warnings' => new external_warnings() + ) + ); + } +} diff --git a/classes/external/editissue.php b/classes/external/editissue.php new file mode 100644 index 0000000..77b8456 --- /dev/null +++ b/classes/external/editissue.php @@ -0,0 +1,139 @@ +. + +/** + * External (web service) function calls for editing a question issue. + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_qtracker\external; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . "/externallib.php"); +require_once($CFG ->libdir . '/questionlib.php'); + +use external_value; +use external_function_parameters; +use moodle_exception; +use external_single_structure; +use external_warnings; +use local_qtracker\issue; + + +class editissue extends \external_api { + + /** + * Returns description of method parameters + * @return external_function_parameters + */ + public static function edit_issue_parameters() { + return new external_function_parameters( + array( + 'issueid' => new external_value(PARAM_INT, 'issue id'), + 'issuetitle' => new external_value(PARAM_TEXT, 'issue title'), + 'issuedescription' => new external_value(PARAM_TEXT, 'issue description'), + ) + ); + } + + /** + * Returns welcome message + * @return string welcome message + */ + public static function edit_issue($issueid, $issuetitle, $issuedescription) { + global $USER, $DB; + + $added = false; + $warnings = array(); + + //Parameter validation + $params = self::validate_parameters(self::edit_issue_parameters(), + array( + 'issueid' => (int) $issueid, + 'issuetitle' => $issuetitle, + 'issuedescription' => $issuedescription, + ) + ); + + //Context validation + // TODO: ensure proper validation.... + $context = \context_user::instance($USER->id); + self::validate_context($context); + + //Capability checking + if (!has_capability('local/qtracker:createissue', $context)) { + throw new \moodle_exception('cannoteditissue', 'local_qtracker'); + } + + if (!$DB->record_exists_select('qtracker_issue', 'id = :issueid AND userid = :userid', + array( + 'issueid' => $params['issueid'], + 'userid' => $USER->id + ) + )) { + throw new \moodle_exception('cannoteditissue', 'local_qtracker', '', $params['issueid']); + } + + if (empty($params['issuetitle'])){ + $warnings[] = array( + 'item' => 'issuetitle', + 'itemid' => 0, + 'warningcode' => 'fielderror', + 'message' => 'Empty issue title.' + ); + } + + if (empty($params['issuedescription'])) { + $warnings[] = array( + 'item' => 'issuedescription', + 'itemid' => 0, + 'warningcode' => 'fielderror', + 'message' => 'Empty issue description.', + ); + } + if (empty($warnings)) { + $issue = issue::load($params['issueid']); + $issue->set_title($params['issuetitle']); + $issue->set_description($params['issuedescription']); + $added = true; + } + + $result = array(); + $result['status'] = $added; + $result['issueid'] = $params['issueid']; + $result['warnings'] = $warnings; + + return $result; + } + + /** + * Returns description of method result value + * @return external_description + */ + public static function edit_issue_returns() { + return new external_single_structure( + array( + 'status' => new external_value(PARAM_BOOL, 'status: true if success'), + 'issueid' => new external_value(PARAM_INT, 'The id of the new issue'), + 'warnings' => new external_warnings() + ) + ); + } +} diff --git a/classes/external/getissue.php b/classes/external/getissue.php new file mode 100644 index 0000000..081017b --- /dev/null +++ b/classes/external/getissue.php @@ -0,0 +1,138 @@ +. + +/** + * External (web service) function calls for retrieving a question issue. + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_qtracker\external; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . "/externallib.php"); +require_once($CFG ->libdir . '/questionlib.php'); + +use external_value; +use external_function_parameters; +use external_single_structure; +use external_warnings; +use local_qtracker\issue; + +class getissue extends \external_api { + + /** + * Returns description of method parameters + * @return external_function_parameters + */ + public static function get_issue_parameters() { + return new external_function_parameters( + array( + 'issueid' => new external_value(PARAM_INT, 'issue id') + ) + ); + } + + + /** + * Returns welcome message + * @return string welcome message + */ + public static function get_issue($issueid) { + global $USER, $DB; + + $status = false; + $issuedata = array(); + $warnings = array(); + + //Parameter validation + $params = self::validate_parameters(self::get_issue_parameters(), + array( + 'issueid' => (int) $issueid, + ) + ); + + //Context validation + // TODO: ensure proper validation.... + $context = \context_user::instance($USER->id); + self::validate_context($context); + + //Capability checking + if (!has_capability('local/qtracker:readissue', $context)) { + throw new \moodle_exception('cannotgetissue', 'local_qtracker'); + } + + if (!$DB->record_exists_select('qtracker_issue', 'id = :issueid AND userid = :userid', + array( + 'issueid' => $params['issueid'], + 'userid' => $USER->id + ) + )) { + throw new \moodle_exception('cannotgetissue', 'local_qtracker', '', $params['issueid']); + } + + if (empty($warnings)) { + $issue = issue::load($params['issueid']); + + $issuedata['id'] = $issue->get_id(); + $issuedata['title'] = $issue->get_title(); + $issuedata['description'] = $issue->get_description(); + $issuedata['questionid'] = $issue->get_questionid(); + $issuedata['questionusageid'] = $issue->get_qubaid(); + $issuedata['slot'] = $issue->get_slot(); + $issuedata['userid'] = $issue->get_userid(); + $issuedata['timecreated'] = $issue->get_timecreated(); + + $status = true; + } + + $result = array(); + $result['status'] = $status; + $result['issue'] = $issuedata; + $result['warnings'] = $warnings; + + return $result; + } + + /** + * Returns description of method result value + * @return external_description + */ + public static function get_issue_returns() { + return new external_single_structure( + array( + 'status' => new external_value(PARAM_BOOL, 'status: true if success'), + 'issue' => new external_single_structure( + array( + 'id' => new external_value(PARAM_INT, 'The id of the issue'), + 'title' => new external_value(PARAM_TEXT, 'The issue title.'), + 'description' => new external_value(PARAM_TEXT, 'The issue description.'), + 'questionid' => new external_value(PARAM_INT, 'The question id for this issue.'), + 'questionusageid' => new external_value(PARAM_INT, 'The question usage id for this issue.'), + 'slot' => new external_value(PARAM_INT, 'The issslot for the question for the issue.'), + 'userid' => new external_value(PARAM_INT, 'The user id for the user who created the issue.'), + 'timecreated' => new external_value(PARAM_INT, 'The time the issue was created.'), + ) + ), + 'warnings' => new external_warnings() + ) + ); + + } +} diff --git a/externallib.php b/classes/external/newissue.php similarity index 57% rename from externallib.php rename to classes/external/newissue.php index 2bf68a3..8b5b2a7 100644 --- a/externallib.php +++ b/classes/external/newissue.php @@ -14,15 +14,30 @@ // along with Moodle. If not, see . /** - * External Web Service Template + * External (web service) function calls for creating a new question issue. * - * @package localwstemplate - * @copyright 2011 Moodle Pty Ltd (http://moodle.com) + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ + +namespace local_qtracker\external; + +defined('MOODLE_INTERNAL') || die(); + require_once($CFG->libdir . "/externallib.php"); +require_once($CFG ->libdir . '/questionlib.php'); -class local_qtracker_external extends external_api { +use external_value; +use external_function_parameters; +use moodle_exception; +use external_single_structure; +use external_warnings; +use local_qtracker\issue; + + +class newissue extends \external_api { /** * Returns description of method parameters @@ -31,7 +46,8 @@ class local_qtracker_external extends external_api { public static function new_issue_parameters() { return new external_function_parameters( array( - 'questionid' => new external_value(PARAM_INT, 'question id'), + 'qubaid' => new external_value(PARAM_INT, 'question usage id'), + 'slot' => new external_value(PARAM_INT, 'slot'), 'issuetitle' => new external_value(PARAM_TEXT, 'issue title'), 'issuedescription' => new external_value(PARAM_TEXT, 'issue description'), ) @@ -42,7 +58,7 @@ public static function new_issue_parameters() { * Returns welcome message * @return string welcome message */ - public static function new_issue($questionid, $issuetitle, $issuedescription) { + public static function new_issue($qubaid, $slot, $issuetitle, $issuedescription) { global $USER, $DB; $added = false; @@ -51,7 +67,8 @@ public static function new_issue($questionid, $issuetitle, $issuedescription) { //Parameter validation $params = self::validate_parameters(self::new_issue_parameters(), array( - 'questionid' => (int) $questionid, + 'qubaid' => (int) $qubaid, + 'slot' => (int) $slot, 'issuetitle' => $issuetitle, 'issuedescription' => $issuedescription, ) @@ -63,34 +80,48 @@ public static function new_issue($questionid, $issuetitle, $issuedescription) { self::validate_context($context); //Capability checking - //OPTIONAL but in most web service it should present if (!has_capability('local/qtracker:createissue', $context)) { throw new moodle_exception('cannotcreateissue', 'local_qtracker'); } - // Check if question exists. - $question = $DB->get_record('question', array('id' => $questionid)); - if ($question === false) { + if (empty($params['issuetitle'])){ + $warnings[] = array( + 'item' => 'issuetitle', + 'itemid' => 0, + 'warningcode' => 'fielderror', + 'message' => 'Empty issue title.' + ); + } + + if (empty($params['issuedescription'])) { $warnings[] = array( - 'item' => 'question', - 'itemid' => $questionid, - 'warningcode' => 'unknownquestionidnumber', - 'message' => 'Unknown question ID ' . $questionid - ); - } else { // Insert new issue - $dataobject = new \stdClass; - $dataobject->questionid = $questionid; - $dataobject->title = $issuetitle; - $dataobject->description = $issuedescription; - $dataobject->userid = $USER->id; - $time = time(); - $dataobject->timecreated = $time; - $DB->insert_record('qtracker_issue', $dataobject); + 'item' => 'issuedescription', + 'itemid' => 0, + 'warningcode' => 'fielderror', + 'message' => 'Empty issue description.', + ); + } + + $quba = \question_engine::load_questions_usage_by_activity($params['qubaid']); + $question = $quba->get_question($params['slot']); + + $issueid = 0; + + if (empty($warnings)) { + $issue = issue::create( + $params['issuetitle'], + $params['issuedescription'], + $question, + $quba, + $params['slot'] + ); + $issueid = $issue->get_id(); $added = true; } $result = array(); $result['status'] = $added; + $result['issueid'] = $issueid; $result['warnings'] = $warnings; return $result; @@ -104,6 +135,7 @@ public static function new_issue_returns() { return new external_single_structure( array( 'status' => new external_value(PARAM_BOOL, 'status: true if success'), + 'issueid' => new external_value(PARAM_INT, 'The id of the new issue'), 'warnings' => new external_warnings() ) ); diff --git a/classes/issue.php b/classes/issue.php index 6d3f82e..2dbc2d7 100644 --- a/classes/issue.php +++ b/classes/issue.php @@ -14,15 +14,184 @@ // along with Moodle. If not, see . /** - * External Web Service Template + * @package mod_capquiz + * @author Aleksander Skrede + * @author Sebastian S. Gundersen + * @copyright 2019 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_qtracker; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Question issue class. * - * @package localwstemplate - * @copyright 2011 Moodle Pty Ltd (http://moodle.com) + * @package local_qtracker + * @copyright 2020 André Storhaug * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -require_once($CFG->libdir . "/externallib.php"); - class issue { - // TODO create issue class here... See the db fields... + /** + * @var \stdClass + */ + protected $issue = null; + + /** + * Constructor. + * + * @param int|\stdClass $issue + * @return void + */ + public function __construct($issue) { + global $DB; + if (is_scalar($issue)) { + $issue = $DB->get_record('qtracker_issue', array('id' => $issue), '*', MUST_EXIST); + if (!$issue) { + throw new \moodle_exception('errorunexistingmodel', 'analytics', '', $issue); + } + } + $this->issue = $issue; + } + + /** + * Returns the issue id. + * + * @return int + */ + public function get_id() { + return $this->issue->id; + } + + /** + * Returns the issue title. + * + * @return int + */ + public function get_title() { + return $this->issue->title; + } + + /** + * Returns the issue description. + * + * @return int + */ + public function get_description() { + return $this->issue->description; + } + + /** + * Returns the issue questionusageid. + * + * @return int + */ + public function get_qubaid() { + return $this->issue->questionusageid; + } + + /** + * Returns the issue questionid. + * + * @return int + */ + public function get_questionid() { + return $this->issue->questionid; + } + + /** + * Returns the issue slot. + * + * @return int + */ + public function get_slot() { + return $this->issue->slot; + } + + /** + * Returns the issue userid. + * + * @return int + */ + public function get_userid() { + return $this->issue->userid; + } + + /** + * Returns the issue timecreated. + * + * @return int + */ + public function get_timecreated() { + return $this->issue->timecreated; + } + + /** + * Returns a plain \stdClass with the issue data. + * + * @return \stdClass + */ + public function get_issue_obj() { + return $this->issue; + } + + public static function load(int $issueid) { + global $DB; + $issueobj = $DB->get_record('qtracker_issue', ['id' => $issueid]); + if ($issueobj === false) { + return null; + } + return new issue($issueobj); + } + + /** + * Creates a new issue. + * + * @return issue + */ + public static function create($title, $description, \question_definition $question, $quba = null, $slot = null) { + global $USER, $DB; + + $issueobj = new \stdClass(); + $issueobj->title = $title; + $issueobj->description = $description; + $issueobj->questionid = $question->id; + $issueobj->questionusageid = $quba->get_id(); + $issueobj->slot = $slot; + $issueobj->userid = $USER->id; + $time = time(); + $issueobj->timecreated = $time; + //$issueobj->timemodified = $time; + //$issueobj->usermodified = $USER->id; + + $id = $DB->insert_record('qtracker_issue', $issueobj); + $issueobj->id = $id; + + $issue = new issue($issueobj); + return $issue; + } + + /** + * Delete this issue. + * + * @return void + */ + public function delete() { + global $DB; + return $DB->delete_records('qtracker_issue', array('id' => $this->get_id())); + } + + public function set_title($title) { + global $DB; + $this->issue->title = $title; + $DB->update_record('qtracker_issue', $this->issue); + } + + public function set_description($title) { + global $DB; + $this->issue->description = $title; + $DB->update_record('qtracker_issue', $this->issue); + } } diff --git a/classes/output/issue_registration_block.php b/classes/output/issue_registration_block.php index 904312a..0beb4f4 100644 --- a/classes/output/issue_registration_block.php +++ b/classes/output/issue_registration_block.php @@ -17,9 +17,9 @@ /** * Renderable for block * - * @package block_studiosity - * @author Andrew Madden - * @copyright 2019 Catalyst IT + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -27,8 +27,6 @@ defined('MOODLE_INTERNAL') || die; -use local_qtracker\form\view\issue_registration_form; -use moodle_url; use renderable; use renderer_base; use templatable; @@ -37,11 +35,11 @@ /** - * Studiosity block class. + * Question issue registration block class. * - * @package block_studiosity - * @author Andrew Madden - * @copyright 2019 Catalyst IT + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class issue_registration_block implements renderable, templatable { @@ -49,33 +47,49 @@ class issue_registration_block implements renderable, templatable { /** @var \question_definition[] Array of {@link \question_definition} */ public $questions = array(); - /** @var int User ID */ - public $userid; + /** @var \question_usage_by_activity */ + protected $quba; /** @var array of stdclass strings to display */ public $slots = array(); + /** @var array of existing issue ids display */ + public $issueids = array(); + /** @var help_icon The help icon. */ protected $helpicon; + // TODO: create an alternative (class) for registering issues that are not linked to an attempt.... + /** * Construct the contents of the block * @param \question_definition[] $questions The questions that can be filed issues for. * @param int $userid The id of the user. * @throws \coding_exception If called at incorrect times */ - public function __construct(array $questions, $userid, $slots=null) { - $this->questions = $questions; - $this->userid = $userid; + public function __construct(\question_usage_by_activity $quba, $slots) { + + $this->quba = $quba; $this->slots = $slots; - if (!is_null($slots)) { - if (count($questions) != count($slots)) { - throw new \coding_exception('The number of questions and slots does not match.'); - } + + //Todo remove questions..... + foreach ($this->slots as $slot) { + $this->questions[] = $this->quba->get_question($slot); } + $this->load_issues(); $this->helpicon = new help_icon('question', 'local_qtracker'); } + private function load_issues() { + global $DB; + + $queryparams = ['questionusageid' => $this->quba->get_id()]; + list($sql, $params) = $DB->get_in_or_equal($this->slots, SQL_PARAMS_NAMED); + $queryparams += $params; + $where = 'questionusageid = :questionusageid AND slot ' . $sql; + $this->issueids = $DB->get_fieldset_select('qtracker_issue', 'id', $where, $queryparams); + } + /** * Export the data. * @@ -86,37 +100,43 @@ public function export_for_template(renderer_base $output) { global $PAGE; $url = $PAGE->url; $data = new stdClass(); - $data->userid = $this->userid; + //TODO: only check if questions exists... otherwise i dont need them... if (count($this->questions) > 1) { $data->hasmultiple = true; $select = new stdClass(); $options = array(); - $select->name = "questionid"; + $select->name = "slot";; $select->label = "Question"; $select->helpicon = $this->helpicon->export_for_template($output); foreach ($this->questions as $key => $question) { $option = new stdClass(); - $option->value = $question->id; + $option->value = $this->slots[$key]; $option->name = $this->slots[$key]; array_push($options, $option); } $select->options = $options; $data->select = $select; - } else { $data->hasmultiple = false; - $data->questionid = $this->questions[0]->id; + $data->slot = $this->slots[0]; } + $data->qubaid = $this->quba->get_id(); $data->action = $url; $data->tooltip = "This is a tooltip"; + $button = new stdClass(); + $button->type = "submit"; + $button->classes = "col-auto"; + $button->label = "Submit new issue"; + $data->button = $button; + $data->issueids = json_encode($this->issueids); + // TODO: Fix this as both the button and the select gets this. Wrap in separate mustashe templates. - $data->label = "Submit new issue"; //$data->questions = $questions; return $data; diff --git a/classes/output/question_issues_page.php b/classes/output/question_issues_page.php index 1f518af..1a09060 100644 --- a/classes/output/question_issues_page.php +++ b/classes/output/question_issues_page.php @@ -15,28 +15,28 @@ // along with Moodle. If not, see . /** - * Class containing data for question issues. + * Renderable for issues page * * @package local_qtracker - * @copyright 2020 André Storhaug + * @author André Storhaug + * @copyright 2020 NTNU * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ + namespace local_qtracker\output; + defined('MOODLE_INTERNAL') || die(); use coding_exception; use dml_exception; use moodle_exception; -use moodle_url; use renderable; use renderer_base; -use single_select; use stdClass; use templatable; - /** - * Class containing data for question issues. + * Class containing data for question issues page. * * @copyright 2020 André Storhaug * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later diff --git a/classes/output/question_issues_table.php b/classes/output/question_issues_table.php index c10cb67..da9a0ae 100644 --- a/classes/output/question_issues_table.php +++ b/classes/output/question_issues_table.php @@ -15,10 +15,11 @@ // along with Moodle. If not, see . /** - * Class containing data for question issues. + * Table of question issues. * * @package local_qtracker - * @copyright 2020 André Storhaug + * @author André Storhaug + * @copyright 2020 NTNU * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -140,11 +141,11 @@ protected function define_table_columns() { 'questionid' => get_string('questionid', 'local_qtracker'), 'title' => get_string('title', 'local_qtracker'), 'description' => get_string('description', 'local_qtracker'), - 'datecreated' => get_string('datecreated', 'local_qtracker') + 'timecreated' => get_string('timecreated', 'local_qtracker') ); // Add remaining headers. - $cols = array_merge($cols, array('actions' => get_string('actions'))); + //$cols = array_merge($cols, array('actions' => get_string('actions'))); $this->define_columns(array_keys($cols)); $this->define_headers(array_values($cols)); diff --git a/classes/output/renderer.php b/classes/output/renderer.php index 2b446af..76b6f64 100644 --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -14,24 +14,28 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . +/** + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + namespace local_qtracker\output; -use Error; -use moodle_url; +defined('MOODLE_INTERNAL') || die(); + use plugin_renderer_base; use templatable; -defined('MOODLE_INTERNAL') || die(); - /** - * @package local_qtracker - * @author Aleksander Skrede - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * Question Tracker renderer. + * + * @package local_qtracker + * @copyright 2020 André Storhaug + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class renderer extends plugin_renderer_base -{ +class renderer extends plugin_renderer_base { /** * Render the review page for the deletion of expired contexts. diff --git a/db/access.php b/db/access.php index ff0ec2c..7fdb0e7 100644 --- a/db/access.php +++ b/db/access.php @@ -1,6 +1,5 @@ array( 'riskbitmask' => RISK_SPAM, 'captype' => 'write', @@ -9,5 +8,21 @@ 'user' => CAP_ALLOW ), ), + 'local/qtracker:deleteissue' => array( + 'riskbitmask' => RISK_SPAM, + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => array( + 'user' => CAP_ALLOW + ), + ), + 'local/qtracker:readissue' => array( + 'riskbitmask' => RISK_PERSONAL, + 'captype' => 'read', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => array( + 'user' => CAP_ALLOW + ), + ), ); diff --git a/db/install.xml b/db/install.xml index 072e0d9..972b5ca 100755 --- a/db/install.xml +++ b/db/install.xml @@ -10,12 +10,15 @@ + + + diff --git a/db/services.php b/db/services.php index 468658c..64130b6 100644 --- a/db/services.php +++ b/db/services.php @@ -35,24 +35,61 @@ // We defined the web service functions to install. $functions = array( 'local_qtracker_new_issue' => array( - 'classname' => 'local_qtracker_external', + 'classname' => 'local_qtracker\external\newissue', 'methodname' => 'new_issue', - 'classpath' => 'local/qtracker/externallib.php', + 'classpath' => '', 'description' => 'Register a new question issue.', 'type' => 'write', 'ajax' => true, //'capabilities' => 'moodle/course:managegroups', 'capabilities' => array(), // capabilities required by the function. 'loginrequired' => true, - + ), + 'local_qtracker_edit_issue' => array( + 'classname' => 'local_qtracker\external\editissue', + 'methodname' => 'edit_issue', + 'classpath' => '', + 'description' => 'Edit an existing question issue.', + 'type' => 'write', + 'ajax' => true, + //'capabilities' => 'moodle/course:managegroups', + 'capabilities' => array(), // capabilities required by the function. + 'loginrequired' => true, + ), + 'local_qtracker_delete_issue' => array( + 'classname' => 'local_qtracker\external\deleteissue', + 'methodname' => 'delete_issue', + 'classpath' => '', + 'description' => 'Delete an existing question issue.', + 'type' => 'write', + 'ajax' => true, + //'capabilities' => 'moodle/course:managegroups', + 'capabilities' => array(), // capabilities required by the function. + 'loginrequired' => true, + ), + 'local_qtracker_get_issue' => array( + 'classname' => 'local_qtracker\external\getissue', + 'methodname' => 'get_issue', + 'classpath' => '', + 'description' => 'Get an existing question issue.', + 'type' => 'read', + 'ajax' => true, + //'capabilities' => 'moodle/course:managegroups', + 'capabilities' => array(), // capabilities required by the function. + 'loginrequired' => true, ) ); // We define the services to install as pre-build services. A pre-build service is not editable by administrator. $services = array( 'Question tracker service' => array( - 'functions' => array ('local_qtracker_new_issue'), - 'restrictedusers' => 0, - 'enabled'=>1, + 'functions' => array( + 'local_qtracker_new_issue', + 'local_qtracker_edit_issue', + 'local_qtracker_delete_issue', + 'local_qtracker_get_issue' + ), + 'restrictedusers' => 0, + 'enabled' => 1, ) ); diff --git a/db/upgrade.php b/db/upgrade.php index 2d9f706..e09b574 100755 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -41,7 +41,29 @@ function xmldb_local_qtracker_upgrade($oldversion) { $dbman->add_field($table, $field); $dbman->add_key($table, $key); } - upgrade_mod_savepoint(true, 2020070800, 'capquiz'); + upgrade_plugin_savepoint(true, 2020070800, 'local', 'qtracker'); + + } + + if ($oldversion < 2020071000) { + // Define table capquiz_user_rating to be created. + $table = new xmldb_table('qtracker_issue'); + + $qufield = new xmldb_field( + 'questionusageid', XMLDB_TYPE_INTEGER, 10); + $qukey = new xmldb_key( + 'questionusageid', XMLDB_KEY_FOREIGN, array('questionusageid'), 'question_usages', array('id')); + $slotfield = new xmldb_field( + 'slot', XMLDB_TYPE_INTEGER, 10); + + if (!$dbman->field_exists($table, $qufield)) { + $dbman->add_field($table, $qufield); + $dbman->add_key($table, $qukey); + } + if (!$dbman->field_exists($table, $slotfield)) { + $dbman->add_field($table, $slotfield); + } + upgrade_plugin_savepoint(true, 2020071000, 'local', 'qtracker'); } return true; } diff --git a/lang/en/local_qtracker.php b/lang/en/local_qtracker.php index 9f52918..2f55f13 100755 --- a/lang/en/local_qtracker.php +++ b/lang/en/local_qtracker.php @@ -39,13 +39,27 @@ $string['questionid'] = 'Question ID'; $string['title'] = 'Title'; $string['description'] = 'Description'; -$string['datecreated'] = 'Created Time'; +$string['timecreated'] = 'Created Time'; $string['question_problem_details'] = 'If you have feedback for this question, please type it below.'; -$string['cannotcreateissue'] = 'You cannot create a new question issue.'; +$string['cannotcreateissue'] = 'Cannot create a new question issue.'; +$string['cannoteditissue'] = 'Cannot edit question issue with ID {$a}.'; +$string['cannotdeleteissue'] = 'Cannot delete question issue with ID {$a}.'; +$string['cannotgetissue'] = 'Cannot get q¨uestion issue with ID {$a}.'; $string['question'] = 'Question'; $string['question_help'] = 'Select the question you want to register a new issue for.'; $string['unknownquestionidnumber'] = 'Unknown question ID "{$a}"'; +$string['unknownqubaidnumber'] = 'Unknown question usage ID "{$a}"'; $string['title'] = 'Title'; -$string['leavecomment'] = 'Leave a comment'; +$string['leavecomment'] = 'Leave A comment'; + +$string['issuecreated'] = 'Issue successfully created.'; +$string['issueupdated'] = 'Issue successfully updated.'; +$string['issuedeleted'] = 'Issue successfully deleted.'; + +$string['issues'] = 'Issues'; + +$string['submitnewissue'] = 'Submit new issue'; +$string['validtitle'] = 'Please provide a valid title.'; +$string['validdescription'] = 'Please provide a valid description.'; diff --git a/lib.php b/lib.php index d201b44..6a35d5c 100644 --- a/lib.php +++ b/lib.php @@ -22,18 +22,41 @@ */ - /** - * Adds module specific settings to the settings block +/** + * This function extends the navigation with the report items * - * @param settings_navigation $settings The settings navigation object - * @param navigation_node $choicenode The node to add module settings to + * @param navigation_node $navigation The navigation node to extend + * @param stdClass $course The course to object for the report + * @param stdClass $context The context of the course */ -function local_qtracker_extend_navigation($navigation){ - $pluginname = 'I\'m in navigation!'; - $url = new moodle_url('/local/localplugin/index.php'); - //$navigation->add($pluginname, $url, navigation_node::TYPE_SETTING); +function local_qtracker_extend_navigation_course($navigation, $course, $context) { + global $CFG; + + + if ($context->contextlevel == CONTEXT_COURSE) { + $params = array('courseid' => $context->instanceid); + } else if ($context->contextlevel == CONTEXT_MODULE) { + $params = array('cmid' => $context->instanceid); + } else { + return; } -function qtracker_get_view($calendar, $view, $includenavigation = true, bool $skipevents = false) { + $qtrackernode = $navigation->add( + get_string('pluginname', 'local_qtracker'), + null, + navigation_node::TYPE_CONTAINER, + null, + 'qtracker' + ); + + //$contexts = new question_edit_contexts($context); + //if ($contexts->have_one_edit_tab_cap('questions')) { + $qtrackernode->add(get_string('issues', 'local_qtracker'), new moodle_url( + $CFG->wwwroot . '/local/qtracker/view.php', + $params + ), navigation_node::TYPE_SETTING, null, 'issues'); + //} +} -} \ No newline at end of file +function qtracker_get_view($calendar, $view, $includenavigation = true, bool $skipevents = false) { +} diff --git a/locallib.php b/locallib.php deleted file mode 100644 index a9fc15a..0000000 --- a/locallib.php +++ /dev/null @@ -1,22 +0,0 @@ -. - -/** - * @package local_qtracker - * @author André Storhaug - * @copyright 2020 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ \ No newline at end of file diff --git a/templates/button.mustache b/templates/button.mustache index fee19c9..090acaa 100644 --- a/templates/button.mustache +++ b/templates/button.mustache @@ -25,7 +25,7 @@ } }}
    - diff --git a/templates/issue_registration_block.mustache b/templates/issue_registration_block.mustache index 249ce32..46cb122 100644 --- a/templates/issue_registration_block.mustache +++ b/templates/issue_registration_block.mustache @@ -35,7 +35,7 @@ "name": "questionid", "options": [ { "name": "Option 1", "value": "V", "selected": true }, - { "name": "Option 2", "value": "V", "selected": true } + { "name": "Option 2", "value": "V", "quba": "10", "slot": "2", "selected": true } ] } } @@ -49,7 +49,7 @@ } }}
    -
    + {{#hasmultiple}} {{#select}}
    @@ -58,23 +58,30 @@ {{/select}} {{/hasmultiple}} {{^hasmultiple}} -
    - -
    + {{/hasmultiple}} +
    - + +
    + {{#str}} validtitle, local_qtracker {{/str}} +
    - + +
    + {{#str}} validdescription, local_qtracker {{/str}} +
    -
    + {{#button}} +
    {{>local_qtracker/button}}
    + {{/button}}
    {{#js}} -require(['jquery', 'local_qtracker/newissue'], function($, NewIssue) { - NewIssue.init('[data-action=newissue]'); +require(['jquery', 'local_qtracker/block_form_manager'], function($, BlockFormManager) { + BlockFormManager.init('[data-action=newissue]', '{{{issueids}}}'); }); {{/js}} diff --git a/version.php b/version.php index 67baadf..02feb4a 100755 --- a/version.php +++ b/version.php @@ -23,7 +23,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2020070800; +$plugin->version = 2020072400; $plugin->requires = 2016120500; $plugin->cron = 0; $plugin->component = 'local_qtracker'; diff --git a/view.php b/view.php index 3781fe7..e2ac947 100644 --- a/view.php +++ b/view.php @@ -15,7 +15,7 @@ } require_login($course); -$url = new moodle_url('/local/qtracker/view.php', array('id' => $courseid)); +$url = new moodle_url('/local/qtracker/view.php', array('courseid' => $courseid)); $PAGE->set_url($url); $PAGE->set_pagelayout('standard'); @@ -35,5 +35,3 @@ echo $renderer->render($questionissuespage); echo $OUTPUT->footer(); - -?> From c9b87487259fce075ffad245f127d80bc20cd1a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Storhaug?= Date: Fri, 24 Jul 2020 13:09:10 +0200 Subject: [PATCH 13/83] Add setup questions table page --- classes/output/questions_page.php | 80 ++++++++++++ classes/output/questions_table.php | 196 +++++++++++++++++++++++++++++ classes/output/renderer.php | 12 ++ templates/questions.mustache | 27 ++++ view.php | 6 +- 5 files changed, 318 insertions(+), 3 deletions(-) create mode 100644 classes/output/questions_page.php create mode 100644 classes/output/questions_table.php create mode 100644 templates/questions.mustache diff --git a/classes/output/questions_page.php b/classes/output/questions_page.php new file mode 100644 index 0000000..b0bc2a6 --- /dev/null +++ b/classes/output/questions_page.php @@ -0,0 +1,80 @@ +. + +/** + * Renderable for questions page + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_qtracker\output; + +defined('MOODLE_INTERNAL') || die(); + +use coding_exception; +use dml_exception; +use moodle_exception; +use renderable; +use renderer_base; +use stdClass; +use templatable; + +/** + * Class containing data for question page. + * + * @copyright 2020 André Storhaug + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class questions_page implements renderable, templatable { + + /** The default number of results to be shown per page. */ + const DEFAULT_PAGE_SIZE = 20; + + protected $questionstable = []; + + /** + * Construct this renderable. + * + * @param \local_qtracker\questions_table $questionstable + */ + public function __construct(questions_table $questionstable) { + $this->questionstable = $questionstable; + } + + /** + * Export this data so it can be used as the context for a mustache template. + * + * @param renderer_base $output + * @return stdClass + * @throws coding_exception + * @throws dml_exception + * @throws moodle_exception + */ + public function export_for_template(renderer_base $output) { + $data = new stdClass(); + + ob_start(); + $this->questionstable->out(self::DEFAULT_PAGE_SIZE, true); + $questions = ob_get_contents(); + ob_end_clean(); + $data->questions = $questions; + + return $data; + } +} diff --git a/classes/output/questions_table.php b/classes/output/questions_table.php new file mode 100644 index 0000000..3365131 --- /dev/null +++ b/classes/output/questions_table.php @@ -0,0 +1,196 @@ +. + +/** + * Table of question issues. + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_qtracker\output; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . '/tablelib.php'); + +use context_system; +use moodle_url; +use table_sql; + +/** + * Questions table. + * + * @package local_qtracker + * @copyright 2020 André Storhaug + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class questions_table extends table_sql { + + /** + * Sets up the table. + * + * @param string $uniqueid Unique id of table. + * @param moodle_url $url The base URL. + */ + public function __construct($uniqueid, $url) { + global $CFG; + parent::__construct($uniqueid); + // TODO: determine which context to use... + $context = context_system::instance(); + + // This object should not be used without the right permissions. + require_capability('moodle/role:manage', $context); // DO WE NEED THIS? + + // Define columns in the table. + $this->define_table_columns(); + // Set the baseurl + $this->define_baseurl($url); + // Define configs. + $this->define_table_configs(); + // Define SQL. + $this->setup_sql_queries(); + } + + /** + * Generate the display of the id column. + * @param object $data the table row being output. + * @return string HTML content to go inside the td. + */ + public function col_id($data) { + if ($data->id) { + return $data->id; + } else { + return '-'; + } + } + + /** + * Generate the display of the question name column. + * @param object $data the table row being output. + * @return string HTML content to go inside the td. + */ + protected function col_questionid($data) { + if ($data->questionid) { + return $data->questionid; + } else { + return '-'; + } + } + + /** + * Generate the display of the title. + * @param object $data the table row being output. + * @return string HTML content to go inside the td. + */ + protected function col_title($data) { + if ($data->title) { + return $data->title; + } else { + return '-'; + } + } + + /** + * Generate the display of the description. + * @param object $data the table row being output. + * @return string HTML content to go inside the td. + */ + protected function col_description($data) { + if ($data->description) { + return $data->description; + } else { + return '-'; + } + } + + /** + * The timecreated column. + * @param stdClass $data The row data. + * @return string + */ + public function col_timecreated($data) { + return userdate($data->timecreated); + } + + /** + * TODO: touch up this + * Setup the headers for the table. + */ + protected function define_table_columns() { + + // Define headers and columns. + //TODO: define strings in lang file. + $cols = array( + 'id' => get_string('id', 'local_qtracker'), + 'questionid' => get_string('questionid', 'local_qtracker'), + 'title' => get_string('title', 'local_qtracker'), + 'description' => get_string('description', 'local_qtracker'), + 'timecreated' => get_string('timecreated', 'local_qtracker') + ); + + // Add remaining headers. + //$cols = array_merge($cols, array('actions' => get_string('actions'))); + + $this->define_columns(array_keys($cols)); + $this->define_headers(array_values($cols)); + } + + /** + * Define table configs. + */ + protected function define_table_configs() { + $this->collapsible(false); + $this->sortable(true); + $this->pageable(true); + } + + /** + * Builds the SQL query. + * + * @return array containing sql to use and an array of params. + */ + public function setup_sql_queries() { + // TODO: Write SQL to retrieve distinct all rows... + $fields = 'DISTINCT'; + $fields .= '*'; + $from = '{qtracker_issue} qs'; + $where = '1=1'; + $params = array(); // TODO: find a way to only get the correct contexts.. For now just get everything (keep this empty)... + + // The WHERE clause is vital here, because some parts of tablelib.php will expect to + // add bits like ' AND x = 1' on the end, and that needs to leave to valid SQL. + $this->set_count_sql("SELECT COUNT(1) FROM (SELECT $fields FROM $from WHERE $where) temp WHERE 1 = 1", $params); + + list($fields, $from, $where, $params) = $this->update_sql_after_count($fields, $from, $where, $params); + $this->set_sql($fields, $from, $where, $params); + } + + /** + * A chance for subclasses to modify the SQL after the count query has been generated, + * and before the full query is constructed. + * @param string $fields SELECT list. + * @param string $from JOINs part of the SQL. + * @param string $where WHERE clauses. + * @param array $params Query params. + * @return array with 4 elements ($fields, $from, $where, $params) as from base_sql. + */ + protected function update_sql_after_count($fields, $from, $where, $params) { + return [$fields, $from, $where, $params]; + } +} diff --git a/classes/output/renderer.php b/classes/output/renderer.php index 76b6f64..a9abdf7 100644 --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -49,6 +49,18 @@ public function render_question_issues_page(question_issues_page $page) { return parent::render_from_template('local_qtracker/question_issues', $data); } + /** + * Render the review page for the deletion of expired contexts. + * + * @param questions_page $page + * @return string html for the page + * @throws moodle_exception + */ + public function render_questions_page(questions_page $page) { + $data = $page->export_for_template($this); + return parent::render_from_template('local_qtracker/questions', $data); + } + /** * Renders a block. * diff --git a/templates/questions.mustache b/templates/questions.mustache new file mode 100644 index 0000000..69c73ae --- /dev/null +++ b/templates/questions.mustache @@ -0,0 +1,27 @@ +{{! + 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 . +}} +{{! + @template local_qtracker/questions + + Example context (json): + { + "questions": "raw html" + } +}} +
    + {{{questions}}} +
    diff --git a/view.php b/view.php index e2ac947..b62dce0 100644 --- a/view.php +++ b/view.php @@ -29,9 +29,9 @@ echo $OUTPUT->header(); // Get table renderer and display table here.... -$table = new \local_qtracker\output\question_issues_table(uniqid(), $url); +$table = new \local_qtracker\output\questions_table(uniqid(), $url); $renderer = $PAGE->get_renderer('local_qtracker'); -$questionissuespage = new \local_qtracker\output\question_issues_page($table); -echo $renderer->render($questionissuespage); +$questionspage = new \local_qtracker\output\questions_page($table); +echo $renderer->render($questionspage); echo $OUTPUT->footer(); From 554daaeff67ad40b43502122b2c44e428bcee51d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Storhaug?= Date: Fri, 24 Jul 2020 13:15:43 +0200 Subject: [PATCH 14/83] Add slot field --- db/install.xml | 1 + db/upgrade.php | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/db/install.xml b/db/install.xml index 972b5ca..38375de 100755 --- a/db/install.xml +++ b/db/install.xml @@ -12,6 +12,7 @@ + diff --git a/db/upgrade.php b/db/upgrade.php index e09b574..14418f7 100755 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -65,5 +65,16 @@ function xmldb_local_qtracker_upgrade($oldversion) { } upgrade_plugin_savepoint(true, 2020071000, 'local', 'qtracker'); } + if ($oldversion < 2020072400) { + // Define table capquiz_user_rating to be created. + $table = new xmldb_table('qtracker_issue'); + + $field = new xmldb_field( + 'slot', XMLDB_TYPE_TEXT); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + upgrade_plugin_savepoint(true, 2020072400, 'local', 'qtracker'); + } return true; } From 8d48df8f0bd73983416e4ee063d55d36e32dbc1b Mon Sep 17 00:00:00 2001 From: HomKham <46867728+HomKham@users.noreply.github.com> Date: Thu, 30 Jul 2020 00:05:02 +0200 Subject: [PATCH 15/83] Commit questions table --- classes/output/question_issues_table.php | 3 -- classes/output/questions_table.php | 61 +++++++++++++----------- lang/en/local_qtracker.php | 5 ++ 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/classes/output/question_issues_table.php b/classes/output/question_issues_table.php index da9a0ae..5dfccab 100644 --- a/classes/output/question_issues_table.php +++ b/classes/output/question_issues_table.php @@ -144,9 +144,6 @@ protected function define_table_columns() { 'timecreated' => get_string('timecreated', 'local_qtracker') ); - // Add remaining headers. - //$cols = array_merge($cols, array('actions' => get_string('actions'))); - $this->define_columns(array_keys($cols)); $this->define_headers(array_values($cols)); } diff --git a/classes/output/questions_table.php b/classes/output/questions_table.php index 3365131..5dc0bcd 100644 --- a/classes/output/questions_table.php +++ b/classes/output/questions_table.php @@ -68,64 +68,70 @@ public function __construct($uniqueid, $url) { } /** - * Generate the display of the id column. + * Generate the display of the question name column. * @param object $data the table row being output. * @return string HTML content to go inside the td. */ - public function col_id($data) { - if ($data->id) { - return $data->id; + protected function col_questionid($data) { + if ($data->questionid) { + return $data->questionid; } else { return '-'; } } /** - * Generate the display of the question name column. + * Generate the display of the title. * @param object $data the table row being output. * @return string HTML content to go inside the td. */ - protected function col_questionid($data) { - if ($data->questionid) { - return $data->questionid; + protected function col_title($data) { + if ($data->title) { + return $data->title; + //need to change it to correct link + //return ''.$data->title.''; } else { return '-'; } } /** - * Generate the display of the title. + * Generate the display of the new. * @param object $data the table row being output. * @return string HTML content to go inside the td. */ - protected function col_title($data) { - if ($data->title) { - return $data->title; + protected function col_new($data) { + if ($data->new) { + return $data->new; } else { return '-'; } } /** - * Generate the display of the description. + * Generate the display of the open. * @param object $data the table row being output. * @return string HTML content to go inside the td. */ - protected function col_description($data) { - if ($data->description) { - return $data->description; + protected function col_open($data) { + if ($data->open) { + return $data->open; } else { return '-'; } } /** - * The timecreated column. - * @param stdClass $data The row data. - * @return string + * Generate the display of the close. + * @param object $data the table row being output. + * @return string HTML content to go inside the td. */ - public function col_timecreated($data) { - return userdate($data->timecreated); + protected function col_close($data) { + if ($data->close) { + return $data->close; + } else { + return '-'; + } } /** @@ -137,20 +143,18 @@ protected function define_table_columns() { // Define headers and columns. //TODO: define strings in lang file. $cols = array( - 'id' => get_string('id', 'local_qtracker'), 'questionid' => get_string('questionid', 'local_qtracker'), 'title' => get_string('title', 'local_qtracker'), - 'description' => get_string('description', 'local_qtracker'), - 'timecreated' => get_string('timecreated', 'local_qtracker') + 'new' => get_string('new', 'local_qtracker'), + 'open' => get_string('open', 'local_qtracker'), + 'close' => get_string('close', 'local_qtracker') ); - // Add remaining headers. - //$cols = array_merge($cols, array('actions' => get_string('actions'))); - $this->define_columns(array_keys($cols)); $this->define_headers(array_values($cols)); } + /** * Define table configs. */ @@ -169,6 +173,9 @@ public function setup_sql_queries() { // TODO: Write SQL to retrieve distinct all rows... $fields = 'DISTINCT'; $fields .= '*'; + $fields .= "COUNT(IF(state='new',1, NULL)) 'new', + COUNT(IF(state='open',1, NULL)) 'open', + COUNT(IF(state='close',1, NULL)) 'close'"; $from = '{qtracker_issue} qs'; $where = '1=1'; $params = array(); // TODO: find a way to only get the correct contexts.. For now just get everything (keep this empty)... diff --git a/lang/en/local_qtracker.php b/lang/en/local_qtracker.php index 2f55f13..c0667fe 100755 --- a/lang/en/local_qtracker.php +++ b/lang/en/local_qtracker.php @@ -63,3 +63,8 @@ $string['submitnewissue'] = 'Submit new issue'; $string['validtitle'] = 'Please provide a valid title.'; $string['validdescription'] = 'Please provide a valid description.'; + +//issues open,close, new +$string['new'] = 'New'; +$string['open'] = 'Open'; +$string['close'] = 'Close'; From c1a371d23a682fe3a6a66c754a1c5fac986d559e Mon Sep 17 00:00:00 2001 From: HomKham <46867728+HomKham@users.noreply.github.com> Date: Fri, 31 Jul 2020 00:16:52 +0200 Subject: [PATCH 16/83] Change sql_query and add extra_cols --- classes/output/questions_table.php | 59 +++++++++--------------------- lang/en/local_qtracker.php | 4 -- 2 files changed, 18 insertions(+), 45 deletions(-) diff --git a/classes/output/questions_table.php b/classes/output/questions_table.php index 5dc0bcd..1aed745 100644 --- a/classes/output/questions_table.php +++ b/classes/output/questions_table.php @@ -96,41 +96,21 @@ protected function col_title($data) { } /** - * Generate the display of the new. - * @param object $data the table row being output. - * @return string HTML content to go inside the td. - */ - protected function col_new($data) { - if ($data->new) { - return $data->new; - } else { - return '-'; - } - } - - /** - * Generate the display of the open. - * @param object $data the table row being output. - * @return string HTML content to go inside the td. + * Generate the display of the new, open and close column + * @param $cols extra_colums (new, open and close) + * @param $data the table row being output + * @return |null string html content to go inside the td. */ - protected function col_open($data) { - if ($data->open) { - return $data->open; - } else { - return '-'; - } - } - - /** - * Generate the display of the close. - * @param object $data the table row being output. - * @return string HTML content to go inside the td. - */ - protected function col_close($data) { - if ($data->close) { - return $data->close; - } else { - return '-'; + public function other_cols($cols, $data) { + switch ($cols) { + case 'new': + return $data->new; + case 'open': + return $data->open; + case 'close': + return $data->close; + default: + return null; } } @@ -144,10 +124,7 @@ protected function define_table_columns() { //TODO: define strings in lang file. $cols = array( 'questionid' => get_string('questionid', 'local_qtracker'), - 'title' => get_string('title', 'local_qtracker'), - 'new' => get_string('new', 'local_qtracker'), - 'open' => get_string('open', 'local_qtracker'), - 'close' => get_string('close', 'local_qtracker') + 'title' => get_string('title', 'local_qtracker') ); $this->define_columns(array_keys($cols)); @@ -173,9 +150,9 @@ public function setup_sql_queries() { // TODO: Write SQL to retrieve distinct all rows... $fields = 'DISTINCT'; $fields .= '*'; - $fields .= "COUNT(IF(state='new',1, NULL)) 'new', - COUNT(IF(state='open',1, NULL)) 'open', - COUNT(IF(state='close',1, NULL)) 'close'"; + $fields .= "COUNT(1) filter (where qs.state = 'new') AS new, + COUNT(1) filter (where qs.state = 'open') AS open, + COUNT(1) filter (where qs.state = 'close') AS close"; $from = '{qtracker_issue} qs'; $where = '1=1'; $params = array(); // TODO: find a way to only get the correct contexts.. For now just get everything (keep this empty)... diff --git a/lang/en/local_qtracker.php b/lang/en/local_qtracker.php index c0667fe..903661a 100755 --- a/lang/en/local_qtracker.php +++ b/lang/en/local_qtracker.php @@ -64,7 +64,3 @@ $string['validtitle'] = 'Please provide a valid title.'; $string['validdescription'] = 'Please provide a valid description.'; -//issues open,close, new -$string['new'] = 'New'; -$string['open'] = 'Open'; -$string['close'] = 'Close'; From f2e2d93c55b3c9d445322b480c8ef868b3416bcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Storhaug?= Date: Fri, 31 Jul 2020 15:02:21 +0200 Subject: [PATCH 17/83] Merge changes from feat/question-table --- classes/output/questions_table.php | 44 ++++++++++++++++++++++++------ lang/en/local_qtracker.php | 6 ++-- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/classes/output/questions_table.php b/classes/output/questions_table.php index 5dc0bcd..2aee100 100644 --- a/classes/output/questions_table.php +++ b/classes/output/questions_table.php @@ -87,8 +87,9 @@ protected function col_questionid($data) { */ protected function col_title($data) { if ($data->title) { - return $data->title; - //need to change it to correct link + $id = $data->id; + $title = \html_writer::link("#", $data->title, array('onclick' => "showIssuesInPane($id);return false;")); + return $title; //need to change it to correct link //return ''.$data->title.''; } else { return '-'; @@ -122,12 +123,12 @@ protected function col_open($data) { } /** - * Generate the display of the close. + * Generate the display of the closed. * @param object $data the table row being output. * @return string HTML content to go inside the td. */ - protected function col_close($data) { - if ($data->close) { + protected function col_closed($data) { + if ($data->d) { return $data->close; } else { return '-'; @@ -147,7 +148,7 @@ protected function define_table_columns() { 'title' => get_string('title', 'local_qtracker'), 'new' => get_string('new', 'local_qtracker'), 'open' => get_string('open', 'local_qtracker'), - 'close' => get_string('close', 'local_qtracker') + 'closed' => get_string('closed', 'local_qtracker') ); $this->define_columns(array_keys($cols)); @@ -175,14 +176,14 @@ public function setup_sql_queries() { $fields .= '*'; $fields .= "COUNT(IF(state='new',1, NULL)) 'new', COUNT(IF(state='open',1, NULL)) 'open', - COUNT(IF(state='close',1, NULL)) 'close'"; + COUNT(IF(state='closed',1, NULL)) 'closed'"; $from = '{qtracker_issue} qs'; $where = '1=1'; $params = array(); // TODO: find a way to only get the correct contexts.. For now just get everything (keep this empty)... // The WHERE clause is vital here, because some parts of tablelib.php will expect to // add bits like ' AND x = 1' on the end, and that needs to leave to valid SQL. - $this->set_count_sql("SELECT COUNT(1) FROM (SELECT $fields FROM $from WHERE $where) temp WHERE 1 = 1", $params); + //$this->set_count_sql("SELECT COUNT(1) FROM (SELECT $fields FROM $from WHERE $where) temp WHERE 1 = 1", $params); list($fields, $from, $where, $params) = $this->update_sql_after_count($fields, $from, $where, $params); $this->set_sql($fields, $from, $where, $params); @@ -200,4 +201,31 @@ public function setup_sql_queries() { protected function update_sql_after_count($fields, $from, $where, $params) { return [$fields, $from, $where, $params]; } + + + public function wrap_html_start() { + if ($this->is_downloading()) { + return; + } + + //echo '
    '; + echo '
    '; + echo '
    '; + echo '
    '; + echo '
    '; + echo '
    '; + + } + + public function wrap_html_finish() { + global $PAGE; + if ($this->is_downloading()) { + return; + } + + echo '
    '; + echo '
    '; + echo '
    '; + echo '
    '; + } } diff --git a/lang/en/local_qtracker.php b/lang/en/local_qtracker.php index c0667fe..3f26936 100755 --- a/lang/en/local_qtracker.php +++ b/lang/en/local_qtracker.php @@ -45,7 +45,8 @@ $string['cannotcreateissue'] = 'Cannot create a new question issue.'; $string['cannoteditissue'] = 'Cannot edit question issue with ID {$a}.'; $string['cannotdeleteissue'] = 'Cannot delete question issue with ID {$a}.'; -$string['cannotgetissue'] = 'Cannot get q¨uestion issue with ID {$a}.'; +$string['cannotgetissue'] = 'Cannot get question issue with ID {$a}.'; +$string['cannotgetissues'] = 'Cannot get question issues.'; $string['question'] = 'Question'; $string['question_help'] = 'Select the question you want to register a new issue for.'; @@ -64,7 +65,8 @@ $string['validtitle'] = 'Please provide a valid title.'; $string['validdescription'] = 'Please provide a valid description.'; +$string['commentedon'] = 'commented on '; //issues open,close, new $string['new'] = 'New'; $string['open'] = 'Open'; -$string['close'] = 'Close'; +$string['closed'] = 'Closed'; From 37a83b7de678999eed2125169235c0a97f1f5fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Storhaug?= Date: Wed, 5 Aug 2020 21:10:38 +0200 Subject: [PATCH 18/83] Add questions list view --- .travis.yml | 48 ++++ amd/src/block_form_manager.js | 37 +-- amd/src/issue.js | 17 +- amd/src/issue_manager.js | 18 +- amd/src/questions_table.js | 212 +++++++++++++++++ .../{deleteissue.php => delete_issue.php} | 23 +- .../{editissue.php => edit_issue.php} | 23 +- .../external/{getissue.php => get_issue.php} | 40 ++-- classes/external/get_issues.php | 218 ++++++++++++++++++ classes/external/get_question.php | 108 +++++++++ classes/external/helper.php | 56 +++++ classes/external/issue_exporter.php | 88 +++++++ .../external/{newissue.php => new_issue.php} | 12 +- classes/form/view/issue_registration_form.php | 15 +- classes/issue.php | 31 ++- classes/output/issue_registration_block.php | 8 +- classes/output/question_issue_page.php | 84 +++++++ classes/output/questions_page.php | 5 +- db/access.php | 54 ++++- db/install.xml | 1 + db/services.php | 62 +++-- db/upgrade.php | 25 +- issue.php | 77 +++++++ lib.php | 63 ++++- settings.php | 12 +- styles.css | 73 ++++++ templates/button.mustache | 7 +- templates/issue_registration_block.mustache | 32 ++- templates/issue_state_badge.mustache | 37 +++ templates/issues_pane.mustache | 57 +++++ templates/issues_pane_item.mustache | 49 ++++ templates/question_issue_page.mustache | 38 +++ templates/question_issues.mustache | 4 +- templates/questions.mustache | 21 +- templates/select.mustache | 9 +- version.php | 14 +- view.php | 48 +++- 37 files changed, 1536 insertions(+), 190 deletions(-) create mode 100644 .travis.yml create mode 100644 amd/src/questions_table.js rename classes/external/{deleteissue.php => delete_issue.php} (89%) rename classes/external/{editissue.php => edit_issue.php} (92%) rename classes/external/{getissue.php => get_issue.php} (72%) create mode 100644 classes/external/get_issues.php create mode 100644 classes/external/get_question.php create mode 100644 classes/external/helper.php create mode 100644 classes/external/issue_exporter.php rename classes/external/{newissue.php => new_issue.php} (89%) create mode 100644 classes/output/question_issue_page.php create mode 100644 issue.php create mode 100644 styles.css create mode 100644 templates/issue_state_badge.mustache create mode 100644 templates/issues_pane.mustache create mode 100644 templates/issues_pane_item.mustache create mode 100644 templates/question_issue_page.mustache diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..13d7155 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,48 @@ +language: php + +addons: + postgresql: "9.5" + +services: + - mysql + - postgresql + - docker + +cache: + directories: + - $HOME/.composer/cache + - $HOME/.npm + +php: + - 7.2 + - 7.3 + - 7.4 + +env: + global: + - MOODLE_BRANCH=MOODLE_39_STABLE + matrix: + - DB=pgsql + - DB=mysqli + +before_install: + - phpenv config-rm xdebug.ini + - cd ../.. + - composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^3 + - export PATH="$(cd ci/bin; pwd):$(cd ci/vendor/bin; pwd):$PATH" + +install: + - moodle-plugin-ci install + +script: + - moodle-plugin-ci phplint + - moodle-plugin-ci phpcpd + - moodle-plugin-ci phpmd + - moodle-plugin-ci codechecker + - moodle-plugin-ci validate + - moodle-plugin-ci savepoints + - moodle-plugin-ci mustache + - moodle-plugin-ci grunt + - moodle-plugin-ci phpdoc + - moodle-plugin-ci phpunit + - moodle-plugin-ci behat diff --git a/amd/src/block_form_manager.js b/amd/src/block_form_manager.js index dc61f64..f71d59a 100644 --- a/amd/src/block_form_manager.js +++ b/amd/src/block_form_manager.js @@ -50,11 +50,12 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss * * Each call to init gets it's own instance of this class. */ - var BlockFormManager = function (selector, issueids) { + var BlockFormManager = function (selector, issueids, contextid) { + this.contextid = contextid; this.form = $(selector); this.form.closest('.card-text').prepend(''); this.issueManager = new IssueManager(); - this.init(selector, JSON.parse(issueids)); + this.init(JSON.parse(issueids)); }; /** @@ -94,17 +95,19 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss * @private * @return {Promise} */ - BlockFormManager.prototype.init = function (selector, issueids) { + BlockFormManager.prototype.init = function (issueids = []) { // Init all slots let slots = $(SELECTORS.SLOT_SELECT_OPTION); if (slots.length == 0) slots = $(SELECTORS.SLOT); slots.map((index, option) => { - let issue = new Issue(null, parseInt(option.value)); - issue.changeState(Issue.STATES.NEW); + let issue = new Issue(null, parseInt(option.value), this.contextid); + issue.isSaved = false;//changeState(Issue.STATES.NEW); this.issueManager.addIssue(issue); }); + this.issueManager.loadIssues(issueids).then(() => { + var formData = new FormData(this.form[0]); this.issueManager.setActiveIssue(parseInt(formData.get('slot'))); @@ -150,11 +153,10 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss BlockFormManager.prototype.reflectFormState = function () { let issue = this.issueManager.getActiveIssue(); - var state = issue.getState(); - if (state === Issue.STATES.EXISTING) { + if (issue.isSaved === true) { //state === Issue.STATES.EXISTING) { this.toggleDeleteButton(true); this.toggleUpdateButton(true); - } else if (state === Issue.STATES.NEW) { + } else if (issue.isSaved === false) {//state === Issue.STATES.NEW) { this.clearForm(); } @@ -173,6 +175,7 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss // We could trigger an event instead. // Yuk. + console.log("jijjijij") Y.use('moodle-core-formchangechecker', function () { M.core_formchangechecker.reset_form_dirty_state(); }); @@ -257,7 +260,7 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss }; this.notify(notification); }.bind(this)); - this.issueManager.getActiveIssue().changeState(Issue.STATES.NEW);; + this.issueManager.getActiveIssue().isSaved = false;//changeState(Issue.STATES.NEW);; this.clearForm(); }.bind(this), fail: this.handleFormSubmissionFailure.bind(this) @@ -277,6 +280,7 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss args: { qubaid: formData.get('qubaid'), slot: formData.get('slot'), + contextid: this.contextid, issuetitle: formData.get('issuetitle'), issuedescription: formData.get('issuedescription'), }, @@ -289,7 +293,7 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss }; this.notify(notification); }.bind(this)); - this.issueManager.getActiveIssue().changeState(Issue.STATES.EXISTING) + this.issueManager.getActiveIssue().isSaved = true;//changeState(Issue.STATES.EXISTING) //this.setAction(ACTION.EDITISSUE); // TODO: add delete button. this.toggleUpdateButton(true); @@ -416,6 +420,13 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss return; } + + if (this.issueManager.getActiveIssue().isSaved === true) { + this.editIssue(); + } else { + this.createIssue(); + } +/* var state = this.issueManager.getActiveIssue().getState(); switch (state) { case Issue.STATES.NEW: @@ -430,7 +441,7 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss break; default: break; - } + }*/ }; BlockFormManager.prototype.validateForm = function () { @@ -476,8 +487,8 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss * @param {string} issueids The ids of existing issues to load. * @return {BlockFormManager} */ - init: function (selector, issueids) { - return new BlockFormManager(selector, issueids); + init: function (selector, issueids, contextid) { + return new BlockFormManager(selector, issueids, contextid); } }; }); diff --git a/amd/src/issue.js b/amd/src/issue.js index 26fdfea..2168b97 100644 --- a/amd/src/issue.js +++ b/amd/src/issue.js @@ -33,15 +33,16 @@ define(['jquery', 'core/str', 'core/ajax'], function ($, Str, Ajax) { * * Each call to init gets it's own instance of this class. */ - var Issue = function (id = null, slot = null) { + var Issue = function (id = null, slot = null, contextid) { this.id = id; this.slot = slot; + this.contextid = contextid; }; Issue.STATES = { NEW: "new", - EXISTING: "existing", - DELETED: "deleted", + OPEN: "open", + CLOSED: "closed", } /** @@ -68,9 +69,11 @@ define(['jquery', 'core/str', 'core/ajax'], function ($, Str, Ajax) { */ Issue.prototype.description = ""; - Issue.prototype.state = Issue.STATES.NEW; + Issue.prototype.contextid = null; + + Issue.prototype.isSaved = false; - Issue.prototype.inDB = false; + Issue.prototype.state = Issue.STATES.NEW; /** * Initialise the class. @@ -123,6 +126,10 @@ define(['jquery', 'core/str', 'core/ajax'], function ($, Str, Ajax) { return this.state; }; + Issue.prototype.getContextid = function () { + return this.contextid; + }; + /** * return {Promise} */ diff --git a/amd/src/issue_manager.js b/amd/src/issue_manager.js index 5216a68..9fee83a 100644 --- a/amd/src/issue_manager.js +++ b/amd/src/issue_manager.js @@ -23,7 +23,7 @@ * @copyright 2020 NTNU * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define(['jquery', 'local_qtracker/events', 'local_qtracker/issue'], function ($, qtrackerEvents, Issue) { +define(['jquery', 'local_qtracker/issue'], function ($, Issue) { /** * Constructor @@ -33,7 +33,7 @@ define(['jquery', 'local_qtracker/events', 'local_qtracker/issue'], function ($, * * Each call to init gets it's own instance of this class. */ - var IssueManager = function () { }; + var IssueManager = function () {}; /** * @var {Form} form @@ -43,7 +43,6 @@ define(['jquery', 'local_qtracker/events', 'local_qtracker/issue'], function ($, IssueManager.prototype.activeIssue = null; - IssueManager.prototype.getActiveIssue = function () { return this.activeIssue; }; @@ -51,9 +50,6 @@ define(['jquery', 'local_qtracker/events', 'local_qtracker/issue'], function ($, IssueManager.prototype.setActiveIssue = function (slot) { let newIssue = this.getIssueBySlot(slot); this.activeIssue = newIssue; - /* if (newIssue) { - $(document).trigger(qtrackerEvents.ISSUEMANAGER_ACTIVEISSUE_CHANGED) - } */ return newIssue; }; @@ -81,9 +77,10 @@ define(['jquery', 'local_qtracker/events', 'local_qtracker/issue'], function ($, this.issues.set(issue.getSlot(), issue); }; - IssueManager.prototype.loadIssues = function (issueids) { - var promises = []; - for (const id of issueids) { + IssueManager.prototype.loadIssues = function (issueids = []) { + let promises = []; + for (let i = 0; i < issueids.length; i++) { + const id = issueids[i]; let promise = Issue.load(id).then((response) => { let issue = this.getIssueBySlot(response.issue.slot) if (!issue) { @@ -92,12 +89,11 @@ define(['jquery', 'local_qtracker/events', 'local_qtracker/issue'], function ($, issue.setId(response.issue.id); issue.setTitle(response.issue.title); issue.setDescription(response.issue.description); - issue.changeState(Issue.STATES.EXISTING); + issue.isSaved = true;//changeState(Issue.STATES.EXISTING); this.addIssue(issue); }); promises.push(promise); } - return Promise.all(promises); }; diff --git a/amd/src/questions_table.js b/amd/src/questions_table.js new file mode 100644 index 0000000..9bec181 --- /dev/null +++ b/amd/src/questions_table.js @@ -0,0 +1,212 @@ +// 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 . + +/** + * Manager for managing table of questions with issues. + * + * @module local_qtracker/IssueManager + * @class IssueManager + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +import $ from 'jquery'; +import Templates from 'core/templates'; +import Ajax from 'core/ajax'; +import url from 'core/url'; +/** + * Constructor + * @constructor + * @param {String} selector used to find triggers for the new group modal. + * @param {int} contextid + * + * Each call to init gets it's own instance of this class. + */ +class QuestionsTable { + courseid = null; + + constructor(courseid) { + this.courseid = courseid; + + this.init(); + //TODO: make sortable open, closed, new... + //TODO: make issue.php.. (make the attempt accessable!) + //TODO: make it possible to create manual issue.... + //TODO: Fix sorting...... + + //const { html, js } = await Templates.renderForPromise(userGrade.templatename, userGrade.grade); + } + + async init() { + var hidden = true; + + let context = { + close: { + "key": "fa-times", + "title": "Close", + "alt": "Close pane", + "extraclasses": "", + "unmappedIcon": false + } + }; + + await Templates.render('local_qtracker/issues_pane', context).then((html, js) => { + Templates.replaceNodeContents('#questions-table-sidebar', html, js); + }); + + window.showIssuesInPane = async function (id, state = null) { + $('.issues-pane-content .issues').empty(); + $('.issues-pane-content .loading').addClass("show"); + + // Get question title. + let questionData = await this.loadQuestionData(id) + let question = questionData.question; + let questionEditUrl = this.getQuestionEditUrl(this.courseid, id); + let link = $('').attr("href", questionEditUrl).html(question.name + " #" + question.id); + $('.issues-pane-title').html(link); + + + // Get issues data. + let issuesResponse = await this.loadIssues(id, state); + let issues = issuesResponse.issues; + + if (hidden) lol(); + + // Get users data. + let userids = [...new Set(issues.map(issue => issue.userid))]; + let usersData = await this.loadUsersData(userids); + + // Render issue items. + let promises = [] + issues.forEach(async issueData => { + let userData = usersData.find( ({ id }) => id === issueData.userid ); + promises.push(this.addIssueItem(issueData, userData)); + }); + + // When all issue item promises are resolved. + $.when.apply($, promises).done(function () { + $('.issues-pane-content .loading').removeClass("show"); + $.each(arguments, (index, argument) => { + Templates.appendNodeContents('.issues-pane-content .issues', argument.html, argument.js); + }); + }).catch(e => { + console.error(e) + }) + + }.bind(this); + + window.closeIssuesPane = function () { + if (!hidden) + lol(); + }; + + window.lol = function togglePane() { + $('.qtracker-container').toggleClass('push-pane-over'); + $('#issues-pane').toggleClass("show"); + hidden = !hidden; + }; + } + + /** + * + * @param {*} issueData + * @return Promise + */ + async addIssueItem(issueData, userData) { + // Fetch user data. + let issueurl = url.relativeUrl('/local/qtracker/issue.php', { + courseid: this.courseid, + issueid: issueData.id, + }); + let userurl = url.relativeUrl('/user/view.php', { + course: this.courseid, + id: userData.id, + }); + + //Render issues pane + let paneContext = { + issueurl: issueurl, + userurl: userurl, + profileimageurl: userData.profileimageurlsmall, + fullname: userData.fullname, + timecreated: issueData.timecreated, + title: issueData.title, + description: issueData.description, + }; + let state = issueData.state; + paneContext[state] = true; + + return Templates.render('local_qtracker/issues_pane_item', paneContext) + .then(function (html, js) { + return { html: html, js: js }; + }); + } + + async loadIssues(id, state = null) { + let criteria = [ + { key: 'questionid', value: id }, + ]; + if (state) { + criteria.push({ key: 'state', value: state }); + } + let issuesData = await Ajax.call([{ + methodname: 'local_qtracker_get_issues', + args: { criteria: criteria } + }])[0]; + + return issuesData; + } + + async loadUsersData(ids) { + let usersData = await Ajax.call([{ + methodname: 'core_user_get_users_by_field', + args: { + field: 'id', + values: ids + } + }])[0]; + + return usersData; + } + + getQuestionEditUrl(courseid, questionid) { + let returnurl = encodeURIComponent(location.pathname + location.search); + let editurl = url.relativeUrl('/question/question.php', { + courseid: courseid, + id: questionid, + returnurl: returnurl, + }); + console.log(new URL(editurl)) + return editurl; + } + + decodeHTML(html) { + var doc = new DOMParser().parseFromString(html, "text/html"); + return doc.documentElement.textContent; + } + + async loadQuestionData(id) { + let userData = await Ajax.call([{ + methodname: 'local_qtracker_get_question', + args: { + id: id + } + }])[0]; + return userData; + } +} + +export default QuestionsTable; diff --git a/classes/external/deleteissue.php b/classes/external/delete_issue.php similarity index 89% rename from classes/external/deleteissue.php rename to classes/external/delete_issue.php index a08874a..fc0b0bd 100644 --- a/classes/external/deleteissue.php +++ b/classes/external/delete_issue.php @@ -28,6 +28,7 @@ require_once($CFG->libdir . "/externallib.php"); require_once($CFG ->libdir . '/questionlib.php'); +require_once($CFG->dirroot . '/local/qtracker/lib.php'); use external_value; use external_function_parameters; @@ -37,7 +38,7 @@ use local_qtracker\issue; -class deleteissue extends \external_api { +class delete_issue extends \external_api { /** * Returns description of method parameters @@ -68,16 +69,6 @@ public static function delete_issue($issueid) { ) ); - //Context validation - // TODO: ensure proper validation.... - $context = \context_user::instance($USER->id); - self::validate_context($context); - - //Capability checking - if (!has_capability('local/qtracker:createissue', $context)) { - throw new \moodle_exception('cannotdeleteissue', 'local_qtracker'); - } - if (!$DB->record_exists_select('qtracker_issue', 'id = :issueid AND userid = :userid', array( 'issueid' => $params['issueid'], @@ -87,8 +78,16 @@ public static function delete_issue($issueid) { throw new \moodle_exception('cannotdeleteissue', 'local_qtracker', '', $params['issueid']); } + $issue = issue::load($params['issueid']); + + //Context validation + $context = \context::instance_by_id($issue->get_contextid()); + self::validate_context($context); + + //Capability checking + issue_require_capability_on($issue->get_issue_obj(), 'edit'); + if (empty($warnings)) { - $issue = issue::load($params['issueid']); $deleted = $issue->delete(); } diff --git a/classes/external/editissue.php b/classes/external/edit_issue.php similarity index 92% rename from classes/external/editissue.php rename to classes/external/edit_issue.php index 77b8456..924933f 100644 --- a/classes/external/editissue.php +++ b/classes/external/edit_issue.php @@ -28,6 +28,7 @@ require_once($CFG->libdir . "/externallib.php"); require_once($CFG ->libdir . '/questionlib.php'); +require_once($CFG->dirroot . '/local/qtracker/lib.php'); use external_value; use external_function_parameters; @@ -37,7 +38,7 @@ use local_qtracker\issue; -class editissue extends \external_api { +class edit_issue extends \external_api { /** * Returns description of method parameters @@ -72,16 +73,6 @@ public static function edit_issue($issueid, $issuetitle, $issuedescription) { ) ); - //Context validation - // TODO: ensure proper validation.... - $context = \context_user::instance($USER->id); - self::validate_context($context); - - //Capability checking - if (!has_capability('local/qtracker:createissue', $context)) { - throw new \moodle_exception('cannoteditissue', 'local_qtracker'); - } - if (!$DB->record_exists_select('qtracker_issue', 'id = :issueid AND userid = :userid', array( 'issueid' => $params['issueid'], @@ -91,6 +82,15 @@ public static function edit_issue($issueid, $issuetitle, $issuedescription) { throw new \moodle_exception('cannoteditissue', 'local_qtracker', '', $params['issueid']); } + $issue = issue::load($params['issueid']); + + //Context validation + $context = \context::instance_by_id($issue->get_contextid()); + self::validate_context($context); + + //Capability checking + issue_require_capability_on($issue->get_issue_obj(), 'edit'); + if (empty($params['issuetitle'])){ $warnings[] = array( 'item' => 'issuetitle', @@ -109,7 +109,6 @@ public static function edit_issue($issueid, $issuetitle, $issuedescription) { ); } if (empty($warnings)) { - $issue = issue::load($params['issueid']); $issue->set_title($params['issuetitle']); $issue->set_description($params['issuedescription']); $added = true; diff --git a/classes/external/getissue.php b/classes/external/get_issue.php similarity index 72% rename from classes/external/getissue.php rename to classes/external/get_issue.php index 081017b..2dcdf14 100644 --- a/classes/external/getissue.php +++ b/classes/external/get_issue.php @@ -28,14 +28,16 @@ require_once($CFG->libdir . "/externallib.php"); require_once($CFG ->libdir . '/questionlib.php'); +require_once($CFG->dirroot . '/local/qtracker/lib.php'); use external_value; use external_function_parameters; use external_single_structure; use external_warnings; use local_qtracker\issue; +use local_qtracker\external\helper; -class getissue extends \external_api { +class get_issue extends \external_api { /** * Returns description of method parameters @@ -68,16 +70,6 @@ public static function get_issue($issueid) { ) ); - //Context validation - // TODO: ensure proper validation.... - $context = \context_user::instance($USER->id); - self::validate_context($context); - - //Capability checking - if (!has_capability('local/qtracker:readissue', $context)) { - throw new \moodle_exception('cannotgetissue', 'local_qtracker'); - } - if (!$DB->record_exists_select('qtracker_issue', 'id = :issueid AND userid = :userid', array( 'issueid' => $params['issueid'], @@ -87,12 +79,21 @@ public static function get_issue($issueid) { throw new \moodle_exception('cannotgetissue', 'local_qtracker', '', $params['issueid']); } - if (empty($warnings)) { - $issue = issue::load($params['issueid']); + $issue = issue::load($params['issueid']); + //Context validation + $context = \context::instance_by_id($issue->get_contextid()); + self::validate_context($context); + + //Capability checking + issue_require_capability_on($issue->get_issue_obj(), 'view'); + + + if (empty($warnings)) { $issuedata['id'] = $issue->get_id(); $issuedata['title'] = $issue->get_title(); $issuedata['description'] = $issue->get_description(); + $issuedata['state'] = $issue->get_state(); $issuedata['questionid'] = $issue->get_questionid(); $issuedata['questionusageid'] = $issue->get_qubaid(); $issuedata['slot'] = $issue->get_slot(); @@ -118,18 +119,7 @@ public static function get_issue_returns() { return new external_single_structure( array( 'status' => new external_value(PARAM_BOOL, 'status: true if success'), - 'issue' => new external_single_structure( - array( - 'id' => new external_value(PARAM_INT, 'The id of the issue'), - 'title' => new external_value(PARAM_TEXT, 'The issue title.'), - 'description' => new external_value(PARAM_TEXT, 'The issue description.'), - 'questionid' => new external_value(PARAM_INT, 'The question id for this issue.'), - 'questionusageid' => new external_value(PARAM_INT, 'The question usage id for this issue.'), - 'slot' => new external_value(PARAM_INT, 'The issslot for the question for the issue.'), - 'userid' => new external_value(PARAM_INT, 'The user id for the user who created the issue.'), - 'timecreated' => new external_value(PARAM_INT, 'The time the issue was created.'), - ) - ), + 'issue' => helper::issue_description(), 'warnings' => new external_warnings() ) ); diff --git a/classes/external/get_issues.php b/classes/external/get_issues.php new file mode 100644 index 0000000..8f75697 --- /dev/null +++ b/classes/external/get_issues.php @@ -0,0 +1,218 @@ +. + +/** + * External (web service) function calls for retrieving a question issue. + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_qtracker\external; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . "/externallib.php"); +require_once($CFG->libdir . '/questionlib.php'); +require_once($CFG->dirroot . '/local/qtracker/lib.php'); + +use external_value; +use external_function_parameters; +use external_single_structure; +use external_multiple_structure; +use external_warnings; +use local_qtracker\issue; +use local_qtracker\external\helper; +use local_qtracker\external\issue_exporter; + +class get_issues extends \external_api { + + /** + * Returns description of get_issues() parameters. + * + * @return external_function_parameters + * @since Moodle 2.5 + */ + public static function get_issues_parameters() { + return new external_function_parameters( + array( + 'criteria' => new external_multiple_structure( + new external_single_structure( + array( + 'key' => new external_value(PARAM_ALPHA, 'the issue column to search, expected keys (value format) are: + "id" (int) matching issue id, + "questionid" (int) issue questionid, + "state" (string) issue state, + "title" (Sstring) issue last name (Note: you can use % for searching but it may be considerably slower!)'), + 'value' => new external_value(PARAM_RAW, 'the value to search') + ) + ), + 'the key/value pairs to be considered in issue search. Values can not be empty. + Specify different keys only once (fullname => \'issue1\', auth => \'manual\', ...) - + key occurences are forbidden. + The search is executed with AND operator on the criterias. Invalid criterias (keys) are ignored, + the search is still executed on the valid criterias. + You can search without criteria, but the function is not designed for it. + It could very slow or timeout. The function is designed to search some specific issues.' + ) + ) + ); + } + + /** + * Retrieve matching issue. + * + * @throws moodle_exception + * @param array $criteria the allowed array keys are id/lastname/firstname/idnumber/issuename/email/auth. + * @return array An array of arrays containing issue profiles. + * @since Moodle 2.5 + */ + public static function get_issues($criteria = array()) { + global $CFG, $issue, $DB, $PAGE, $USER; + + require_once($CFG->dirroot . "/local/qtracker/lib.php"); + $params = self::validate_parameters( + self::get_issues_parameters(), + array('criteria' => $criteria) + ); + + // Validate the criteria and retrieve the issues. + $issues = array(); + $warnings = array(); + $sqlparams = array(); + $usedkeys = array(); + + $sql = '1 = 1'; + foreach ($params['criteria'] as $criteriaindex => $criteria) { + + // Check that the criteria has never been used. + if (array_key_exists($criteria['key'], $usedkeys)) { + throw new moodle_exception('keyalreadyset', '', '', null, 'The key ' . $criteria['key'] . ' can only be sent once'); + } else { + $usedkeys[$criteria['key']] = true; + } + + $invalidcriteria = false; + // Clean the parameters. + $paramtype = PARAM_RAW; + switch ($criteria['key']) { + case 'id': + $paramtype = PARAM_INT; + break; + case 'questionid': + $paramtype = PARAM_INT; + break; + case 'userid': + $paramtype = PARAM_INT; + break; + case 'state': + $paramtype = PARAM_TEXT; + break; + case 'title': + $paramtype = PARAM_TEXT; + break; + default: + // Send back a warning that this search key is not supported in this version. + // This warning will make the function extandable without breaking clients. + $warnings[] = array( + 'item' => $criteria['key'], + 'warningcode' => 'invalidfieldparameter', + 'message' => + 'The search key \'' . $criteria['key'] . '\' is not supported, look at the web service documentation' + ); + // Do not add this invalid criteria to the created SQL request. + $invalidcriteria = true; + unset($params['criteria'][$criteriaindex]); + break; + } + + if (!$invalidcriteria) { + $cleanedvalue = clean_param($criteria['value'], $paramtype); + + $sql .= ' AND '; + + // Create the SQL. + switch ($criteria['key']) { + case 'id': + case 'questionid': + case 'state': + $sql .= $criteria['key'] . ' = :' . $criteria['key']; + $sqlparams[$criteria['key']] = $cleanedvalue; + break; + case 'title': + $sql .= $DB->sql_like($criteria['key'], ':' . $criteria['key'], false); + $sqlparams[$criteria['key']] = $cleanedvalue; + break; + default: + break; + } + } + } + + $issues = $DB->get_records_select('qtracker_issue', $sql, $sqlparams, 'id ASC'); + + // Finally retrieve each issues information. + $returnedissues = array(); + foreach ($issues as $issue) { + //Context validation + $context = \context::instance_by_id($issue->contextid); + self::validate_context($context); + + //Capability checking + issue_require_capability_on($issue, 'view'); + + + $renderer = $PAGE->get_renderer('core'); + $exporter = new issue_exporter($issue, ['context' => $context]); + $issuedetails = $exporter->export($renderer); + // Return the issue only if all the searched fields are returned. + // Otherwise it means that the $issue was not allowed to search the returned issue. + if (!empty($issuedetails)) { + $validissue = true; + + foreach ($params['criteria'] as $criteria) { + if (empty($issuedetails->{$criteria['key']})) { + $validissue = false; + } + } + + if ($validissue) { + $returnedissues[] = $issuedetails; + } + } + } + + return array('issues' => $returnedissues, 'warnings' => $warnings); + } + + /** + * Returns description of get_issues result value. + * + * @return external_description + * @since Moodle 2.5 + */ + public static function get_issues_returns() { + return new external_single_structure( + array( + 'issues' => new external_multiple_structure( + issue_exporter::get_read_structure() + ), + 'warnings' => new external_warnings('always set to \'key\'', 'faulty key name') + ) + ); + } +} diff --git a/classes/external/get_question.php b/classes/external/get_question.php new file mode 100644 index 0000000..5bd7041 --- /dev/null +++ b/classes/external/get_question.php @@ -0,0 +1,108 @@ +. + +/** + * External (web service) function calls for retrieving a question issue. + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_qtracker\external; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . "/externallib.php"); +require_once($CFG ->libdir . '/questionlib.php'); +require_once($CFG->dirroot . '/local/qtracker/lib.php'); + +use external_value; +use external_function_parameters; +use external_single_structure; +use external_warnings; +use local_qtracker\external\helper; + +class get_question extends \external_api { + + /** + * Returns description of method parameters + * @return external_function_parameters + */ + public static function get_question_parameters() { + return new external_function_parameters( + array( + 'id' => new external_value(PARAM_INT, 'question id') + ) + ); + } + + + /** + * Returns welcome message + * @return string welcome message + */ + public static function get_question($questionid) { + global $PAGE, $USER; + + $status = false; + $warnings = array(); + + //Parameter validation + $params = self::validate_parameters(self::get_question_parameters(), + array( + 'id' => (int) $questionid, + ) + ); + + $question = \question_bank::load_question_data($params['id']); + if (!$question) { + throw new \moodle_exception('cannotgetquestion', 'local_qtracker', '', $params['id']); + } + + //Context validation + $context = \context::instance_by_id($question->contextid); + self::validate_context($context); + + question_require_capability_on($question, 'view'); + + $renderer = $PAGE->get_renderer('core'); + $exporter = new \core_question\external\question_summary_exporter($question, ['context' => $context]); + $questionsummary = $exporter->export($renderer); + + $result = array(); + $result['status'] = $status; + $result['question'] = $questionsummary; + $result['warnings'] = $warnings; + + return $result; + } + + /** + * Returns description of method result value + * @return external_description + */ + public static function get_question_returns() { + return new external_single_structure( + array( + 'status' => new external_value(PARAM_BOOL, 'status: true if success'), + 'question' => \core_question\external\question_summary_exporter::get_read_structure(), + 'warnings' => new external_warnings() + ) + ); + + } +} diff --git a/classes/external/helper.php b/classes/external/helper.php new file mode 100644 index 0000000..01b3da9 --- /dev/null +++ b/classes/external/helper.php @@ -0,0 +1,56 @@ +. + +/** + * External (web service) function calls for retrieving a question issue. + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_qtracker\external; + +defined('MOODLE_INTERNAL') || die(); + +use external_value; +use external_single_structure; + +class helper { + /** + * Create issue return value description. + * + * @param array $additionalfields some additional field + * @return single_structure_description + */ + public static function issue_description($additionalfields = array()) { + $issuefields = array( + 'id' => new external_value(PARAM_INT, 'The id of the issue'), + 'title' => new external_value(PARAM_TEXT, 'The issue title.'), + 'description' => new external_value(PARAM_TEXT, 'The issue description.'), + 'state' => new external_value(PARAM_TEXT, 'The issue state.'), + 'questionid' => new external_value(PARAM_INT, 'The question id for this issue.'), + 'questionusageid' => new external_value(PARAM_INT, 'The question usage id for this issue.'), + 'slot' => new external_value(PARAM_INT, 'The issslot for the question for the issue.'), + 'userid' => new external_value(PARAM_INT, 'The user id for the user who created the issue.'), + 'timecreated' => new external_value(PARAM_INT, 'The time the issue was created.'), + ); + if (!empty($additionalfields)) { + $issuefields = array_merge($issuefields, $additionalfields); + } + return new external_single_structure($issuefields); + } +} diff --git a/classes/external/issue_exporter.php b/classes/external/issue_exporter.php new file mode 100644 index 0000000..c30e9df --- /dev/null +++ b/classes/external/issue_exporter.php @@ -0,0 +1,88 @@ +. + +/** + * Exporter for exporting question issue data. + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_qtracker\external; + +defined('MOODLE_INTERNAL') || die(); + +use \core\external\exporter; +use \renderer_base; + +/** + * Class for displaying a list of issue data. + * + * @package local_qtracker + * @copyright 2020 André Storhaug + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class issue_exporter extends exporter { + + + /** + * Return the list of additional properties. + * + * @return array + */ + protected static function define_other_properties() { + return [ + 'id' => [ + 'type' => PARAM_INT, + ], + 'title' => [ + 'type' => PARAM_TEXT, + ], + 'description' => [ + 'type' => PARAM_TEXT, + ], + 'questionid' => [ + 'type' => PARAM_INT, + ], + 'questionusageid' => [ + 'type' => PARAM_INT, + ], + 'slot' => [ + 'type' => PARAM_INT, + ], + 'state' => [ + 'type' => PARAM_TEXT, + ], + 'contextid' => [ + 'type' => PARAM_INT, + ], + 'userid' => [ + 'type' => PARAM_INT, + ], + 'timecreated' => [ + 'type' => PARAM_INT, + ], + ]; + } + + protected static function define_related() { + return array( + 'context' => 'context', + ); + } +} diff --git a/classes/external/newissue.php b/classes/external/new_issue.php similarity index 89% rename from classes/external/newissue.php rename to classes/external/new_issue.php index 8b5b2a7..8e40bf8 100644 --- a/classes/external/newissue.php +++ b/classes/external/new_issue.php @@ -28,6 +28,7 @@ require_once($CFG->libdir . "/externallib.php"); require_once($CFG ->libdir . '/questionlib.php'); +require_once($CFG->dirroot . '/local/qtracker/lib.php'); use external_value; use external_function_parameters; @@ -37,7 +38,7 @@ use local_qtracker\issue; -class newissue extends \external_api { +class new_issue extends \external_api { /** * Returns description of method parameters @@ -48,6 +49,7 @@ public static function new_issue_parameters() { array( 'qubaid' => new external_value(PARAM_INT, 'question usage id'), 'slot' => new external_value(PARAM_INT, 'slot'), + 'contextid' => new external_value(PARAM_INT, 'issue context'), 'issuetitle' => new external_value(PARAM_TEXT, 'issue title'), 'issuedescription' => new external_value(PARAM_TEXT, 'issue description'), ) @@ -58,7 +60,7 @@ public static function new_issue_parameters() { * Returns welcome message * @return string welcome message */ - public static function new_issue($qubaid, $slot, $issuetitle, $issuedescription) { + public static function new_issue($qubaid, $slot, $contextid, $issuetitle, $issuedescription) { global $USER, $DB; $added = false; @@ -69,6 +71,7 @@ public static function new_issue($qubaid, $slot, $issuetitle, $issuedescription) array( 'qubaid' => (int) $qubaid, 'slot' => (int) $slot, + 'contextid' => (int) $contextid, 'issuetitle' => $issuetitle, 'issuedescription' => $issuedescription, ) @@ -76,11 +79,11 @@ public static function new_issue($qubaid, $slot, $issuetitle, $issuedescription) //Context validation // TODO: ensure proper validation.... - $context = \context_user::instance($USER->id); + $context = \context::instance_by_id($params['contextid']); self::validate_context($context); //Capability checking - if (!has_capability('local/qtracker:createissue', $context)) { + if (!has_capability('local/qtracker:addissue', $context)) { throw new moodle_exception('cannotcreateissue', 'local_qtracker'); } @@ -112,6 +115,7 @@ public static function new_issue($qubaid, $slot, $issuetitle, $issuedescription) $params['issuetitle'], $params['issuedescription'], $question, + $params['contextid'], $quba, $params['slot'] ); diff --git a/classes/form/view/issue_registration_form.php b/classes/form/view/issue_registration_form.php index 32bf47b..e2a1193 100644 --- a/classes/form/view/issue_registration_form.php +++ b/classes/form/view/issue_registration_form.php @@ -14,18 +14,23 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -namespace local_qtracker\form\view; +/** + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace local_qtracker\form\view; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/formslib.php'); /** - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @package local_qtracker + * @copyright 2020 André Storhaug + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class issue_registration_form extends \moodleform { diff --git a/classes/issue.php b/classes/issue.php index 2dbc2d7..69b6876 100644 --- a/classes/issue.php +++ b/classes/issue.php @@ -14,11 +14,10 @@ // along with Moodle. If not, see . /** - * @package mod_capquiz - * @author Aleksander Skrede - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace local_qtracker; @@ -83,6 +82,15 @@ public function get_description() { return $this->issue->description; } + /** + * Returns the issue description. + * + * @return int + */ + public function get_state() { + return $this->issue->state; + } + /** * Returns the issue questionusageid. * @@ -110,6 +118,15 @@ public function get_slot() { return $this->issue->slot; } + /** + * Returns the issue contextid. + * + * @return int + */ + public function get_contextid() { + return $this->issue->contextid; + } + /** * Returns the issue userid. * @@ -151,7 +168,7 @@ public static function load(int $issueid) { * * @return issue */ - public static function create($title, $description, \question_definition $question, $quba = null, $slot = null) { + public static function create($title, $description, \question_definition $question, $contextid, $quba = null, $slot = null) { global $USER, $DB; $issueobj = new \stdClass(); @@ -160,6 +177,8 @@ public static function create($title, $description, \question_definition $questi $issueobj->questionid = $question->id; $issueobj->questionusageid = $quba->get_id(); $issueobj->slot = $slot; + $issueobj->contextid = $contextid; + $issueobj->state = 'new'; $issueobj->userid = $USER->id; $time = time(); $issueobj->timecreated = $time; diff --git a/classes/output/issue_registration_block.php b/classes/output/issue_registration_block.php index 0beb4f4..75f06da 100644 --- a/classes/output/issue_registration_block.php +++ b/classes/output/issue_registration_block.php @@ -33,13 +33,11 @@ use stdClass; use help_icon; - /** * Question issue registration block class. * * @package local_qtracker - * @author André Storhaug - * @copyright 2020 NTNU + * @copyright 2020 André Storhaug * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class issue_registration_block implements renderable, templatable { @@ -67,10 +65,11 @@ class issue_registration_block implements renderable, templatable { * @param int $userid The id of the user. * @throws \coding_exception If called at incorrect times */ - public function __construct(\question_usage_by_activity $quba, $slots) { + public function __construct(\question_usage_by_activity $quba, $slots, $contextid) { $this->quba = $quba; $this->slots = $slots; + $this->contextid = $contextid; //Todo remove questions..... foreach ($this->slots as $slot) { @@ -135,6 +134,7 @@ public function export_for_template(renderer_base $output) { $button->label = "Submit new issue"; $data->button = $button; $data->issueids = json_encode($this->issueids); + $data->contextid = $this->contextid; // TODO: Fix this as both the button and the select gets this. Wrap in separate mustashe templates. diff --git a/classes/output/question_issue_page.php b/classes/output/question_issue_page.php new file mode 100644 index 0000000..4060c8e --- /dev/null +++ b/classes/output/question_issue_page.php @@ -0,0 +1,84 @@ +. + +/** + * Renderable for issues page + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_qtracker\output; + +defined('MOODLE_INTERNAL') || die(); + +use coding_exception; +use dml_exception; +use moodle_exception; +use renderable; +use renderer_base; +use stdClass; +use templatable; +use local_qtracker\issue; +use local_qtracker\external\issue_exporter; + +/** + * Class containing data for question issue page. + * + * @copyright 2020 André Storhaug + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class question_issue_page implements renderable, templatable { + + /** The default number of results to be shown per page. */ + const DEFAULT_PAGE_SIZE = 20; + + protected $questionissue = null; + + protected $courseid = []; + + /** + * Construct this renderable. + * + * @param \local_qtracker\question_issues_table $questionissuestable + */ + public function __construct(issue $questionissue, $courseid) { + $this->questionissue = $questionissue; + $this->courseid = $courseid; + } + + /** + * Export this data so it can be used as the context for a mustache template. + * + * @param renderer_base $output + * @return stdClass + * @throws coding_exception + * @throws dml_exception + * @throws moodle_exception + */ + public function export_for_template(renderer_base $output) { + $data = new stdClass(); + + $context = \context_course::instance($this->courseid); + $exporter = new issue_exporter($this->questionissue->get_issue_obj(), ['context' => $context]); + $issuedetails = $exporter->export($output); + $data->questionissue = $issuedetails; +print_r($data->questionissue); + return $data; + } +} diff --git a/classes/output/questions_page.php b/classes/output/questions_page.php index b0bc2a6..bbd8788 100644 --- a/classes/output/questions_page.php +++ b/classes/output/questions_page.php @@ -53,8 +53,9 @@ class questions_page implements renderable, templatable { * * @param \local_qtracker\questions_table $questionstable */ - public function __construct(questions_table $questionstable) { + public function __construct(questions_table $questionstable, $courseid) { $this->questionstable = $questionstable; + $this->courseid = $courseid; } /** @@ -67,6 +68,7 @@ public function __construct(questions_table $questionstable) { * @throws moodle_exception */ public function export_for_template(renderer_base $output) { + global $PAGE; $data = new stdClass(); ob_start(); @@ -74,6 +76,7 @@ public function export_for_template(renderer_base $output) { $questions = ob_get_contents(); ob_end_clean(); $data->questions = $questions; + $data->courseid = $this->courseid; return $data; } diff --git a/db/access.php b/db/access.php index 7fdb0e7..d09dd6f 100644 --- a/db/access.php +++ b/db/access.php @@ -1,28 +1,68 @@ . + +/** + * This file contains the capabilities defined for the qtracter module + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + $capabilities = array( - 'local/qtracker:createissue' => array( - 'riskbitmask' => RISK_SPAM, + + 'local/qtracker:addissue' => array( + 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_COURSE, 'archetypes' => array( 'user' => CAP_ALLOW ), ), - 'local/qtracker:deleteissue' => array( - 'riskbitmask' => RISK_SPAM, + 'local/qtracker:editmine' => array( + 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_COURSE, 'archetypes' => array( 'user' => CAP_ALLOW ), ), - 'local/qtracker:readissue' => array( - 'riskbitmask' => RISK_PERSONAL, + 'local/qtracker:editall' => array( + 'riskbitmask' => RISK_SPAM | RISK_XSS, + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => array( + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ), + ), + 'local/qtracker:viewmine' => array( 'captype' => 'read', 'contextlevel' => CONTEXT_COURSE, 'archetypes' => array( 'user' => CAP_ALLOW ), ), - + 'local/qtracker:viewall' => array( + 'captype' => 'read', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => array( + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ), + ), ); diff --git a/db/install.xml b/db/install.xml index 38375de..50ea731 100755 --- a/db/install.xml +++ b/db/install.xml @@ -14,6 +14,7 @@ + diff --git a/db/services.php b/db/services.php index 64130b6..3ec45f2 100644 --- a/db/services.php +++ b/db/services.php @@ -15,18 +15,11 @@ // along with Moodle. If not, see . /** - * This file contains the library of functions and constants for the lti module + * This file contains the services for the qtracter module * - * @package mod_lti - * @copyright 2009 Marc Alier, Jordi Piguillem, Nikolas Galanis - * marc.alier@upc.edu - * @copyright 2009 Universitat Politecnica de Catalunya http://www.upc.edu - * @author Marc Alier - * @author Jordi Piguillem - * @author Nikolas Galanis - * @author Chris Scribner - * @copyright 2015 Vital Source Technologies http://vitalsource.com - * @author Stephen Vickers + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -35,7 +28,7 @@ // We defined the web service functions to install. $functions = array( 'local_qtracker_new_issue' => array( - 'classname' => 'local_qtracker\external\newissue', + 'classname' => 'local_qtracker\external\new_issue', 'methodname' => 'new_issue', 'classpath' => '', 'description' => 'Register a new question issue.', @@ -46,7 +39,7 @@ 'loginrequired' => true, ), 'local_qtracker_edit_issue' => array( - 'classname' => 'local_qtracker\external\editissue', + 'classname' => 'local_qtracker\external\edit_issue', 'methodname' => 'edit_issue', 'classpath' => '', 'description' => 'Edit an existing question issue.', @@ -57,7 +50,7 @@ 'loginrequired' => true, ), 'local_qtracker_delete_issue' => array( - 'classname' => 'local_qtracker\external\deleteissue', + 'classname' => 'local_qtracker\external\delete_issue', 'methodname' => 'delete_issue', 'classpath' => '', 'description' => 'Delete an existing question issue.', @@ -68,7 +61,7 @@ 'loginrequired' => true, ), 'local_qtracker_get_issue' => array( - 'classname' => 'local_qtracker\external\getissue', + 'classname' => 'local_qtracker\external\get_issue', 'methodname' => 'get_issue', 'classpath' => '', 'description' => 'Get an existing question issue.', @@ -77,6 +70,42 @@ //'capabilities' => 'moodle/course:managegroups', 'capabilities' => array(), // capabilities required by the function. 'loginrequired' => true, + ), + 'local_qtracker_get_question' => array( + 'classname' => 'local_qtracker\external\get_question', + 'methodname' => 'get_question', + 'classpath' => '', + 'description' => 'Get question by id.', + 'type' => 'read', + 'ajax' => true, + 'loginrequired' => true, + ), + 'local_qtracker_get_question_preview_url' => array( + 'classname' => 'local_qtracker\external\get_question_preview_url', + 'methodname' => 'get_question_preview_url', + 'classpath' => '', + 'description' => 'Get question preview url.', + 'type' => 'read', + 'ajax' => true, + 'loginrequired' => true, + ), + 'local_qtracker_get_question_edit_url' => array( + 'classname' => 'local_qtracker\external\get_question_edit_url', + 'methodname' => 'get_question_edit_url', + 'classpath' => '', + 'description' => 'Get question edit url.', + 'type' => 'read', + 'ajax' => true, + 'loginrequired' => true, + ), + 'local_qtracker_get_issues' => array( + 'classname' => 'local_qtracker\external\get_issues', + 'methodname' => 'get_issues', + 'classpath' => '', + 'description' => 'Get issues.', + 'type' => 'read', + 'ajax' => true, + 'loginrequired' => true, ) ); @@ -87,7 +116,8 @@ 'local_qtracker_new_issue', 'local_qtracker_edit_issue', 'local_qtracker_delete_issue', - 'local_qtracker_get_issue' + 'local_qtracker_get_issue', + 'local_qtracker_get_issues', ), 'restrictedusers' => 0, 'enabled' => 1, diff --git a/db/upgrade.php b/db/upgrade.php index 14418f7..8647962 100755 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -15,10 +15,10 @@ // along with Moodle. If not, see . /** - * @package local_qtracker - * @author André Storhaug - * @copyright 2020 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); @@ -65,16 +65,27 @@ function xmldb_local_qtracker_upgrade($oldversion) { } upgrade_plugin_savepoint(true, 2020071000, 'local', 'qtracker'); } - if ($oldversion < 2020072400) { + if ($oldversion < 2020072402) { // Define table capquiz_user_rating to be created. $table = new xmldb_table('qtracker_issue'); $field = new xmldb_field( - 'slot', XMLDB_TYPE_TEXT); + 'state', XMLDB_TYPE_TEXT); if (!$dbman->field_exists($table, $field)) { $dbman->add_field($table, $field); } - upgrade_plugin_savepoint(true, 2020072400, 'local', 'qtracker'); + upgrade_plugin_savepoint(true, 2020072402, 'local', 'qtracker'); + } + if ($oldversion < 2020072412) { + // Define table capquiz_user_rating to be created. + $table = new xmldb_table('qtracker_issue'); + + $field = new xmldb_field( + 'contextid', XMLDB_TYPE_INTEGER, 10, null, null, null, 253 ); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + upgrade_plugin_savepoint(true, 2020072412, 'local', 'qtracker'); } return true; } diff --git a/issue.php b/issue.php new file mode 100644 index 0000000..5811bfc --- /dev/null +++ b/issue.php @@ -0,0 +1,77 @@ +. + +/** + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_qtracker; + +use local_qtracker\issue; +use local_qtracker\output\question_issue_page; + +require_once('../../config.php'); +require_once($CFG->dirroot . '/local/qtracker/lib.php'); + +global $DB, $OUTPUT, $PAGE; + +// Check for all required variables. +$courseid = required_param('courseid', PARAM_INT); +$issueid = required_param('issueid', PARAM_INT); + +if (!$course = $DB->get_record('course', array('id' => $courseid))) { + print_error('invalidcourseid'); +} + +require_login($course); + +$url = new \moodle_url('/local/qtracker/issue.php'); +$url->param('courseid', $courseid); +$url->param('issueid', $issueid); + +$returnurl = new \moodle_url('/local/qtracker/issues.php'); +$returnurl->param('courseid', $courseid); + +$PAGE->set_url($url); +$PAGE->set_pagelayout('incourse'); +$PAGE->set_heading(get_string('pluginname', 'local_qtracker')); + +// Return to issues list. +if (optional_param('return', false, PARAM_BOOL)) { + redirect($returnurl); +} + +$issuesnode = $PAGE->navbar->add(get_string('pluginname', 'local_qtracker'), null, \navigation_node::TYPE_CONTAINER, null, 'qtracker'); +$issuesnode->add(get_string('issues', 'local_qtracker'), +new \moodle_url('/local/qtracker/view.php', array('courseid' => $courseid))); +$issuesnode->add(get_string('issue', 'local_qtracker')); + +$issue = issue::load($issueid); + +//Capability checking +issue_require_capability_on($issue->get_issue_obj(), 'view'); + +echo $OUTPUT->header(); + +$renderer = $PAGE->get_renderer('local_qtracker'); + +$questionissuepage = new question_issue_page($issue, $courseid); +echo $renderer->render($questionissuepage); + +echo $OUTPUT->footer(); diff --git a/lib.php b/lib.php index 6a35d5c..7fb10ea 100644 --- a/lib.php +++ b/lib.php @@ -15,12 +15,15 @@ // along with Moodle. If not, see . /** - * @package local_qtracker - * @author André Storhaug - * @copyright 2020 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +defined('MOODLE_INTERNAL') || die(); + +use local_qtracker\issue; /** * This function extends the navigation with the report items @@ -49,8 +52,9 @@ function local_qtracker_extend_navigation_course($navigation, $course, $context) 'qtracker' ); - //$contexts = new question_edit_contexts($context); - //if ($contexts->have_one_edit_tab_cap('questions')) { + //TODO: Check if the user has ANY question issue context capabilities. + //$contexts = new issue_edit_contexts($context); + //if ($contexts->have_one_edit_tab_cap('issues')) { $qtrackernode->add(get_string('issues', 'local_qtracker'), new moodle_url( $CFG->wwwroot . '/local/qtracker/view.php', $params @@ -58,5 +62,50 @@ function local_qtracker_extend_navigation_course($navigation, $course, $context) //} } -function qtracker_get_view($calendar, $view, $includenavigation = true, bool $skipevents = false) { +/** + * Check capability on category + * + * @param mixed $issueorid object or id. If an object is passed, it should include ->contextid and ->userid. + * @param string $cap 'add', 'edit', 'view'. + * @param integer $notused no longer used. + * @return boolean this user has the capability $cap for this issue $issue? + */ +function issue_has_capability_on($issueorid, $cap) { + global $USER; + + if (is_numeric($issueorid)) { + $issue = issue::load((int)$issueorid); + } else if (is_object($issueorid)) { + if (isset($issueorid->contextid) && isset($issueorid->userid)) { + $issue = $issueorid; + } + + if (!isset($issue) && isset($issueorid->id) && $issueorid->id != 0) { + $issue = issue::load($issueorid->id)->get_issue_obj(); + } + } else { + throw new coding_exception('$issueorid parameter needs to be an integer or an object.'); + } + + $context = context::instance_by_id($issue->contextid); + // These are existing issues capabilities. + // Each of these has a 'mine' and 'all' version that is appended to the capability name. + $capabilitieswithallandmine = ['edit' => 1, 'view' => 1]; + + if (!isset($capabilitieswithallandmine[$cap])) { + return has_capability('local/qtracker:' . $cap, $context); + } else { + return has_capability('local/qtracker:' . $cap . 'all', $context) || + ($issue->userid == $USER->id && has_capability('local/qtracker:' . $cap . 'mine', $context)); + } +} + +/** + * Require capability on issue. + */ +function issue_require_capability_on($issue, $cap) { + if (!issue_has_capability_on($issue, $cap)) { + print_error('nopermissions', '', '', $cap); + } + return true; } diff --git a/settings.php b/settings.php index 4fc3508..23fa9a6 100644 --- a/settings.php +++ b/settings.php @@ -17,14 +17,14 @@ /** * Main interface to Question Tracker * - * @package local_qtracker - * @author André Storhaug - * @copyright 2020 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); -/* +/* $settings->add(new admin_setting_heading('sampleheader', get_string('headerconfig', 'local_qtracker'), get_string('descconfig', 'local_qtracker'))); @@ -32,4 +32,4 @@ $settings->add(new admin_setting_configcheckbox('qtracker/foo', get_string('labelfoo', 'local_qtracker'), get_string('descfoo', 'local_qtracker'), - '0')); */ \ No newline at end of file + '0')); */ diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..a8a4e0e --- /dev/null +++ b/styles.css @@ -0,0 +1,73 @@ +#issues-pane { + width: 100%; + z-index: 30; + border: 1px solid #dee2e6; +} + +.issues-pane-header { + padding: .75rem; + font-size: 1em; + top: 0; +} + +.issues-pane-footer { + padding: .75rem; + font-size: 1em; + bottom: 0; +} + +.qtracker-container { + position: relative; + min-height: 400px; +} + +.qtracker-container .icon { + height: 20px; + font-size: 20px; + width: auto; +} + +.push-pane-over { + padding-right: 0; +} + +#questions-table-wrapper { + position: relative; + min-height: 360px; +} + +.questions-table { + width: 100%; +} + +.flex-grow { + flex: 1 0 auto; +} + +@media (min-width: 768px) { + #issues-pane { + position: absolute; + top: calc(-1.25rem - 2px); + right: calc(-1.25rem); + height: calc(100% + 2*1.25rem + 2px); + border: unset; + border-left: 1px solid #dee2e6; + box-shadow: -3px 0 5px rgba(36, 41, 46, .05); + width: calc(40% - -1.25rem); + z-index: 30; + animation: show-pane .2s cubic-bezier(0, 0, 0, 1); + } + .push-pane-over { + padding-right: 40%; + transition: padding 0.2s; + } +} + +@keyframes show-pane { + from { + right: calc(-400px - 1.25rem); + } + to { + right: -1.25rem; + } +} diff --git a/templates/button.mustache b/templates/button.mustache index 090acaa..2e03dd7 100644 --- a/templates/button.mustache +++ b/templates/button.mustache @@ -17,11 +17,16 @@ {{! @template local_qtracker/button + Button template. + Example context (json): { + "classes": "", + "type": "submit", "primary": true, "tooltip": "A button.", - "label": "Button" + "label": "Button", + "disabled": false } }}
    diff --git a/templates/issue_registration_block.mustache b/templates/issue_registration_block.mustache index 46cb122..d5d7cc2 100644 --- a/templates/issue_registration_block.mustache +++ b/templates/issue_registration_block.mustache @@ -15,37 +15,31 @@ along with Moodle. If not, see . }} {{! - @template core_admin/setting_configmultiselect + @template local_qtracker/issue_registration_block - Admin multiselect setting template. + Issue registration block template. Context variables required for this template: - * name - form element name - * id - element id - * size - element size + * action - form element name + * name - element id + * qubaid - question bank id * options - list of options containing name, value, selected + For a full list of the context for this template see the course_competencies_page renderable + Example context (json): { + "action": "https://domain.example/file.php", "name": "test", - "id": "test0", - "size": "3", "hasmultiple": true, "select": { "name": "questionid", "options": [ - { "name": "Option 1", "value": "V", "selected": true }, - { "name": "Option 2", "value": "V", "quba": "10", "slot": "2", "selected": true } + { "name": "Option 1", "value": "1", "selected": true }, + { "name": "Option 2", "value": "2", "selected": false } ] - } - } -}} -{{! - @template mod_capquiz/configure_badge_rating - - Example context (json): - { - "form": "raw html for the form" + }, + "qubaid": "10" } }}
    @@ -82,6 +76,6 @@
    {{#js}} require(['jquery', 'local_qtracker/block_form_manager'], function($, BlockFormManager) { - BlockFormManager.init('[data-action=newissue]', '{{{issueids}}}'); + BlockFormManager.init('[data-action=newissue]', '{{{issueids}}}', {{contextid}}); }); {{/js}} diff --git a/templates/issue_state_badge.mustache b/templates/issue_state_badge.mustache new file mode 100644 index 0000000..de5e8d8 --- /dev/null +++ b/templates/issue_state_badge.mustache @@ -0,0 +1,37 @@ +{{! + 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 . +}} +{{! + @template local_qtracker/issue_state_badge + + Issue state badge template. + + Example context (json): + { + "new": true + } +}} +
    +{{#new}} + {{#str}} new, local_qtracker {{/str}} +{{/new}} +{{#open}} + {{#str}} open, local_qtracker {{/str}} +{{/open}} +{{#closed}} + {{#str}} closed, local_qtracker {{/str}} +{{/closed}} +
    diff --git a/templates/issues_pane.mustache b/templates/issues_pane.mustache new file mode 100644 index 0000000..9945eef --- /dev/null +++ b/templates/issues_pane.mustache @@ -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 . +}} +{{! + @template local_qtracker/issues_pane + + Issue pane template. + + Example context (json): + { + } +}} +
    +
    + +
    +
    +
    + {{>core/loading}} +
    +
    +
    +
    + +
    +
    diff --git a/templates/issues_pane_item.mustache b/templates/issues_pane_item.mustache new file mode 100644 index 0000000..5f57e76 --- /dev/null +++ b/templates/issues_pane_item.mustache @@ -0,0 +1,49 @@ +{{! + 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 . +}} +{{! + @template local_qtracker/issues_pane_item + + Issue pane item template. + + Example context (json): + { + "profileimageurl": "https://moodle.org/pix/u/f3.png" + "fullname": "André Storhaug", + "userurl": "https://example.com/user/profile.php?id=3", + "timecreated": "1587655101", + "title": "Issue title", + "description": "Issue description" + } +}} + diff --git a/templates/question_issue_page.mustache b/templates/question_issue_page.mustache new file mode 100644 index 0000000..c034af6 --- /dev/null +++ b/templates/question_issue_page.mustache @@ -0,0 +1,38 @@ +{{! + 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 . +}} +{{! + @template local_qtracker/questions_issue_page + + Question issue page template. + + Example context (json): + { + "questionissue": { + "id": "2", + "title": "Issue title", + "description": "Issue description", + }, + } +}} +
    + {{#questionissue}} + {{id}} + {{title}} + {{description}} + {{/questionissue}} +
    + diff --git a/templates/question_issues.mustache b/templates/question_issues.mustache index 388ab45..f63f018 100644 --- a/templates/question_issues.mustache +++ b/templates/question_issues.mustache @@ -15,7 +15,9 @@ along with Moodle. If not, see . }} {{! - @template local_qtracker/question_issues + @template local_qtracker/question_issues_page + + Question issues page template Example context (json): { diff --git a/templates/questions.mustache b/templates/questions.mustache index 69c73ae..1ae3451 100644 --- a/templates/questions.mustache +++ b/templates/questions.mustache @@ -15,13 +15,28 @@ along with Moodle. If not, see . }} {{! - @template local_qtracker/questions + @template local_qtracker/questions_page + + Questions page template Example context (json): { "questions": "raw html" } }} -
    - {{{questions}}} +
    +
    + Lorem ipsum ex nulla officia est et. Anim laboris irure nulla nulla ut do veniam anim eiusmod dolor eu. Excepteur deserunt do labore qui culpa deserunt sint aliqua nostrud occaecat nostrud proident ad. +
    +
    +
    +
    + {{{questions}}} +
    +
    +{{#js}} +require(['jquery', 'local_qtracker/questions_table'], function($, QuestionsTable) { + new QuestionsTable({{courseid}}); +}); +{{/js}} diff --git a/templates/select.mustache b/templates/select.mustache index 6c072cc..20fb1c9 100644 --- a/templates/select.mustache +++ b/templates/select.mustache @@ -15,14 +15,9 @@ along with Moodle. If not, see . }} {{! - @template core_admin/setting_configselect + @template local_qtracker/select - Admin select setting template. - - Context variables required for this template: - * name - form element name - * id - element id - * options - list of options containing name, value, selected + Select template. Example context (json): { diff --git a/version.php b/version.php index 02feb4a..615849a 100755 --- a/version.php +++ b/version.php @@ -15,17 +15,17 @@ // along with Moodle. If not, see . /** - * @package local_qtracker - * @author André Storhaug - * @copyright 2020 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2020072400; +$plugin->version = 2020072414; $plugin->requires = 2016120500; $plugin->cron = 0; $plugin->component = 'local_qtracker'; -$plugin->maturity = MATURITY_ALPHA; -$plugin->release = '0.0.1'; +$plugin->maturity = MATURITY_STABLE; +$plugin->release = '0.1.0'; diff --git a/view.php b/view.php index b62dce0..a2f6721 100644 --- a/view.php +++ b/view.php @@ -1,37 +1,63 @@ . + +/** + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_qtracker; require_once('../../config.php'); +require_once($CFG->dirroot . '/local/qtracker/lib.php'); global $DB, $OUTPUT, $PAGE; // Check for all required variables. $courseid = required_param('courseid', PARAM_INT); -// Next look for optional variables. -$id = optional_param('id', 0, PARAM_INT); - if (!$course = $DB->get_record('course', array('id' => $courseid))) { - print_error('invalidcourse', 'block_simplehtml', $courseid); + print_error('invalidcourseid'); } +$context = \context_course::instance($course->id); require_login($course); -$url = new moodle_url('/local/qtracker/view.php', array('courseid' => $courseid)); +require_capability('local/qtracker:viewall', $context); + +$url = new \moodle_url('/local/qtracker/view.php', array('courseid' => $courseid)); $PAGE->set_url($url); -$PAGE->set_pagelayout('standard'); +$PAGE->set_pagelayout('incourse'); $PAGE->set_heading(get_string('pluginname', 'local_qtracker')); -$settingsnode = $PAGE->settingsnav->add(get_string('frontpagesettings'), null, navigation_node::TYPE_SETTING, null); -$editurl = new moodle_url('/blocks/simplehtml/view.php', array('id' => $id, 'courseid' => $courseid)); +/* +$settingsnode = $PAGE->settingsnav->add(get_string('frontpagesettings'), null, \navigation_node::TYPE_SETTING, null); +$editurl = new \moodle_url('/blocks/simplehtml/view.php', array('id' => $id, 'courseid' => $courseid)); $editnode = $settingsnode->add(get_string('resetpage', 'my'), $editurl); $editnode->make_active(); +*/ echo $OUTPUT->header(); -// Get table renderer and display table here.... -$table = new \local_qtracker\output\questions_table(uniqid(), $url); +// Get table renderer and display table. +$table = new \local_qtracker\output\questions_table(uniqid(), $url, $context); $renderer = $PAGE->get_renderer('local_qtracker'); -$questionspage = new \local_qtracker\output\questions_page($table); +$questionspage = new \local_qtracker\output\questions_page($table, $courseid); echo $renderer->render($questionspage); echo $OUTPUT->footer(); From cd773b0188ccf4cb4ea19f8fc0cdf0d80bdf1f7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Storhaug?= Date: Wed, 5 Aug 2020 21:42:23 +0200 Subject: [PATCH 19/83] Add coverage reporting --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 13d7155..9879a04 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,5 +44,8 @@ script: - moodle-plugin-ci mustache - moodle-plugin-ci grunt - moodle-plugin-ci phpdoc - - moodle-plugin-ci phpunit + - moodle-plugin-ci phpunit --coverage-clover - moodle-plugin-ci behat + +after_success: + - moodle-plugin-ci coveralls-upload From 0b5b6ffcdbbecbebc6db5ba31f8fb60b94aeabeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Storhaug?= Date: Wed, 5 Aug 2020 22:36:58 +0200 Subject: [PATCH 20/83] Update README.md --- README.md | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d5f3f33..eef50ee 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,25 @@ -# moodle-local_qtracker -:bug: Local Moodle plugin providing issue tracking for Moodle questions. +# Question Tracker + +> :bug: Local Moodle plugin providing issue tracking for Moodle questions. + +[![Build Status](https://travis-ci.org/KQMATH/moodle-local_qtracker.svg?branch=master)](https://travis-ci.org/KQMATH/moodle-local_qtracker) +[![Coverage Status](https://coveralls.io/repos/github/KQMATH/moodle-local_qtracker/badge.svg?branch=master)](https://coveralls.io/github/KQMATH/moodle-local_qtracker?branch=master) + +This local Moodle plugin provides issue tracking capabilities for Moodle questions. + +Developing questions can often be a long and tedious process. And, despite your best efforts, mistakes do happen. A question might turn out to be hard to understand, or simply contains a typo. This plugin provides a way for a user (student) to provide **feedback** to the teacher on a particular question. + +Question Tracker provides an interface for administrating the collected issues reported by students. The student interfaces are provided in the for of [Moodle blocks](https://docs.moodle.org/en/Blocks). These are separate plugins, and needs to be developed for a particular [Moodle activity module](https://docs.moodle.org/en/Activities) using [Moodle questions](https://docs.moodle.org/39/en/Questions). This provides maximum flexibility, and ease of use! + +## Question Tracker Blocks +- [Moodle Quiz activity module](https://github.com/KQMATH/moodle-block_quizqtracker) +- [CAPQuiz activity module](https://github.com/KQMATH/moodle-block_capquizqtracker) + +## Documentation +Documentation is available [here](https://github.com/KQMATH/moodle-local_qtracker/wiki), including [installation instructions](https://github.com/KQMATH/moodle-local_qtracker/wiki/Installation-instructions). + +## Credits +Question Tracker is created by [André Storhaug](https://github.com/andstor) + +## License +Question Tracker is licensed under the [GNU General Public, License Version 3](https://github.com/KQMATH/moodle-local_qtracker/LICENSE). From 9519024f2014046bd17e9fc5e9c95e1313b5c2b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Storhaug?= Date: Wed, 5 Aug 2020 23:09:08 +0200 Subject: [PATCH 21/83] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index eef50ee..9bc4258 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Question Tracker +# QTracker > :bug: Local Moodle plugin providing issue tracking for Moodle questions. @@ -9,9 +9,9 @@ This local Moodle plugin provides issue tracking capabilities for Moodle questio Developing questions can often be a long and tedious process. And, despite your best efforts, mistakes do happen. A question might turn out to be hard to understand, or simply contains a typo. This plugin provides a way for a user (student) to provide **feedback** to the teacher on a particular question. -Question Tracker provides an interface for administrating the collected issues reported by students. The student interfaces are provided in the for of [Moodle blocks](https://docs.moodle.org/en/Blocks). These are separate plugins, and needs to be developed for a particular [Moodle activity module](https://docs.moodle.org/en/Activities) using [Moodle questions](https://docs.moodle.org/39/en/Questions). This provides maximum flexibility, and ease of use! +QTracker provides an interface for administrating the collected issues reported by students. The student interfaces are provided in the for of [Moodle blocks](https://docs.moodle.org/en/Blocks). These are separate plugins, and needs to be developed for a particular [Moodle activity module](https://docs.moodle.org/en/Activities) using [Moodle questions](https://docs.moodle.org/39/en/Questions). This provides maximum flexibility, and ease of use! -## Question Tracker Blocks +## QTracker Blocks - [Moodle Quiz activity module](https://github.com/KQMATH/moodle-block_quizqtracker) - [CAPQuiz activity module](https://github.com/KQMATH/moodle-block_capquizqtracker) @@ -19,7 +19,7 @@ Question Tracker provides an interface for administrating the collected issues r Documentation is available [here](https://github.com/KQMATH/moodle-local_qtracker/wiki), including [installation instructions](https://github.com/KQMATH/moodle-local_qtracker/wiki/Installation-instructions). ## Credits -Question Tracker is created by [André Storhaug](https://github.com/andstor) +QTracker is created by [André Storhaug](https://github.com/andstor) ## License -Question Tracker is licensed under the [GNU General Public, License Version 3](https://github.com/KQMATH/moodle-local_qtracker/LICENSE). +QTracker is licensed under the [GNU General Public, License Version 3](https://github.com/KQMATH/moodle-local_qtracker/LICENSE). From 10a0a1faf1bd1455a478f88c6a803e5a03236bab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Storhaug?= Date: Wed, 5 Aug 2020 23:36:32 +0200 Subject: [PATCH 22/83] Create CHANGELOG.md --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c967865 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org). + +## [Unreleased] + +## [0.1.0] - YYYY-MM-DD +### Fixed +### Added +### Changed + + +[Unreleased]: https://github.com/KQMATH/moodle-mod_capquiz/compare/v0.1.0...HEAD + +[0.1.1]: https://github.com/KQMATH/moodle-mod_capquiz/compare/v0.1.0...v0.1.1 From 859ff4084d5477b220610ed3c21d647e7fa35104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Storhaug?= Date: Wed, 5 Aug 2020 23:37:55 +0200 Subject: [PATCH 23/83] Create CODE_OF_CONDUCT.md --- CODE_OF_CONDUCT.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..5582eb6 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at hasc@ntnu.no. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq From a1f283a381dfaf69bb06dc0db2ce2f4b5e56dd19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Storhaug?= Date: Wed, 5 Aug 2020 23:42:00 +0200 Subject: [PATCH 24/83] Create CONTRIBUTING.md --- CONTRIBUTING.md | 66 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..02deebe --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,66 @@ +capquiz# How to contribute + +Thank you for your interest in contributing to the QTracker project. + +## How can you help? + +* Report issues +* Fix typos and grammar +* Add new rules +* Improve existing rules and workflow + +## Best practices + +- Bugfixes should only contain changes that are related to the purpose of the bug. +- Description should contain an explanation for the proposed changes. +- It's recommended to consult feature requests with the team before starting implementation +- Send us an email if you need assistance with any work. + +## Making changes + +* Fork this repository on [GitHub](https://github.com/KQMATH/moodle-local_qtracker). +* All development work should follow the [GitFlow](https://nvie.com/posts/a-successful-git-branching-model/) branching model. +* Make sure you have added the necessary tests for your changes. +* If applicable, include a link to the issue in the commit message body. + +## Submitting changes + +* Push your changes to a topic branch in your fork of the repository. +* Submit a pull request to the repository in the [KQMATH GitHub organization](https://github.com/KQMATH) +and choose branch you want to patch (usually develop). +* Add detail about the change to the pull request including screenshots + if the change affects the UI. + +## Reviewing changes + +* After submitting a pull request, one of QTracker team members will review it. +* Changes may be requested to conform to our style guide and internal + requirements. +* When the changes are approved and all tests are passing, a QTracker team + member will merge them. +* Note: if you have write access to the repository, do not directly merge pull + requests. Let another team member review your pull request and approve it. + +## Style guide + +* This repository uses [Markdown](https://daringfireball.net/projects/markdown/) syntax. +* The preferred spelling of English words is the [American + English](https://en.wikipedia.org/wiki/American_English) (e.g. behavior, not + behaviour). +* The required coding style is the [Moodle cooding style](https://docs.moodle.org/dev/Coding_style). + + +## License + +By contributing to this repository you agree that all contributions are subject to the +GNU General Public License v3.0 under thepublic domain. +See [LICENSE](https://github.com/KQMATH/moodle-local_qtracker/blob/master/LICENSE) +file for more information. + +## Review and release process + +* Each addition and rule change is discussed and reviewed internally by QTracker + core team. +* When contents are updated, [CHANGELOG.md](/CHANGELOG.md) file is updated and a + new tag is released. Repository follows [semantic versioning](http://semver.org/). +* The [official release](https://moodle.org/plugins/local_qtracker) at Moodle must be manually updated accordingly. From d119701ec0f33d751e10aec1335d64a364c42ba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Storhaug?= Date: Mon, 14 Sep 2020 05:28:18 +0200 Subject: [PATCH 25/83] Working version --- README.md | 27 +-- amd/build/block_form_manager.min.js | 2 + amd/build/block_form_manager.min.js.map | 1 + amd/build/issue.min.js | 2 + amd/build/issue.min.js.map | 1 + amd/build/issue_manager.min.js | 2 + amd/build/issue_manager.min.js.map | 1 + amd/build/questions_table.min.js | 2 + amd/build/questions_table.min.js.map | 1 + amd/src/questions_table.js | 9 - classes/external/issue_comment_exporter.php | 73 ++++++++ classes/form/view/issue_registration_form.php | 80 -------- classes/form/view/question_details_form.php | 57 ++++++ classes/issue.php | 67 +++++++ classes/issue_comment.php | 171 ++++++++++++++++++ classes/output/question_issue_page.php | 101 ++++++++++- db/install.xml | 14 ++ db/upgrade.php | 58 ------ issue.php | 49 ++++- lang/en/local_qtracker.php | 21 +++ lib.php | 1 + settings.php | 35 ---- styles.css | 13 ++ templates/button.mustache | 10 +- templates/issue_comment.mustache | 88 +++++++++ templates/issue_description.mustache | 40 ++++ templates/issue_state_badge.mustache | 4 +- templates/issues_pane_item.mustache | 4 +- templates/question_issue_page.mustache | 95 +++++++++- templates/questions.mustache | 4 +- version.php | 4 +- view.php | 7 - 32 files changed, 805 insertions(+), 239 deletions(-) create mode 100644 amd/build/block_form_manager.min.js create mode 100644 amd/build/block_form_manager.min.js.map create mode 100644 amd/build/issue.min.js create mode 100644 amd/build/issue.min.js.map create mode 100644 amd/build/issue_manager.min.js create mode 100644 amd/build/issue_manager.min.js.map create mode 100644 amd/build/questions_table.min.js create mode 100644 amd/build/questions_table.min.js.map create mode 100644 classes/external/issue_comment_exporter.php delete mode 100644 classes/form/view/issue_registration_form.php create mode 100644 classes/form/view/question_details_form.php create mode 100644 classes/issue_comment.php delete mode 100644 settings.php create mode 100644 templates/issue_comment.mustache create mode 100644 templates/issue_description.mustache diff --git a/README.md b/README.md index 9bc4258..d5f3f33 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,2 @@ -# QTracker - -> :bug: Local Moodle plugin providing issue tracking for Moodle questions. - -[![Build Status](https://travis-ci.org/KQMATH/moodle-local_qtracker.svg?branch=master)](https://travis-ci.org/KQMATH/moodle-local_qtracker) -[![Coverage Status](https://coveralls.io/repos/github/KQMATH/moodle-local_qtracker/badge.svg?branch=master)](https://coveralls.io/github/KQMATH/moodle-local_qtracker?branch=master) - -This local Moodle plugin provides issue tracking capabilities for Moodle questions. - -Developing questions can often be a long and tedious process. And, despite your best efforts, mistakes do happen. A question might turn out to be hard to understand, or simply contains a typo. This plugin provides a way for a user (student) to provide **feedback** to the teacher on a particular question. - -QTracker provides an interface for administrating the collected issues reported by students. The student interfaces are provided in the for of [Moodle blocks](https://docs.moodle.org/en/Blocks). These are separate plugins, and needs to be developed for a particular [Moodle activity module](https://docs.moodle.org/en/Activities) using [Moodle questions](https://docs.moodle.org/39/en/Questions). This provides maximum flexibility, and ease of use! - -## QTracker Blocks -- [Moodle Quiz activity module](https://github.com/KQMATH/moodle-block_quizqtracker) -- [CAPQuiz activity module](https://github.com/KQMATH/moodle-block_capquizqtracker) - -## Documentation -Documentation is available [here](https://github.com/KQMATH/moodle-local_qtracker/wiki), including [installation instructions](https://github.com/KQMATH/moodle-local_qtracker/wiki/Installation-instructions). - -## Credits -QTracker is created by [André Storhaug](https://github.com/andstor) - -## License -QTracker is licensed under the [GNU General Public, License Version 3](https://github.com/KQMATH/moodle-local_qtracker/LICENSE). +# moodle-local_qtracker +:bug: Local Moodle plugin providing issue tracking for Moodle questions. diff --git a/amd/build/block_form_manager.min.js b/amd/build/block_form_manager.min.js new file mode 100644 index 0000000..a8ba0f9 --- /dev/null +++ b/amd/build/block_form_manager.min.js @@ -0,0 +1,2 @@ +define ("local_qtracker/block_form_manager",["jquery","core/str","core/templates","core/ajax","local_qtracker/issue","local_qtracker/issue_manager"],function(a,b,c,d,e,f){var g={SLOT:"[name=\"slot\"]",SLOT_SELECT_OPTION:"[name=\"slot\"] option",TITLE:"[name=\"issuetitle\"]",DESCRIPTION:"[name=\"issuedescription\"]",SUBMIT_BUTTON:"button[type=\"submit\"]",DELETE_BUTTON:"#qtracker-delete"},h=[g.TITLE,g.DESCRIPTION],i=null,j=function(b,c,d){this.contextid=d;this.form=a(b);this.form.closest(".card-text").prepend("");this.issueManager=new f;this.init(JSON.parse(c))};j.prototype.form=null;j.prototype.contextid=-1;j.prototype.issueid=null;j.prototype.issues=[];j.prototype.issueManager=null;j.prototype.init=function(){var b=this,c=0.\n\n/**\n * Manager for a Question Tracker Block form.\n *\n * @module local_qtracker/BlockFormManager\n * @class BlockFormManager\n * @package local_qtracker\n * @author André Storhaug \n * @copyright 2020 NTNU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/issue', 'local_qtracker/issue_manager'],\n function ($, Str, Templates, Ajax, Issue, IssueManager) {\n var SELECTORS = {\n SLOT: '[name=\"slot\"]',\n SLOT_SELECT_OPTION: '[name=\"slot\"] option',\n TITLE: '[name=\"issuetitle\"]',\n DESCRIPTION: '[name=\"issuedescription\"]',\n SUBMIT_BUTTON: 'button[type=\"submit\"]',\n DELETE_BUTTON: '#qtracker-delete',\n }\n\n let VALIDATION_ELEMENTS = [\n SELECTORS.TITLE,\n SELECTORS.DESCRIPTION,\n ];\n\n var NOTIFICATION_DURATION = 7500;\n var notificationTimeoutHandle = null;\n\n /**\n * Constructor\n *\n * @param {String} selector used to find triggers for the new group modal.\n * @param {int} contextid\n *\n * Each call to init gets it's own instance of this class.\n */\n var BlockFormManager = function (selector, issueids, contextid) {\n this.contextid = contextid;\n this.form = $(selector);\n this.form.closest('.card-text').prepend('');\n this.issueManager = new IssueManager();\n this.init(JSON.parse(issueids));\n };\n\n /**\n * @var {Form} form\n * @private\n */\n BlockFormManager.prototype.form = null;\n\n /**\n * @var {int} contextid\n * @private\n */\n BlockFormManager.prototype.contextid = -1;\n\n /**\n * @var {int} issueid\n * @private\n */\n BlockFormManager.prototype.issueid = null;\n\n /**\n * @var {int} issueid\n * @private\n */\n BlockFormManager.prototype.issues = [];\n\n /**\n * @var {int} issueid\n * @private\n */\n BlockFormManager.prototype.issueManager = null;\n\n /**\n * Initialise the class.\n *\n * @param {String} selector used to find triggers for the new question issue.\n * @private\n * @return {Promise}\n */\n BlockFormManager.prototype.init = function (issueids = []) {\n // Init all slots\n let slots = $(SELECTORS.SLOT_SELECT_OPTION);\n if (slots.length == 0) slots = $(SELECTORS.SLOT);\n slots.map((index, option) => {\n let issue = new Issue(null, parseInt(option.value), this.contextid);\n issue.isSaved = false;//changeState(Issue.STATES.NEW);\n this.issueManager.addIssue(issue);\n });\n\n\n this.issueManager.loadIssues(issueids).then(() => {\n\n var formData = new FormData(this.form[0]);\n this.issueManager.setActiveIssue(parseInt(formData.get('slot')));\n\n this.reflectFormState()\n\n // Issue title event listener.\n let titleElement = this.form.find(SELECTORS.TITLE)\n titleElement.change((event) => {\n this.issueManager.getActiveIssue().setTitle(event.target.value);\n });\n /* $(document).on(qtrackerEvents.CHANGED_SLOT_BLOCK_FORM, (event, value) => {\n titleElement.val(value);\n }); */\n\n // Issue description event listener.\n let descriptionElement = this.form.find(SELECTORS.DESCRIPTION)\n descriptionElement.change((event) => {\n this.issueManager.getActiveIssue().setDescription(event.target.value);\n });\n /* $(document).on(qtrackerEvents.CHANGED_SLOT_BLOCK_FORM, (event, value) => {\n descriptionElement.val(value)\n }); */\n\n //\n\n\n // Load existing issues.\n var slotElement = this.form.find(SELECTORS.SLOT);\n slotElement.change(this.handleSlotChange.bind(this));\n\n this.form.on('submit', this.submitFormAjax.bind(this));\n\n }).catch((error) => {\n console.error(error);\n });\n };\n\n BlockFormManager.prototype.handleSlotChange = function (e) {\n this.issueManager.setActiveIssue(parseInt(e.target.value))\n this.reflectFormState();\n this.resetValidation()\n }\n\n BlockFormManager.prototype.reflectFormState = function () {\n let issue = this.issueManager.getActiveIssue();\n if (issue.isSaved === true) { //state === Issue.STATES.EXISTING) {\n this.toggleDeleteButton(true);\n this.toggleUpdateButton(true);\n } else if (issue.isSaved === false) {//state === Issue.STATES.NEW) {\n this.clearForm();\n }\n\n this.restoreForm();\n }\n\n /**\n * @method handleFormSubmissionResponse\n * @private\n * @return {Promise}\n */\n BlockFormManager.prototype.handleFormSubmissionResponse = function (response) {\n\n // TODO: handle response.status === false\n // TODO: handle response.warning ...\n\n // We could trigger an event instead.\n // Yuk.\n console.log(\"jijjijij\")\n Y.use('moodle-core-formchangechecker', function () {\n M.core_formchangechecker.reset_form_dirty_state();\n });\n //document.location.reload();\n\n this.issueManager.getActiveIssue().setId(response.issueid);\n };\n\n /**\n * @method handleFormSubmissionFailure\n * @private\n * @return {Promise}\n */\n BlockFormManager.prototype.handleFormSubmissionFailure = function (response) {\n // Oh noes! Epic fail :(\n // Ah wait - this is normal. We need to re-display the form with errors!\n console.error(\"An error occured\");\n console.error(response);\n };\n\n BlockFormManager.prototype.clearForm = function () {\n // Remove delete button.\n this.form.find('#qtracker-delete').remove();\n this.resetValidation();\n Str.get_string('submitnewissue', 'local_qtracker').then(function (string) {\n this.form.find('button[type=\"submit\"]').html(string);\n }.bind(this));\n }\n\n BlockFormManager.prototype.restoreForm = function () {\n let issue = this.issueManager.getActiveIssue();\n this.form.find('[name=\"issuetitle\"]').val(issue.getTitle());\n this.form.find('[name=\"issuedescription\"]').val(issue.getDescription());\n\n };\n\n /**\n * @method editIssue\n * @private\n * @return {Promise}\n */\n BlockFormManager.prototype.editIssue = function () {\n var formData = new FormData(this.form[0]);\n Ajax.call([{\n methodname: 'local_qtracker_edit_issue',\n args: {\n issueid: this.issueManager.getActiveIssue().getId(),\n issuetitle: formData.get('issuetitle'),\n issuedescription: formData.get('issuedescription'),\n },\n done: function (response) {\n Str.get_string('issueupdated', 'local_qtracker').then(function (string) {\n let notification = {\n message: string,\n announce: true,\n type: \"success\",\n };\n this.notify(notification);\n }.bind(this));\n this.handleFormSubmissionResponse(response);\n }.bind(this),\n fail: this.handleFormSubmissionFailure.bind(this)\n }]);\n };\n /**\n * @method editIssue\n * @private\n * @return {Promise}\n */\n BlockFormManager.prototype.deleteIssue = function () {\n Ajax.call([{\n methodname: 'local_qtracker_delete_issue',\n args: {\n issueid: this.issueManager.getActiveIssue().getId(),\n },\n done: function (response) {\n Str.get_string('issuedeleted', 'local_qtracker').then(function (string) {\n let notification = {\n message: string,\n announce: true,\n type: \"success\",\n };\n this.notify(notification);\n }.bind(this));\n this.issueManager.getActiveIssue().isSaved = false;//changeState(Issue.STATES.NEW);;\n this.clearForm();\n }.bind(this),\n fail: this.handleFormSubmissionFailure.bind(this)\n }]);\n };\n\n /**\n * @method handleFormSubmissionFailure\n * @private\n * @return {Promise}\n */\n BlockFormManager.prototype.createIssue = function () {\n var formData = new FormData(this.form[0]);\n // Now we can continue...\n Ajax.call([{\n methodname: 'local_qtracker_new_issue',\n args: {\n qubaid: formData.get('qubaid'),\n slot: formData.get('slot'),\n contextid: this.contextid,\n issuetitle: formData.get('issuetitle'),\n issuedescription: formData.get('issuedescription'),\n },\n done: function (response) {\n Str.get_string('issuecreated', 'local_qtracker').then(function (string) {\n let notification = {\n message: string,\n announce: true,\n type: \"success\",\n };\n this.notify(notification);\n }.bind(this));\n this.issueManager.getActiveIssue().isSaved = true;//changeState(Issue.STATES.EXISTING)\n //this.setAction(ACTION.EDITISSUE);\n // TODO: add delete button.\n this.toggleUpdateButton(true);\n this.toggleDeleteButton(true);\n\n this.handleFormSubmissionResponse(response);\n }.bind(this),\n fail: this.handleFormSubmissionFailure.bind(this)\n }]);\n };\n\n /**\n * Cancel any typing pause timer.\n */\n BlockFormManager.prototype.cancelNotificationTimer = function () {\n if (notificationTimeoutHandle) {\n clearTimeout(notificationTimeoutHandle);\n }\n notificationTimeoutHandle = null;\n }\n\n BlockFormManager.prototype.notify = function (notification) {\n notification = $.extend({\n closebutton: true,\n announce: true,\n type: 'error',\n extraclasses: \"show\",\n }, notification);\n\n let types = {\n 'success': 'core/notification_success',\n 'info': 'core/notification_info',\n 'warning': 'core/notification_warning',\n 'error': 'core/notification_error',\n };\n\n this.cancelNotificationTimer();\n\n let template = types[notification.type];\n Templates.render(template, notification)\n .then((html, js) => {\n $('#qtracker-notifications').html(html);\n Templates.runTemplateJS(js);\n\n notificationTimeoutHandle = setTimeout(() => {\n $('#qtracker-notifications').find('.alert').alert('close');\n }, NOTIFICATION_DURATION);\n })\n .catch((error) => {\n console.error(error)\n });\n }\n /**\n * @method handleFormSubmissionFailure\n * @private\n * @return {Promise}\n */\n BlockFormManager.prototype.toggleUpdateButton = function (show) {\n if (show) {\n Str.get_string('update', 'core').then(function (updateStr) {\n this.form.find(SELECTORS.SUBMIT_BUTTON).html(updateStr);\n }.bind(this));\n } else {\n Str.get_string('submitnewissue', 'local_qtracker').then(function (updateStr) {\n this.form.find(SELECTORS.SUBMIT_BUTTON).html(updateStr);\n }.bind(this));\n }\n }\n /**\n * @method handleFormSubmissionFailure\n * @private\n * @return {Promise}\n */\n BlockFormManager.prototype.toggleDeleteButton = function (show) {\n const context = {\n type: \"button\",\n classes: \"col-auto\",\n label: \"Delete\",\n id: \"qtracker-delete\",\n };\n\n let deleteButton = this.form.find(SELECTORS.DELETE_BUTTON);\n if (deleteButton.length == 0 && show) {\n Templates.render('local_qtracker/button', context)\n .then(function (html, js) {\n var container = this.form.find('button').closest(\".form-row\");\n Templates.appendNodeContents(container, html, js);\n this.form.find('#qtracker-delete').on('click', function () {\n this.deleteIssue()\n }.bind(this));\n }.bind(this));\n } else {\n if (show) {\n deleteButton.show();\n } else {\n deleteButton.hide();\n }\n }\n }\n\n /**\n * @method handleFormSubmissionFailure\n * @private\n * @return {Promise}\n */\n BlockFormManager.prototype.setAction = function (newaction) {\n\n this.form.data('action', newaction);\n };\n\n /**\n * Private method\n *\n * @method submitFormAjax\n * @private\n * @param {Event} e Form submission event.\n */\n BlockFormManager.prototype.submitFormAjax = function (e) {\n // We don't want to do a real form submission.\n e.preventDefault();\n e.stopPropagation();\n\n if (!this.validateForm()) {\n return;\n }\n\n\n if (this.issueManager.getActiveIssue().isSaved === true) {\n this.editIssue();\n } else {\n this.createIssue();\n }\n/*\n var state = this.issueManager.getActiveIssue().getState();\n switch (state) {\n case Issue.STATES.NEW:\n this.createIssue();\n break;\n case Issue.STATES.EXISTING:\n this.editIssue();\n break;\n case Issue.STATES.DELETED:\n this.issueManager.getActiveIssue().changeState(Issue.STATES.NEW)\n this.createIssue();\n break;\n default:\n break;\n }*/\n };\n\n BlockFormManager.prototype.validateForm = function () {\n let valid = true;\n VALIDATION_ELEMENTS.forEach(selector => {\n let element = this.form.find(selector)\n if (element.val() != \"\" && element.prop(\"validity\").valid) {\n element.removeClass(\"is-invalid\").addClass(\"is-valid\");\n } else {\n element.removeClass(\"is-valid\").addClass(\"is-invalid\");\n valid = false;\n }\n });\n return valid;\n };\n\n BlockFormManager.prototype.resetValidation = function () {\n VALIDATION_ELEMENTS.forEach(selector => {\n let element = this.form.find(selector)\n element.removeClass(\"is-invalid\").removeClass(\"is-valid\")\n });\n };\n\n /**\n * This triggers a form submission, so that any mform elements can do final tricks before the form submission is processed.\n *\n * @method submitForm\n * @param {Event} e Form submission event.\n * @private\n */\n BlockFormManager.prototype.submitForm = function (e) {\n e.preventDefault();\n this.form.submit();\n };\n\n return /** @alias module:local_qtracker/BlockFormManager */ {\n\n /**\n * Initialise the module.\n *\n * @method init\n * @param {string} selector The selector used to find the form for to use for this module.\n * @param {string} issueids The ids of existing issues to load.\n * @return {BlockFormManager}\n */\n init: function (selector, issueids, contextid) {\n return new BlockFormManager(selector, issueids, contextid);\n }\n };\n });\n"],"file":"block_form_manager.min.js"} \ No newline at end of file diff --git a/amd/build/issue.min.js b/amd/build/issue.min.js new file mode 100644 index 0000000..1eeafff --- /dev/null +++ b/amd/build/issue.min.js @@ -0,0 +1,2 @@ +define ("local_qtracker/issue",["jquery","core/str","core/ajax"],function(a,b,c){var d=function(){var a=0.\n\n/**\n * Module for representing a question issue.\n *\n * @module local_qtracker/Issue\n * @class Issue\n * @package local_qtracker\n * @author André Storhaug \n * @copyright 2020 NTNU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/str', 'core/ajax'], function ($, Str, Ajax) {\n\n /**\n * Constructor\n *\n * @param {String} selector used to find triggers for the new group modal.\n * @param {int} contextid\n *\n * Each call to init gets it's own instance of this class.\n */\n var Issue = function (id = null, slot = null, contextid) {\n this.id = id;\n this.slot = slot;\n this.contextid = contextid;\n };\n\n Issue.STATES = {\n NEW: \"new\",\n OPEN: \"open\",\n CLOSED: \"closed\",\n }\n\n /**\n * @var {int} id The id of this issue\n * @private\n */\n Issue.prototype.id = null;\n\n /**\n * @var {int} id The slot for this issue\n * @private\n */\n Issue.prototype.slot = null;\n\n /**\n * @var {string} title The title for this issue\n * @private\n */\n Issue.prototype.title = \"\";\n\n /**\n * @var {string} title The description for this issue\n * @private\n */\n Issue.prototype.description = \"\";\n\n Issue.prototype.contextid = null;\n\n Issue.prototype.isSaved = false;\n\n Issue.prototype.state = Issue.STATES.NEW;\n\n /**\n * Initialise the class.\n *\n * @param {String} selector used to find triggers for the new group modal.\n * @private\n * @return {Promise}\n */\n Issue.prototype.setId = function (id) {\n this.id = id;\n };\n\n /**\n * Initialise the class.\n *\n * @param {String} selector used to find triggers for the new group modal.\n * @private\n * @return {Promise}\n */\n Issue.prototype.getId = function () {\n return this.id;\n };\n\n Issue.prototype.getSlot = function () {\n return this.slot;\n };\n\n Issue.prototype.getTitle = function () {\n return this.title;\n };\n\n Issue.prototype.setTitle = function (title) {\n this.title = title;\n };\n\n Issue.prototype.getDescription = function () {\n return this.description;\n };\n\n\n Issue.prototype.setDescription = function (description) {\n this.description = description;\n };\n\n Issue.prototype.changeState = function (state) {\n this.state = state;\n };\n\n Issue.prototype.getState = function () {\n return this.state;\n };\n\n Issue.prototype.getContextid = function () {\n return this.contextid;\n };\n\n /**\n * return {Promise}\n */\n Issue.load = function (id) {\n return Ajax.call([\n { methodname: 'local_qtracker_get_issue', args: { issueid: id} }\n ])[0];\n };\n\n return Issue;\n});\n"],"file":"issue.min.js"} \ No newline at end of file diff --git a/amd/build/issue_manager.min.js b/amd/build/issue_manager.min.js new file mode 100644 index 0000000..9a6cd10 --- /dev/null +++ b/amd/build/issue_manager.min.js @@ -0,0 +1,2 @@ +function _slicedToArray(a,b){return _arrayWithHoles(a)||_iterableToArrayLimit(a,b)||_unsupportedIterableToArray(a,b)||_nonIterableRest()}function _nonIterableRest(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function _iterableToArrayLimit(a,b){if("undefined"==typeof Symbol||!(Symbol.iterator in Object(a)))return;var c=[],d=!0,e=!1,f=void 0;try{for(var g=a[Symbol.iterator](),h;!(d=(h=g.next()).done);d=!0){c.push(h.value);if(b&&c.length===b)break}}catch(a){e=!0;f=a}finally{try{if(!d&&null!=g["return"])g["return"]()}finally{if(e)throw f}}return c}function _arrayWithHoles(a){if(Array.isArray(a))return a}function _createForOfIteratorHelper(a){if("undefined"==typeof Symbol||null==a[Symbol.iterator]){if(Array.isArray(a)||(a=_unsupportedIterableToArray(a))){var b=0,c=function(){};return{s:c,n:function n(){if(b>=a.length)return{done:!0};return{done:!1,value:a[b++]}},e:function e(a){throw a},f:c}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var d,e=!0,f=!1,g;return{s:function s(){d=a[Symbol.iterator]()},n:function n(){var a=d.next();e=a.done;return a},e:function e(a){f=!0;g=a},f:function f(){try{if(!e&&null!=d.return)d.return()}finally{if(f)throw g}}}}function _unsupportedIterableToArray(a,b){if(!a)return;if("string"==typeof a)return _arrayLikeToArray(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return _arrayLikeToArray(a,b)}function _arrayLikeToArray(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);c.\n\n/**\n * Manager for managing question issues.\n *\n * @module local_qtracker/IssueManager\n * @class IssueManager\n * @package local_qtracker\n * @author André Storhaug \n * @copyright 2020 NTNU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'local_qtracker/issue'], function ($, Issue) {\n\n /**\n * Constructor\n * @constructor\n * @param {String} selector used to find triggers for the new group modal.\n * @param {int} contextid\n *\n * Each call to init gets it's own instance of this class.\n */\n var IssueManager = function () {};\n\n /**\n * @var {Form} form\n * @private\n */\n IssueManager.prototype.issues = new Map();\n\n IssueManager.prototype.activeIssue = null;\n\n IssueManager.prototype.getActiveIssue = function () {\n return this.activeIssue;\n };\n\n IssueManager.prototype.setActiveIssue = function (slot) {\n let newIssue = this.getIssueBySlot(slot);\n this.activeIssue = newIssue;\n return newIssue;\n };\n\n /**\n * This triggers a form submission, so that any mform elements can do final tricks before the form submission is processed.\n *\n * @method submitForm\n * @param {Event} e Form submission event.\n * @private\n */\n IssueManager.prototype.getIssueBySlot = function (slot) {\n return this.issues.get(slot);\n };\n\n IssueManager.prototype.getIssueById = function (id) {\n for (const [slot, issue] of this.issues) {\n if (issue.getId() !== null && issue.getId() === id) {\n return issue;\n }\n };\n return false;\n };\n\n IssueManager.prototype.addIssue = function (issue) {\n this.issues.set(issue.getSlot(), issue);\n };\n\n IssueManager.prototype.loadIssues = function (issueids = []) {\n let promises = [];\n for (let i = 0; i < issueids.length; i++) {\n const id = issueids[i];\n let promise = Issue.load(id).then((response) => {\n let issue = this.getIssueBySlot(response.issue.slot)\n if (!issue) {\n issue = new Issue(response.issue.id, response.issue.slot);\n }\n issue.setId(response.issue.id);\n issue.setTitle(response.issue.title);\n issue.setDescription(response.issue.description);\n issue.isSaved = true;//changeState(Issue.STATES.EXISTING);\n this.addIssue(issue);\n });\n promises.push(promise);\n }\n return Promise.all(promises);\n };\n\n return IssueManager;\n});\n"],"file":"issue_manager.min.js"} \ No newline at end of file diff --git a/amd/build/questions_table.min.js b/amd/build/questions_table.min.js new file mode 100644 index 0000000..b7c0226 --- /dev/null +++ b/amd/build/questions_table.min.js @@ -0,0 +1,2 @@ +define ("local_qtracker/questions_table",["exports","jquery","core/templates","core/ajax","core/url"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=f(b);c=f(c);d=f(d);e=f(e);function f(a){return a&&a.__esModule?a:{default:a}}function g(a){return k(a)||j(a)||i(a)||h()}function h(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function i(a,b){if(!a)return;if("string"==typeof a)return l(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return l(a,b)}function j(a){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(a))return Array.from(a)}function k(a){if(Array.isArray(a))return l(a)}function l(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);c").attr("href",k).html(j.name+" #"+j.id);(0,b.default)(".issues-pane-title").html(l);a.next=12;return this.loadIssues(e,h);case 12:m=a.sent;o=m.issues;if(d)lol();p=g(new Set(o.map(function(a){return a.userid})));a.next=18;return this.loadUsersData(p);case 18:q=a.sent;r=[];o.forEach(function(){var a=n(regeneratorRuntime.mark(function a(b){var c;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:c=q.find(function(a){var c=a.id;return c===b.userid});r.push(f.addIssueItem(b,c));case 2:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}());b.default.when.apply(b.default,r).done(function(){(0,b.default)(".issues-pane-content .loading").removeClass("show");b.default.each(arguments,function(a,b){c.default.appendNodeContents(".issues-pane-content .issues",b.html,b.js)})}).catch(function(a){console.error(a)});case 22:case"end":return a.stop();}}},a,this)}));return function(){return a.apply(this,arguments)}}().bind(this);window.closeIssuesPane=function(){if(!d)lol()};window.lol=function(){(0,b.default)(".qtracker-container").toggleClass("push-pane-over");(0,b.default)("#issues-pane").toggleClass("show");d=!d};case 7:case"end":return a.stop();}}},a,this)}));return function init(){return a.apply(this,arguments)}}()},{key:"addIssueItem",value:function(){var a=n(regeneratorRuntime.mark(function a(b,d){var f,g,h,i;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:f=e.default.relativeUrl("/local/qtracker/issue.php",{courseid:this.courseid,issueid:b.id});g=e.default.relativeUrl("/user/view.php",{course:this.courseid,id:d.id});h={issueurl:f,userurl:g,profileimageurl:d.profileimageurlsmall,fullname:d.fullname,timecreated:b.timecreated,title:b.title,description:b.description};i=b.state;h[i]=!0;return a.abrupt("return",c.default.render("local_qtracker/issues_pane_item",h).then(function(a,b){return{html:a,js:b}}));case 6:case"end":return a.stop();}}},a,this)}));return function addIssueItem(){return a.apply(this,arguments)}}()},{key:"loadIssues",value:function(){var a=n(regeneratorRuntime.mark(function a(b){var c,e,f,g=arguments;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:c=1.\n\n/**\n * Manager for managing table of questions with issues.\n *\n * @module local_qtracker/IssueManager\n * @class IssueManager\n * @package local_qtracker\n * @author André Storhaug \n * @copyright 2020 NTNU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport $ from 'jquery';\nimport Templates from 'core/templates';\nimport Ajax from 'core/ajax';\nimport url from 'core/url';\n/**\n * Constructor\n * @constructor\n * @param {String} selector used to find triggers for the new group modal.\n * @param {int} contextid\n *\n * Each call to init gets it's own instance of this class.\n */\nclass QuestionsTable {\n courseid = null;\n\n constructor(courseid) {\n this.courseid = courseid;\n\n this.init();\n }\n\n async init() {\n var hidden = true;\n\n let context = {\n close: {\n \"key\": \"fa-times\",\n \"title\": \"Close\",\n \"alt\": \"Close pane\",\n \"extraclasses\": \"\",\n \"unmappedIcon\": false\n }\n };\n\n await Templates.render('local_qtracker/issues_pane', context).then((html, js) => {\n Templates.replaceNodeContents('#questions-table-sidebar', html, js);\n });\n\n window.showIssuesInPane = async function (id, state = null) {\n $('.issues-pane-content .issues').empty();\n $('.issues-pane-content .loading').addClass(\"show\");\n\n // Get question title.\n let questionData = await this.loadQuestionData(id)\n let question = questionData.question;\n let questionEditUrl = this.getQuestionEditUrl(this.courseid, id);\n let link = $('').attr(\"href\", questionEditUrl).html(question.name + \" #\" + question.id);\n $('.issues-pane-title').html(link);\n\n // Get issues data.\n let issuesResponse = await this.loadIssues(id, state);\n let issues = issuesResponse.issues;\n\n if (hidden) lol();\n\n // Get users data.\n let userids = [...new Set(issues.map(issue => issue.userid))];\n let usersData = await this.loadUsersData(userids);\n\n // Render issue items.\n let promises = []\n issues.forEach(async issueData => {\n let userData = usersData.find( ({ id }) => id === issueData.userid );\n promises.push(this.addIssueItem(issueData, userData));\n });\n\n // When all issue item promises are resolved.\n $.when.apply($, promises).done(function () {\n $('.issues-pane-content .loading').removeClass(\"show\");\n $.each(arguments, (index, argument) => {\n Templates.appendNodeContents('.issues-pane-content .issues', argument.html, argument.js);\n });\n }).catch(e => {\n console.error(e)\n })\n\n }.bind(this);\n\n window.closeIssuesPane = function () {\n if (!hidden)\n lol();\n };\n\n window.lol = function togglePane() {\n $('.qtracker-container').toggleClass('push-pane-over');\n $('#issues-pane').toggleClass(\"show\");\n hidden = !hidden;\n };\n }\n\n /**\n *\n * @param {*} issueData\n * @return Promise\n */\n async addIssueItem(issueData, userData) {\n // Fetch user data.\n let issueurl = url.relativeUrl('/local/qtracker/issue.php', {\n courseid: this.courseid,\n issueid: issueData.id,\n });\n let userurl = url.relativeUrl('/user/view.php', {\n course: this.courseid,\n id: userData.id,\n });\n\n //Render issues pane\n let paneContext = {\n issueurl: issueurl,\n userurl: userurl,\n profileimageurl: userData.profileimageurlsmall,\n fullname: userData.fullname,\n timecreated: issueData.timecreated,\n title: issueData.title,\n description: issueData.description,\n };\n let state = issueData.state;\n paneContext[state] = true;\n\n return Templates.render('local_qtracker/issues_pane_item', paneContext)\n .then(function (html, js) {\n return { html: html, js: js };\n });\n }\n\n async loadIssues(id, state = null) {\n let criteria = [\n { key: 'questionid', value: id },\n ];\n if (state) {\n criteria.push({ key: 'state', value: state });\n }\n let issuesData = await Ajax.call([{\n methodname: 'local_qtracker_get_issues',\n args: { criteria: criteria }\n }])[0];\n\n return issuesData;\n }\n\n async loadUsersData(ids) {\n let usersData = await Ajax.call([{\n methodname: 'core_user_get_users_by_field',\n args: {\n field: 'id',\n values: ids\n }\n }])[0];\n return usersData;\n }\n\n getQuestionEditUrl(courseid, questionid) {\n let returnurl = encodeURIComponent(location.pathname + location.search);\n let editurl = url.relativeUrl('/question/question.php', {\n courseid: courseid,\n id: questionid,\n returnurl: returnurl,\n });\n return editurl;\n }\n\n decodeHTML(html) {\n var doc = new DOMParser().parseFromString(html, \"text/html\");\n return doc.documentElement.textContent;\n }\n\n async loadQuestionData(id) {\n let userData = await Ajax.call([{\n methodname: 'local_qtracker_get_question',\n args: {\n id: id\n }\n }])[0];\n return userData;\n }\n}\n\nexport default QuestionsTable;\n"],"file":"questions_table.min.js"} \ No newline at end of file diff --git a/amd/src/questions_table.js b/amd/src/questions_table.js index 9bec181..d705fc2 100644 --- a/amd/src/questions_table.js +++ b/amd/src/questions_table.js @@ -42,12 +42,6 @@ class QuestionsTable { this.courseid = courseid; this.init(); - //TODO: make sortable open, closed, new... - //TODO: make issue.php.. (make the attempt accessable!) - //TODO: make it possible to create manual issue.... - //TODO: Fix sorting...... - - //const { html, js } = await Templates.renderForPromise(userGrade.templatename, userGrade.grade); } async init() { @@ -78,7 +72,6 @@ class QuestionsTable { let link = $('').attr("href", questionEditUrl).html(question.name + " #" + question.id); $('.issues-pane-title').html(link); - // Get issues data. let issuesResponse = await this.loadIssues(id, state); let issues = issuesResponse.issues; @@ -178,7 +171,6 @@ class QuestionsTable { values: ids } }])[0]; - return usersData; } @@ -189,7 +181,6 @@ class QuestionsTable { id: questionid, returnurl: returnurl, }); - console.log(new URL(editurl)) return editurl; } diff --git a/classes/external/issue_comment_exporter.php b/classes/external/issue_comment_exporter.php new file mode 100644 index 0000000..4c2d1e4 --- /dev/null +++ b/classes/external/issue_comment_exporter.php @@ -0,0 +1,73 @@ +. + +/** + * Exporter for exporting question issue data. + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_qtracker\external; + +defined('MOODLE_INTERNAL') || die(); + +use \core\external\exporter; +use \renderer_base; + +/** + * Class for displaying a list of issue comment data. + * + * @package local_qtracker + * @copyright 2020 André Storhaug + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class issue_comment_exporter extends exporter { + + + /** + * Return the list of additional properties. + * + * @return array + */ + protected static function define_other_properties() { + return [ + 'id' => [ + 'type' => PARAM_INT, + ], + 'description' => [ + 'type' => PARAM_RAW, + ], + 'issueid' => [ + 'type' => PARAM_INT, + ], + 'userid' => [ + 'type' => PARAM_INT, + ], + 'timecreated' => [ + 'type' => PARAM_INT, + ], + ]; + } + + protected static function define_related() { + return array( + 'context' => 'context', + ); + } +} diff --git a/classes/form/view/issue_registration_form.php b/classes/form/view/issue_registration_form.php deleted file mode 100644 index e2a1193..0000000 --- a/classes/form/view/issue_registration_form.php +++ /dev/null @@ -1,80 +0,0 @@ -. - -/** - * @package local_qtracker - * @author André Storhaug - * @copyright 2020 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace local_qtracker\form\view; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->libdir . '/formslib.php'); - -/** - * @package local_qtracker - * @copyright 2020 André Storhaug - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class issue_registration_form extends \moodleform { - - - // TODO: FIX EVERYTHING IN THIS FILE AND REPLACE THE CURRENT JS SETUP. - - - public function __construct($questions, \moodle_url $url) { - $this->questions = $questions; - parent::__construct($url); - } - - public function definition() { - $form = $this->_form; - - $options = []; - $i=0; - foreach ($this->questions as $key => $question) { - $options[$i] = $question->name; - $i++; - } - - $form->addElement('select', 'question', null, $options); - //$mform->addHelpButton('question', 'question', 'block_community'); - //$mform->setDefault('question', $question); - - - $form->addElement('text', 'default_user_rating', get_string('default_user_rating', 'capquiz')); - $form->setType('default_user_rating', PARAM_INT); - $form->setDefault('default_user_rating', 'lol'); - $form->addRule('default_user_rating', get_string('default_user_rating_required', 'capquiz'), 'required', null, 'client'); - - $form->addElement('submit', 'submitbutton', get_string('savechanges')); - } - - public function validations($data, $files) { - $errors = []; - if (empty($data['default_user_rating'])) { - $errors['default_user_rating'] = get_string('default_user_rating_required', 'capquiz'); - } - if (empty($data['starstopass']) || $data['starstopass'] < 0 || $data['starstopass'] > 5) { - $errors['starstopass'] = get_string('stars_to_pass_required', 'capquiz'); - } - return $errors; - } - -} diff --git a/classes/form/view/question_details_form.php b/classes/form/view/question_details_form.php new file mode 100644 index 0000000..b0c6d72 --- /dev/null +++ b/classes/form/view/question_details_form.php @@ -0,0 +1,57 @@ +. + +/** + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_qtracker\form\view; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . '/formslib.php'); + +/** + * @package local_qtracker + * @copyright 2020 André Storhaug + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class question_details_form extends \moodleform { + + public function __construct($question, \moodle_url $url) { + $this->question = $question; + parent::__construct($url); + } + + public function definition() { + $mform = $this->_form; + + $mform->addElement('header', 'question' , + get_string('question', 'core') . " - " . $this->question->name); + + $description = \html_writer::start_div(); + $description .= $this->question->name; + $description .= $this->question->questiontext; + $description .= \html_writer::end_div(); + + $mform->addElement('html', $description); + $mform->setExpanded('question', false); + + } +} diff --git a/classes/issue.php b/classes/issue.php index 69b6876..d181a7e 100644 --- a/classes/issue.php +++ b/classes/issue.php @@ -38,6 +38,11 @@ class issue { */ protected $issue = null; + /** + * @var \stdClass + */ + protected $comments = array(); + /** * Constructor. * @@ -154,6 +159,35 @@ public function get_issue_obj() { return $this->issue; } + /** + * Returns a plain \stdClass with the issue data. + * + * @return \stdClass + */ + public function create_comment($description) { + $comment = issue_comment::create($description, $this); + $comments = $this->get_comments(); + array_push($comments, $comment); + return $comment; + } + + /** + * Add a new commentto this issue. + * + * @return \stdClass + */ + public function get_comments() { + global $DB; + if (empty($this->comments)) { + $this->comments = array(); + $comments = $DB->get_records('qtracker_comment', ['issueid' => $this->get_id()]); + foreach ($comments as $comment) { + array_push($this->comments, new issue_comment($comment)); + } + } + return $this->comments; + } + public static function load(int $issueid) { global $DB; $issueobj = $DB->get_record('qtracker_issue', ['id' => $issueid]); @@ -192,6 +226,39 @@ public static function create($title, $description, \question_definition $questi return $issue; } + /** + * Delete this issue. + * + * @return void + */ + public function close() { + global $DB; + $this->issue->state = "closed"; + $DB->update_record('qtracker_issue', $this->issue); + } + + /** + * Delete this issue. + * + * @return void + */ + public function open() { + global $DB; + $this->issue->state = "open"; + $DB->update_record('qtracker_issue', $this->issue); + } + + /** + * Delete this issue. + * + * @return void + */ + public function comment() { + + $this->comments; + $DB->update_record('qtracker_issue', $this->issue); + } + /** * Delete this issue. * diff --git a/classes/issue_comment.php b/classes/issue_comment.php new file mode 100644 index 0000000..13b536d --- /dev/null +++ b/classes/issue_comment.php @@ -0,0 +1,171 @@ +. + +/** + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_qtracker; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Question comment class. + * + * @package local_qtracker + * @copyright 2020 André Storhaug + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class issue_comment { + + /** + * @var \stdClass + */ + protected $comment = null; + + /** + * Constructor. + * + * @param int|\stdClass $comment + * @return void + */ + public function __construct($comment) { + global $DB; + if (is_scalar($comment)) { + $comment = $DB->get_record('qtracker_comment', array('id' => $comment), '*', MUST_EXIST); + if (!$comment) { + throw new \moodle_exception('errorunexistingmodel', 'analytics', '', $comment); + } + } + $this->comment = $comment; + } + + /** + * Returns the comment id. + * + * @return int + */ + public function get_id() { + return $this->comment->id; + } + + /** + * Returns the related issue id. + * + * @return int + */ + public function get_issue_id() { + return $this->comment->issueid; + } + + /** + * Returns the comment description. + * + * @return int + */ + public function get_description() { + return $this->comment->description; + } + + /** + * Returns the comment userid. + * + * @return int + */ + public function get_userid() { + return $this->comment->userid; + } + + /** + * Returns the comment timecreated. + * + * @return int + */ + public function get_timecreated() { + return $this->comment->timecreated; + } + + /** + * Returns a plain \stdClass with the comment data. + * + * @return \stdClass + */ + public function get_comment_obj() { + return $this->comment; + } + + /** + * Returns a plain \stdClass with the comment data. + * + * @return \stdClass + */ + public function get_comments() { + global $DB; + + return $this->comment; + } + + public static function load(int $comment) { + global $DB; + $commentobj = $DB->get_record('qtracker_comment', ['id' => $comment]); + if ($commentobj === false) { + return null; + } + return new issue_comment($commentobj); + } + + /** + * Creates a new comment. + * + * @return issue_comment + */ + public static function create($description, issue $issue) { + global $USER, $DB; + + $commentobj = new \stdClass(); + $commentobj->description = $description; + $commentobj->issueid = $issue->get_id(); + $commentobj->userid = $USER->id; + $time = time(); + $commentobj->timecreated = $time; + //$commentobj->timemodified = $time; + //$commentobj->usermodified = $USER->id; + + $id = $DB->insert_record('qtracker_comment', $commentobj); + $commentobj->id = $id; + + $comment = new issue_comment($commentobj); + return $comment; + } + + /** + * Delete this comment. + * + * @return void + */ + public function delete() { + global $DB; + return $DB->delete_records('qtracker_comment', array('id' => $this->get_id())); + } + + public function set_description($title) { + global $DB; + $this->comment->description = $title; + $DB->update_record('qtracker_comment', $this->comment); + } +} diff --git a/classes/output/question_issue_page.php b/classes/output/question_issue_page.php index 4060c8e..40d6b6c 100644 --- a/classes/output/question_issue_page.php +++ b/classes/output/question_issue_page.php @@ -27,6 +27,8 @@ defined('MOODLE_INTERNAL') || die(); +require_once($CFG->libdir . '/questionlib.php'); + use coding_exception; use dml_exception; use moodle_exception; @@ -36,6 +38,8 @@ use templatable; use local_qtracker\issue; use local_qtracker\external\issue_exporter; +use local_qtracker\external\issue_comment_exporter; +use local_qtracker\form\view\question_details_form; /** * Class containing data for question issue page. @@ -72,13 +76,104 @@ public function __construct(issue $questionissue, $courseid) { * @throws moodle_exception */ public function export_for_template(renderer_base $output) { + global $USER, $DB, $PAGE; $data = new stdClass(); $context = \context_course::instance($this->courseid); - $exporter = new issue_exporter($this->questionissue->get_issue_obj(), ['context' => $context]); - $issuedetails = $exporter->export($output); + $issueexporter = new issue_exporter($this->questionissue->get_issue_obj(), ['context' => $context]); + $issuedetails = $issueexporter->export($output); + + // Process default issue description + $issuedescription = new stdClass(); + $user = $DB->get_record('user', array('id' => $issuedetails->userid)); + $issuedescription->fullname = $user->username; + $issuedescription->userurl = "http://lol.no"; + $userpicture = new \user_picture($user); + $userpicture->size = 0; // Size f2. + $issuedescription->profileimageurl = $userpicture->get_url($PAGE)->out(false); + + $issuedetails->issuedescription = $issuedescription; + + + $commentsdetails = array(); + + // Process all issue comments... + $comments = $this->questionissue->get_comments(); + foreach ($comments as $comment) { + $commentexporter = new issue_comment_exporter($comment->get_comment_obj(), ['context' => $context]); + $commentdetails = $commentexporter->export($output); + // Get the user data. + $user = $DB->get_record('user', array('id' => $commentdetails->userid)); + $commentdetails->fullname = $user->username; + $userurl = new \moodle_url('/user/view.php'); + $userurl->param('id', $user->id); + $userurl->param('course', $this->courseid); + $commentdetails->userurl = $userurl; + $userpicture = new \user_picture($user); + $userpicture->size = 0; // Size f2. + $commentdetails->profileimageurl = $userpicture->get_url($PAGE)->out(false); + + $deleteurl = new \moodle_url('/local/qtracker/issue.php'); + $deleteurl->param('courseid', $this->courseid); + $deleteurl->param('issueid', $this->questionissue->get_id()); + $deleteurl->param('deletecommentid', $commentdetails->id); + $commentdetails->deleteurl = $deleteurl; + array_push($commentsdetails, $commentdetails); + } + + $issuedetails->comments = $commentsdetails; + $issuedetails->{$issuedetails->state} = true; $data->questionissue = $issuedetails; -print_r($data->questionissue); + + // Set the user picture data. + $user = $DB->get_record('user', array('id' => $USER->id)); + $userpicture = new \user_picture($user); + $userpicture->size = 0; // Size f2. + $data->profileimageurl = $userpicture->get_url($PAGE)->out(false); + + if ($this->questionissue->get_state() == "closed") { + $reopenbutton = new stdClass(); + $reopenbutton->label = get_string('reopenissue', 'local_qtracker'); + $reopenbutton->name = "reopenissue"; + $reopenbutton->value = true; + $data->reopenbutton = $reopenbutton; + } else { + $closebutton = new stdClass(); + $closebutton->label = get_string('closeissue', 'local_qtracker'); + $closebutton->name = "closeissue"; + $closebutton->value = true; + $data->closebutton = $closebutton; + } + + $commentbutton = new stdClass(); + $commentbutton->primary = true; + $commentbutton->name = "commentissue"; + $commentbutton->value = true; + $commentbutton->label = get_string('comment', 'local_qtracker'); + $data->commentbutton = $commentbutton; + + $question = \question_bank::load_question($this->questionissue->get_questionid()); + question_require_capability_on($question, 'use'); + + $questiondata = new stdClass(); + $questiondata->questionid = $question->id; + $questiondata->questionname = $question->name; + $questiondata->preview_url = question_preview_url($question->id, null, null, null, null, $context); + + $edit_url = new \moodle_url('/question/question.php'); + $edit_url->param('id', $question->id); + $edit_url->param('courseid', $this->courseid); + $questiondata->edit_url = $edit_url; + + $form = new question_details_form($question, $PAGE->url); + $questiondata->questiontext = $form->render(); + $data->question = $questiondata; + + // Setup text editor + $editor = editors_get_preferred_editor(FORMAT_HTML); + $options = array(); + $editor->use_editor('commenteditor', $options); + return $data; } } diff --git a/db/install.xml b/db/install.xml index 50ea731..ac93ab7 100755 --- a/db/install.xml +++ b/db/install.xml @@ -24,5 +24,19 @@ + + + + + + + + + + + + + +
    diff --git a/db/upgrade.php b/db/upgrade.php index 8647962..e1f3327 100755 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -28,64 +28,6 @@ function xmldb_local_qtracker_upgrade($oldversion) { $dbman = $DB->get_manager(); //TODO perform upgrades here... - if ($oldversion < 2020070800) { - // Define table capquiz_user_rating to be created. - $table = new xmldb_table('qtracker_issue'); - $field = new xmldb_field( - 'userid', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, null, -1); - $key = new xmldb_key( - 'userid', XMLDB_KEY_FOREIGN, array('userid'), 'user', array('id')); - - if (!$dbman->field_exists($table, $field)) { - $dbman->add_field($table, $field); - $dbman->add_key($table, $key); - } - upgrade_plugin_savepoint(true, 2020070800, 'local', 'qtracker'); - - } - - if ($oldversion < 2020071000) { - // Define table capquiz_user_rating to be created. - $table = new xmldb_table('qtracker_issue'); - - $qufield = new xmldb_field( - 'questionusageid', XMLDB_TYPE_INTEGER, 10); - $qukey = new xmldb_key( - 'questionusageid', XMLDB_KEY_FOREIGN, array('questionusageid'), 'question_usages', array('id')); - $slotfield = new xmldb_field( - 'slot', XMLDB_TYPE_INTEGER, 10); - - if (!$dbman->field_exists($table, $qufield)) { - $dbman->add_field($table, $qufield); - $dbman->add_key($table, $qukey); - } - if (!$dbman->field_exists($table, $slotfield)) { - $dbman->add_field($table, $slotfield); - } - upgrade_plugin_savepoint(true, 2020071000, 'local', 'qtracker'); - } - if ($oldversion < 2020072402) { - // Define table capquiz_user_rating to be created. - $table = new xmldb_table('qtracker_issue'); - - $field = new xmldb_field( - 'state', XMLDB_TYPE_TEXT); - if (!$dbman->field_exists($table, $field)) { - $dbman->add_field($table, $field); - } - upgrade_plugin_savepoint(true, 2020072402, 'local', 'qtracker'); - } - if ($oldversion < 2020072412) { - // Define table capquiz_user_rating to be created. - $table = new xmldb_table('qtracker_issue'); - - $field = new xmldb_field( - 'contextid', XMLDB_TYPE_INTEGER, 10, null, null, null, 253 ); - if (!$dbman->field_exists($table, $field)) { - $dbman->add_field($table, $field); - } - upgrade_plugin_savepoint(true, 2020072412, 'local', 'qtracker'); - } return true; } diff --git a/issue.php b/issue.php index 5811bfc..9889701 100644 --- a/issue.php +++ b/issue.php @@ -58,20 +58,57 @@ } $issuesnode = $PAGE->navbar->add(get_string('pluginname', 'local_qtracker'), null, \navigation_node::TYPE_CONTAINER, null, 'qtracker'); -$issuesnode->add(get_string('issues', 'local_qtracker'), -new \moodle_url('/local/qtracker/view.php', array('courseid' => $courseid))); +$issuesnode->add( + get_string('issues', 'local_qtracker'), + new \moodle_url('/local/qtracker/view.php', array('courseid' => $courseid)) +); $issuesnode->add(get_string('issue', 'local_qtracker')); + + +// Load issue $issue = issue::load($issueid); +// TODO: require capability for editing issues +// Process issue actions +$commentissue = optional_param('commentissue', false, PARAM_BOOL); +$commenttext = optional_param('commenteditor', false, PARAM_RAW); +if ($commentissue) { + $issue->create_comment($commenttext); + redirect($PAGE->url); +} + +$closeissue = optional_param('closeissue', false, PARAM_BOOL); +if ($closeissue) { + $issue->close(); + redirect($PAGE->url); +} + +$reopenissue = optional_param('reopenissue', false, PARAM_BOOL); +if ($reopenissue) { + $issue->open(); + redirect($PAGE->url); +} + +$deletecommentid = optional_param('deletecommentid', null, PARAM_INT); +if (!is_null($deletecommentid)) { + $comment = issue_comment::load($deletecommentid); + $comment->delete(); + redirect($PAGE->url); +} + //Capability checking issue_require_capability_on($issue->get_issue_obj(), 'view'); -echo $OUTPUT->header(); - $renderer = $PAGE->get_renderer('local_qtracker'); - $questionissuepage = new question_issue_page($issue, $courseid); -echo $renderer->render($questionissuepage); +$data = $renderer->render($questionissuepage); + +echo $OUTPUT->header(); +echo $data; echo $OUTPUT->footer(); + +if ($issue->get_state() == 'new') { + $issue->open(); +} diff --git a/lang/en/local_qtracker.php b/lang/en/local_qtracker.php index 2964ecb..5a8b179 100755 --- a/lang/en/local_qtracker.php +++ b/lang/en/local_qtracker.php @@ -68,8 +68,29 @@ $string['validdescription'] = 'Please provide a valid description.'; $string['commentedon'] = 'commented on '; +$string['openedissueon'] = 'opened issue on '; $string['name'] = 'Name'; +$string['tags'] = 'Tags'; + $string['new'] = 'New'; $string['open'] = 'Open'; $string['closed'] = 'Closed'; + +$string['reopenissue'] = 'Reopen issue'; +$string['closeissue'] = 'Close issue'; +$string['comment'] = 'Comment'; + +$string['confirm'] = 'Confirm'; +$string['deletecomment'] = 'Delete comment'; +$string['confirmdeletecomment'] = 'Are you sure you want to delete this comment?'; + +$string['preview'] = 'Preview'; +$string['edit'] = 'Edit'; +$string['questionissues'] = 'Question issues'; + +$string['qtracker:addissue'] = 'Add new issue'; +$string['qtracker:editmine'] = 'Edit your own issues'; +$string['qtracker:editall'] = 'Edit all issues'; +$string['qtracker:viewmine'] = 'Edit your own issues'; +$string['qtracker:viewall'] = 'View all issues'; diff --git a/lib.php b/lib.php index 7fb10ea..37f590a 100644 --- a/lib.php +++ b/lib.php @@ -88,6 +88,7 @@ function issue_has_capability_on($issueorid, $cap) { } $context = context::instance_by_id($issue->contextid); + // These are existing issues capabilities. // Each of these has a 'mine' and 'all' version that is appended to the capability name. $capabilitieswithallandmine = ['edit' => 1, 'view' => 1]; diff --git a/settings.php b/settings.php deleted file mode 100644 index 23fa9a6..0000000 --- a/settings.php +++ /dev/null @@ -1,35 +0,0 @@ -. - -/** - * Main interface to Question Tracker - * - * @package local_qtracker - * @author André Storhaug - * @copyright 2020 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); -/* -$settings->add(new admin_setting_heading('sampleheader', - get_string('headerconfig', 'local_qtracker'), - get_string('descconfig', 'local_qtracker'))); - -$settings->add(new admin_setting_configcheckbox('qtracker/foo', - get_string('labelfoo', 'local_qtracker'), - get_string('descfoo', 'local_qtracker'), - '0')); */ diff --git a/styles.css b/styles.css index a8a4e0e..cd29e3d 100644 --- a/styles.css +++ b/styles.css @@ -16,6 +16,10 @@ bottom: 0; } +.qtracker-action-item { + line-height: 2.5em; +} + .qtracker-container { position: relative; min-height: 400px; @@ -44,6 +48,15 @@ flex: 1 0 auto; } +.questiontext { + position: relative; + zoom: 1; + padding-left: .3em; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + @media (min-width: 768px) { #issues-pane { position: absolute; diff --git a/templates/button.mustache b/templates/button.mustache index 2e03dd7..05f3d68 100644 --- a/templates/button.mustache +++ b/templates/button.mustache @@ -30,8 +30,10 @@ } }}
    - +
    diff --git a/templates/issue_comment.mustache b/templates/issue_comment.mustache new file mode 100644 index 0000000..f079196 --- /dev/null +++ b/templates/issue_comment.mustache @@ -0,0 +1,88 @@ +{{! + 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 . +}} +{{! + @template local_qtracker/issue_state_badge + + Issue state badge template. + + Example context (json): + { + "new": true + } +}} +
    +
    + +
    +
    + {{fullname}}  +
    + {{#str}} commentedon, local_qtracker {{/str}} + {{#userdate}} {{timecreated}}, {{#str}} strftimedate, core_langconfig {{/str}} {{/userdate}} +
    + +
    +
    + {{{description}}} +
    +
    +
    +
    +{{#js}} +require(['jquery', 'core/modal_factory', 'core/modal_events', 'core/templates', 'core/str'], function($, ModalFactory, ModalEvents, Templates, str) { + let trigger = $('#comment_{{id}}'); + let strObj = [ + { + key: 'confirm', + component: 'local_qtracker' + }, + { + key: 'confirmdeletecomment', + component: 'local_qtracker' + }, + { + key: 'deletecomment', + component: 'local_qtracker' + } + ]; + str.get_strings(strObj).then(function (strings) { + ModalFactory.create({ + type: ModalFactory.types.SAVE_CANCEL, + title: strings[2], + body: strings[1], + }, trigger) + .done(function(modal) { + modal.setSaveButtonText(strings[0]) + modal.getRoot().on(ModalEvents.save, function(e) { + // Stop the default save button behaviour which is to close the modal. + e.preventDefault(); + let form = $('#comment_form_{{id}}'); + $('').attr({ + type: "hidden", + name: "deletecommentid", + value: {{id}} + }).appendTo(form); + form.submit(); + }); + }); + }); +}); +{{/js}} diff --git a/templates/issue_description.mustache b/templates/issue_description.mustache new file mode 100644 index 0000000..c22f4eb --- /dev/null +++ b/templates/issue_description.mustache @@ -0,0 +1,40 @@ +{{! + 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 . +}} +{{! + @template local_qtracker/issue_description + + Issue description template. + + Example context (json): + {} +}} +
    + +
    +
    + {{fullname}}  +
    + {{#str}} openedissueon, local_qtracker {{/str}} + {{#userdate}} {{timecreated}}, {{#str}} strftimedate, core_langconfig {{/str}} {{/userdate}} +
    +
    +
    +
    + {{{description}}} +
    +
    +
    diff --git a/templates/issue_state_badge.mustache b/templates/issue_state_badge.mustache index de5e8d8..db156d5 100644 --- a/templates/issue_state_badge.mustache +++ b/templates/issue_state_badge.mustache @@ -24,7 +24,7 @@ "new": true } }} -
    + {{#new}} {{#str}} new, local_qtracker {{/str}} {{/new}} @@ -34,4 +34,4 @@ {{#closed}} {{#str}} closed, local_qtracker {{/str}} {{/closed}} -
    + diff --git a/templates/issues_pane_item.mustache b/templates/issues_pane_item.mustache index 5f57e76..2f31718 100644 --- a/templates/issues_pane_item.mustache +++ b/templates/issues_pane_item.mustache @@ -40,7 +40,9 @@
    - {{>local_qtracker/issue_state_badge}} +
    + {{>local_qtracker/issue_state_badge}} +
    {{title}} diff --git a/templates/question_issue_page.mustache b/templates/question_issue_page.mustache index c034af6..1a2b053 100644 --- a/templates/question_issue_page.mustache +++ b/templates/question_issue_page.mustache @@ -29,10 +29,97 @@ } }}
    + {{#questionissue}} - {{id}} - {{title}} - {{description}} +
    +
    +
    +

    + {{title}} #{{id}} +

    +
    +
    +

    + {{>local_qtracker/issue_state_badge}} +

    +
    +
    {{/questionissue}} -
    +
    + {{#question}} + {{! Question data here }} +
    +
    +
    {{{questiontext}}}
    +
    +
    +
    {{#str}} actions, core {{/str}}
    + + +
    +
    + {{/question}} + {{#questionissue}} +
    +
    +
    + {{#issuedescription}} + {{> local_qtracker/issue_description}} + {{/issuedescription}} + {{#comments}} +
    + {{> local_qtracker/issue_comment}} +
    + {{/comments}} +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + {{#closebutton}} + {{> local_qtracker/button}} + {{/closebutton}} + {{#reopenbutton}} + {{> local_qtracker/button}} + {{/reopenbutton}} +
    + {{#commentbutton}} + {{> local_qtracker/button}} + {{/commentbutton}} +
    +
    +
    +
    +
    +
    +
    + {{#str}} tags, local_qtracker {{/str}} +
    +
    + Tags here... +
    +
    +
    +
    +
    +
    + {{/questionissue}} +
    diff --git a/templates/questions.mustache b/templates/questions.mustache index 1ae3451..bca14b7 100644 --- a/templates/questions.mustache +++ b/templates/questions.mustache @@ -25,9 +25,7 @@ } }}
    -
    - Lorem ipsum ex nulla officia est et. Anim laboris irure nulla nulla ut do veniam anim eiusmod dolor eu. Excepteur deserunt do labore qui culpa deserunt sint aliqua nostrud occaecat nostrud proident ad. -
    +

    {{#str}} questionissues, local_qtracker {{/str}}

    diff --git a/version.php b/version.php index 615849a..8d8af2c 100755 --- a/version.php +++ b/version.php @@ -23,9 +23,9 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2020072414; +$plugin->version = 2020091500; $plugin->requires = 2016120500; $plugin->cron = 0; $plugin->component = 'local_qtracker'; -$plugin->maturity = MATURITY_STABLE; +$plugin->maturity = MATURITY_BETA; $plugin->release = '0.1.0'; diff --git a/view.php b/view.php index a2f6721..1e1ee0e 100644 --- a/view.php +++ b/view.php @@ -45,13 +45,6 @@ $PAGE->set_pagelayout('incourse'); $PAGE->set_heading(get_string('pluginname', 'local_qtracker')); -/* -$settingsnode = $PAGE->settingsnav->add(get_string('frontpagesettings'), null, \navigation_node::TYPE_SETTING, null); -$editurl = new \moodle_url('/blocks/simplehtml/view.php', array('id' => $id, 'courseid' => $courseid)); -$editnode = $settingsnode->add(get_string('resetpage', 'my'), $editurl); -$editnode->make_active(); -*/ - echo $OUTPUT->header(); // Get table renderer and display table. From 321276db59721d9d04048889c3674e06f505f71c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Storhaug?= Date: Wed, 16 Sep 2020 02:22:41 +0200 Subject: [PATCH 26/83] Add event handler for deleting issues when question is deleted --- classes/event/question_deleted_observer.php | 56 +++++++++++++++++++++ classes/issue.php | 6 ++- db/events.php | 33 ++++++++++++ version.php | 2 +- 4 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 classes/event/question_deleted_observer.php create mode 100644 db/events.php diff --git a/classes/event/question_deleted_observer.php b/classes/event/question_deleted_observer.php new file mode 100644 index 0000000..47c0571 --- /dev/null +++ b/classes/event/question_deleted_observer.php @@ -0,0 +1,56 @@ +. + +/** + * Event observers supported by this module. + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_qtracker\event; + +use local_qtracker\issue; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Event observers supported by this module. + * + * @package local_qtracker + * @copyright 2020 André Storhaug + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class question_deleted_observer { + + /** + * Delete related question issues when question is deleted. + * + * @param \core\event\question_deleted $event + */ + public static function question_deleted(\core\event\question_deleted $event) { + global $DB; + // Delete all issues for given question. + $records = $DB->get_records('qtracker_issue', ['questionid' => $event->objectid], '', 'id'); + + foreach ($records as $record) { + $issue = issue::load($record->id); + $issue->delete(); + } + } +} diff --git a/classes/issue.php b/classes/issue.php index d181a7e..17b0be6 100644 --- a/classes/issue.php +++ b/classes/issue.php @@ -260,12 +260,16 @@ public function comment() { } /** - * Delete this issue. + * Delete this issue and related comments. * * @return void */ public function delete() { global $DB; + $comments = $this->get_comments(); + foreach ($comments as $comment) { + $comment->delete(); + } return $DB->delete_records('qtracker_issue', array('id' => $this->get_id())); } diff --git a/db/events.php b/db/events.php new file mode 100644 index 0000000..13ff8cd --- /dev/null +++ b/db/events.php @@ -0,0 +1,33 @@ +. + +/** + * This file defines observers needed by the plugin. + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$observers = [ + [ + 'eventname' => '\core\event\question_deleted', + 'callback' => '\local_qtracker\event\question_deleted_observer::question_deleted', + ], +]; diff --git a/version.php b/version.php index 8d8af2c..d749e1f 100755 --- a/version.php +++ b/version.php @@ -23,7 +23,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2020091500; +$plugin->version = 2020091600; $plugin->requires = 2016120500; $plugin->cron = 0; $plugin->component = 'local_qtracker'; From b38ffb24a497b992e5f407f5768fe659e336a389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Storhaug?= Date: Wed, 16 Sep 2020 02:23:24 +0200 Subject: [PATCH 27/83] Redirect to main page if issue does not exist --- issue.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/issue.php b/issue.php index 9889701..9692469 100644 --- a/issue.php +++ b/issue.php @@ -68,6 +68,10 @@ // Load issue $issue = issue::load($issueid); +if (!$issue) { + $issuesurl = new \moodle_url('/local/qtracker/view.php', array('courseid' => $courseid)); + redirect($issuesurl); +} // TODO: require capability for editing issues // Process issue actions From 61dd516d2dc4899997b0dc9f51b21827956a2808 Mon Sep 17 00:00:00 2001 From: hgeorgsch Date: Tue, 22 Sep 2020 12:52:40 +0200 Subject: [PATCH 28/83] Update README.md --- README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/README.md b/README.md index d5f3f33..01814a6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,35 @@ # moodle-local_qtracker :bug: Local Moodle plugin providing issue tracking for Moodle questions. + +The QTracker system allows students to comment and ask questions about individual questions in a quiz. The idea and design follows the principles of issue tracker systems, although it is still work in progress and some features will be missed still. + +In addition to the QTracker module, a separate block-type module is needed to add the interface to a given activity type. Such block modules have been made for ++ [Core Quiz](https://github.com/KQMATH/moodle-block_quizqtracker) ++ [CAPQuiz](https://github.com/KQMATH/moodle-block_capquizqtracker) + +The QTracker functionality is normally accessed via the appropriate block plugin. + +## Add the tracker to an Activity + +1. Open the activity as teacher. +2. Turn editing on. +3. Click «Add Block» in the left hand menu. +4. Choose the appropriate block - single-click - this is slow to react. + +The block should now appear both in the student and teacher interface, with different contents, but it must be tested. Sometimes it is necessary to tweak the settings, found in the gear menu in the block itself. + +## As student + +The student interface is straight forward. Enter a title and a text, and hit submit. + +## Managing issues + +In the teacher interface to the activity, the block shows a link to manage issues. + +**TODO** list features. + +## Note on publishing as LTI + +Observe that LTI does not support blocks in the student view. This module does not work when students access the acitivity over LTI. + +For our own use, we have patched the core moodle installation to show block in LTI, and we are contemplating more permanent solutions. From aa100b783ce6eab5815e3e45e0f88f21f2ff9ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Storhaug?= Date: Tue, 22 Sep 2020 16:56:13 +0200 Subject: [PATCH 29/83] Remove wrong capability check Fixes #14 --- classes/output/question_issues_table.php | 3 --- classes/output/questions_table.php | 2 -- version.php | 2 +- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/classes/output/question_issues_table.php b/classes/output/question_issues_table.php index 5dfccab..e6b7fad 100644 --- a/classes/output/question_issues_table.php +++ b/classes/output/question_issues_table.php @@ -54,9 +54,6 @@ public function __construct($uniqueid, $url) { // TODO: determine which context to use... $context = context_system::instance(); - // This object should not be used without the right permissions. - require_capability('moodle/role:manage', $context); // DO WE NEED THIS? - // Define columns in the table. $this->define_table_columns(); // Set the baseurl diff --git a/classes/output/questions_table.php b/classes/output/questions_table.php index 565ba5f..ad323bd 100644 --- a/classes/output/questions_table.php +++ b/classes/output/questions_table.php @@ -53,8 +53,6 @@ public function __construct($uniqueid, $url, $context) { parent::__construct($uniqueid); // TODO: determine which context to use... $this->context = $context; - // This object should not be used without the right permissions. - require_capability('moodle/role:manage', $context); // DO WE NEED THIS? // Define columns in the table. $this->define_table_columns(); diff --git a/version.php b/version.php index d749e1f..0654eca 100755 --- a/version.php +++ b/version.php @@ -23,7 +23,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2020091600; +$plugin->version = 2020092200; $plugin->requires = 2016120500; $plugin->cron = 0; $plugin->component = 'local_qtracker'; From ef849191ad975af3f858faeb600be16ae542e967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Storhaug?= Date: Thu, 4 Feb 2021 00:32:29 +0100 Subject: [PATCH 30/83] Create moodle-ci.yml --- .github/workflows/moodle-ci.yml | 104 ++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 .github/workflows/moodle-ci.yml diff --git a/.github/workflows/moodle-ci.yml b/.github/workflows/moodle-ci.yml new file mode 100644 index 0000000..6743a3c --- /dev/null +++ b/.github/workflows/moodle-ci.yml @@ -0,0 +1,104 @@ +name: build + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-18.04 + + services: + postgres: + image: postgres:9.6 + env: + POSTGRES_USER: 'postgres' + POSTGRES_HOST_AUTH_METHOD: 'trust' + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3 + mariadb: + image: mariadb:10 + env: + MYSQL_USER: 'root' + MYSQL_ALLOW_EMPTY_PASSWORD: "true" + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 3 + + strategy: + fail-fast: false + matrix: + php: ['7.2', '7.3', '7.4'] + moodle-branch: ['MOODLE_310_STABLE'] + database: [pgsql, mariadb] + + steps: + - name: Check out repository code + uses: actions/checkout@v2 + with: + path: plugin + + - name: Setup PHP ${{ matrix.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + + - name: Initialise moodle-plugin-ci + run: | + composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^3 + echo $(cd ci/bin; pwd) >> $GITHUB_PATH + echo $(cd ci/vendor/bin; pwd) >> $GITHUB_PATH + sudo locale-gen en_AU.UTF-8 + + - name: Install moodle-plugin-ci + run: | + moodle-plugin-ci install --plugin ./plugin --db-host=127.0.0.1 + env: + DB: ${{ matrix.database }} + MOODLE_BRANCH: ${{ matrix.moodle-branch }} + + - name: PHP Lint + if: ${{ always() }} + run: moodle-plugin-ci phplint + + - name: PHP Copy/Paste Detector + continue-on-error: true # This step will show errors but will not fail + if: ${{ always() }} + run: moodle-plugin-ci phpcpd + + - name: PHP Mess Detector + continue-on-error: true # This step will show errors but will not fail + if: ${{ always() }} + run: moodle-plugin-ci phpmd + + - name: Moodle Code Checker + if: ${{ always() }} + run: moodle-plugin-ci codechecker --max-warnings 0 + + - name: Moodle PHPDoc Checker + if: ${{ always() }} + run: moodle-plugin-ci phpdoc + + - name: Validating + if: ${{ always() }} + run: moodle-plugin-ci validate + + - name: Check upgrade savepoints + if: ${{ always() }} + run: moodle-plugin-ci savepoints + + - name: Mustache Lint + if: ${{ always() }} + run: moodle-plugin-ci mustache + + - name: Grunt + if: ${{ always() }} + run: moodle-plugin-ci grunt --max-lint-warnings 0 + + - name: PHPUnit tests + if: ${{ always() }} + run: moodle-plugin-ci phpunit --coverage-clover + + - name: Behat features + if: ${{ always() }} + run: moodle-plugin-ci behat --profile chrome From e26e0220c51b80bfceb96bace079d6f162aedd8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Storhaug?= Date: Thu, 4 Feb 2021 00:36:12 +0100 Subject: [PATCH 31/83] Update moodle-ci.yml --- .github/workflows/moodle-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/moodle-ci.yml b/.github/workflows/moodle-ci.yml index 6743a3c..f090810 100644 --- a/.github/workflows/moodle-ci.yml +++ b/.github/workflows/moodle-ci.yml @@ -28,7 +28,7 @@ jobs: fail-fast: false matrix: php: ['7.2', '7.3', '7.4'] - moodle-branch: ['MOODLE_310_STABLE'] + moodle-branch: ['MOODLE_310_STABLE', 'MOODLE_39_STABLE'] database: [pgsql, mariadb] steps: From cc8e493e82ad7ae27ba15cdf6d05a9f3879c5a70 Mon Sep 17 00:00:00 2001 From: David Rise Knotten Date: Fri, 5 Feb 2021 16:05:43 +0100 Subject: [PATCH 32/83] Solved most Moodle Code Checker issues --- classes/external/delete_issue.php | 9 +++---- classes/external/edit_issue.php | 11 ++++---- classes/external/get_issue.php | 10 +++----- classes/external/get_issues.php | 9 +++---- classes/external/get_question.php | 7 +++--- classes/external/helper.php | 1 - classes/external/new_issue.php | 11 ++++---- classes/issue.php | 13 +++++----- classes/issue_comment.php | 5 ++-- classes/output/issue_registration_block.php | 7 +++--- classes/output/question_issue_page.php | 5 ++-- classes/output/question_issues_table.php | 4 +-- classes/output/questions_table.php | 28 ++++++++++----------- db/services.php | 16 ++++++------ db/upgrade.php | 2 +- issue.php | 13 ++++++---- lang/en/local_qtracker.php | 2 +- lib.php | 9 +++---- 18 files changed, 76 insertions(+), 86 deletions(-) diff --git a/classes/external/delete_issue.php b/classes/external/delete_issue.php index fc0b0bd..709c18f 100644 --- a/classes/external/delete_issue.php +++ b/classes/external/delete_issue.php @@ -1,5 +1,4 @@ libdir . "/externallib.php"); -require_once($CFG ->libdir . '/questionlib.php'); +require_once($CFG->libdir . '/questionlib.php'); require_once($CFG->dirroot . '/local/qtracker/lib.php'); use external_value; @@ -62,7 +61,7 @@ public static function delete_issue($issueid) { $deleted = false; $warnings = array(); - //Parameter validation + // Parameter validation. $params = self::validate_parameters(self::delete_issue_parameters(), array( 'issueid' => (int) $issueid, @@ -80,11 +79,11 @@ public static function delete_issue($issueid) { $issue = issue::load($params['issueid']); - //Context validation + // Context validation. $context = \context::instance_by_id($issue->get_contextid()); self::validate_context($context); - //Capability checking + // Capability checking. issue_require_capability_on($issue->get_issue_obj(), 'edit'); if (empty($warnings)) { diff --git a/classes/external/edit_issue.php b/classes/external/edit_issue.php index 924933f..5cce2ac 100644 --- a/classes/external/edit_issue.php +++ b/classes/external/edit_issue.php @@ -1,5 +1,4 @@ libdir . "/externallib.php"); -require_once($CFG ->libdir . '/questionlib.php'); +require_once($CFG->libdir . '/questionlib.php'); require_once($CFG->dirroot . '/local/qtracker/lib.php'); use external_value; @@ -64,7 +63,7 @@ public static function edit_issue($issueid, $issuetitle, $issuedescription) { $added = false; $warnings = array(); - //Parameter validation + // Parameter validation. $params = self::validate_parameters(self::edit_issue_parameters(), array( 'issueid' => (int) $issueid, @@ -84,14 +83,14 @@ public static function edit_issue($issueid, $issuetitle, $issuedescription) { $issue = issue::load($params['issueid']); - //Context validation + // Context validation. $context = \context::instance_by_id($issue->get_contextid()); self::validate_context($context); - //Capability checking + // Capability checking. issue_require_capability_on($issue->get_issue_obj(), 'edit'); - if (empty($params['issuetitle'])){ + if (empty($params['issuetitle'])) { $warnings[] = array( 'item' => 'issuetitle', 'itemid' => 0, diff --git a/classes/external/get_issue.php b/classes/external/get_issue.php index 2dcdf14..0eddbbf 100644 --- a/classes/external/get_issue.php +++ b/classes/external/get_issue.php @@ -1,5 +1,4 @@ libdir . "/externallib.php"); -require_once($CFG ->libdir . '/questionlib.php'); +require_once($CFG->libdir . '/questionlib.php'); require_once($CFG->dirroot . '/local/qtracker/lib.php'); use external_value; @@ -63,7 +62,7 @@ public static function get_issue($issueid) { $issuedata = array(); $warnings = array(); - //Parameter validation + // Parameter validation. $params = self::validate_parameters(self::get_issue_parameters(), array( 'issueid' => (int) $issueid, @@ -81,14 +80,13 @@ public static function get_issue($issueid) { $issue = issue::load($params['issueid']); - //Context validation + // Context validation. $context = \context::instance_by_id($issue->get_contextid()); self::validate_context($context); - //Capability checking + // Capability checking. issue_require_capability_on($issue->get_issue_obj(), 'view'); - if (empty($warnings)) { $issuedata['id'] = $issue->get_id(); $issuedata['title'] = $issue->get_title(); diff --git a/classes/external/get_issues.php b/classes/external/get_issues.php index 8f75697..2674e54 100644 --- a/classes/external/get_issues.php +++ b/classes/external/get_issues.php @@ -1,5 +1,4 @@ new external_value(PARAM_RAW, 'the value to search') ) ), @@ -168,14 +168,13 @@ public static function get_issues($criteria = array()) { // Finally retrieve each issues information. $returnedissues = array(); foreach ($issues as $issue) { - //Context validation + // Context validation. $context = \context::instance_by_id($issue->contextid); self::validate_context($context); - //Capability checking + // Capability checking. issue_require_capability_on($issue, 'view'); - $renderer = $PAGE->get_renderer('core'); $exporter = new issue_exporter($issue, ['context' => $context]); $issuedetails = $exporter->export($renderer); diff --git a/classes/external/get_question.php b/classes/external/get_question.php index 5bd7041..c0af0a1 100644 --- a/classes/external/get_question.php +++ b/classes/external/get_question.php @@ -1,5 +1,4 @@ libdir . "/externallib.php"); -require_once($CFG ->libdir . '/questionlib.php'); +require_once($CFG->libdir . '/questionlib.php'); require_once($CFG->dirroot . '/local/qtracker/lib.php'); use external_value; @@ -61,7 +60,7 @@ public static function get_question($questionid) { $status = false; $warnings = array(); - //Parameter validation + // Parameter validation. $params = self::validate_parameters(self::get_question_parameters(), array( 'id' => (int) $questionid, @@ -73,7 +72,7 @@ public static function get_question($questionid) { throw new \moodle_exception('cannotgetquestion', 'local_qtracker', '', $params['id']); } - //Context validation + // Context validation. $context = \context::instance_by_id($question->contextid); self::validate_context($context); diff --git a/classes/external/helper.php b/classes/external/helper.php index 01b3da9..413d2b8 100644 --- a/classes/external/helper.php +++ b/classes/external/helper.php @@ -1,5 +1,4 @@ libdir . "/externallib.php"); -require_once($CFG ->libdir . '/questionlib.php'); +require_once($CFG->libdir . '/questionlib.php'); require_once($CFG->dirroot . '/local/qtracker/lib.php'); use external_value; @@ -66,7 +65,7 @@ public static function new_issue($qubaid, $slot, $contextid, $issuetitle, $issue $added = false; $warnings = array(); - //Parameter validation + // Parameter validation. $params = self::validate_parameters(self::new_issue_parameters(), array( 'qubaid' => (int) $qubaid, @@ -77,17 +76,17 @@ public static function new_issue($qubaid, $slot, $contextid, $issuetitle, $issue ) ); - //Context validation + // Context validation. // TODO: ensure proper validation.... $context = \context::instance_by_id($params['contextid']); self::validate_context($context); - //Capability checking + // Capability checking. if (!has_capability('local/qtracker:addissue', $context)) { throw new moodle_exception('cannotcreateissue', 'local_qtracker'); } - if (empty($params['issuetitle'])){ + if (empty($params['issuetitle'])) { $warnings[] = array( 'item' => 'issuetitle', 'itemid' => 0, diff --git a/classes/issue.php b/classes/issue.php index 17b0be6..85d19ec 100644 --- a/classes/issue.php +++ b/classes/issue.php @@ -1,5 +1,4 @@ comments)) { @@ -216,8 +215,8 @@ public static function create($title, $description, \question_definition $questi $issueobj->userid = $USER->id; $time = time(); $issueobj->timecreated = $time; - //$issueobj->timemodified = $time; - //$issueobj->usermodified = $USER->id; + // $issueobj->timemodified = $time; + // $issueobj->usermodified = $USER->id; $id = $DB->insert_record('qtracker_issue', $issueobj); $issueobj->id = $id; diff --git a/classes/issue_comment.php b/classes/issue_comment.php index 13b536d..8c27a4e 100644 --- a/classes/issue_comment.php +++ b/classes/issue_comment.php @@ -1,5 +1,4 @@ userid = $USER->id; $time = time(); $commentobj->timecreated = $time; - //$commentobj->timemodified = $time; - //$commentobj->usermodified = $USER->id; + // $commentobj->timemodified = $time; + // $commentobj->usermodified = $USER->id; $id = $DB->insert_record('qtracker_comment', $commentobj); $commentobj->id = $id; diff --git a/classes/output/issue_registration_block.php b/classes/output/issue_registration_block.php index 75f06da..eb0b195 100644 --- a/classes/output/issue_registration_block.php +++ b/classes/output/issue_registration_block.php @@ -71,7 +71,7 @@ public function __construct(\question_usage_by_activity $quba, $slots, $contexti $this->slots = $slots; $this->contextid = $contextid; - //Todo remove questions..... + // Todo remove questions..... foreach ($this->slots as $slot) { $this->questions[] = $this->quba->get_question($slot); } @@ -100,8 +100,7 @@ public function export_for_template(renderer_base $output) { $url = $PAGE->url; $data = new stdClass(); - - //TODO: only check if questions exists... otherwise i dont need them... + // TODO: only check if questions exists... otherwise i dont need them... if (count($this->questions) > 1) { $data->hasmultiple = true; @@ -138,7 +137,7 @@ public function export_for_template(renderer_base $output) { // TODO: Fix this as both the button and the select gets this. Wrap in separate mustashe templates. - //$data->questions = $questions; + // $data->questions = $questions; return $data; } } diff --git a/classes/output/question_issue_page.php b/classes/output/question_issue_page.php index 40d6b6c..6b3f8fb 100644 --- a/classes/output/question_issue_page.php +++ b/classes/output/question_issue_page.php @@ -83,7 +83,7 @@ public function export_for_template(renderer_base $output) { $issueexporter = new issue_exporter($this->questionissue->get_issue_obj(), ['context' => $context]); $issuedetails = $issueexporter->export($output); - // Process default issue description + // Process default issue description. $issuedescription = new stdClass(); $user = $DB->get_record('user', array('id' => $issuedetails->userid)); $issuedescription->fullname = $user->username; @@ -94,7 +94,6 @@ public function export_for_template(renderer_base $output) { $issuedetails->issuedescription = $issuedescription; - $commentsdetails = array(); // Process all issue comments... @@ -169,7 +168,7 @@ public function export_for_template(renderer_base $output) { $questiondata->questiontext = $form->render(); $data->question = $questiondata; - // Setup text editor + // Setup text editor. $editor = editors_get_preferred_editor(FORMAT_HTML); $options = array(); $editor->use_editor('commenteditor', $options); diff --git a/classes/output/question_issues_table.php b/classes/output/question_issues_table.php index e6b7fad..55dd205 100644 --- a/classes/output/question_issues_table.php +++ b/classes/output/question_issues_table.php @@ -56,7 +56,7 @@ public function __construct($uniqueid, $url) { // Define columns in the table. $this->define_table_columns(); - // Set the baseurl + // Set the baseurl. $this->define_baseurl($url); // Define configs. $this->define_table_configs(); @@ -132,7 +132,7 @@ public function col_timecreated($data) { protected function define_table_columns() { // Define headers and columns. - //TODO: define strings in lang file. + // TODO: define strings in lang file. $cols = array( 'id' => get_string('id', 'local_qtracker'), 'questionid' => get_string('questionid', 'local_qtracker'), diff --git a/classes/output/questions_table.php b/classes/output/questions_table.php index ad323bd..5de4715 100644 --- a/classes/output/questions_table.php +++ b/classes/output/questions_table.php @@ -56,7 +56,7 @@ public function __construct($uniqueid, $url, $context) { // Define columns in the table. $this->define_table_columns(); - // Set the baseurl + // Set the baseurl. $this->define_baseurl($url); // Define configs. $this->define_table_configs(); @@ -86,8 +86,8 @@ protected function col_name($data) { if ($data->name) { $id = $data->id; $name = \html_writer::link("#", $data->name, array('onclick' => "showIssuesInPane($id);return false;")); - return $name; //need to change it to correct link - //return ''.$data->title.''; + return $name; // need to change it to correct link. + // return ''.$data->title.''; } else { return '-'; } @@ -123,7 +123,7 @@ public function other_cols($cols, $data) { protected function define_table_columns() { // Define headers and columns. - //TODO: define strings in lang file. + // TODO: define strings in lang file. $cols = array( 'id' => get_string('questionid', 'local_qtracker'), 'name' => get_string('name', 'local_qtracker'), @@ -202,12 +202,12 @@ public function wrap_html_start() { return; } - //echo '
    '; - //echo '
    '; - //echo '
    '; - //echo '
    '; - //echo '
    '; - //echo '
    '; + // echo '
    '; + // echo '
    '; + // echo '
    '; + // echo '
    '; + // echo '
    '; + // echo '
    '; } @@ -217,9 +217,9 @@ public function wrap_html_finish() { return; } - //echo '
    '; - //echo '
    '; - //echo '
    '; - //echo '
    '; + // echo '
    '; + // echo '
    '; + // echo '
    '; + // echo '
    '; } } diff --git a/db/services.php b/db/services.php index 3ec45f2..5a3352d 100644 --- a/db/services.php +++ b/db/services.php @@ -34,8 +34,8 @@ 'description' => 'Register a new question issue.', 'type' => 'write', 'ajax' => true, - //'capabilities' => 'moodle/course:managegroups', - 'capabilities' => array(), // capabilities required by the function. + // 'capabilities' => 'moodle/course:managegroups', + 'capabilities' => array(), // Capabilities required by the function. 'loginrequired' => true, ), 'local_qtracker_edit_issue' => array( @@ -45,8 +45,8 @@ 'description' => 'Edit an existing question issue.', 'type' => 'write', 'ajax' => true, - //'capabilities' => 'moodle/course:managegroups', - 'capabilities' => array(), // capabilities required by the function. + // 'capabilities' => 'moodle/course:managegroups', + 'capabilities' => array(), // Capabilities required by the function. 'loginrequired' => true, ), 'local_qtracker_delete_issue' => array( @@ -56,8 +56,8 @@ 'description' => 'Delete an existing question issue.', 'type' => 'write', 'ajax' => true, - //'capabilities' => 'moodle/course:managegroups', - 'capabilities' => array(), // capabilities required by the function. + // 'capabilities' => 'moodle/course:managegroups', + 'capabilities' => array(), // Capabilities required by the function. 'loginrequired' => true, ), 'local_qtracker_get_issue' => array( @@ -67,8 +67,8 @@ 'description' => 'Get an existing question issue.', 'type' => 'read', 'ajax' => true, - //'capabilities' => 'moodle/course:managegroups', - 'capabilities' => array(), // capabilities required by the function. + // 'capabilities' => 'moodle/course:managegroups', + 'capabilities' => array(), // Capabilities required by the function. 'loginrequired' => true, ), 'local_qtracker_get_question' => array( diff --git a/db/upgrade.php b/db/upgrade.php index e1f3327..cbe15db 100755 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -27,7 +27,7 @@ function xmldb_local_qtracker_upgrade($oldversion) { global $DB; $dbman = $DB->get_manager(); - //TODO perform upgrades here... + // TODO perform upgrades here... return true; } diff --git a/issue.php b/issue.php index 9692469..cb3596d 100644 --- a/issue.php +++ b/issue.php @@ -57,7 +57,10 @@ redirect($returnurl); } -$issuesnode = $PAGE->navbar->add(get_string('pluginname', 'local_qtracker'), null, \navigation_node::TYPE_CONTAINER, null, 'qtracker'); +$issuesnode = $PAGE->navbar->add( + get_string('pluginname', 'local_qtracker'), + null, \navigation_node::TYPE_CONTAINER, null, 'qtracker' +); $issuesnode->add( get_string('issues', 'local_qtracker'), new \moodle_url('/local/qtracker/view.php', array('courseid' => $courseid)) @@ -66,15 +69,15 @@ -// Load issue +// Load issue. $issue = issue::load($issueid); if (!$issue) { $issuesurl = new \moodle_url('/local/qtracker/view.php', array('courseid' => $courseid)); redirect($issuesurl); } -// TODO: require capability for editing issues -// Process issue actions +// TODO: require capability for editing issues. +// Process issue actions. $commentissue = optional_param('commentissue', false, PARAM_BOOL); $commenttext = optional_param('commenteditor', false, PARAM_RAW); if ($commentissue) { @@ -101,7 +104,7 @@ redirect($PAGE->url); } -//Capability checking +// Capability checking. issue_require_capability_on($issue->get_issue_obj(), 'view'); $renderer = $PAGE->get_renderer('local_qtracker'); diff --git a/lang/en/local_qtracker.php b/lang/en/local_qtracker.php index 5a8b179..00d456d 100755 --- a/lang/en/local_qtracker.php +++ b/lang/en/local_qtracker.php @@ -34,7 +34,7 @@ $string['headerconfig'] = 'Config section header'; $string['labelfoo'] = 'Config label'; -// question issues table +// Question issues table. $string['id'] = 'ID'; $string['questionid'] = 'Question ID'; $string['title'] = 'Title'; diff --git a/lib.php b/lib.php index 37f590a..bfa302b 100644 --- a/lib.php +++ b/lib.php @@ -35,7 +35,6 @@ function local_qtracker_extend_navigation_course($navigation, $course, $context) { global $CFG; - if ($context->contextlevel == CONTEXT_COURSE) { $params = array('courseid' => $context->instanceid); } else if ($context->contextlevel == CONTEXT_MODULE) { @@ -52,14 +51,14 @@ function local_qtracker_extend_navigation_course($navigation, $course, $context) 'qtracker' ); - //TODO: Check if the user has ANY question issue context capabilities. - //$contexts = new issue_edit_contexts($context); - //if ($contexts->have_one_edit_tab_cap('issues')) { + // TODO: Check if the user has ANY question issue context capabilities. + // $contexts = new issue_edit_contexts($context); + // if ($contexts->have_one_edit_tab_cap('issues')) { $qtrackernode->add(get_string('issues', 'local_qtracker'), new moodle_url( $CFG->wwwroot . '/local/qtracker/view.php', $params ), navigation_node::TYPE_SETTING, null, 'issues'); - //} + // } } /** From 1949378f793bdc712873d405f4e53fca6bcd6ecc Mon Sep 17 00:00:00 2001 From: David Rise Knotten Date: Sat, 6 Feb 2021 17:58:16 +0100 Subject: [PATCH 33/83] Solved most PHPDoc checker issues --- .gitignore | 2 ++ classes/external/delete_issue.php | 17 ++++++++++--- classes/external/edit_issue.php | 19 ++++++++++++--- classes/external/get_issue.php | 15 ++++++++++-- classes/external/get_issues.php | 8 ++++++ classes/external/get_question.php | 15 ++++++++++-- classes/external/helper.php | 8 ++++++ classes/external/issue_comment_exporter.php | 4 +++ classes/external/issue_exporter.php | 4 +++ classes/external/new_issue.php | 20 ++++++++++++--- classes/form/view/question_details_form.php | 12 +++++++++ classes/issue.php | 27 +++++++++++++++++++++ classes/issue_comment.php | 18 +++++++++++++- classes/output/issue_registration_block.php | 12 ++++++--- classes/output/question_issue_page.php | 3 +++ classes/output/question_issues_page.php | 1 + classes/output/questions_page.php | 2 ++ classes/output/questions_table.php | 7 ++++++ classes/output/renderer.php | 2 ++ db/upgrade.php | 6 +++++ lang/en/local_qtracker.php | 2 ++ lib.php | 7 ++++++ version.php | 2 ++ view.php | 2 ++ 24 files changed, 198 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index a367c48..233cd7d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .vscode *.code-workspace + +.idea/ diff --git a/classes/external/delete_issue.php b/classes/external/delete_issue.php index 709c18f..73ae315 100644 --- a/classes/external/delete_issue.php +++ b/classes/external/delete_issue.php @@ -35,8 +35,15 @@ use external_single_structure; use external_warnings; use local_qtracker\issue; +use mysql_xdevapi\Result; - +/** + * Class delete_issue + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class delete_issue extends \external_api { /** @@ -52,8 +59,11 @@ public static function delete_issue_parameters() { } /** - * Returns welcome message - * @return string welcome message + * Deletes issue with id $issueid + * + * @param $issueid id of the issue to be deleted + * + * @return array $result containing status, the issueid and any warnings */ public static function delete_issue($issueid) { global $USER, $DB; @@ -100,6 +110,7 @@ public static function delete_issue($issueid) { /** * Returns description of method result value + * * @return external_description */ public static function delete_issue_returns() { diff --git a/classes/external/edit_issue.php b/classes/external/edit_issue.php index 5cce2ac..06cf6e9 100644 --- a/classes/external/edit_issue.php +++ b/classes/external/edit_issue.php @@ -36,7 +36,14 @@ use external_warnings; use local_qtracker\issue; - +/** + * edit_issue class + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class edit_issue extends \external_api { /** @@ -54,8 +61,13 @@ public static function edit_issue_parameters() { } /** - * Returns welcome message - * @return string welcome message + * Edits issue + * + * @param $issueid id of the issue to be edited + * @param $issuetitle new issue title + * @param $issuedescription new issue description + * + * @return array with status, issueid and any warnings */ public static function edit_issue($issueid, $issuetitle, $issuedescription) { global $USER, $DB; @@ -123,6 +135,7 @@ public static function edit_issue($issueid, $issuetitle, $issuedescription) { /** * Returns description of method result value + * * @return external_description */ public static function edit_issue_returns() { diff --git a/classes/external/get_issue.php b/classes/external/get_issue.php index 0eddbbf..0bc3489 100644 --- a/classes/external/get_issue.php +++ b/classes/external/get_issue.php @@ -36,6 +36,14 @@ use local_qtracker\issue; use local_qtracker\external\helper; +/** + * get_issue class + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class get_issue extends \external_api { /** @@ -52,8 +60,11 @@ public static function get_issue_parameters() { /** - * Returns welcome message - * @return string welcome message + * Returns issue with the id $issueid + * + * @param $issueid id of the issue to be returned + * + * @return array with status, the issuedata, and any warnings */ public static function get_issue($issueid) { global $USER, $DB; diff --git a/classes/external/get_issues.php b/classes/external/get_issues.php index 2674e54..d482a4d 100644 --- a/classes/external/get_issues.php +++ b/classes/external/get_issues.php @@ -38,6 +38,14 @@ use local_qtracker\external\helper; use local_qtracker\external\issue_exporter; +/** + * get_issues class + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class get_issues extends \external_api { /** diff --git a/classes/external/get_question.php b/classes/external/get_question.php index c0af0a1..d210548 100644 --- a/classes/external/get_question.php +++ b/classes/external/get_question.php @@ -35,6 +35,14 @@ use external_warnings; use local_qtracker\external\helper; +/** + * get_question class + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class get_question extends \external_api { /** @@ -51,8 +59,11 @@ public static function get_question_parameters() { /** - * Returns welcome message - * @return string welcome message + * Retrieves question + * + * @param $questionid id of the question to be retrieved + * + * @return array with status, summary of the question and any warnings */ public static function get_question($questionid) { global $PAGE, $USER; diff --git a/classes/external/helper.php b/classes/external/helper.php index 413d2b8..792a3f6 100644 --- a/classes/external/helper.php +++ b/classes/external/helper.php @@ -28,6 +28,14 @@ use external_value; use external_single_structure; +/** + * helper class + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class helper { /** * Create issue return value description. diff --git a/classes/external/issue_comment_exporter.php b/classes/external/issue_comment_exporter.php index 4c2d1e4..61a582e 100644 --- a/classes/external/issue_comment_exporter.php +++ b/classes/external/issue_comment_exporter.php @@ -30,6 +30,7 @@ use \core\external\exporter; use \renderer_base; + /** * Class for displaying a list of issue comment data. * @@ -65,6 +66,9 @@ protected static function define_other_properties() { ]; } + /** + * @return array + */ protected static function define_related() { return array( 'context' => 'context', diff --git a/classes/external/issue_exporter.php b/classes/external/issue_exporter.php index c30e9df..2b15257 100644 --- a/classes/external/issue_exporter.php +++ b/classes/external/issue_exporter.php @@ -80,6 +80,10 @@ protected static function define_other_properties() { ]; } + + /** + * @return array + */ protected static function define_related() { return array( 'context' => 'context', diff --git a/classes/external/new_issue.php b/classes/external/new_issue.php index e5995f5..c90ac87 100644 --- a/classes/external/new_issue.php +++ b/classes/external/new_issue.php @@ -36,7 +36,14 @@ use external_warnings; use local_qtracker\issue; - +/** + * new_issue class + * + * @package local_qtracker + * @author André Storhaug + * @copyright 2020 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class new_issue extends \external_api { /** @@ -56,8 +63,15 @@ public static function new_issue_parameters() { } /** - * Returns welcome message - * @return string welcome message + * Creates new issue + * + * @param $issuetitle new issues title + * @param $issuedescription new issues description + * @param $contextid + * @param $qubaid + * @param $slot + * + * @return array with status, issueid and any warnings */ public static function new_issue($qubaid, $slot, $contextid, $issuetitle, $issuedescription) { global $USER, $DB; diff --git a/classes/form/view/question_details_form.php b/classes/form/view/question_details_form.php index b0c6d72..d983a20 100644 --- a/classes/form/view/question_details_form.php +++ b/classes/form/view/question_details_form.php @@ -15,6 +15,8 @@ // along with Moodle. If not, see . /** + * Question form + * * @package local_qtracker * @author André Storhaug * @copyright 2020 NTNU @@ -28,17 +30,27 @@ require_once($CFG->libdir . '/formslib.php'); /** + * Question form + * * @package local_qtracker * @copyright 2020 André Storhaug * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class question_details_form extends \moodleform { + /** + * question_details_form constructor. + * @param $question + * @param \moodle_url $url + */ public function __construct($question, \moodle_url $url) { $this->question = $question; parent::__construct($url); } + /** + * + */ public function definition() { $mform = $this->_form; diff --git a/classes/issue.php b/classes/issue.php index 85d19ec..dce3726 100644 --- a/classes/issue.php +++ b/classes/issue.php @@ -13,6 +13,8 @@ // along with Moodle. If not, see . /** + * Issue + * * @package local_qtracker * @author André Storhaug * @copyright 2020 NTNU @@ -161,6 +163,8 @@ public function get_issue_obj() { /** * Returns a plain \stdClass with the issue data. * + * @param $description + * * @return \stdClass */ public function create_comment($description) { @@ -187,6 +191,12 @@ public function get_comments() { return $this->comments; } + /** + * Loads and returns issue with id $issueid + * + * @param int $issueid + * @return issue|null + */ public static function load(int $issueid) { global $DB; $issueobj = $DB->get_record('qtracker_issue', ['id' => $issueid]); @@ -199,6 +209,13 @@ public static function load(int $issueid) { /** * Creates a new issue. * + * @param $description + * @param $contextid + * @param null $slot + * @param null $quba + * @param \question_definition $question + * @param $title + * * @return issue */ public static function create($title, $description, \question_definition $question, $contextid, $quba = null, $slot = null) { @@ -272,12 +289,22 @@ public function delete() { return $DB->delete_records('qtracker_issue', array('id' => $this->get_id())); } + /** + * Sets this issues title to $title + * + * @param $title + */ public function set_title($title) { global $DB; $this->issue->title = $title; $DB->update_record('qtracker_issue', $this->issue); } + /** + * Sets this issues description to $title + * + * @param $title + */ public function set_description($title) { global $DB; $this->issue->description = $title; diff --git a/classes/issue_comment.php b/classes/issue_comment.php index 8c27a4e..271ba35 100644 --- a/classes/issue_comment.php +++ b/classes/issue_comment.php @@ -119,6 +119,13 @@ public function get_comments() { return $this->comment; } + /** + * Loads and returns issue_comment with id $comment + * + * @param $comment + * + * @return issue_comment + */ public static function load(int $comment) { global $DB; $commentobj = $DB->get_record('qtracker_comment', ['id' => $comment]); @@ -131,6 +138,9 @@ public static function load(int $comment) { /** * Creates a new comment. * + * @param $description + * @param issue $issue + * * @return issue_comment */ public static function create($description, issue $issue) { @@ -142,7 +152,6 @@ public static function create($description, issue $issue) { $commentobj->userid = $USER->id; $time = time(); $commentobj->timecreated = $time; - // $commentobj->timemodified = $time; // $commentobj->usermodified = $USER->id; $id = $DB->insert_record('qtracker_comment', $commentobj); @@ -162,6 +171,13 @@ public function delete() { return $DB->delete_records('qtracker_comment', array('id' => $this->get_id())); } + /** + * Sets description of this comment + * + * @param $title + * + * @return void + */ public function set_description($title) { global $DB; $this->comment->description = $title; diff --git a/classes/output/issue_registration_block.php b/classes/output/issue_registration_block.php index eb0b195..7aa4b14 100644 --- a/classes/output/issue_registration_block.php +++ b/classes/output/issue_registration_block.php @@ -59,11 +59,12 @@ class issue_registration_block implements renderable, templatable { // TODO: create an alternative (class) for registering issues that are not linked to an attempt.... + /** * Construct the contents of the block - * @param \question_definition[] $questions The questions that can be filed issues for. - * @param int $userid The id of the user. - * @throws \coding_exception If called at incorrect times + * @param \question_usage_by_activity $quba + * @param $slots + * @param $contextid */ public function __construct(\question_usage_by_activity $quba, $slots, $contextid) { @@ -79,6 +80,11 @@ public function __construct(\question_usage_by_activity $quba, $slots, $contexti $this->helpicon = new help_icon('question', 'local_qtracker'); } + /** + * Loads issues + * + * @return void + */ private function load_issues() { global $DB; diff --git a/classes/output/question_issue_page.php b/classes/output/question_issue_page.php index 6b3f8fb..4ec7ed5 100644 --- a/classes/output/question_issue_page.php +++ b/classes/output/question_issue_page.php @@ -52,14 +52,17 @@ class question_issue_page implements renderable, templatable { /** The default number of results to be shown per page. */ const DEFAULT_PAGE_SIZE = 20; + /** @var issue|null */ protected $questionissue = null; + /** @var array */ protected $courseid = []; /** * Construct this renderable. * * @param \local_qtracker\question_issues_table $questionissuestable + * @param courseid */ public function __construct(issue $questionissue, $courseid) { $this->questionissue = $questionissue; diff --git a/classes/output/question_issues_page.php b/classes/output/question_issues_page.php index 1a09060..767dbf6 100644 --- a/classes/output/question_issues_page.php +++ b/classes/output/question_issues_page.php @@ -46,6 +46,7 @@ class question_issues_page implements renderable, templatable { /** The default number of results to be shown per page. */ const DEFAULT_PAGE_SIZE = 20; + /** @var array|question_issues_table|\local_qtracker\question_issues_table */ protected $questionissuestable = []; /** diff --git a/classes/output/questions_page.php b/classes/output/questions_page.php index bbd8788..1ce4369 100644 --- a/classes/output/questions_page.php +++ b/classes/output/questions_page.php @@ -46,12 +46,14 @@ class questions_page implements renderable, templatable { /** The default number of results to be shown per page. */ const DEFAULT_PAGE_SIZE = 20; + /** @var array|questions_table|\local_qtracker\questions_table */ protected $questionstable = []; /** * Construct this renderable. * * @param \local_qtracker\questions_table $questionstable + * @param $courseid */ public function __construct(questions_table $questionstable, $courseid) { $this->questionstable = $questionstable; diff --git a/classes/output/questions_table.php b/classes/output/questions_table.php index 5de4715..694688d 100644 --- a/classes/output/questions_table.php +++ b/classes/output/questions_table.php @@ -46,6 +46,7 @@ class questions_table extends table_sql { * Sets up the table. * * @param string $uniqueid Unique id of table. + * @param $context * @param moodle_url $url The base URL. */ public function __construct($uniqueid, $url, $context) { @@ -197,6 +198,9 @@ protected function update_sql_after_count($fields, $from, $where, $params) { } + /** + * Not in use + */ public function wrap_html_start() { if ($this->is_downloading()) { return; @@ -211,6 +215,9 @@ public function wrap_html_start() { } + /** + * Not in use + */ public function wrap_html_finish() { global $PAGE; if ($this->is_downloading()) { diff --git a/classes/output/renderer.php b/classes/output/renderer.php index a9abdf7..7e1cf43 100644 --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -15,6 +15,8 @@ // along with Moodle. If not, see . /** + * Renderer + * * @package local_qtracker * @author André Storhaug * @copyright 2020 NTNU diff --git a/db/upgrade.php b/db/upgrade.php index cbe15db..0204158 100755 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -15,6 +15,8 @@ // along with Moodle. If not, see . /** + * Upgrade + * * @package local_qtracker * @author André Storhaug * @copyright 2020 NTNU @@ -23,6 +25,10 @@ defined('MOODLE_INTERNAL') || die(); +/** + * @param $oldversion + * @return bool + */ function xmldb_local_qtracker_upgrade($oldversion) { global $DB; $dbman = $DB->get_manager(); diff --git a/lang/en/local_qtracker.php b/lang/en/local_qtracker.php index 00d456d..0a62b48 100755 --- a/lang/en/local_qtracker.php +++ b/lang/en/local_qtracker.php @@ -15,6 +15,8 @@ // along with Moodle. If not, see . /** + * English keywords + * * @package local_qtracker * @author André Storhaug * @copyright 2020 NTNU diff --git a/lib.php b/lib.php index bfa302b..e8b3b06 100644 --- a/lib.php +++ b/lib.php @@ -15,6 +15,8 @@ // along with Moodle. If not, see . /** + * lib + * * @package local_qtracker * @author André Storhaug * @copyright 2020 NTNU @@ -102,6 +104,11 @@ function issue_has_capability_on($issueorid, $cap) { /** * Require capability on issue. + * + * @param $issue + * @param $cap + * + * @return boolean */ function issue_require_capability_on($issue, $cap) { if (!issue_has_capability_on($issue, $cap)) { diff --git a/version.php b/version.php index 0654eca..a9884ed 100755 --- a/version.php +++ b/version.php @@ -15,6 +15,8 @@ // along with Moodle. If not, see . /** + * Version data + * * @package local_qtracker * @author André Storhaug * @copyright 2020 NTNU diff --git a/view.php b/view.php index 1e1ee0e..5b3c8aa 100644 --- a/view.php +++ b/view.php @@ -15,6 +15,8 @@ // along with Moodle. If not, see . /** + * View + * * @package local_qtracker * @author André Storhaug * @copyright 2020 NTNU From c89ea7ecca465e44ec4db7443041a9609197ec21 Mon Sep 17 00:00:00 2001 From: David Rise Knotten Date: Mon, 8 Feb 2021 18:06:59 +0100 Subject: [PATCH 34/83] Solved most mustache-lint issues --- templates/button.mustache | 4 +++- templates/issue_comment.mustache | 3 ++- templates/issue_description.mustache | 6 ++++-- templates/issue_registration_block.mustache | 3 ++- templates/issues_pane_item.mustache | 2 +- templates/question_issue_page.mustache | 2 +- templates/select.mustache | 2 +- 7 files changed, 14 insertions(+), 8 deletions(-) diff --git a/templates/button.mustache b/templates/button.mustache index 05f3d68..112dcdc 100644 --- a/templates/button.mustache +++ b/templates/button.mustache @@ -26,7 +26,9 @@ "primary": true, "tooltip": "A button.", "label": "Button", - "disabled": false + "disabled": false, + "id": "SomeId", + "name": "SomeName" } }}
    diff --git a/templates/issue_comment.mustache b/templates/issue_comment.mustache index f079196..b0c22ec 100644 --- a/templates/issue_comment.mustache +++ b/templates/issue_comment.mustache @@ -22,11 +22,12 @@ Example context (json): { "new": true + "profileimageurl": "https://moodle.org/pix/u/f3.png" } }}
    - + User picture
    {{fullname}}  diff --git a/templates/issue_description.mustache b/templates/issue_description.mustache index c22f4eb..745375c 100644 --- a/templates/issue_description.mustache +++ b/templates/issue_description.mustache @@ -20,10 +20,12 @@ Issue description template. Example context (json): - {} + { + "profileimageurl": "https://moodle.org/pix/u/f3.png" + } }}
    - + User picture
    {{fullname}}  diff --git a/templates/issue_registration_block.mustache b/templates/issue_registration_block.mustache index d5d7cc2..f3973c6 100644 --- a/templates/issue_registration_block.mustache +++ b/templates/issue_registration_block.mustache @@ -39,7 +39,8 @@ { "name": "Option 2", "value": "2", "selected": false } ] }, - "qubaid": "10" + "qubaid": "10", + "uniqid": "1" } }}
    diff --git a/templates/issues_pane_item.mustache b/templates/issues_pane_item.mustache index 2f31718..3795332 100644 --- a/templates/issues_pane_item.mustache +++ b/templates/issues_pane_item.mustache @@ -21,7 +21,7 @@ Example context (json): { - "profileimageurl": "https://moodle.org/pix/u/f3.png" + "profileimageurl": "https://moodle.org/pix/u/f3.png", "fullname": "André Storhaug", "userurl": "https://example.com/user/profile.php?id=3", "timecreated": "1587655101", diff --git a/templates/question_issue_page.mustache b/templates/question_issue_page.mustache index 1a2b053..c02499c 100644 --- a/templates/question_issue_page.mustache +++ b/templates/question_issue_page.mustache @@ -25,7 +25,7 @@ "id": "2", "title": "Issue title", "description": "Issue description", - }, + } } }}
    diff --git a/templates/select.mustache b/templates/select.mustache index 20fb1c9..93e6a41 100644 --- a/templates/select.mustache +++ b/templates/select.mustache @@ -38,7 +38,7 @@ {{#helpicon}} {{>core/help_icon}} {{/helpicon}} - {{#options}} {{#optgroup}} From 3f699fca4ac8878d0d432102fcf65a05a670dd62 Mon Sep 17 00:00:00 2001 From: David Rise Knotten Date: Tue, 9 Feb 2021 01:09:52 +0100 Subject: [PATCH 35/83] Solved most Grunt issues up until line 131 --- amd/src/block_form_manager.js | 140 +++++++++++++++++----------------- amd/src/issue_manager.js | 25 +++--- 2 files changed, 82 insertions(+), 83 deletions(-) diff --git a/amd/src/block_form_manager.js b/amd/src/block_form_manager.js index f71d59a..f5ac7bd 100644 --- a/amd/src/block_form_manager.js +++ b/amd/src/block_form_manager.js @@ -24,7 +24,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/issue', 'local_qtracker/issue_manager'], - function ($, Str, Templates, Ajax, Issue, IssueManager) { + function($, Str, Templates, Ajax, Issue, IssueManager) { var SELECTORS = { SLOT: '[name="slot"]', SLOT_SELECT_OPTION: '[name="slot"] option', @@ -32,7 +32,7 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss DESCRIPTION: '[name="issuedescription"]', SUBMIT_BUTTON: 'button[type="submit"]', DELETE_BUTTON: '#qtracker-delete', - } + }; let VALIDATION_ELEMENTS = [ SELECTORS.TITLE, @@ -46,11 +46,12 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss * Constructor * * @param {String} selector used to find triggers for the new group modal. + * @param {string} issueids * @param {int} contextid * * Each call to init gets it's own instance of this class. */ - var BlockFormManager = function (selector, issueids, contextid) { + var BlockFormManager = function(selector, issueids, contextid) { this.contextid = contextid; this.form = $(selector); this.form.closest('.card-text').prepend(''); @@ -91,17 +92,16 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss /** * Initialise the class. * - * @param {String} selector used to find triggers for the new question issue. + * @param {*[]} issueids selector used to find triggers for the new question issue. * @private - * @return {Promise} */ - BlockFormManager.prototype.init = function (issueids = []) { + BlockFormManager.prototype.init = function(issueids = []) { // Init all slots let slots = $(SELECTORS.SLOT_SELECT_OPTION); - if (slots.length == 0) slots = $(SELECTORS.SLOT); + if (slots.length == 0) {slots = $(SELECTORS.SLOT);} slots.map((index, option) => { let issue = new Issue(null, parseInt(option.value), this.contextid); - issue.isSaved = false;//changeState(Issue.STATES.NEW); + issue.isSaved = false;// ChangeState(Issue.STATES.NEW); this.issueManager.addIssue(issue); }); @@ -111,10 +111,10 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss var formData = new FormData(this.form[0]); this.issueManager.setActiveIssue(parseInt(formData.get('slot'))); - this.reflectFormState() + this.reflectFormState(); // Issue title event listener. - let titleElement = this.form.find(SELECTORS.TITLE) + let titleElement = this.form.find(SELECTORS.TITLE); titleElement.change((event) => { this.issueManager.getActiveIssue().setTitle(event.target.value); }); @@ -123,7 +123,7 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss }); */ // Issue description event listener. - let descriptionElement = this.form.find(SELECTORS.DESCRIPTION) + let descriptionElement = this.form.find(SELECTORS.DESCRIPTION); descriptionElement.change((event) => { this.issueManager.getActiveIssue().setDescription(event.target.value); }); @@ -145,67 +145,67 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss }); }; - BlockFormManager.prototype.handleSlotChange = function (e) { - this.issueManager.setActiveIssue(parseInt(e.target.value)) + BlockFormManager.prototype.handleSlotChange = function(e) { + this.issueManager.setActiveIssue(parseInt(e.target.value)); this.reflectFormState(); - this.resetValidation() - } + this.resetValidation(); + }; - BlockFormManager.prototype.reflectFormState = function () { + BlockFormManager.prototype.reflectFormState = function() { let issue = this.issueManager.getActiveIssue(); - if (issue.isSaved === true) { //state === Issue.STATES.EXISTING) { + if (issue.isSaved === true) { // State === Issue.STATES.EXISTING) { this.toggleDeleteButton(true); this.toggleUpdateButton(true); - } else if (issue.isSaved === false) {//state === Issue.STATES.NEW) { + } else if (issue.isSaved === false) { // State === Issue.STATES.NEW) { this.clearForm(); } this.restoreForm(); - } + }; /** * @method handleFormSubmissionResponse + * @param response * @private - * @return {Promise} */ - BlockFormManager.prototype.handleFormSubmissionResponse = function (response) { + BlockFormManager.prototype.handleFormSubmissionResponse = function(response) { // TODO: handle response.status === false // TODO: handle response.warning ... // We could trigger an event instead. // Yuk. - console.log("jijjijij") - Y.use('moodle-core-formchangechecker', function () { + console.log("jijjijij"); + Y.use('moodle-core-formchangechecker', function() { M.core_formchangechecker.reset_form_dirty_state(); }); - //document.location.reload(); + // Document.location.reload(); this.issueManager.getActiveIssue().setId(response.issueid); }; /** * @method handleFormSubmissionFailure + * @param response * @private - * @return {Promise} */ - BlockFormManager.prototype.handleFormSubmissionFailure = function (response) { + BlockFormManager.prototype.handleFormSubmissionFailure = function(response) { // Oh noes! Epic fail :( // Ah wait - this is normal. We need to re-display the form with errors! console.error("An error occured"); console.error(response); }; - BlockFormManager.prototype.clearForm = function () { + BlockFormManager.prototype.clearForm = function() { // Remove delete button. this.form.find('#qtracker-delete').remove(); this.resetValidation(); - Str.get_string('submitnewissue', 'local_qtracker').then(function (string) { + Str.get_string('submitnewissue', 'local_qtracker').then(function(string) { this.form.find('button[type="submit"]').html(string); }.bind(this)); - } + }; - BlockFormManager.prototype.restoreForm = function () { + BlockFormManager.prototype.restoreForm = function() { let issue = this.issueManager.getActiveIssue(); this.form.find('[name="issuetitle"]').val(issue.getTitle()); this.form.find('[name="issuedescription"]').val(issue.getDescription()); @@ -215,9 +215,8 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss /** * @method editIssue * @private - * @return {Promise} */ - BlockFormManager.prototype.editIssue = function () { + BlockFormManager.prototype.editIssue = function() { var formData = new FormData(this.form[0]); Ajax.call([{ methodname: 'local_qtracker_edit_issue', @@ -226,8 +225,8 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss issuetitle: formData.get('issuetitle'), issuedescription: formData.get('issuedescription'), }, - done: function (response) { - Str.get_string('issueupdated', 'local_qtracker').then(function (string) { + done: function(response) { + Str.get_string('issueupdated', 'local_qtracker').then(function(string) { let notification = { message: string, announce: true, @@ -243,16 +242,15 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss /** * @method editIssue * @private - * @return {Promise} */ - BlockFormManager.prototype.deleteIssue = function () { + BlockFormManager.prototype.deleteIssue = function() { Ajax.call([{ methodname: 'local_qtracker_delete_issue', args: { issueid: this.issueManager.getActiveIssue().getId(), }, - done: function (response) { - Str.get_string('issuedeleted', 'local_qtracker').then(function (string) { + done: function() { + Str.get_string('issuedeleted', 'local_qtracker').then(function(string) { let notification = { message: string, announce: true, @@ -260,7 +258,7 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss }; this.notify(notification); }.bind(this)); - this.issueManager.getActiveIssue().isSaved = false;//changeState(Issue.STATES.NEW);; + this.issueManager.getActiveIssue().isSaved = false;// ChangeState(Issue.STATES.NEW);; this.clearForm(); }.bind(this), fail: this.handleFormSubmissionFailure.bind(this) @@ -270,9 +268,8 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss /** * @method handleFormSubmissionFailure * @private - * @return {Promise} */ - BlockFormManager.prototype.createIssue = function () { + BlockFormManager.prototype.createIssue = function() { var formData = new FormData(this.form[0]); // Now we can continue... Ajax.call([{ @@ -284,8 +281,8 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss issuetitle: formData.get('issuetitle'), issuedescription: formData.get('issuedescription'), }, - done: function (response) { - Str.get_string('issuecreated', 'local_qtracker').then(function (string) { + done: function(response) { + Str.get_string('issuecreated', 'local_qtracker').then(function(string) { let notification = { message: string, announce: true, @@ -293,8 +290,8 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss }; this.notify(notification); }.bind(this)); - this.issueManager.getActiveIssue().isSaved = true;//changeState(Issue.STATES.EXISTING) - //this.setAction(ACTION.EDITISSUE); + this.issueManager.getActiveIssue().isSaved = true;// ChangeState(Issue.STATES.EXISTING) + // This.setAction(ACTION.EDITISSUE); // TODO: add delete button. this.toggleUpdateButton(true); this.toggleDeleteButton(true); @@ -308,14 +305,14 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss /** * Cancel any typing pause timer. */ - BlockFormManager.prototype.cancelNotificationTimer = function () { + BlockFormManager.prototype.cancelNotificationTimer = function() { if (notificationTimeoutHandle) { clearTimeout(notificationTimeoutHandle); } notificationTimeoutHandle = null; - } + }; - BlockFormManager.prototype.notify = function (notification) { + BlockFormManager.prototype.notify = function(notification) { notification = $.extend({ closebutton: true, announce: true, @@ -343,31 +340,31 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss }, NOTIFICATION_DURATION); }) .catch((error) => { - console.error(error) + console.error(error); }); - } + }; /** * @method handleFormSubmissionFailure + * @param show * @private - * @return {Promise} */ - BlockFormManager.prototype.toggleUpdateButton = function (show) { + BlockFormManager.prototype.toggleUpdateButton = function(show) { if (show) { Str.get_string('update', 'core').then(function (updateStr) { this.form.find(SELECTORS.SUBMIT_BUTTON).html(updateStr); }.bind(this)); } else { - Str.get_string('submitnewissue', 'local_qtracker').then(function (updateStr) { + Str.get_string('submitnewissue', 'local_qtracker').then(function(updateStr) { this.form.find(SELECTORS.SUBMIT_BUTTON).html(updateStr); }.bind(this)); } - } + }; /** * @method handleFormSubmissionFailure + * @param show * @private - * @return {Promise} */ - BlockFormManager.prototype.toggleDeleteButton = function (show) { + BlockFormManager.prototype.toggleDeleteButton = function(show) { const context = { type: "button", classes: "col-auto", @@ -378,11 +375,11 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss let deleteButton = this.form.find(SELECTORS.DELETE_BUTTON); if (deleteButton.length == 0 && show) { Templates.render('local_qtracker/button', context) - .then(function (html, js) { + .then(function(html, js) { var container = this.form.find('button').closest(".form-row"); Templates.appendNodeContents(container, html, js); - this.form.find('#qtracker-delete').on('click', function () { - this.deleteIssue() + this.form.find('#qtracker-delete').on('click', function() { + this.deleteIssue(); }.bind(this)); }.bind(this)); } else { @@ -392,14 +389,14 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss deleteButton.hide(); } } - } + }; /** * @method handleFormSubmissionFailure + * @param newaction * @private - * @return {Promise} */ - BlockFormManager.prototype.setAction = function (newaction) { + BlockFormManager.prototype.setAction = function(newaction) { this.form.data('action', newaction); }; @@ -411,7 +408,7 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss * @private * @param {Event} e Form submission event. */ - BlockFormManager.prototype.submitFormAjax = function (e) { + BlockFormManager.prototype.submitFormAjax = function(e) { // We don't want to do a real form submission. e.preventDefault(); e.stopPropagation(); @@ -427,7 +424,7 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss this.createIssue(); } /* - var state = this.issueManager.getActiveIssue().getState(); + Var state = this.issueManager.getActiveIssue().getState(); switch (state) { case Issue.STATES.NEW: this.createIssue(); @@ -444,10 +441,10 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss }*/ }; - BlockFormManager.prototype.validateForm = function () { + BlockFormManager.prototype.validateForm = function() { let valid = true; VALIDATION_ELEMENTS.forEach(selector => { - let element = this.form.find(selector) + let element = this.form.find(selector); if (element.val() != "" && element.prop("validity").valid) { element.removeClass("is-invalid").addClass("is-valid"); } else { @@ -458,10 +455,10 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss return valid; }; - BlockFormManager.prototype.resetValidation = function () { + BlockFormManager.prototype.resetValidation = function() { VALIDATION_ELEMENTS.forEach(selector => { - let element = this.form.find(selector) - element.removeClass("is-invalid").removeClass("is-valid") + let element = this.form.find(selector); + element.removeClass("is-invalid").removeClass("is-valid"); }); }; @@ -472,7 +469,7 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss * @param {Event} e Form submission event. * @private */ - BlockFormManager.prototype.submitForm = function (e) { + BlockFormManager.prototype.submitForm = function(e) { e.preventDefault(); this.form.submit(); }; @@ -485,9 +482,10 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss * @method init * @param {string} selector The selector used to find the form for to use for this module. * @param {string} issueids The ids of existing issues to load. + * @param {int} contextid * @return {BlockFormManager} */ - init: function (selector, issueids, contextid) { + init: function(selector, issueids, contextid) { return new BlockFormManager(selector, issueids, contextid); } }; diff --git a/amd/src/issue_manager.js b/amd/src/issue_manager.js index 9fee83a..6c6f5d5 100644 --- a/amd/src/issue_manager.js +++ b/amd/src/issue_manager.js @@ -23,7 +23,7 @@ * @copyright 2020 NTNU * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define(['jquery', 'local_qtracker/issue'], function ($, Issue) { +define(['jquery', 'local_qtracker/issue'], function($, Issue) { /** * Constructor @@ -33,7 +33,7 @@ define(['jquery', 'local_qtracker/issue'], function ($, Issue) { * * Each call to init gets it's own instance of this class. */ - var IssueManager = function () {}; + var IssueManager = function() {}; /** * @var {Form} form @@ -43,11 +43,11 @@ define(['jquery', 'local_qtracker/issue'], function ($, Issue) { IssueManager.prototype.activeIssue = null; - IssueManager.prototype.getActiveIssue = function () { + IssueManager.prototype.getActiveIssue = function() { return this.activeIssue; }; - IssueManager.prototype.setActiveIssue = function (slot) { + IssueManager.prototype.setActiveIssue = function(slot) { let newIssue = this.getIssueBySlot(slot); this.activeIssue = newIssue; return newIssue; @@ -57,39 +57,40 @@ define(['jquery', 'local_qtracker/issue'], function ($, Issue) { * This triggers a form submission, so that any mform elements can do final tricks before the form submission is processed. * * @method submitForm - * @param {Event} e Form submission event. + * @param slot * @private + * @return slot */ - IssueManager.prototype.getIssueBySlot = function (slot) { + IssueManager.prototype.getIssueBySlot = function(slot) { return this.issues.get(slot); }; - IssueManager.prototype.getIssueById = function (id) { + IssueManager.prototype.getIssueById = function(id) { for (const [slot, issue] of this.issues) { if (issue.getId() !== null && issue.getId() === id) { return issue; } - }; + } return false; }; - IssueManager.prototype.addIssue = function (issue) { + IssueManager.prototype.addIssue = function(issue) { this.issues.set(issue.getSlot(), issue); }; - IssueManager.prototype.loadIssues = function (issueids = []) { + IssueManager.prototype.loadIssues = function(issueids = []) { let promises = []; for (let i = 0; i < issueids.length; i++) { const id = issueids[i]; let promise = Issue.load(id).then((response) => { - let issue = this.getIssueBySlot(response.issue.slot) + let issue = this.getIssueBySlot(response.issue.slot); if (!issue) { issue = new Issue(response.issue.id, response.issue.slot); } issue.setId(response.issue.id); issue.setTitle(response.issue.title); issue.setDescription(response.issue.description); - issue.isSaved = true;//changeState(Issue.STATES.EXISTING); + issue.isSaved = true;// ChangeState(Issue.STATES.EXISTING); this.addIssue(issue); }); promises.push(promise); From 7bd33c6d68d86d047e92f901e1b8dfa374c083f1 Mon Sep 17 00:00:00 2001 From: David Rise Knotten Date: Wed, 10 Feb 2021 14:43:36 +0100 Subject: [PATCH 36/83] Solved most of the grunt issues after line 131 --- amd/src/issue.js | 37 +++++++++++++++++++------------------ amd/src/issue_manager.js | 2 +- amd/src/questions_table.js | 36 +++++++++++++++++++----------------- 3 files changed, 39 insertions(+), 36 deletions(-) diff --git a/amd/src/issue.js b/amd/src/issue.js index 2168b97..433e982 100644 --- a/amd/src/issue.js +++ b/amd/src/issue.js @@ -23,17 +23,18 @@ * @copyright 2020 NTNU * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define(['jquery', 'core/str', 'core/ajax'], function ($, Str, Ajax) { +define(['jquery', 'core/str', 'core/ajax'], function($, Str, Ajax) { /** * Constructor * - * @param {String} selector used to find triggers for the new group modal. + * @param {int} slot + * @param id * @param {int} contextid * * Each call to init gets it's own instance of this class. */ - var Issue = function (id = null, slot = null, contextid) { + var Issue = function(id = null, slot = null, contextid) { this.id = id; this.slot = slot; this.contextid = contextid; @@ -43,7 +44,7 @@ define(['jquery', 'core/str', 'core/ajax'], function ($, Str, Ajax) { NEW: "new", OPEN: "open", CLOSED: "closed", - } + }; /** * @var {int} id The id of this issue @@ -78,11 +79,10 @@ define(['jquery', 'core/str', 'core/ajax'], function ($, Str, Ajax) { /** * Initialise the class. * - * @param {String} selector used to find triggers for the new group modal. * @private - * @return {Promise} + * @param {int} id */ - Issue.prototype.setId = function (id) { + Issue.prototype.setId = function(id) { this.id = id; }; @@ -93,49 +93,50 @@ define(['jquery', 'core/str', 'core/ajax'], function ($, Str, Ajax) { * @private * @return {Promise} */ - Issue.prototype.getId = function () { + Issue.prototype.getId = function() { return this.id; }; - Issue.prototype.getSlot = function () { + Issue.prototype.getSlot = function() { return this.slot; }; - Issue.prototype.getTitle = function () { + Issue.prototype.getTitle = function() { return this.title; }; - Issue.prototype.setTitle = function (title) { + Issue.prototype.setTitle = function(title) { this.title = title; }; - Issue.prototype.getDescription = function () { + Issue.prototype.getDescription = function() { return this.description; }; - Issue.prototype.setDescription = function (description) { + Issue.prototype.setDescription = function(description) { this.description = description; }; - Issue.prototype.changeState = function (state) { + Issue.prototype.changeState = function(state) { this.state = state; }; - Issue.prototype.getState = function () { + Issue.prototype.getState = function() { return this.state; }; - Issue.prototype.getContextid = function () { + Issue.prototype.getContextid = function() { return this.contextid; }; /** - * return {Promise} + * @return {Promise} + * @param {int} id */ Issue.load = function (id) { return Ajax.call([ - { methodname: 'local_qtracker_get_issue', args: { issueid: id} } + {methodname: 'local_qtracker_get_issue', args: {issueid: id}} ])[0]; }; diff --git a/amd/src/issue_manager.js b/amd/src/issue_manager.js index 6c6f5d5..08aa695 100644 --- a/amd/src/issue_manager.js +++ b/amd/src/issue_manager.js @@ -59,7 +59,7 @@ define(['jquery', 'local_qtracker/issue'], function($, Issue) { * @method submitForm * @param slot * @private - * @return slot + * @return */ IssueManager.prototype.getIssueBySlot = function(slot) { return this.issues.get(slot); diff --git a/amd/src/questions_table.js b/amd/src/questions_table.js index d705fc2..705eccd 100644 --- a/amd/src/questions_table.js +++ b/amd/src/questions_table.js @@ -61,12 +61,12 @@ class QuestionsTable { Templates.replaceNodeContents('#questions-table-sidebar', html, js); }); - window.showIssuesInPane = async function (id, state = null) { + window.showIssuesInPane = async function(id, state = null) { $('.issues-pane-content .issues').empty(); $('.issues-pane-content .loading').addClass("show"); // Get question title. - let questionData = await this.loadQuestionData(id) + let questionData = await this.loadQuestionData(id); let question = questionData.question; let questionEditUrl = this.getQuestionEditUrl(this.courseid, id); let link = $('').attr("href", questionEditUrl).html(question.name + " #" + question.id); @@ -76,34 +76,35 @@ class QuestionsTable { let issuesResponse = await this.loadIssues(id, state); let issues = issuesResponse.issues; - if (hidden) lol(); + if (hidden) {lol();} // Get users data. let userids = [...new Set(issues.map(issue => issue.userid))]; let usersData = await this.loadUsersData(userids); // Render issue items. - let promises = [] + let promises = []; issues.forEach(async issueData => { - let userData = usersData.find( ({ id }) => id === issueData.userid ); + let userData = usersData.find(({id}) => id === issueData.userid); promises.push(this.addIssueItem(issueData, userData)); }); // When all issue item promises are resolved. - $.when.apply($, promises).done(function () { + $.when.apply($, promises).done(function() { $('.issues-pane-content .loading').removeClass("show"); $.each(arguments, (index, argument) => { Templates.appendNodeContents('.issues-pane-content .issues', argument.html, argument.js); }); }).catch(e => { - console.error(e) - }) + console.error(e); + }); }.bind(this); - window.closeIssuesPane = function () { - if (!hidden) + window.closeIssuesPane = function() { + if (!hidden) { lol(); + } }; window.lol = function togglePane() { @@ -116,7 +117,8 @@ class QuestionsTable { /** * * @param {*} issueData - * @return Promise + * @param userData + * @return {Promise} */ async addIssueItem(issueData, userData) { // Fetch user data. @@ -129,7 +131,7 @@ class QuestionsTable { id: userData.id, }); - //Render issues pane + // Render issues pane let paneContext = { issueurl: issueurl, userurl: userurl, @@ -143,21 +145,21 @@ class QuestionsTable { paneContext[state] = true; return Templates.render('local_qtracker/issues_pane_item', paneContext) - .then(function (html, js) { - return { html: html, js: js }; + .then(function(html, js) { + return {html: html, js: js}; }); } async loadIssues(id, state = null) { let criteria = [ - { key: 'questionid', value: id }, + {key: 'questionid', value: id}, ]; if (state) { - criteria.push({ key: 'state', value: state }); + criteria.push({key: 'state', value: state}); } let issuesData = await Ajax.call([{ methodname: 'local_qtracker_get_issues', - args: { criteria: criteria } + args: {criteria: criteria} }])[0]; return issuesData; From 9772c033481f7d3a0803ef51e6a0b5036ae05418 Mon Sep 17 00:00:00 2001 From: David Rise Knotten Date: Thu, 11 Feb 2021 14:01:21 +0100 Subject: [PATCH 37/83] Solved a few additional issues --- classes/form/view/question_details_form.php | 1 + styles.css | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/classes/form/view/question_details_form.php b/classes/form/view/question_details_form.php index d983a20..465fda0 100644 --- a/classes/form/view/question_details_form.php +++ b/classes/form/view/question_details_form.php @@ -48,6 +48,7 @@ public function __construct($question, \moodle_url $url) { parent::__construct($url); } + /** * */ diff --git a/styles.css b/styles.css index cd29e3d..b416b52 100644 --- a/styles.css +++ b/styles.css @@ -62,7 +62,7 @@ position: absolute; top: calc(-1.25rem - 2px); right: calc(-1.25rem); - height: calc(100% + 2*1.25rem + 2px); + height: calc(100% + 2 * 1.25rem + 2px); border: unset; border-left: 1px solid #dee2e6; box-shadow: -3px 0 5px rgba(36, 41, 46, .05); From 2d0b8efd6f51fc56ab754066b526cd225d2d1e78 Mon Sep 17 00:00:00 2001 From: David Rise Knotten Date: Thu, 11 Feb 2021 15:43:51 +0100 Subject: [PATCH 38/83] Fixed moodle licensing paragraph and some PHPdocs --- classes/external/delete_issue.php | 2 ++ classes/external/edit_issue.php | 8 +++++--- classes/external/get_issue.php | 6 +++--- classes/external/get_issues.php | 2 ++ classes/external/get_question.php | 2 ++ classes/external/helper.php | 2 ++ classes/external/new_issue.php | 2 ++ classes/form/view/question_details_form.php | 6 +++--- classes/issue.php | 2 ++ classes/issue_comment.php | 2 ++ classes/output/question_issue_page.php | 8 ++++---- lib.php | 10 +++------- 12 files changed, 32 insertions(+), 20 deletions(-) diff --git a/classes/external/delete_issue.php b/classes/external/delete_issue.php index 73ae315..ff426a7 100644 --- a/classes/external/delete_issue.php +++ b/classes/external/delete_issue.php @@ -1,4 +1,6 @@ question = $question; @@ -50,7 +50,7 @@ public function __construct($question, \moodle_url $url) { /** - * + * Defines form */ public function definition() { $mform = $this->_form; diff --git a/classes/issue.php b/classes/issue.php index dce3726..0a663e5 100644 --- a/classes/issue.php +++ b/classes/issue.php @@ -1,4 +1,6 @@ questionname = $question->name; $questiondata->preview_url = question_preview_url($question->id, null, null, null, null, $context); - $edit_url = new \moodle_url('/question/question.php'); - $edit_url->param('id', $question->id); - $edit_url->param('courseid', $this->courseid); - $questiondata->edit_url = $edit_url; + $editurl = new \moodle_url('/question/question.php'); + $editurl->param('id', $question->id); + $editurl->param('courseid', $this->courseid); + $questiondata->edit_url = $editurl; $form = new question_details_form($question, $PAGE->url); $questiondata->questiontext = $form->render(); diff --git a/lib.php b/lib.php index e8b3b06..9d5ef39 100644 --- a/lib.php +++ b/lib.php @@ -54,13 +54,10 @@ function local_qtracker_extend_navigation_course($navigation, $course, $context) ); // TODO: Check if the user has ANY question issue context capabilities. - // $contexts = new issue_edit_contexts($context); - // if ($contexts->have_one_edit_tab_cap('issues')) { $qtrackernode->add(get_string('issues', 'local_qtracker'), new moodle_url( $CFG->wwwroot . '/local/qtracker/view.php', $params ), navigation_node::TYPE_SETTING, null, 'issues'); - // } } /** @@ -68,7 +65,6 @@ function local_qtracker_extend_navigation_course($navigation, $course, $context) * * @param mixed $issueorid object or id. If an object is passed, it should include ->contextid and ->userid. * @param string $cap 'add', 'edit', 'view'. - * @param integer $notused no longer used. * @return boolean this user has the capability $cap for this issue $issue? */ function issue_has_capability_on($issueorid, $cap) { @@ -105,10 +101,10 @@ function issue_has_capability_on($issueorid, $cap) { /** * Require capability on issue. * - * @param $issue - * @param $cap + * @param mixed $issue object or id. If an object is passed, it should include ->contextid and ->userid. + * @param string $cap 'add', 'edit', 'view'. * - * @return boolean + * @return boolean this user has the capability $cap for this issue $issue? */ function issue_require_capability_on($issue, $cap) { if (!issue_has_capability_on($issue, $cap)) { From 492b5f07d128e19d26f23955f5ab61af0b18a211 Mon Sep 17 00:00:00 2001 From: David Rise Knotten Date: Thu, 11 Feb 2021 21:04:04 +0100 Subject: [PATCH 39/83] Solved some PHPdoc parameter issues --- classes/external/delete_issue.php | 2 +- classes/external/get_issue.php | 1 + classes/external/get_question.php | 2 +- classes/external/new_issue.php | 10 +++++----- classes/issue.php | 14 +++++++------- classes/issue_comment.php | 8 +++++--- classes/output/issue_registration_block.php | 2 +- classes/output/question_issue_page.php | 4 ++-- classes/output/questions_page.php | 2 +- classes/output/questions_table.php | 6 +++--- 10 files changed, 27 insertions(+), 24 deletions(-) diff --git a/classes/external/delete_issue.php b/classes/external/delete_issue.php index ff426a7..0e238cf 100644 --- a/classes/external/delete_issue.php +++ b/classes/external/delete_issue.php @@ -63,7 +63,7 @@ public static function delete_issue_parameters() { /** * Deletes issue with id $issueid * - * @param $issueid id of the issue to be deleted + * @param int $issueid id of the issue to be deleted * * @return array $result containing status, the issueid and any warnings */ diff --git a/classes/external/get_issue.php b/classes/external/get_issue.php index 340fb39..568303d 100644 --- a/classes/external/get_issue.php +++ b/classes/external/get_issue.php @@ -64,6 +64,7 @@ public static function get_issue_parameters() { * Returns issue with the id $issueid * * @param int $issueid id of the issue to be returned + * * @return array with status, the issuedata, and any warnings */ public static function get_issue($issueid) { diff --git a/classes/external/get_question.php b/classes/external/get_question.php index a91f990..306374d 100644 --- a/classes/external/get_question.php +++ b/classes/external/get_question.php @@ -63,7 +63,7 @@ public static function get_question_parameters() { /** * Retrieves question * - * @param $questionid id of the question to be retrieved + * @param int $questionid id of the question to be retrieved * * @return array with status, summary of the question and any warnings */ diff --git a/classes/external/new_issue.php b/classes/external/new_issue.php index 577a8e9..27dda42 100644 --- a/classes/external/new_issue.php +++ b/classes/external/new_issue.php @@ -67,11 +67,11 @@ public static function new_issue_parameters() { /** * Creates new issue * - * @param $issuetitle new issues title - * @param $issuedescription new issues description - * @param $contextid - * @param $qubaid - * @param $slot + * @param string $issuetitle new issues title + * @param string $issuedescription new issues description + * @param int $contextid new issues context id + * @param int $qubaid new issues quba id + * @param int $slot new issues slot * * @return array with status, issueid and any warnings */ diff --git a/classes/issue.php b/classes/issue.php index 0a663e5..54ba2cf 100644 --- a/classes/issue.php +++ b/classes/issue.php @@ -15,7 +15,7 @@ // along with Moodle. If not, see . /** - * Issue + * Issue class * * @package local_qtracker * @author André Storhaug @@ -165,7 +165,7 @@ public function get_issue_obj() { /** * Returns a plain \stdClass with the issue data. * - * @param $description + * @param string $description * * @return \stdClass */ @@ -211,12 +211,12 @@ public static function load(int $issueid) { /** * Creates a new issue. * - * @param $description - * @param $contextid + * @param string $description + * @param int $contextid * @param null $slot * @param null $quba * @param \question_definition $question - * @param $title + * @param string $title * * @return issue */ @@ -294,7 +294,7 @@ public function delete() { /** * Sets this issues title to $title * - * @param $title + * @param string $title */ public function set_title($title) { global $DB; @@ -305,7 +305,7 @@ public function set_title($title) { /** * Sets this issues description to $title * - * @param $title + * @param string $title */ public function set_description($title) { global $DB; diff --git a/classes/issue_comment.php b/classes/issue_comment.php index 79109e6..b67277a 100644 --- a/classes/issue_comment.php +++ b/classes/issue_comment.php @@ -15,6 +15,8 @@ // along with Moodle. If not, see . /** + * Issue comment class + * * @package local_qtracker * @author André Storhaug * @copyright 2020 NTNU @@ -124,7 +126,7 @@ public function get_comments() { /** * Loads and returns issue_comment with id $comment * - * @param $comment + * @param int $comment * * @return issue_comment */ @@ -140,7 +142,7 @@ public static function load(int $comment) { /** * Creates a new comment. * - * @param $description + * @param string $description * @param issue $issue * * @return issue_comment @@ -176,7 +178,7 @@ public function delete() { /** * Sets description of this comment * - * @param $title + * @param string $title * * @return void */ diff --git a/classes/output/issue_registration_block.php b/classes/output/issue_registration_block.php index 7aa4b14..2a8f8cc 100644 --- a/classes/output/issue_registration_block.php +++ b/classes/output/issue_registration_block.php @@ -42,7 +42,7 @@ */ class issue_registration_block implements renderable, templatable { - /** @var \question_definition[] Array of {@link \question_definition} */ + /** @var \question_definition[] Array of {@link https://docs.moodle.org/dev/Question_types} */ public $questions = array(); /** @var \question_usage_by_activity */ diff --git a/classes/output/question_issue_page.php b/classes/output/question_issue_page.php index b86d5d4..f126919 100644 --- a/classes/output/question_issue_page.php +++ b/classes/output/question_issue_page.php @@ -72,8 +72,8 @@ public function __construct(issue $questionissue, $courseid) { /** * Export this data so it can be used as the context for a mustache template. * - * @param renderer_base $output - * @return stdClass + * @param renderer_base $output render base + * @return stdClass $data containing the questions data * @throws coding_exception * @throws dml_exception * @throws moodle_exception diff --git a/classes/output/questions_page.php b/classes/output/questions_page.php index 1ce4369..fae6423 100644 --- a/classes/output/questions_page.php +++ b/classes/output/questions_page.php @@ -53,7 +53,7 @@ class questions_page implements renderable, templatable { * Construct this renderable. * * @param \local_qtracker\questions_table $questionstable - * @param $courseid + * @param int $courseid the id of the course */ public function __construct(questions_table $questionstable, $courseid) { $this->questionstable = $questionstable; diff --git a/classes/output/questions_table.php b/classes/output/questions_table.php index 694688d..862030d 100644 --- a/classes/output/questions_table.php +++ b/classes/output/questions_table.php @@ -46,7 +46,7 @@ class questions_table extends table_sql { * Sets up the table. * * @param string $uniqueid Unique id of table. - * @param $context + * @param \context_course $context * @param moodle_url $url The base URL. */ public function __construct($uniqueid, $url, $context) { @@ -96,8 +96,8 @@ protected function col_name($data) { /** * Generate the display of the new, open and close column - * @param $cols extra_colums (new, open and close) - * @param $data the table row being output + * @param string $cols extra_colums (new, open and close) + * @param object $data the table row being output * @return |null string html content to go inside the td. */ public function other_cols($cols, $data) { From 79c6a8b37ed6cfa881f4ccb718caa0ad259f5c2c Mon Sep 17 00:00:00 2001 From: David Rise Knotten Date: Fri, 12 Feb 2021 15:16:15 +0100 Subject: [PATCH 40/83] Misc fixes --- amd/src/block_form_manager.js | 7 +++++-- amd/src/issue.js | 4 ++-- amd/src/questions_table.js | 8 +++++--- templates/issue_comment.mustache | 2 +- templates/issue_registration_block.mustache | 1 + templates/issues_pane_item.mustache | 3 +-- templates/question_issue_page.mustache | 2 +- 7 files changed, 16 insertions(+), 11 deletions(-) diff --git a/amd/src/block_form_manager.js b/amd/src/block_form_manager.js index f5ac7bd..7464658 100644 --- a/amd/src/block_form_manager.js +++ b/amd/src/block_form_manager.js @@ -98,7 +98,9 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss BlockFormManager.prototype.init = function(issueids = []) { // Init all slots let slots = $(SELECTORS.SLOT_SELECT_OPTION); - if (slots.length == 0) {slots = $(SELECTORS.SLOT);} + if (slots.length == 0) { + slots = $(SELECTORS.SLOT); + } slots.map((index, option) => { let issue = new Issue(null, parseInt(option.value), this.contextid); issue.isSaved = false;// ChangeState(Issue.STATES.NEW); @@ -341,6 +343,7 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss }) .catch((error) => { console.error(error); + throw error; }); }; /** @@ -350,7 +353,7 @@ define(['jquery', 'core/str', 'core/templates', 'core/ajax', 'local_qtracker/iss */ BlockFormManager.prototype.toggleUpdateButton = function(show) { if (show) { - Str.get_string('update', 'core').then(function (updateStr) { + Str.get_string('update', 'core').then(function(updateStr) { this.form.find(SELECTORS.SUBMIT_BUTTON).html(updateStr); }.bind(this)); } else { diff --git a/amd/src/issue.js b/amd/src/issue.js index 433e982..9128719 100644 --- a/amd/src/issue.js +++ b/amd/src/issue.js @@ -28,8 +28,8 @@ define(['jquery', 'core/str', 'core/ajax'], function($, Str, Ajax) { /** * Constructor * + * @param {int} id * @param {int} slot - * @param id * @param {int} contextid * * Each call to init gets it's own instance of this class. @@ -134,7 +134,7 @@ define(['jquery', 'core/str', 'core/ajax'], function($, Str, Ajax) { * @return {Promise} * @param {int} id */ - Issue.load = function (id) { + Issue.load = function(id) { return Ajax.call([ {methodname: 'local_qtracker_get_issue', args: {issueid: id}} ])[0]; diff --git a/amd/src/questions_table.js b/amd/src/questions_table.js index 705eccd..0decb90 100644 --- a/amd/src/questions_table.js +++ b/amd/src/questions_table.js @@ -76,7 +76,9 @@ class QuestionsTable { let issuesResponse = await this.loadIssues(id, state); let issues = issuesResponse.issues; - if (hidden) {lol();} + if (hidden) { + lol(); + } // Get users data. let userids = [...new Set(issues.map(issue => issue.userid))]; @@ -116,8 +118,8 @@ class QuestionsTable { /** * - * @param {*} issueData - * @param userData + * @param {object} issueData + * @param {object} userData * @return {Promise} */ async addIssueItem(issueData, userData) { diff --git a/templates/issue_comment.mustache b/templates/issue_comment.mustache index b0c22ec..fa9c780 100644 --- a/templates/issue_comment.mustache +++ b/templates/issue_comment.mustache @@ -21,7 +21,7 @@ Example context (json): { - "new": true + "new": true, "profileimageurl": "https://moodle.org/pix/u/f3.png" } }} diff --git a/templates/issue_registration_block.mustache b/templates/issue_registration_block.mustache index f3973c6..9d9e981 100644 --- a/templates/issue_registration_block.mustache +++ b/templates/issue_registration_block.mustache @@ -33,6 +33,7 @@ "name": "test", "hasmultiple": true, "select": { + "id": "someId", "name": "questionid", "options": [ { "name": "Option 1", "value": "1", "selected": true }, diff --git a/templates/issues_pane_item.mustache b/templates/issues_pane_item.mustache index 3795332..b9979f9 100644 --- a/templates/issues_pane_item.mustache +++ b/templates/issues_pane_item.mustache @@ -31,7 +31,7 @@ }}