From 8fabc738824a82f2d8c625f37d4eec48366c799e Mon Sep 17 00:00:00 2001 From: Serge Gauthier Date: Tue, 2 Feb 2016 10:50:13 -0500 Subject: [PATCH] MDL-52675 tool_lp: When deleting template delete or unlink derived plans --- admin/tool/lp/amd/build/actionselector.min.js | 1 + .../tool/lp/amd/build/templateactions.min.js | 2 +- admin/tool/lp/amd/src/actionselector.js | 193 ++++++++++++++++++ admin/tool/lp/amd/src/templateactions.js | 69 +++++-- admin/tool/lp/classes/api.php | 38 +++- admin/tool/lp/classes/external.php | 57 +++++- admin/tool/lp/classes/plan.php | 10 + admin/tool/lp/classes/template.php | 9 + admin/tool/lp/classes/template_competency.php | 12 ++ admin/tool/lp/db/services.php | 10 +- admin/tool/lp/lang/en/tool_lp.php | 3 + .../lp/templates/action_selector.mustache | 45 ++++ admin/tool/lp/tests/api_test.php | 101 +++++++++ admin/tool/lp/version.php | 2 +- 14 files changed, 531 insertions(+), 21 deletions(-) create mode 100644 admin/tool/lp/amd/build/actionselector.min.js create mode 100644 admin/tool/lp/amd/src/actionselector.js create mode 100644 admin/tool/lp/templates/action_selector.mustache diff --git a/admin/tool/lp/amd/build/actionselector.min.js b/admin/tool/lp/amd/build/actionselector.min.js new file mode 100644 index 00000000000..709d7cf0ed4 --- /dev/null +++ b/admin/tool/lp/amd/build/actionselector.min.js @@ -0,0 +1 @@ +define(["jquery","core/notification","core/ajax","core/templates","tool_lp/dialogue","tool_lp/event_base"],function(a,b,c,d,e,f){var g=function(a,b,c,d,e){var g=this;f.prototype.constructor.apply(this,[]),g._title=a,g._message=b,g._actions=c,g._confirm=d,g._cancel=e,g._selectedValue=null,g._reset()};return g.prototype=Object.create(f.prototype),g.prototype._selectedValue=null,g.prototype._popup=null,g.prototype._title=null,g.prototype._message=null,g.prototype._actions=null,g.prototype._confirm=null,g.prototype._cancel=null,g.prototype._callback=null,g.prototype._afterRender=function(){var b=this;b._find('[data-action="action-selector-confirm"]').attr("disabled","disabled"),b._find('[data-region="action-selector-radio-buttons"]').change(function(){b._selectedValue=a("input[type='radio']:checked").val(),b._find('[data-action="action-selector-confirm"]').removeAttr("disabled"),b._refresh.bind(b)}.bind(b)),b._find('[data-action="action-selector-cancel"]').click(function(a){a.preventDefault(),b.close()}.bind(b)),b._find('[data-action="action-selector-confirm"]').click(function(a){a.preventDefault(),b._selectedValue.length&&(b._trigger("save",{action:b._selectedValue}),b.close())}.bind(b))},g.prototype.close=function(){var a=this;a._popup.close(),a._reset()},g.prototype.display=function(){var a=this;return a._render().then(function(b){a._popup=new e(a._title,b,a._afterRender.bind(a))}.bind(a)).fail(b.exception)},g.prototype._find=function(b){return a(this._popup.getContent()).find(b)},g.prototype._refresh=function(){var a=this;return a._render().then(function(b){a._find('[data-region="action-selector"]').replaceWith(b),a._afterRender()}.bind(a))},g.prototype._render=function(){var a=this,b=[];for(var c in a._actions)b.push(a._actions[c]);var e={message:a._message,choices:b,confirm:a._confirm,cancel:a._cancel};return d.render("tool_lp/action_selector",e)},g.prototype._reset=function(){this._popup=null,this._selectedValue=""},g}); \ No newline at end of file diff --git a/admin/tool/lp/amd/build/templateactions.min.js b/admin/tool/lp/amd/build/templateactions.min.js index d180b15f3a7..e99d6c22899 100644 --- a/admin/tool/lp/amd/build/templateactions.min.js +++ b/admin/tool/lp/amd/build/templateactions.min.js @@ -1 +1 @@ -define(["jquery","core/templates","core/ajax","core/notification","core/str"],function(a,b,c,d,e){var f=0,g=0,h=function(c,d){a('[data-region="managetemplates"]').replaceWith(c),b.runTemplateJS(d)},i=function(a){b.render("tool_lp/manage_templates_page",a).done(h).fail(d.exception)},j=function(){var a=c.call([{methodname:"tool_lp_delete_template",args:{id:g}},{methodname:"tool_lp_data_for_templates_manage_page",args:{pagecontext:{contextid:f}}}]);a[1].done(i).fail(d.exception)},k=function(b){b.preventDefault(),g=a(this).attr("data-templateid");var e=c.call([{methodname:"tool_lp_duplicate_template",args:{id:g}},{methodname:"tool_lp_data_for_templates_manage_page",args:{pagecontext:{contextid:f}}}]);e[1].done(i).fail(d.exception)},l=function(b){b.preventDefault();var f=a(this).attr("data-templateid");g=f;var h=c.call([{methodname:"tool_lp_read_template",args:{id:g}}]);h[0].done(function(a){e.get_strings([{key:"confirm",component:"moodle"},{key:"deletetemplate",component:"tool_lp",param:a.shortname},{key:"delete",component:"moodle"},{key:"cancel",component:"moodle"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],j)}).fail(d.exception)}).fail(d.exception)};return{deleteHandler:l,duplicateHandler:k,init:function(a){f=a}}}); \ No newline at end of file +define(["jquery","core/templates","core/ajax","core/notification","core/str","tool_lp/actionselector"],function(a,b,c,d,e,f){var g=0,h=0,i=!0,j=function(c,d){a('[data-region="managetemplates"]').replaceWith(c),b.runTemplateJS(d)},k=function(a){b.render("tool_lp/manage_templates_page",a).done(j).fail(d.exception)},l=function(){window.alert(i);var a=c.call([{methodname:"tool_lp_delete_template",args:{id:h,deleteplans:i}},{methodname:"tool_lp_data_for_templates_manage_page",args:{pagecontext:{contextid:g}}}]);a[1].done(k).fail(d.exception)},m=function(b){b.preventDefault(),h=a(this).attr("data-templateid");var e=c.call([{methodname:"tool_lp_duplicate_template",args:{id:h}},{methodname:"tool_lp_data_for_templates_manage_page",args:{pagecontext:{contextid:g}}}]);e[1].done(k).fail(d.exception)},n=function(b){b.preventDefault();var g=a(this).attr("data-templateid");h=g,i=!0;var j=c.call([{methodname:"tool_lp_read_template",args:{id:h}},{methodname:"tool_lp_template_has_related_data",args:{id:h}}]);j[0].done(function(a){j[1].done(function(b){b?e.get_strings([{key:"deletetemplate",component:"tool_lp",param:a.shortname},{key:"deletetemplatewithplans",component:"tool_lp"},{key:"deleteplans",component:"tool_lp"},{key:"unlinkplanstemplate",component:"tool_lp"},{key:"confirm",component:"moodle"},{key:"cancel",component:"moodle"}]).done(function(a){var b=[{text:a[2],value:"delete"},{text:a[3],value:"unlink"}],c=new f(a[0],a[1],b,a[4],a[5]);c.display(),c.on("save",function(a,b){window.alert(b.action),"delete"!=b.action&&(i=!1),l()})}).fail(d.exception):e.get_strings([{key:"confirm",component:"moodle"},{key:"deletetemplate",component:"tool_lp",param:a.shortname},{key:"delete",component:"moodle"},{key:"cancel",component:"moodle"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],l)}).fail(d.exception)}).fail(d.exception)}).fail(d.exception)};return{deleteHandler:n,duplicateHandler:m,init:function(a){g=a}}}); \ No newline at end of file diff --git a/admin/tool/lp/amd/src/actionselector.js b/admin/tool/lp/amd/src/actionselector.js new file mode 100644 index 00000000000..fa65700c27c --- /dev/null +++ b/admin/tool/lp/amd/src/actionselector.js @@ -0,0 +1,193 @@ +// 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 . + +/** + * Action selector. + * + * To handle 'save' events use: actionselector.on('save') + * This will receive the information to display in popup. + * The actions have the format [{'text': sometext, 'value' : somevalue}] + * + * @package tool_lp + * @copyright 2016 Serge Gauthier - + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +define(['jquery', + 'core/notification', + 'core/ajax', + 'core/templates', + 'tool_lp/dialogue', + 'tool_lp/event_base'], + function($, Notification, Ajax, Templates, Dialogue, EventBase) { + + /** + * Action selector class. + * @param {String} title The title of popup. + * @param {String} message The message to display. + * @param {object} actions The actions that can be selected. + * @param {String} confirm Text for confirm button. + * @param {String} cancel Text for cancel button. + */ + var ActionSelector = function(title, message, actions, confirm, cancel) { + var self = this; + + EventBase.prototype.constructor.apply(this, []); + self._title = title; + self._message = message; + self._actions = actions; + self._confirm = confirm; + self._cancel = cancel; + self._selectedValue = null; + self._reset(); + }; + + ActionSelector.prototype = Object.create(EventBase.prototype); + + /** @type {String} The value that was selected. */ + ActionSelector.prototype._selectedValue = null; + /** @type {Dialogue} The reference to the dialogue. */ + ActionSelector.prototype._popup = null; + /** @type {String} The title of popup. */ + ActionSelector.prototype._title = null; + /** @type {String} The message in popup. */ + ActionSelector.prototype._message = null; + /** @type {object} The information for radion buttons. */ + ActionSelector.prototype._actions = null; + /** @type {String} The text for confirm button. */ + ActionSelector.prototype._confirm = null; + /** @type {String} The text for cancel button. */ + ActionSelector.prototype._cancel = null; + + /** + * Hook to executed after the view is rendered. + * + * @method _afterRender + */ + ActionSelector.prototype._afterRender = function() { + var self = this; + + // Confirm button is disabled until a choice is done. + self._find('[data-action="action-selector-confirm"]').attr('disabled', 'disabled'); + + // Add listener for radio buttons change. + self._find('[data-region="action-selector-radio-buttons"]').change(function() { + self._selectedValue = $("input[type='radio']:checked").val(); + self._find('[data-action="action-selector-confirm"]').removeAttr('disabled'); + self._refresh.bind(self); + }.bind(self)); + + // Add listener for cancel. + self._find('[data-action="action-selector-cancel"]').click(function(e) { + e.preventDefault(); + self.close(); + }.bind(self)); + + // Add listener for confirm. + self._find('[data-action="action-selector-confirm"]').click(function(e) { + e.preventDefault(); + if (!self._selectedValue.length) { + return; + } + self._trigger('save', { action: self._selectedValue }); + self.close(); + }.bind(self)); + }; + + /** + * Close the dialogue. + * + * @method close + */ + ActionSelector.prototype.close = function() { + var self = this; + self._popup.close(); + self._reset(); + }; + + /** + * Opens the action selector. + * + * @method display + * @return {Promise} + */ + ActionSelector.prototype.display = function() { + var self = this; + return self._render().then(function(html) { + self._popup = new Dialogue( + self._title, + html, + self._afterRender.bind(self) + ); + }.bind(self)).fail(Notification.exception); + }; + + /** + * Find a node in the dialogue. + * + * @param {String} selector + * @method _find + */ + ActionSelector.prototype._find = function(selector) { + return $(this._popup.getContent()).find(selector); + }; + + /** + * Refresh the view. + * + * @method _refresh + * @return {Promise} + */ + ActionSelector.prototype._refresh = function() { + var self = this; + return self._render().then(function(html) { + self._find('[data-region="action-selector"]').replaceWith(html); + self._afterRender(); + }.bind(self)); + }; + + /** + * Render the dialogue. + * + * @method _render + * @return {Promise} + */ + ActionSelector.prototype._render = function() { + var self = this; + var choices = []; + for (var i in self._actions) { + choices.push(self._actions[i]); + } + var content = {'message': self._message, 'choices' : choices, + 'confirm' : self._confirm, 'cancel' : self._cancel}; + + return Templates.render('tool_lp/action_selector', content); + }; + + /** + * Reset the dialogue properties. + * + * This does not reset everything, just enough to reset the UI. + * + * @method _reset + */ + ActionSelector.prototype._reset = function() { + this._popup = null; + this._selectedValue = ''; + }; + + return /** @alias module:tool_lp/actionselector */ ActionSelector; + +}); diff --git a/admin/tool/lp/amd/src/templateactions.js b/admin/tool/lp/amd/src/templateactions.js index 2416ed5f5e2..fd980f4cd3d 100644 --- a/admin/tool/lp/amd/src/templateactions.js +++ b/admin/tool/lp/amd/src/templateactions.js @@ -21,7 +21,8 @@ * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define(['jquery', 'core/templates', 'core/ajax', 'core/notification', 'core/str'], function($, templates, ajax, notification, str) { +define(['jquery', 'core/templates', 'core/ajax', 'core/notification', 'core/str','tool_lp/actionselector'], + function($, templates, ajax, notification, str, Actionselector) { // Private variables and functions. /** @var {Number} pagecontextid The id of the context */ @@ -29,6 +30,9 @@ define(['jquery', 'core/templates', 'core/ajax', 'core/notification', 'core/str' /** @var {Number} templateid The id of the template */ var templateid = 0; + + /** @var {Boolean} Action to apply to plans when deleting a template */ + var deleteplans = true; /** * Callback to replace the dom element with the rendered template. @@ -63,7 +67,8 @@ define(['jquery', 'core/templates', 'core/ajax', 'core/notification', 'core/str' // We are chaining ajax requests here. var requests = ajax.call([{ methodname: 'tool_lp_delete_template', - args: { id: templateid } + args: { id: templateid, + deleteplans: deleteplans } }, { methodname: 'tool_lp_data_for_templates_manage_page', args: { @@ -110,26 +115,60 @@ define(['jquery', 'core/templates', 'core/ajax', 'core/notification', 'core/str' var id = $(this).attr('data-templateid'); templateid = id; + deleteplans = true; var requests = ajax.call([{ methodname: 'tool_lp_read_template', args: { id: templateid } + }, { + methodname: 'tool_lp_template_has_related_data', + args: { id: templateid } }]); requests[0].done(function(template) { - str.get_strings([ - { key: 'confirm', component: 'moodle' }, - { key: 'deletetemplate', component: 'tool_lp', param: template.shortname }, - { key: 'delete', component: 'moodle' }, - { key: 'cancel', component: 'moodle' } - ]).done(function (strings) { - notification.confirm( - strings[0], // Confirm. - strings[1], // Delete learning plan template X? - strings[2], // Delete. - strings[3], // Cancel. - doDelete - ); + requests[1].done(function(templatehasrelateddata) { + if (templatehasrelateddata) { + str.get_strings([ + { key: 'deletetemplate', component: 'tool_lp', param: template.shortname }, + { key: 'deletetemplatewithplans', component: 'tool_lp' }, + { key: 'deleteplans', component: 'tool_lp' }, + { key: 'unlinkplanstemplate', component: 'tool_lp' }, + { key: 'confirm', component: 'moodle' }, + { key: 'cancel', component: 'moodle' } + ]).done(function (strings) { + var actions = [{'text': strings[2], 'value' : 'delete'}, + {'text': strings[3], 'value' : 'unlink'}]; + var actionselector = new Actionselector( + strings[0], // Title. + strings[1], // Message + actions, // Radio button options. + strings[4], // Confirm. + strings[5]); // Cancel. + actionselector.display(); + actionselector.on('save', function(e, data) { + if (data.action != 'delete') { + deleteplans = false; + } + doDelete(); + }); + }).fail(notification.exception); + } + else { + str.get_strings([ + { key: 'confirm', component: 'moodle' }, + { key: 'deletetemplate', component: 'tool_lp', param: template.shortname }, + { key: 'delete', component: 'moodle' }, + { key: 'cancel', component: 'moodle' } + ]).done(function (strings) { + notification.confirm( + strings[0], // Confirm. + strings[1], // Delete learning plan template X? + strings[2], // Delete. + strings[3], // Cancel. + doDelete + ); + }).fail(notification.exception); + } }).fail(notification.exception); }).fail(notification.exception); diff --git a/admin/tool/lp/classes/api.php b/admin/tool/lp/classes/api.php index 2fa7e596403..c5d8f60fd17 100644 --- a/admin/tool/lp/classes/api.php +++ b/admin/tool/lp/classes/api.php @@ -1136,9 +1136,10 @@ class api { * Requires tool/lp:templatemanage capability. * * @param int $id The record to delete. + * @param boolean $deleteplans True to delete plans associaated to template, false to unlink them. * @return boolean */ - public static function delete_template($id) { + public static function delete_template($id, $deleteplans = true) { global $DB; $template = new template($id); @@ -1159,6 +1160,22 @@ class api { } } + // Still OK, delete or unlink the plans from the template. + if ($success) { + $plans = plan::get_records(array('templateid' => $template->get_id())); + foreach ($plans as $plan) { + $success = $deleteplans ? self::delete_plan($plan->get_id()) : self::unlink_plan_from_template($plan); + if (!$success) { + break; + } + } + } + + // Still OK, delete the template comptencies. + if ($success) { + $success = template_competency::delete_by_templateid($template->get_id()); + } + // OK - all set. if ($success) { $success = $template->delete(); @@ -2571,6 +2588,25 @@ class api { return $uc->update(); } + /** + * Check if template has related data. + * + * @param int $templateid The id of the template to check. + * @return boolean + */ + public static function template_has_related_data($templateid) { + // First we do a permissions check. + $template = new template($templateid); + + if (!$template->can_read()) { + throw new required_capability_exception($template->get_context(), 'tool/lp:templateread', 'nopermissions', ''); + } + + // OK - all set. + return $template->has_plans(); + + } + /** * List all the related competencies. * diff --git a/admin/tool/lp/classes/external.php b/admin/tool/lp/classes/external.php index 101acb47fa1..268cdddba17 100644 --- a/admin/tool/lp/classes/external.php +++ b/admin/tool/lp/classes/external.php @@ -1898,8 +1898,15 @@ class external extends external_api { VALUE_REQUIRED ); + $deleteplans = new external_value( + PARAM_BOOL, + 'Boolean to indicate if plans must be deleted', + VALUE_REQUIRED + ); + $params = array( 'id' => $id, + 'deleteplans' => $deleteplans ); return new external_function_parameters($params); } @@ -1908,18 +1915,20 @@ class external extends external_api { * Delete a learning plan template * * @param int $id The learning plan template id + * @param boolean $deleteplans True to delete the plans associated to template or false to unlink them * @return boolean */ - public static function delete_template($id) { + public static function delete_template($id, $deleteplans = true) { $params = self::validate_parameters(self::delete_template_parameters(), array( 'id' => $id, + 'deleteplans' => $deleteplans, )); $template = api::read_template($params['id']); self::validate_context($template->get_context()); - return api::delete_template($params['id']); + return api::delete_template($params['id'], $params['deleteplans']); } /** @@ -2835,6 +2844,50 @@ class external extends external_api { return new external_value(PARAM_BOOL, 'The success'); } + /** + * Returns description of template_has_related_data() parameters. + * + * @return \external_function_parameters + */ + public static function template_has_related_data_parameters() { + $templateid = new external_value( + PARAM_INT, + 'The template id', + VALUE_REQUIRED + ); + $params = array( + 'id' => $templateid, + ); + return new external_function_parameters($params); + } + + /** + * Check if template has related data. + * + * @param int $templateid Template id. + * @return boolean + */ + public static function template_has_related_data($templateid) { + $params = self::validate_parameters(self::template_has_related_data_parameters(), + array( + 'id' => $templateid, + )); + + $template = api::read_template($params['id']); + self::validate_context($template->get_context()); + + return api::template_has_related_data($params['id']); + } + + /** + * Returns description of template_has_related_data() result value. + * + * @return \external_description + */ + public static function template_has_related_data_returns() { + return new external_value(PARAM_BOOL, 'True if the template has related data'); + } + /** * Count the competencies (visible to this user) in this learning plan template. * diff --git a/admin/tool/lp/classes/plan.php b/admin/tool/lp/classes/plan.php index 69cfb7ca7e7..9ba78450a3d 100644 --- a/admin/tool/lp/classes/plan.php +++ b/admin/tool/lp/classes/plan.php @@ -662,4 +662,14 @@ class plan extends persistent { return true; } + + /** + * Checks if a template has user plan records. + * + * @param int $templateid The template ID + * @return boolean + */ + public static function has_records_for_template($templateid) { + return self::record_exists_select('templateid = ?', array($templateid)); + } } diff --git a/admin/tool/lp/classes/template.php b/admin/tool/lp/classes/template.php index 869d4d67576..f884feb6f16 100644 --- a/admin/tool/lp/classes/template.php +++ b/admin/tool/lp/classes/template.php @@ -193,4 +193,13 @@ class template extends persistent { return true; } + /** + * Returns true when the template has user learning plans. + * + * @return boolean + */ + public function has_plans() { + return plan::has_records_for_template($this->get_id()); + } + } diff --git a/admin/tool/lp/classes/template_competency.php b/admin/tool/lp/classes/template_competency.php index fd710120a46..ce95f196dd1 100644 --- a/admin/tool/lp/classes/template_competency.php +++ b/admin/tool/lp/classes/template_competency.php @@ -191,6 +191,18 @@ class template_competency extends persistent { return $instances; } + /** + * Remove the competencies in this template. + * + * @param int $templateid The template id + * @return boolen + */ + public static function delete_by_templateid($templateid) { + global $DB; + + return $DB->delete_records(self::TABLE, array('templateid' => $templateid)); + } + /** * Hook to execute before validate. * diff --git a/admin/tool/lp/db/services.php b/admin/tool/lp/db/services.php index ec4eacf87cd..44b15bd51dc 100644 --- a/admin/tool/lp/db/services.php +++ b/admin/tool/lp/db/services.php @@ -606,7 +606,15 @@ $functions = array( 'capabilities' => 'tool/lp:planmanage', 'ajax' => true, ), - + 'tool_lp_template_has_related_data' => array( + 'classname' => 'tool_lp\external', + 'methodname' => 'template_has_related_data', + 'classpath' => '', + 'description' => 'Check if a template has related data', + 'type' => 'read', + 'capabilities' => 'tool/lp:templateread', + 'ajax' => true, + ), 'tool_lp_get_scale_values' => array( 'classname' => 'tool_lp\external', 'methodname' => 'get_scale_values', diff --git a/admin/tool/lp/lang/en/tool_lp.php b/admin/tool/lp/lang/en/tool_lp.php index 9a734bebdce..79cdece18f8 100644 --- a/admin/tool/lp/lang/en/tool_lp.php +++ b/admin/tool/lp/lang/en/tool_lp.php @@ -76,7 +76,9 @@ $string['deletecompetency'] = 'Delete competency \'{$a}\'?'; $string['deletecompetencyparenthasrule'] = 'Delete competency \'{$a}\'? This will also remove the rule set on its parent.'; $string['deletecompetencyframework'] = 'Delete competency framework \'{$a}\'?'; $string['deleteplan'] = 'Delete plan \'{$a}\'?'; +$string['deleteplans'] = 'Delete the plans'; $string['deletetemplate'] = 'Delete learning plan template \'{$a}\'?'; +$string['deletetemplatewithplans'] = 'This learning plan template has user plans associated. You have to indicate how to process those plans.'; $string['deletethisplan'] = 'Delete this learning plan'; $string['deletethisuserevidence'] = 'Delete this evidence'; $string['deleteuserevidence'] = 'Delete the evidence of prior learning \'{$a}\'?'; @@ -310,6 +312,7 @@ $string['templates'] = 'Learning plan templates'; $string['templateupdated'] = 'Learning plan template updated'; $string['totalrequiredtocomplete'] = 'Total required to complete'; $string['unlinkplantemplate'] = 'Unlink from template'; +$string['unlinkplanstemplate'] = 'Unlink the plans from template'; $string['unlinkplantemplateconfirm'] = 'Unlink the plan \'{$a}\' from its template? Any change made to the template will no longer be applied to the plan. This action can not be undone.'; $string['uponcoursecompletion'] = 'Upon course completion:'; $string['usercommentedonacompetency'] = '{$a->fullname} commented on the competency "{$a->competency}": diff --git a/admin/tool/lp/templates/action_selector.mustache b/admin/tool/lp/templates/action_selector.mustache new file mode 100644 index 00000000000..1f307a7b138 --- /dev/null +++ b/admin/tool/lp/templates/action_selector.mustache @@ -0,0 +1,45 @@ +{{! + 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 . +}} +{{! + Select an action to execute. + + Classes required for JS: + * none + + Data attibutes required for JS: + * none + + Context variables required for this template: + * message - The message to explain why we ask user to select an action + * choices - List of possible actions + * confirm - Text for button confirms + * cancel - Text for button cancel + }} +
+
+ {{message}}
+

+
+ {{#choices}} +
+ {{/choices}} +

+
+ + +
+
diff --git a/admin/tool/lp/tests/api_test.php b/admin/tool/lp/tests/api_test.php index 9d0090198fe..3ff35e9c1ba 100644 --- a/admin/tool/lp/tests/api_test.php +++ b/admin/tool/lp/tests/api_test.php @@ -2709,4 +2709,105 @@ class tool_lp_api_testcase extends advanced_testcase { $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1b->get_ruleoutcome()); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c2->get_ruleoutcome()); } + + public function test_template_has_related_data() { + $this->resetAfterTest(true); + $this->setAdminUser(); + + $dg = $this->getDataGenerator(); + $user = $dg->create_user(); + $lpg = $dg->get_plugin_generator('tool_lp'); + $tpl1 = $lpg->create_template(); + $tpl2 = $lpg->create_template(); + + // Create plans for first template. + $time = time(); + $plan1 = $lpg->create_plan(array('templateid' => $tpl1->get_id(), 'userid' => $user->id, + 'name' => 'Not good name', 'duedate' => $time + 3600, 'description' => 'Ahah', 'descriptionformat' => FORMAT_PLAIN)); + + $this->assertTrue(api::template_has_related_data($tpl1->get_id())); + $this->assertFalse(api::template_has_related_data($tpl2->get_id())); + + } + + public function test_delete_template_delete_plans() { + $this->resetAfterTest(true); + $this->setAdminUser(); + + $dg = $this->getDataGenerator(); + $lpg = $this->getDataGenerator()->get_plugin_generator('tool_lp'); + + $u1 = $dg->create_user(); + $f = $lpg->create_framework(); + $c1 = $lpg->create_competency(array('competencyframeworkid' => $f->get_id())); + $c2 = $lpg->create_competency(array('competencyframeworkid' => $f->get_id())); + + $tpl = $lpg->create_template(); + + $tplc1 = $lpg->create_template_competency(array('templateid' => $tpl->get_id(), 'competencyid' => $c1->get_id(), + 'sortorder' => 1)); + $tplc2 = $lpg->create_template_competency(array('templateid' => $tpl->get_id(), 'competencyid' => $c2->get_id(), + 'sortorder' => 2)); + + $p1 = $lpg->create_plan(array('templateid' => $tpl->get_id(), 'userid' => $u1->id)); + + // Check pre-test. + $this->assertTrue(tool_lp\template::record_exists($tpl->get_id())); + $this->assertEquals(2, \tool_lp\template_competency::count_competencies($tpl->get_id())); + $this->assertEquals(1, count(\tool_lp\plan::get_records(array('templateid' => $tpl->get_id())))); + + $result = api::delete_template($tpl->get_id(), true); + $this->assertTrue($result); + + // Check that the template does not exist anymore. + $this->assertFalse(tool_lp\template::record_exists($tpl->get_id())); + + // Check that associated competencies are also deleted. + $this->assertEquals(0, \tool_lp\template_competency::count_competencies($tpl->get_id())); + + // Check that associated plan are also deleted. + $this->assertEquals(0, count(\tool_lp\plan::get_records(array('templateid' => $tpl->get_id())))); + } + + public function test_delete_template_unlink_plans() { + $this->resetAfterTest(true); + $this->setAdminUser(); + + $dg = $this->getDataGenerator(); + $lpg = $this->getDataGenerator()->get_plugin_generator('tool_lp'); + + $u1 = $dg->create_user(); + $f = $lpg->create_framework(); + $c1 = $lpg->create_competency(array('competencyframeworkid' => $f->get_id())); + $c2 = $lpg->create_competency(array('competencyframeworkid' => $f->get_id())); + + $tpl = $lpg->create_template(); + + $tplc1 = $lpg->create_template_competency(array('templateid' => $tpl->get_id(), 'competencyid' => $c1->get_id(), + 'sortorder' => 1)); + $tplc2 = $lpg->create_template_competency(array('templateid' => $tpl->get_id(), 'competencyid' => $c2->get_id(), + 'sortorder' => 2)); + + $p1 = $lpg->create_plan(array('templateid' => $tpl->get_id(), 'userid' => $u1->id)); + + // Check pre-test. + $this->assertTrue(tool_lp\template::record_exists($tpl->get_id())); + $this->assertEquals(2, \tool_lp\template_competency::count_competencies($tpl->get_id())); + $this->assertEquals(1, count(\tool_lp\plan::get_records(array('templateid' => $tpl->get_id())))); + + $result = api::delete_template($tpl->get_id(), false); + $this->assertTrue($result); + + // Check that the template does not exist anymore. + $this->assertFalse(tool_lp\template::record_exists($tpl->get_id())); + + // Check that associated competencies are also deleted. + $this->assertEquals(0, \tool_lp\template_competency::count_competencies($tpl->get_id())); + + // Check that associated plan still exist but unlink from template. + $plans = \tool_lp\plan::get_records(array('id' => $p1->get_id())); + $this->assertEquals(1, count($plans)); + $this->assertEquals($plans[0]->get_origtemplateid(), $tpl->get_id()); + $this->assertNull($plans[0]->get_templateid()); + } } diff --git a/admin/tool/lp/version.php b/admin/tool/lp/version.php index 022eef9c9bd..9d0dc5f6536 100644 --- a/admin/tool/lp/version.php +++ b/admin/tool/lp/version.php @@ -25,6 +25,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2015111044; // The current plugin version (Date: YYYYMMDDXX). +$plugin->version = 2015111045; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2014110400; // Requires this Moodle version. $plugin->component = 'tool_lp'; // Full name of the plugin (used for diagnostics).