diff --git a/admin/tool/lp/amd/build/frameworkactions.min.js b/admin/tool/lp/amd/build/frameworkactions.min.js
new file mode 100644
index 00000000000..5e5ee423ba6
--- /dev/null
+++ b/admin/tool/lp/amd/build/frameworkactions.min.js
@@ -0,0 +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="managecompetencies"]').replaceWith(c),b.runTemplateJS(d)},i=function(a){b.render("tool_lp/manage_competency_frameworks_page",a).done(h).fail(d.exception)},j=function(b){b.preventDefault(),g=a(this).attr("data-frameworkid");var e=c.call([{methodname:"tool_lp_duplicate_competency_framework",args:{id:g}},{methodname:"tool_lp_data_for_competency_frameworks_manage_page",args:{pagecontext:{contextid:f}}}]);e[1].done(i).fail(d.exception)},k=function(){var a=c.call([{methodname:"tool_lp_delete_competency_framework",args:{id:g}},{methodname:"tool_lp_data_for_competency_frameworks_manage_page",args:{pagecontext:{contextid:f}}}]);a[1].done(i).fail(d.exception)},l=function(b){b.preventDefault();var f=a(this).attr("data-frameworkid");g=f;var h=c.call([{methodname:"tool_lp_read_competency_framework",args:{id:g}}]);h[0].done(function(a){e.get_strings([{key:"confirm",component:"moodle"},{key:"deletecompetencyframework",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],k)}).fail(d.exception)}).fail(d.exception)};return{deleteHandler:l,duplicateHandler:j,init:function(a){f=a}}});
\ No newline at end of file
diff --git a/admin/tool/lp/amd/src/frameworkdelete.js b/admin/tool/lp/amd/src/frameworkactions.js
similarity index 79%
rename from admin/tool/lp/amd/src/frameworkdelete.js
rename to admin/tool/lp/amd/src/frameworkactions.js
index 42adff77689..9eb23138bdd 100644
--- a/admin/tool/lp/amd/src/frameworkdelete.js
+++ b/admin/tool/lp/amd/src/frameworkactions.js
@@ -14,9 +14,9 @@
// along with Moodle. If not, see .
/**
- * Delete competency frameworks via ajax.
+ * Competency frameworks actions via ajax.
*
- * @module tool_lp/frameworkdelete
+ * @module tool_lp/frameworkactions
* @package tool_lp
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -52,6 +52,30 @@ define(['jquery', 'core/templates', 'core/ajax', 'core/notification', 'core/str'
.fail(notification.exception);
};
+ /**
+ * Duplicate a framework and reload the page.
+ * @method doDuplicate
+ * @param {Event} e
+ */
+ var doDuplicate = function(e) {
+ e.preventDefault();
+
+ frameworkid = $(this).attr('data-frameworkid');
+
+ // We are chaining ajax requests here.
+ var requests = ajax.call([{
+ methodname: 'tool_lp_duplicate_competency_framework',
+ args: { id: frameworkid }
+ }, {
+ methodname: 'tool_lp_data_for_competency_frameworks_manage_page',
+ args: {
+ pagecontext: {
+ contextid: pagecontextid
+ }
+ }
+ }]);
+ requests[1].done(reloadList).fail(notification.exception);
+ };
/**
* Delete a framework and reload the page.
*/
@@ -107,7 +131,7 @@ define(['jquery', 'core/templates', 'core/ajax', 'core/notification', 'core/str'
};
- return /** @alias module:tool_lp/frameworkdelete */ {
+ return /** @alias module:tool_lp/frameworkactions */ {
// Public variables and functions.
/**
@@ -117,6 +141,13 @@ define(['jquery', 'core/templates', 'core/ajax', 'core/notification', 'core/str'
*/
deleteHandler: confirmDelete,
+ /**
+ * Expose the event handler for duplicate.
+ * @method duplicateHandler
+ * @param {Event} e
+ */
+ duplicateHandler: doDuplicate,
+
/**
* Initialise the module.
* @method init
diff --git a/admin/tool/lp/classes/api.php b/admin/tool/lp/classes/api.php
index 691af8a7722..a6d8fb803f3 100644
--- a/admin/tool/lp/classes/api.php
+++ b/admin/tool/lp/classes/api.php
@@ -353,6 +353,55 @@ class api {
return $framework;
}
+ /**
+ * Duplicate a competency framework by id.
+ *
+ * Requires tool/lp:competencymanage capability at the system context.
+ *
+ * @param int $id The record to duplicate. All competencies associated and related will be duplicated.
+ * @return competency_framework the framework duplicated
+ */
+ public static function duplicate_framework($id) {
+ $framework = new competency_framework($id);
+ require_capability('tool/lp:competencymanage', $framework->get_context());
+
+ // Get a uniq idnumber based on the origin framework.
+ $idnumber = competency_framework::get_unused_idnumber($framework->get_idnumber());
+ $framework->set_idnumber($idnumber);
+ // Adding the suffix copy to the shortname.
+ $framework->set_shortname(get_string('duplicateditemname', 'tool_lp', $framework->get_shortname()));
+ $framework->set_id(0);
+ $framework->create();
+
+ // Array that match the old competencies ids with the new one to use when copying related competencies.
+ $matchids = self::duplicate_competency_tree($framework->get_id(), competency::get_framework_tree($id), 0, 0);
+
+ // Copy the related competencies.
+ $relcomps = related_competency::get_multiple_relations(array_keys($matchids));
+
+ foreach ($relcomps as $relcomp) {
+ $compid = $relcomp->get_competencyid();
+ $relcompid = $relcomp->get_relatedcompetencyid();
+ if (isset($matchids[$compid]) && isset($matchids[$relcompid])) {
+ $newcompid = $matchids[$compid];
+ $newrelcompid = $matchids[$relcompid];
+ if ($newcompid < $newrelcompid) {
+ $relcomp->set_competencyid($newcompid);
+ $relcomp->set_relatedcompetencyid($newrelcompid);
+ } else {
+ $relcomp->set_competencyid($newrelcompid);
+ $relcomp->set_relatedcompetencyid($newcompid);
+ }
+ $relcomp->set_id(0);
+ $relcomp->create();
+ } else {
+ // Debugging message when there is no match found.
+ debugging('related competency id not found');
+ }
+ }
+ return $framework;
+ }
+
/**
* Delete a competency framework by id.
*
@@ -1361,4 +1410,42 @@ class api {
return false;
}
+
+ /**
+ * Recursively duplicate competencies from a tree, we start duplicating from parents to children to have a correct path.
+ * This method does not copy the related competencies.
+ *
+ * @param int $frameworkid - framework id
+ * @param competency[] $tree - array of competencies object
+ * @param int $oldparent - old parent id
+ * @param int $newparent - new parent id
+ * @return array $matchids - List of old competencies ids matched with new ids.
+ */
+ protected static function duplicate_competency_tree($frameworkid, $tree, $oldparent = 0, $newparent = 0) {
+ $matchids = array();
+ foreach ($tree as $node) {
+ if ($node->competency->get_parentid() == $oldparent) {
+ $parentid = $node->competency->get_id();
+
+ // Create the competency.
+ $competency = $node->competency;
+ $competency->set_competencyframeworkid($frameworkid);
+ $competency->set_parentid($newparent);
+ $competency->set_path('');
+ $competency->set_id(0);
+ $competency->create();
+
+ // Match the old id with the new one.
+ $matchids[$parentid] = $competency->get_id();
+
+ if (!empty($node->children)) {
+ // Duplicate children competency.
+ $childrenids = self::duplicate_competency_tree($frameworkid, $node->children, $parentid, $competency->get_id());
+ // Array_merge does not keep keys when merging so we use the + operator.
+ $matchids = $matchids + $childrenids;
+ }
+ }
+ }
+ return $matchids;
+ }
}
diff --git a/admin/tool/lp/classes/competency.php b/admin/tool/lp/classes/competency.php
index fff17f92ac4..89bc7d826ac 100644
--- a/admin/tool/lp/classes/competency.php
+++ b/admin/tool/lp/classes/competency.php
@@ -413,4 +413,39 @@ class competency extends persistent {
return $DB->count_records_select(self::TABLE, "id $insql", $params, "COUNT(DISTINCT(competencyframeworkid))") == 1;
}
+ /**
+ * Build a framework tree with competency nodes.
+ *
+ * @param int $frameworkid the framework id
+ * @return node[] tree of framework competency nodes
+ */
+ public static function get_framework_tree($frameworkid) {
+
+ $competencies = self::search('', $frameworkid);
+
+ $parentnode = new stdClass();
+ $parentnode->competency = new competency();
+
+ return self::build_tree($competencies, $parentnode);
+ }
+
+ /**
+ * Recursively build up the tree of nodes.
+ *
+ * @param array $all - List of all competency classes.
+ * @param stdClass $parent - the exported parent node
+ */
+ protected static function build_tree($all, $parent) {
+ $tree = array();
+ foreach ($all as $one) {
+ if ($one->get_parentid() == $parent->competency->get_id()) {
+ $node = new stdClass();
+ $node->competency = $one;
+ $node->children = self::build_tree($all, $node);
+ $tree[] = $node;
+ }
+ }
+ return $tree;
+ }
+
}
diff --git a/admin/tool/lp/classes/competency_framework.php b/admin/tool/lp/classes/competency_framework.php
index ae5c39a0205..9edb79846b2 100644
--- a/admin/tool/lp/classes/competency_framework.php
+++ b/admin/tool/lp/classes/competency_framework.php
@@ -343,4 +343,25 @@ class competency_framework extends persistent {
return $list;
}
+ /**
+ * Get a uniq idnumber.
+ *
+ * @param string $idnumber the framework idnumber
+ * @return string
+ */
+ public static function get_unused_idnumber($idnumber) {
+ global $DB;
+
+ $currentidnumber = $idnumber;
+ $counter = 0;
+ // Iteratere while the idnumber exists.
+ while ($DB->record_exists_select(static::TABLE, 'idnumber = ?', array($currentidnumber))) {
+ $suffixidnumber = '_' . ++$counter;
+ $currentidnumber = substr($idnumber, 0, 100 - strlen($suffixidnumber)).$suffixidnumber;
+ }
+
+ // Return the uniq idnumber.
+ return $currentidnumber;
+ }
+
}
diff --git a/admin/tool/lp/classes/external.php b/admin/tool/lp/classes/external.php
index c16dab13f89..0c74171a709 100644
--- a/admin/tool/lp/classes/external.php
+++ b/admin/tool/lp/classes/external.php
@@ -387,6 +387,56 @@ class external extends external_api {
return self::get_competency_framework_external_structure();
}
+ /**
+ * Returns description of duplicate_competency_framework() parameters.
+ *
+ * @return \external_function_parameters
+ */
+ public static function duplicate_competency_framework_parameters() {
+ $id = new external_value(
+ PARAM_INT,
+ 'Data base record id for the framework',
+ VALUE_REQUIRED
+ );
+
+ $params = array(
+ 'id' => $id,
+ );
+ return new external_function_parameters($params);
+ }
+
+ /**
+ * Expose to AJAX
+ * @return boolean
+ */
+ public static function duplicate_competency_framework_is_allowed_from_ajax() {
+ return true;
+ }
+
+ /**
+ * Duplicate a competency framework
+ *
+ * @param int $id The competency framework id
+ * @return boolean
+ */
+ public static function duplicate_competency_framework($id) {
+ $params = self::validate_parameters(self::duplicate_competency_framework_parameters(),
+ array(
+ 'id' => $id,
+ ));
+
+ return api::duplicate_framework($params['id']);
+ }
+
+ /**
+ * Returns description of duplicate_competency_framework() result value.
+ *
+ * @return \external_description
+ */
+ public static function duplicate_competency_framework_returns() {
+ return self::get_competency_framework_external_structure();
+ }
+
/**
* Returns description of delete_competency_framework() parameters.
*
diff --git a/admin/tool/lp/classes/related_competency.php b/admin/tool/lp/classes/related_competency.php
index ccb27a45511..df9c406886a 100644
--- a/admin/tool/lp/classes/related_competency.php
+++ b/admin/tool/lp/classes/related_competency.php
@@ -159,4 +159,28 @@ class related_competency extends persistent {
return $competencies;
}
+ /**
+ * Get the related competencies from competency ids.
+ *
+ * @param int[] $competencyids Array of competency ids.
+ * @return related_competency[]
+ */
+ public static function get_multiple_relations($competencyids) {
+ global $DB;
+
+ list($insql, $params) = $DB->get_in_or_equal($competencyids);
+
+ $records = $DB->get_records_select(self::TABLE,
+ "competencyid $insql OR relatedcompetencyid $insql",
+ array_merge($params, $params)
+ );
+
+ $relatedcompetencies = array();
+ foreach ($records as $record) {
+ unset($record->id);
+ $relatedcompetencies[] = new related_competency(null, $record);
+ }
+ return $relatedcompetencies;
+ }
+
}
diff --git a/admin/tool/lp/db/services.php b/admin/tool/lp/db/services.php
index 254ef53d3eb..194609ec9b8 100644
--- a/admin/tool/lp/db/services.php
+++ b/admin/tool/lp/db/services.php
@@ -44,6 +44,14 @@ $functions = array(
'type' => 'read',
'capabilities' => 'tool/lp:competencyview',
),
+ 'tool_lp_duplicate_competency_framework' => array(
+ 'classname' => 'tool_lp\external',
+ 'methodname' => 'duplicate_competency_framework',
+ 'classpath' => '',
+ 'description' => 'Duplicate a competency framework.',
+ 'type' => 'write',
+ 'capabilities' => 'tool/lp:competencymanage',
+ ),
'tool_lp_delete_competency_framework' => array(
'classname' => 'tool_lp\external',
'methodname' => 'delete_competency_framework',
diff --git a/admin/tool/lp/templates/manage_competency_frameworks_page.mustache b/admin/tool/lp/templates/manage_competency_frameworks_page.mustache
index 9241343f3fc..e46904930a8 100644
--- a/admin/tool/lp/templates/manage_competency_frameworks_page.mustache
+++ b/admin/tool/lp/templates/manage_competency_frameworks_page.mustache
@@ -61,6 +61,11 @@
{{#pix}}t/edit{{/pix}} {{#str}}edit{{/str}}
+
+
+ {{#pix}}t/copy{{/pix}} {{#str}}duplicate{{/str}}
+
+
{{#pix}}t/delete{{/pix}} {{#str}}delete{{/str}}
@@ -89,14 +94,15 @@
{{#js}}
// Initialise the JS.
-require(['tool_lp/frameworkdelete',
+require(['tool_lp/frameworkactions',
'tool_lp/menubar'],
- function(deleteMod, menubar) {
+ function(actionsMod, menubar) {
- deleteMod.init({{pagecontextid}});
+ actionsMod.init({{pagecontextid}});
menubar.enhance('.competencyframeworkactions', {
- "[data-action='deletecompetencyframework']": deleteMod.deleteHandler
+ "[data-action='deletecompetencyframework']": actionsMod.deleteHandler,
+ "[data-action='duplicatecompetencyframework']": actionsMod.duplicateHandler,
});
});
diff --git a/admin/tool/lp/tests/api_test.php b/admin/tool/lp/tests/api_test.php
index 6c1614ce3dd..2d28d60a171 100644
--- a/admin/tool/lp/tests/api_test.php
+++ b/admin/tool/lp/tests/api_test.php
@@ -241,4 +241,67 @@ class tool_lp_api_testcase extends advanced_testcase {
$this->assertEquals($framework3->get_id(), $r2->get_id());
}
+ /**
+ * Test duplicate a framework.
+ */
+ public function test_duplicate_framework() {
+ $lpg = $this->getDataGenerator()->get_plugin_generator('tool_lp');
+ $this->resetAfterTest(true);
+ $this->setAdminUser();
+
+ $syscontext = context_system::instance();
+ $params = array(
+ 'shortname' => 'shortname_a',
+ 'idnumber' => 'idnumber_c',
+ 'description' => 'description',
+ 'descriptionformat' => FORMAT_HTML,
+ 'visible' => true,
+ 'contextid' => $syscontext->id
+ );
+ $framework = $lpg->create_framework($params);
+ $competency1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get_id()));
+ $competency2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get_id()));
+ $competency3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get_id()));
+ $competency4 = $lpg->create_competency(array('competencyframeworkid' => $framework->get_id()));
+ $competencyidnumbers = array($competency1->get_idnumber(),
+ $competency2->get_idnumber(),
+ $competency3->get_idnumber(),
+ $competency4->get_idnumber()
+ );
+
+ api::add_related_competency($competency1->get_id(), $competency2->get_id());
+ api::add_related_competency($competency3->get_id(), $competency4->get_id());
+
+ $frameworkduplicated1 = api::duplicate_framework($framework->get_id());
+ $frameworkduplicated2 = api::duplicate_framework($framework->get_id());
+
+ $this->assertEquals($framework->get_idnumber().'_1', $frameworkduplicated1->get_idnumber());
+ $this->assertEquals($framework->get_idnumber().'_2', $frameworkduplicated2->get_idnumber());
+
+ $competenciesfr1 = api::list_competencies(array('competencyframeworkid' => $frameworkduplicated1->get_id()));
+ $competenciesfr2 = api::list_competencies(array('competencyframeworkid' => $frameworkduplicated2->get_id()));
+
+ $competencyidsfr1 = array();
+ $competencyidsfr2 = array();
+
+ foreach ($competenciesfr1 as $cmp) {
+ $competencyidsfr1[] = $cmp->get_idnumber();
+ }
+ foreach ($competenciesfr2 as $cmp) {
+ $competencyidsfr2[] = $cmp->get_idnumber();
+ }
+
+ $this->assertEmpty(array_diff($competencyidsfr1, $competencyidnumbers));
+ $this->assertEmpty(array_diff($competencyidsfr2, $competencyidnumbers));
+ $this->assertCount(4, $competenciesfr1);
+ $this->assertCount(4, $competenciesfr2);
+
+ // Test the related competencies.
+ reset($competenciesfr1);
+ $compduplicated1 = current($competenciesfr1);
+ $relatedcompetencies = $compduplicated1->get_related_competencies();
+ $comprelated = current($relatedcompetencies);
+ $this->assertEquals($comprelated->get_idnumber(), $competency2->get_idnumber());
+ }
+
}
diff --git a/admin/tool/lp/version.php b/admin/tool/lp/version.php
index c4ed10a33aa..96da15d8ee1 100644
--- a/admin/tool/lp/version.php
+++ b/admin/tool/lp/version.php
@@ -25,7 +25,7 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2015052414; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version = 2015052415; // 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).