From 79ead93dcb7f21fbeaa14198778a171364902bd9 Mon Sep 17 00:00:00 2001 From: raja Date: Fri, 13 Dec 2024 10:29:32 +0530 Subject: [PATCH] Include: Pulse 2.2 features. - PLS-763 --- amd/build/confirmcompletion.min.js | 10 ++ amd/build/confirmcompletion.min.js.map | 1 + amd/src/confirmcompletion.js | 114 ++++++++++++++++++ backup/moodle2/backup_pulse_stepslib.php | 5 +- .../restore_pulse_activity_task.class.php | 2 +- backup/moodle2/restore_pulse_stepslib.php | 1 + classes/completion/custom_completion.php | 3 +- classes/extendpro.php | 11 +- classes/external.php | 67 ++++++++++ classes/helper.php | 95 ++++++++++++++- classes/privacy/provider.php | 8 +- db/install.xml | 4 + db/services.php | 9 ++ db/upgrade.php | 30 +++++ lang/en/pulse.php | 29 +++++ lib.php | 62 +++++++++- mod_form.php | 74 ++++++++++++ settings.php | 25 ++++ version.php | 2 +- view.php | 2 +- 20 files changed, 532 insertions(+), 22 deletions(-) create mode 100644 amd/build/confirmcompletion.min.js create mode 100644 amd/build/confirmcompletion.min.js.map create mode 100644 amd/src/confirmcompletion.js diff --git a/amd/build/confirmcompletion.min.js b/amd/build/confirmcompletion.min.js new file mode 100644 index 0000000..0d62f2f --- /dev/null +++ b/amd/build/confirmcompletion.min.js @@ -0,0 +1,10 @@ +/** + * Manual completion confirmation options. + * + * @module mod_pulse/confirmcompletion + * @copyright 2023, bdecent gmbh bdecent.de + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define("mod_pulse/confirmcompletion",["jquery","core/str","core/modal_factory","core/notification","core/ajax","core/fragment","core/modal_events"],(function($,Str,ModalFactory,notification,Ajax,Fragment,ModalEvents){const getModal=function(id,contextid){var args={id:id};ModalFactory.create({type:ModalFactory.types.SAVE_CANCEL,title:Str.get_string("confirmation","pulse"),body:"",large:!1}).then((function(modal){return modal.show(),Fragment.loadFragment("mod_pulse","get_confirmation_content",contextid,args).done(((html,js)=>(modal.setBody(html),html))).catch(notification.exception),modal.setButtonText("save",Str.get_string("yes")),modal.getRoot().on(ModalEvents.save,(function(e){e.preventDefault(),submitformdata(args),modal.getRoot().find("form").submit(),modal.hide()})),modal.getRoot().on(ModalEvents.hidden,(function(){modal.destroy()})),modal})).catch(notification.exception)},submitformdata=function(params){Ajax.call([{methodname:"mod_pulse_manual_completion",args:params,done:function(response){window.location.reload(),response.message&¬ification.addNotification({message:response.message,type:"success"})}}])};return{init:function(contextid){!function(contextid){document.body.classList.contains("path-course-view")&&document.querySelectorAll(".pulse-user-manualcompletion-btn").forEach((function(element){element.addEventListener("click",(function(e){var id=e.target.className.match(/confirmation-(\d+)/);id&&(id=id[1],getModal(id,contextid))}))}))}(contextid)}}})); + +//# sourceMappingURL=confirmcompletion.min.js.map \ No newline at end of file diff --git a/amd/build/confirmcompletion.min.js.map b/amd/build/confirmcompletion.min.js.map new file mode 100644 index 0000000..858b72a --- /dev/null +++ b/amd/build/confirmcompletion.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"confirmcompletion.min.js","sources":["../src/confirmcompletion.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Manual completion confirmation options.\n *\n * @module mod_pulse/confirmcompletion\n * @copyright 2023, bdecent gmbh bdecent.de\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine([\"jquery\", 'core/str', \"core/modal_factory\", 'core/notification', 'core/ajax', \"core/fragment\", 'core/modal_events'],\n (function($, Str, ModalFactory, notification, Ajax, Fragment, ModalEvents) {\n\n /**\n * Show mark as completion button confirmation modal.\n * @param {init} contextid\n */\n const buttonConfirmation = function(contextid) {\n if (document.body.classList.contains('path-course-view')) {\n var buttons = document.querySelectorAll('.pulse-user-manualcompletion-btn');\n buttons.forEach(function(element) {\n element.addEventListener('click', function(e) {\n var classList = e.target.className;\n var id = classList.match(/confirmation-(\\d+)/);\n if (id) {\n id = id[1];\n getModal(id, contextid);\n }\n });\n });\n }\n };\n\n /**\n * Get the activity completion confirmation modal.\n *\n * @param {array} id instance id\n * @param {int} contextid Context ID\n */\n const getModal = function(id, contextid) {\n var args = {id: id};\n\n ModalFactory.create({\n type: ModalFactory.types.SAVE_CANCEL,\n title: Str.get_string('confirmation', 'pulse'),\n body: '',\n large: false\n }).then(function(modal) {\n modal.show();\n\n Fragment.loadFragment('mod_pulse', 'get_confirmation_content', contextid, args)\n .done((html, js) => {\n modal.setBody(html);\n return html;\n })\n .catch(notification.exception);\n\n modal.setButtonText('save', Str.get_string('yes'));\n\n modal.getRoot().on(ModalEvents.save, function(e) {\n e.preventDefault();\n submitformdata(args);\n modal.getRoot().find('form').submit();\n modal.hide();\n });\n\n modal.getRoot().on(ModalEvents.hidden, function() {\n modal.destroy();\n });\n\n return modal;\n }).catch(notification.exception);\n };\n\n /**\n * Submit and recieve the message form the modal confirmation on the activity completion.\n *\n * @param {string} params\n */\n const submitformdata = function(params) {\n Ajax.call([{\n methodname: 'mod_pulse_manual_completion',\n args: params,\n done: function(response) {\n window.location.reload();\n if (response.message) {\n notification.addNotification({\n message: response.message,\n type: \"success\"\n });\n }\n }\n }]);\n };\n\n return {\n init: function(contextid) {\n buttonConfirmation(contextid);\n },\n };\n}));"],"names":["define","$","Str","ModalFactory","notification","Ajax","Fragment","ModalEvents","getModal","id","contextid","args","create","type","types","SAVE_CANCEL","title","get_string","body","large","then","modal","show","loadFragment","done","html","js","setBody","catch","exception","setButtonText","getRoot","on","save","e","preventDefault","submitformdata","find","submit","hide","hidden","destroy","params","call","methodname","response","window","location","reload","message","addNotification","init","document","classList","contains","querySelectorAll","forEach","element","addEventListener","target","className","match","buttonConfirmation"],"mappings":";;;;;;;AAuBAA,qCAAO,CAAC,SAAU,WAAY,qBAAsB,oBAAqB,YAAa,gBAAiB,sBAClG,SAASC,EAAGC,IAAKC,aAAcC,aAAcC,KAAMC,SAAUC,mBA4BxDC,SAAW,SAASC,GAAIC,eACtBC,KAAO,CAACF,GAAIA,IAEhBN,aAAaS,OAAO,CAChBC,KAAMV,aAAaW,MAAMC,YACzBC,MAAOd,IAAIe,WAAW,eAAgB,SACtCC,KAAM,GACNC,OAAO,IACRC,MAAK,SAASC,cACbA,MAAMC,OAENhB,SAASiB,aAAa,YAAa,2BAA4Bb,UAAWC,MACzEa,MAAK,CAACC,KAAMC,MACTL,MAAMM,QAAQF,MACPA,QAEVG,MAAMxB,aAAayB,WAEpBR,MAAMS,cAAc,OAAQ5B,IAAIe,WAAW,QAE3CI,MAAMU,UAAUC,GAAGzB,YAAY0B,MAAM,SAASC,GAC1CA,EAAEC,iBACFC,eAAezB,MACfU,MAAMU,UAAUM,KAAK,QAAQC,SAC7BjB,MAAMkB,UAGVlB,MAAMU,UAAUC,GAAGzB,YAAYiC,QAAQ,WACnCnB,MAAMoB,aAGHpB,SACRO,MAAMxB,aAAayB,YAQpBO,eAAiB,SAASM,QAC5BrC,KAAKsC,KAAK,CAAC,CACPC,WAAY,8BACZjC,KAAM+B,OACNlB,KAAM,SAASqB,UACXC,OAAOC,SAASC,SACZH,SAASI,SACT7C,aAAa8C,gBAAgB,CACzBD,QAASJ,SAASI,QAClBpC,KAAM,uBAOnB,CACHsC,KAAM,SAASzC,YA/EQ,SAASA,WAC5B0C,SAASlC,KAAKmC,UAAUC,SAAS,qBACnBF,SAASG,iBAAiB,oCAChCC,SAAQ,SAASC,SACrBA,QAAQC,iBAAiB,SAAS,SAASxB,OAEnCzB,GADYyB,EAAEyB,OAAOC,UACNC,MAAM,sBACrBpD,KACAA,GAAKA,GAAG,GACRD,SAASC,GAAIC,kBAuEzBoD,CAAmBpD"} \ No newline at end of file diff --git a/amd/src/confirmcompletion.js b/amd/src/confirmcompletion.js new file mode 100644 index 0000000..17fff4c --- /dev/null +++ b/amd/src/confirmcompletion.js @@ -0,0 +1,114 @@ +// 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 . + +/** + * Manual completion confirmation options. + * + * @module mod_pulse/confirmcompletion + * @copyright 2023, bdecent gmbh bdecent.de + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +define(["jquery", 'core/str', "core/modal_factory", 'core/notification', 'core/ajax', "core/fragment", 'core/modal_events'], + (function($, Str, ModalFactory, notification, Ajax, Fragment, ModalEvents) { + + /** + * Show mark as completion button confirmation modal. + * @param {init} contextid + */ + const buttonConfirmation = function(contextid) { + if (document.body.classList.contains('path-course-view')) { + var buttons = document.querySelectorAll('.pulse-user-manualcompletion-btn'); + buttons.forEach(function(element) { + element.addEventListener('click', function(e) { + var classList = e.target.className; + var id = classList.match(/confirmation-(\d+)/); + if (id) { + id = id[1]; + getModal(id, contextid); + } + }); + }); + } + }; + + /** + * Get the activity completion confirmation modal. + * + * @param {array} id instance id + * @param {int} contextid Context ID + */ + const getModal = function(id, contextid) { + var args = {id: id}; + + ModalFactory.create({ + type: ModalFactory.types.SAVE_CANCEL, + title: Str.get_string('confirmation', 'pulse'), + body: '', + large: false + }).then(function(modal) { + modal.show(); + + Fragment.loadFragment('mod_pulse', 'get_confirmation_content', contextid, args) + .done((html, js) => { + modal.setBody(html); + return html; + }) + .catch(notification.exception); + + modal.setButtonText('save', Str.get_string('yes')); + + modal.getRoot().on(ModalEvents.save, function(e) { + e.preventDefault(); + submitformdata(args); + modal.getRoot().find('form').submit(); + modal.hide(); + }); + + modal.getRoot().on(ModalEvents.hidden, function() { + modal.destroy(); + }); + + return modal; + }).catch(notification.exception); + }; + + /** + * Submit and recieve the message form the modal confirmation on the activity completion. + * + * @param {string} params + */ + const submitformdata = function(params) { + Ajax.call([{ + methodname: 'mod_pulse_manual_completion', + args: params, + done: function(response) { + window.location.reload(); + if (response.message) { + notification.addNotification({ + message: response.message, + type: "success" + }); + } + } + }]); + }; + + return { + init: function(contextid) { + buttonConfirmation(contextid); + }, + }; +})); \ No newline at end of file diff --git a/backup/moodle2/backup_pulse_stepslib.php b/backup/moodle2/backup_pulse_stepslib.php index 1406f66..615a559 100644 --- a/backup/moodle2/backup_pulse_stepslib.php +++ b/backup/moodle2/backup_pulse_stepslib.php @@ -40,7 +40,9 @@ protected function define_structure() { 'pulse_contentformat', 'pulse', 'diff_pulse', 'displaymode', 'boxtype', 'boxicon', 'cssclass', 'resend_pulse', 'completionavailable', 'completionself', 'completionapproval', - 'completionapprovalroles', 'timemodified', + 'completionapprovalroles', 'completionbtnconfirmation', + 'completionbtntext', 'completionbtn_content', + 'completionbtn_contentformat', 'timemodified', ]); $notifiedusers = new backup_nested_element('notifiedusers'); @@ -79,6 +81,7 @@ protected function define_structure() { // Define file annotations. $pulse->annotate_files('mod_pulse', 'intro', null); $pulse->annotate_files('mod_pulse', 'pulse_content', null); + $pulse->annotate_files('mod_pulse', 'completionbtn_content', null); $pulse = \mod_pulse\extendpro::pulse_extend_backup_steps($pulse, $userinfo); diff --git a/backup/moodle2/restore_pulse_activity_task.class.php b/backup/moodle2/restore_pulse_activity_task.class.php index 0cd240f..b1dc23f 100644 --- a/backup/moodle2/restore_pulse_activity_task.class.php +++ b/backup/moodle2/restore_pulse_activity_task.class.php @@ -52,7 +52,7 @@ protected function define_my_steps() { public static function define_decode_contents() { $contents = []; - $contents[] = new restore_decode_content('pulse', ['intro', 'pulse_content'], 'pulse'); + $contents[] = new restore_decode_content('pulse', ['intro', 'pulse_content', 'completionbtn_content'], 'pulse'); \mod_pulse\extendpro::pulse_extend_restore_content($contents); diff --git a/backup/moodle2/restore_pulse_stepslib.php b/backup/moodle2/restore_pulse_stepslib.php index 136f750..85f5794 100644 --- a/backup/moodle2/restore_pulse_stepslib.php +++ b/backup/moodle2/restore_pulse_stepslib.php @@ -156,5 +156,6 @@ protected function after_execute() { // Add pulse related files. $this->add_related_files('mod_pulse', 'intro', null); $this->add_related_files('mod_pulse', 'pulse_content', null); + $this->add_related_files('mod_pulse', 'completionbtn_content', null); } } diff --git a/classes/completion/custom_completion.php b/classes/completion/custom_completion.php index 8bbb287..fa4024a 100644 --- a/classes/completion/custom_completion.php +++ b/classes/completion/custom_completion.php @@ -145,8 +145,9 @@ public function get_custom_rule_descriptions(): array { if ($this->is_available('completionself') ) { $state = $this->get_state('completionself'); if (in_array($state, [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS])) { + $pulse = $this->get_pulse(); $date = \mod_pulse\helper::pulse_already_selfcomplete($this->cm->instance, $this->userid); - $selfstring = get_string('selfmarked', 'pulse', ['date' => $date]); + $selfstring = \mod_pulse\helper::get_complete_state_button_text($pulse->completionbtntext, $date); } } // Approval completion description. diff --git a/classes/extendpro.php b/classes/extendpro.php index dea924c..fad3815 100644 --- a/classes/extendpro.php +++ b/classes/extendpro.php @@ -93,7 +93,8 @@ public static function mod_pulse_extend_form($mform, $instance, $method='') { } } - /** Extende the pro plugins validation error messages. + /** + * Extende the pro plugins validation error messages. * * @param mixed $data module form submitted data. * @param mixed $files Module form submitted files. @@ -108,7 +109,9 @@ public static function mod_pulse_extend_formvalidation($data, $files) { } } - /** Inject form elements into mod instance form. + /** + * Inject form elements into mod instance form. + * * @param mform $mform the form to inject elements into. */ public static function mod_pulse_extend_formdata($mform) { @@ -120,7 +123,9 @@ public static function mod_pulse_extend_formdata($mform) { } } - /** Extend form post process method from pro plugin. + /** + * Extend form post process method from pro plugin. + * * @param object $data module form submitted data object. */ public static function pulse_extend_postprocessing($data) { diff --git a/classes/external.php b/classes/external.php index c39d9af..f3c2403 100644 --- a/classes/external.php +++ b/classes/external.php @@ -27,6 +27,7 @@ defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir.'/externallib.php'); +require_once($CFG->libdir.'/completionlib.php'); /** * Pulse preset external definitions. @@ -85,4 +86,70 @@ public static function apply_presets(int $contextid, string $formdata, $pagepara public static function apply_presets_returns() { return new \external_value(PARAM_RAW, 'Count of Page user notes'); } + + /** + * Manual completion confirmation configdata. + * + * @return void + */ + public static function manual_completion_parameters() { + return new \external_function_parameters( + [ + 'id' => new \external_value(PARAM_INT, 'The id for the course module id'), + ] + ); + } + + /** + * Activity manual completion. + * + * @param int $id course module ID. + * + * @return array $message + */ + public static function manual_completion($id) { + global $DB, $USER; + $params = self::validate_parameters(self::manual_completion_parameters(), ['id' => $id]); + $cm = get_coursemodule_from_id('pulse', $params['id']); + $pulse = $DB->get_record('pulse', ['id' => $cm->instance]); + $course = get_course($cm->course); + $message = ''; + if ($record = $DB->get_record('pulse_completion', ['userid' => $USER->id, 'pulseid' => $cm->instance])) { + $record->selfcompletion = 1; + $record->selfcompletiontime = time(); + $record->timemodified = time(); + $result = $DB->update_record('pulse_completion', $record); + } else { + $record = new \stdclass(); + $record->userid = $USER->id; + $record->pulseid = $cm->instance; + $record->selfcompletion = 1; + $record->selfcompletiontime = time(); + $record->timemodified = time(); + $result = $DB->insert_record('pulse_completion', $record); + } + if ($result) { + $completion = new \completion_info($course); + if ($completion->is_enabled($cm) && $pulse->completionself) { + $completion->update_state($cm, COMPLETION_COMPLETE, $USER->id); + } + $message = get_string('completionmessage', 'pulse'); + } + return [ + 'message' => $message, + ]; + } + + /** + * Retuns the redirect message for manual completion. + * + * @return array message. + */ + public static function manual_completion_returns() { + return new \external_single_structure( + [ + 'message' => new \external_value(PARAM_TEXT, 'Return message'), + ] + ); + } } diff --git a/classes/helper.php b/classes/helper.php index c4ff58b..f8d371e 100644 --- a/classes/helper.php +++ b/classes/helper.php @@ -34,6 +34,8 @@ use stdclass; use context_course; +require_once($CFG->dirroot.'/lib/formslib.php'); + /** * Commonly used method in pulse. */ @@ -344,15 +346,18 @@ public static function cm_completionbuttons(cm_info $cm, stdclass $pulse): strin // Add self mark completed informations. if (!class_exists('core_completion\activity_custom_completion') && $date = self::pulse_already_selfcomplete($cm->instance, $USER->id)) { - $selfmarked = get_string('selfmarked', 'pulse', ['date' => $date]).'
'; + $selfmarked = self::get_complete_state_button_text($pulse->completionbtntext, $date).'
'; $html .= html_writer::tag('div', $selfmarked, ['class' => 'pulse-self-marked badge badge-success']); } else if (!self::pulse_already_selfcomplete($cm->instance, $USER->id)) { - $selfcomplete = new moodle_url('/mod/pulse/approve.php', ['cmid' => $moduleid, 'action' => 'selfcomplete']); - $selfmarklink = html_writer::link($selfcomplete, get_string('markcomplete', 'pulse'), - ['class' => 'btn btn-primary pulse-approve-users'] - ); - $html .= html_writer::tag('div', $selfmarklink, ['class' => 'pulse-approve-users']); + $additionalclass = $pulse->completionbtnconfirmation ? 'confirmation-'. $moduleid : ''; + $buttontext = self::get_not_complete_state_button_text($pulse->completionbtntext); + $selfcomplete = !$pulse->completionbtnconfirmation ? + new moodle_url('/mod/pulse/approve.php', ['cmid' => $moduleid, 'action' => 'selfcomplete']) : + 'javascript:void(0);'; + $selfmarklink = html_writer::link($selfcomplete, $buttontext, + [ 'class' => 'btn btn-primary pulse-user-manualcompletion-btn '. $additionalclass]); + $html .= html_writer::tag('div', $selfmarklink, ['class' => 'pulse-user-manualcompletion']); } } } else { @@ -585,4 +590,82 @@ public static function pulse_has_pro() { return $result; } + /** + * Get the not completed state button text from the module form. + * + * @param int $value Button text value + */ + public static function get_not_complete_state_button_text($value) { + global $CFG; + require_once($CFG->dirroot.'/mod/pulse/lib.php'); + switch ($value){ + case BUTTON_TEXT_ACKNOWLEDGE: + $buttontext = get_string('markcompletebtnstring_custom1', 'pulse'); + break; + case BUTTON_TEXT_CONFIRM: + $buttontext = get_string('markcompletebtnstring_custom2', 'pulse'); + break; + case BUTTON_TEXT_CHOOSE: + $buttontext = get_string('markcompletebtnstring_custom3', 'pulse'); + break; + case BUTTON_TEXT_APPROVE: + $buttontext = get_string('markcompletebtnstring_custom4', 'pulse'); + break; + default: + $buttontext = get_string('markcompletebtnstring_default', 'pulse'); + break; + } + return $buttontext; + } + + /** + * Get the completed state button text from the module form. + * + * @param int $value Button text value. + * @param int $date Completion date. + */ + public static function get_complete_state_button_text($value, $date) { + global $CFG; + require_once($CFG->dirroot.'/mod/pulse/lib.php'); + switch ($value){ + case BUTTON_TEXT_ACKNOWLEDGE: + $buttontext = get_string('markedcompletebtnstring_custom1', 'pulse', ['date' => $date]); + break; + case BUTTON_TEXT_CONFIRM: + $buttontext = get_string('markedcompletebtnstring_custom2', 'pulse', ['date' => $date]); + break; + case BUTTON_TEXT_CHOOSE: + $buttontext = get_string('markedcompletebtnstring_custom3', 'pulse', ['date' => $date]); + break; + case BUTTON_TEXT_APPROVE: + $buttontext = get_string('markedcompletebtnstring_custom4', 'pulse', ['date' => $date]); + break; + default: + $buttontext = get_string('markedcompletebtnstring_default', 'pulse', ['date' => $date]); + break; + } + return $buttontext; + } + + /** + * Postupdate the editor files. + * + * @param object $pulse + * @param mixed $context + */ + public static function postupdate_editor_files($pulse, $context) { + global $DB; + $editors = ['completionbtn_content']; + $upd = new stdClass(); + $upd->id = $pulse->id; + foreach ($editors as $editor) { + $editorformat = $editor . "format"; + $pulse = file_postupdate_standard_editor($pulse, $editor, self::get_editor_options($context), + $context, 'mod_pulse', $editor, 0); + $upd->{$editor} = $pulse->{$editor}; + $upd->{$editorformat} = $pulse->{$editorformat}; + } + $DB->update_record('pulse', $upd); + } + } diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php index 8dffb77..4089b6a 100644 --- a/classes/privacy/provider.php +++ b/classes/privacy/provider.php @@ -83,7 +83,7 @@ public static function get_metadata(collection $collection): collection { * @param int $userid The user to search. * @return contextlist $contextlist The list of contexts used in this plugin. */ - public static function get_contexts_for_userid(int $userid) : contextlist { + public static function get_contexts_for_userid(int $userid): contextlist { $contextlist = new \core_privacy\local\request\contextlist(); // User completions. $sql = "SELECT c.id @@ -270,7 +270,7 @@ public static function export_user_data(approved_contextlist $contextlist) { get_string('completionfor', 'mod_pulse'), array_filter( $completions, - function(stdClass $completion) use ($contextlist) : bool { + function(stdClass $completion) use ($contextlist): bool { return $completion->userid == $contextlist->get_user()->id; } ), @@ -281,7 +281,7 @@ function(stdClass $completion) use ($contextlist) : bool { get_string('approvedby', 'mod_pulse'), array_filter( $completions, - function(stdClass $completion) use ($contextlist) : bool { + function(stdClass $completion) use ($contextlist): bool { return $completion->approvedby == $contextlist->get_user()->id; } ), @@ -393,7 +393,7 @@ public static function generate_invitationdata(int $pulseid, int $userid): ?arra private static function group_by_property(array $classes, string $property): array { return array_reduce( $classes, - function (array $classes, stdClass $class) use ($property) : array { + function (array $classes, stdClass $class) use ($property): array { $classes[$class->{$property}][] = $class; return $classes; }, diff --git a/db/install.xml b/db/install.xml index efc6e36..dfa170f 100644 --- a/db/install.xml +++ b/db/install.xml @@ -24,6 +24,10 @@ + + + + diff --git a/db/services.php b/db/services.php index 0b746ed..90f993c 100644 --- a/db/services.php +++ b/db/services.php @@ -34,4 +34,13 @@ 'ajax' => true, 'loginrequired' => true, ], + + 'mod_pulse_manual_completion' => [ + 'classname' => 'mod_pulse\external', + 'methodname' => 'manual_completion', + 'description' => 'Pulse activity manual confirmation completion', + 'type' => 'write', + 'ajax' => true, + 'loginrequired' => true, + ], ]; diff --git a/db/upgrade.php b/db/upgrade.php index 34932f4..9375779 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -112,5 +112,35 @@ function xmldb_pulse_upgrade($oldversion) { upgrade_mod_savepoint(true, 2021110200, 'pulse'); } + if ($oldversion < 2024033008) { + // Mark as complete options. + $pulsetable = new xmldb_table('pulse'); + $completionbtnconfirmation = new xmldb_field('completionbtnconfirmation', XMLDB_TYPE_INTEGER, '2', null, null, null, + '0', 'completionapprovalroles'); + $completionbtntext = new xmldb_field('completionbtntext', XMLDB_TYPE_INTEGER, '2', null, null, null, + '0', 'completionbtnconfirmation'); + $completionbtncontent = new xmldb_field('completionbtn_content', XMLDB_TYPE_TEXT, null, null, null, + null, null, 'completionbtntext'); + $completionbtncontentformat = new xmldb_field('completionbtn_contentformat', XMLDB_TYPE_INTEGER, '2', null, null, null, + '0', 'completionbtn_content');; + // Completion button confiramtion option. + if (!$dbman->field_exists($pulsetable, $completionbtnconfirmation)) { + $dbman->add_field($pulsetable, $completionbtnconfirmation); + } + // Completion button text option. + if (!$dbman->field_exists($pulsetable, $completionbtntext)) { + $dbman->add_field($pulsetable, $completionbtntext); + } + // Completion button content option. + if (!$dbman->field_exists($pulsetable, $completionbtncontent)) { + $dbman->add_field($pulsetable, $completionbtncontent); + } + // Completion button content format option. + if (!$dbman->field_exists($pulsetable, $completionbtncontentformat)) { + $dbman->add_field($pulsetable, $completionbtncontentformat); + } + upgrade_mod_savepoint(true, 2024033008, 'pulse'); + } + return true; } diff --git a/lang/en/pulse.php b/lang/en/pulse.php index 1620868..9317e46 100644 --- a/lang/en/pulse.php +++ b/lang/en/pulse.php @@ -297,3 +297,32 @@ $string['others_vars'] = 'Others'; $string['others_vars_help'] = 'Additional placeholders'; $string['completionenrolled'] = 'enrolled'; +$string['suspended'] = 'Suspended'; + +// Mark as complete options. +$string['markcompleteoptionheader'] = "Mark as complete options"; +// Require confirmation. +$string['requireconfirm'] = 'Require confirmation'; +$string['requireconfirm_help'] = 'Unchecked (Default): No confirmation is required. Clicking the button will immediately mark the activity as complete.
Checked: A confirmation modal will open, and the user must confirm or cancel the action before the activity is marked as complete.'; +// Button text. +$string['btntext'] = 'Button text'; +$string['btntext_help'] = 'Choose the text that appears on the button users click to mark an activity as complete.'; +// Not completed state button texts. +$string['markcompletebtnstring_default'] = 'Mark as complete'; +$string['markcompletebtnstring_custom1'] = 'Acknowledge'; +$string['markcompletebtnstring_custom2'] = 'Confirm'; +$string['markcompletebtnstring_custom3'] = 'Choose'; +$string['markcompletebtnstring_custom4'] = 'Approve'; +// Completed state labels. +$string['markedcompletebtnstring_default'] = 'Self marked complete on {$a->date}'; +$string['markedcompletebtnstring_custom1'] = 'Completion acknowledged on {$a->date}'; +$string['markedcompletebtnstring_custom2'] = 'Completion confirmed on {$a->date}'; +$string['markedcompletebtnstring_custom3'] = 'Completion chosen on {$a->date}'; +$string['markedcompletebtnstring_custom4'] = 'Completion approved on {$a->date}'; +// Confiramtion modal text. +$string['confirmation'] = "Confirmation"; +$string['confirmtext'] = 'Confirmation modal text'; +$string['confirmtext_help'] = "Enter the content that will appear in the confirmation modal."; +$string['modalconfirm'] = 'Completion Confirmation'; +$string['completionconfirmation'] = 'Are you sure you want to mark this activity as complete?'; +$string['completionmessage'] = 'Marked as completed'; diff --git a/lib.php b/lib.php index 3e5cb03..73f5d1c 100644 --- a/lib.php +++ b/lib.php @@ -27,6 +27,13 @@ define( 'MAX_PULSE_NAME_LENGTH', 50); +// Mark as complete option button texts. +define('BUTTON_TEXT_DEFAULT', 1); // Default. +define('BUTTON_TEXT_ACKNOWLEDGE', 2); // Acknowledge. +define('BUTTON_TEXT_CONFIRM', 3); // Confirm. +define('BUTTON_TEXT_CHOOSE', 4); // Choose. +define('BUTTON_TEXT_APPROVE', 5); // Approve. + global $PAGE; require_once($CFG->libdir."/completionlib.php"); @@ -55,6 +62,11 @@ function pulse_add_instance($pulse) { } // Insert the instance in DB. $pulseid = $DB->insert_record('pulse', $pulse); + + $pulse->id = $pulseid; + // Post update the editor files. + \mod_pulse\helper::postupdate_editor_files($pulse, $context); + // Extend the pro features. \mod_pulse\extendpro::pulse_extend_add_instance($pulseid, $pulse); @@ -95,8 +107,15 @@ function pulse_update_instance($pulse) { $message = get_string('resendnotificationdesc', 'mod_pulse'); \core\notification::add($message, 'info'); } + + $pulse->completionbtnconfirmation = isset($pulse->completionbtnconfirmation) ?? 0; + // Update instance data. $updates = $DB->update_record('pulse', $pulse); + + // Post update the editor files. + \mod_pulse\helper::postupdate_editor_files($pulse, $context); + // Extend the updated module instance pro features. \mod_pulse\extendpro::pulse_extend_update_instance($pulse, $context); return $updates; @@ -192,14 +211,15 @@ function mod_pulse_cm_info_dynamic(cm_info &$cm) { * @param array $options additional options affecting the file serving * @return bool false if the file not found, just send the file otherwise and do not return anything */ -function pulse_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=[]) { +function mod_pulse_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=[]) { // Check the contextlevel is as expected - if your plugin is a block, this becomes CONTEXT_BLOCK, etc. if ($context->contextlevel != CONTEXT_MODULE && $context->contextlevel != CONTEXT_SYSTEM) { return false; } // Get extended plugins fileareas. $availablefiles = \mod_pulse\extendpro::pulse_extend_filearea(); - $availablefiles += ['pulse_content', 'intro', 'notificationheader', 'notificationfooter']; + $availablefiles = array_merge($availablefiles, ['pulse_content', 'intro', + 'notificationheader', 'notificationfooter', 'completionbtn_content']); // Make sure the filearea is one of those used by the plugin. if (!in_array($filearea, $availablefiles)) { return false; @@ -469,7 +489,7 @@ function pulse_mtrace($message, $detail=false) { * * @param array $args Preset ID and Course ID with context. */ -function mod_pulse_output_fragment_get_preset_preview(array $args) : ?string { +function mod_pulse_output_fragment_get_preset_preview(array $args): ?string { global $CFG; $context = $args['context']; @@ -489,7 +509,7 @@ function mod_pulse_output_fragment_get_preset_preview(array $args) : ?string { * * @param array $args Custom config data and Current module form data with context. */ -function mod_pulse_output_fragment_apply_preset(array $args) : ?string { +function mod_pulse_output_fragment_apply_preset(array $args): ?string { global $CFG; $context = $args['context']; @@ -637,3 +657,37 @@ function pulse_email_placeholders($editor) { return $OUTPUT->render_from_template('mod_pulse/vars', $templatecontext); } + +/** + * Add the completion confirmation button js. + * + * @param navigation_node $navigation + * @param stdClass $course + * @param context_course $context + * @return void + */ +function mod_pulse_extend_navigation_course(navigation_node $navigation, stdClass $course, $context) { + global $PAGE; + + // Completion confirmation. + $PAGE->requires->js_call_amd('mod_pulse/confirmcompletion', 'init', ['contextid' => $context->id]); +} + +/** + * Get the manual completion confirmation content. + * + * @param array $params + * @return void + */ +function mod_pulse_output_fragment_get_confirmation_content(array $params) { + global $DB; + $cm = get_coursemodule_from_id('pulse', $params['id']); + $pulse = $DB->get_record('pulse', ['id' => $cm->instance]); + $modulecontext = \context_module::instance($cm->id); + $content = !empty($pulse->completionbtn_content) ? $pulse->completionbtn_content : + get_string('completionconfirmation', 'pulse'); + $contenthtml = file_rewrite_pluginfile_urls($content, 'pluginfile.php', $modulecontext->id, 'mod_pulse', + 'completionbtn_content', 0); + $contenthtml = format_text($contenthtml, $pulse->completionbtn_contentformat, ['trusted' => true, 'noclean' => true]); + return $contenthtml; +} diff --git a/mod_form.php b/mod_form.php index 4dca6e2..78cc9f8 100644 --- a/mod_form.php +++ b/mod_form.php @@ -150,6 +150,9 @@ public function definition() { $mform->setType('boxicon', PARAM_TEXT); $mform->hideIf('boxicon', 'displaymode', 'neq', 1); + // Mark as complete button options. + $this->definition_completionbuttonoption($mform); + $this->standard_coursemodule_elements(); // Email placeholders. @@ -243,6 +246,13 @@ public function data_postprocessing($data) { if (isset($data->completionapprovalroles)) { $data->completionapprovalroles = json_encode($data->completionapprovalroles); } + + $data->completionbtnconfirm = isset($data->completionbtnconfirm) ? 1 : 0; + if (isset($data->completionbtn_content_editor)) { + $data->completionbtn_contentformat = $data->completionbtn_content_editor['format']; + $data->completionbtn_content = $data->completionbtn_content_editor['text']; + } + \mod_pulse\extendpro::pulse_extend_postprocessing($data); } @@ -267,11 +277,29 @@ public function data_preprocessing(&$defaultvalues) { $defaultvalues['pulse_content_editor']['format'] = $pulsecontentformat; $defaultvalues['pulse_content_editor']['itemid'] = $draftitemid; + + $contentdraftitemid = file_get_submitted_draft_itemid('completionbtn_content'); + $content = $defaultvalues['completionbtn_content'] ?? ''; + $contentformat = $defaultvalues['completionbtn_contentformat'] ?? 0; + $defaultvalues['completionbtn_content_editor']['text'] = + file_prepare_draft_area($contentdraftitemid, $this->context->id, + 'mod_pulse', 'completionbtn_content', false, + $editoroptions, + $content); + $defaultvalues['completionbtn_content_editor']['format'] = $contentformat; + $defaultvalues['completionbtn_content_editor']['itemid'] = $contentdraftitemid; + } else { $draftitemid = file_get_submitted_draft_itemid('pulse_content_editor'); file_prepare_draft_area($draftitemid, null, 'mod_pulse', 'pulse_content', false); $defaultvalues['pulse_content_editor']['format'] = editors_get_preferred_format(); $defaultvalues['pulse_content_editor']['itemid'] = $draftitemid; + + $draftitemid = file_get_submitted_draft_itemid('completionbtn_content_editor'); + file_prepare_draft_area($draftitemid, null, 'mod_pulse', 'completionbtn_content', false); + $defaultvalues['completionbtn_content_editor']['format'] = editors_get_preferred_format(); + $defaultvalues['completionbtn_content_editor']['itemid'] = $draftitemid; + } // Set up the completion checkbox which is not part of standard data. @@ -313,4 +341,50 @@ public function validation($data, $files) { } return $errors; } + + /** + * Mark as completion option form fields. + * + * @param moodle_form $mform + */ + public function definition_completionbuttonoption(&$mform) { + // Head: Mark as complete options. + $mform->addElement('header', 'markcompleteoption', get_string('markcompleteoptionheader', 'pulse')); + + // Global config values. + $context = \context_system::instance(); + $completebtnconfirmation = get_config('mod_pulse', 'completionbtnconfirmation'); + $completionbtntext = get_config('mod_pulse', 'completionbtntext'); + $completionbtncontent = get_config('mod_pulse', 'completionbtn_content'); + $btncontenthtml = file_rewrite_pluginfile_urls($completionbtncontent, 'pluginfile.php', $context->id, + 'mod_pulse', 'completionbtn_content', 0); + $btncontenthtml = format_text($btncontenthtml, FORMAT_HTML, ['trusted' => true, 'noclean' => true]); + + // Require confirmation. + $mform->addElement('checkbox', 'completionbtnconfirmation', get_string('requireconfirm', 'pulse')); + $mform->addHelpButton('completionbtnconfirmation', 'requireconfirm', 'mod_pulse'); + $mform->setDefault('completionbtnconfirmation', $completebtnconfirmation ?: false); + + // Marke as complete button text. + $btntexts = [ + BUTTON_TEXT_DEFAULT => get_string('markcompletebtnstring_default', 'pulse'), + BUTTON_TEXT_ACKNOWLEDGE => get_string('markcompletebtnstring_custom1', 'pulse'), + BUTTON_TEXT_CONFIRM => get_string('markcompletebtnstring_custom2', 'pulse'), + BUTTON_TEXT_CHOOSE => get_string('markcompletebtnstring_custom3', 'pulse'), + BUTTON_TEXT_APPROVE => get_string('markcompletebtnstring_custom4', 'pulse'), + ]; + $mform->addElement('select', 'completionbtntext', get_string('btntext', 'pulse'), $btntexts); + $mform->setType('completionbtntext', PARAM_TEXT); + $mform->addHelpButton('completionbtntext', 'btntext', 'mod_pulse'); + $mform->setDefault('completionbtntext', $completionbtntext ?: BUTTON_TEXT_DEFAULT); + + // Confirmation modal text. + $editoroptions = \mod_pulse\helper::get_editor_options(); + $content = $mform->addElement('editor', 'completionbtn_content_editor', get_string('confirmtext', 'pulse'), + ['class' => 'fitem_id_templatevars_editor'], $editoroptions); + $mform->setType('completionbtn_content_editor', PARAM_RAW); + $mform->addHelpButton('completionbtn_content_editor', 'confirmtext', 'mod_pulse'); + $content->setValue(['text' => $btncontenthtml ?? '', 'format' => 1]); + } + } diff --git a/settings.php b/settings.php index c03b05c..f99015d 100644 --- a/settings.php +++ b/settings.php @@ -24,6 +24,8 @@ defined('MOODLE_INTERNAL') || die; +require_once($CFG->dirroot.'/mod/pulse/lib.php'); + $ADMIN->add('modsettings', new admin_category('modpulse', new lang_string('pluginname', 'mod_pulse'))); $settings = new admin_settingpage('pulsegeneralsettings', get_string('generalsettings', 'pulse'), 'moodle/site:config', false); @@ -36,6 +38,29 @@ $settings->add(new admin_setting_configtext('mod_pulse/schedulecount', get_string('schedulecount', 'pulse'), get_string('schedulecountdesc', 'pulse'), 500, PARAM_INT)); + // Mark as complete confirmation. + $settings->add(new admin_setting_configcheckbox('mod_pulse/completionbtnconfirmation', get_string('requireconfirm', 'pulse'), + get_string('requireconfirm_help', 'pulse'), false)); + + // Mark as complete button text. + $title = get_string('btntext' , 'pulse'); + $description = get_string('btntext_help', 'pulse'); + $btntexts = [ + BUTTON_TEXT_DEFAULT => get_string('markcompletebtnstring_default', 'pulse'), + BUTTON_TEXT_ACKNOWLEDGE => get_string('markcompletebtnstring_custom1', 'pulse'), + BUTTON_TEXT_CONFIRM => get_string('markcompletebtnstring_custom2', 'pulse'), + BUTTON_TEXT_CHOOSE => get_string('markcompletebtnstring_custom3', 'pulse'), + BUTTON_TEXT_APPROVE => get_string('markcompletebtnstring_custom4', 'pulse'), + ]; + $settings->add(new admin_setting_configselect('mod_pulse/completionbtntext', $title, $description, BUTTON_TEXT_DEFAULT, + $btntexts)); + + // Confirmation text content. + $name = 'mod_pulse/completionbtn_content'; + $title = get_string('confirmtext', 'pulse'); + $description = get_string('confirmtext_help', 'pulse'); + $settings->add(new admin_setting_confightmleditor($name, $title, $description, '')); + } $ADMIN->add('modpulse', $settings); $settings = null; // Reset the settings. diff --git a/version.php b/version.php index cd3e8c5..1fdb541 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'mod_pulse'; -$plugin->version = 2024033007; +$plugin->version = 2024033008; $plugin->requires = 2022112800; // Requires Moodle 4.1. $plugin->release = 'v2.1'; $plugin->maturity = MATURITY_STABLE; diff --git a/view.php b/view.php index 9602227..e444fc2 100644 --- a/view.php +++ b/view.php @@ -43,7 +43,7 @@ global $USER; -$context = context_course::instance($course->id); +$context = \context_course::instance($course->id); if (class_exists('local_pulsepro\table\reactionreport') && has_capability('local/pulsepro:viewreports', $context, $USER->id)) { $redirecturl = new moodle_url('/local/pulsepro/report.php', ['courseid' => $course->id, 'cmid' => $cm->id]); redirect($redirecturl);