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);