From 6982618e89b2dbb47484b5170cbc45b688e2f131 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 7 Aug 2013 15:51:56 +0100 Subject: [PATCH] MDL-40956 Course: AJAX course duplication --- course/lib.php | 107 +++++++++++++++++++++++++++++- course/rest.php | 12 ++++ course/yui/toolboxes/toolboxes.js | 46 +++++++++++++ 3 files changed, 164 insertions(+), 1 deletion(-) diff --git a/course/lib.php b/course/lib.php index 85c8c588aee..9f5ea46f115 100644 --- a/course/lib.php +++ b/course/lib.php @@ -2039,7 +2039,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) { new moodle_url($baseurl, array('duplicate' => $mod->id)), new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall', 'title' => '')), $str->duplicate, - array('class' => 'editing_duplicate', 'data-action' => 'duplicate') + array('class' => 'editing_duplicate', 'data-action' => 'duplicate', 'data-sr' => $sr) ); } @@ -3380,6 +3380,111 @@ function update_module($moduleinfo) { return $moduleinfo; } +/** + * Duplicate a module on the course. + * + * @param object $course The course + * @param object $cm The course module to duplicate + * @throws moodle_exception if the plugin doesn't support duplication + * @return Object containing: + * - fullcontent: The HTML markup for the created CM + * - cmid: The CMID of the newly created CM + * - redirect: Whether to trigger a redirect following this change + */ +function mod_duplicate_activity($course, $cm, $sr = null) { + global $CFG, $USER, $PAGE, $DB; + + require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); + require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php'); + require_once($CFG->libdir . '/filelib.php'); + + $a = new stdClass(); + $a->modtype = get_string('modulename', $cm->modname); + $a->modname = format_string($cm->name); + + if (!plugin_supports('mod', $cm->modname, FEATURE_BACKUP_MOODLE2)) { + throw new moodle_exception('duplicatenosupport', 'error'); + } + + // backup the activity + + $bc = new backup_controller(backup::TYPE_1ACTIVITY, $cm->id, backup::FORMAT_MOODLE, + backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id); + + $backupid = $bc->get_backupid(); + $backupbasepath = $bc->get_plan()->get_basepath(); + + $bc->execute_plan(); + + $bc->destroy(); + + // restore the backup immediately + + $rc = new restore_controller($backupid, $course->id, + backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, backup::TARGET_CURRENT_ADDING); + + $cmcontext = context_module::instance($cm->id); + if (!$rc->execute_precheck()) { + $precheckresults = $rc->get_precheck_results(); + if (is_array($precheckresults) && !empty($precheckresults['errors'])) { + if (empty($CFG->keeptempdirectoriesonbackup)) { + fulldelete($backupbasepath); + } + } + } + + $rc->execute_plan(); + + // now a bit hacky part follows - we try to get the cmid of the newly + // restored copy of the module + $newcmid = null; + $tasks = $rc->get_plan()->get_tasks(); + foreach ($tasks as $task) { + error_log("Looking at a task"); + if (is_subclass_of($task, 'restore_activity_task')) { + error_log("Looking at a restore_activity_task task"); + if ($task->get_old_contextid() == $cmcontext->id) { + error_log("Contexts match"); + $newcmid = $task->get_moduleid(); + break; + } + } + } + + // if we know the cmid of the new course module, let us move it + // right below the original one. otherwise it will stay at the + // end of the section + if ($newcmid) { + $info = get_fast_modinfo($course); + $newcm = $info->get_cm($newcmid); + $section = $DB->get_record('course_sections', array('id' => $cm->section, 'course' => $cm->course)); + moveto_module($newcm, $section, $cm); + moveto_module($cm, $section, $newcm); + } + rebuild_course_cache($cm->course); + + $rc->destroy(); + + if (empty($CFG->keeptempdirectoriesonbackup)) { + fulldelete($backupbasepath); + } + + $resp = new stdClass(); + if ($newcm) { + $courserenderer = $PAGE->get_renderer('core', 'course'); + $completioninfo = new completion_info($course); + $modulehtml = $courserenderer->course_section_cm($course, $completioninfo, + $newcm, null, array()); + + $resp->fullcontent = $courserenderer->course_section_cm_list_item($course, $completioninfo, $newcm, $sr); + $resp->cmid = $newcm->id; + } else { + // Trigger a redirect + $resp->redirect = true; + } + return $resp; +} + /** * Compare two objects to find out their correct order based on timestamp (to be used by usort). * Sorts by descending order of time. diff --git a/course/rest.php b/course/rest.php index 79a1152b0b1..96dbbd2c6bf 100644 --- a/course/rest.php +++ b/course/rest.php @@ -106,6 +106,18 @@ switch($requestmethod) { set_coursemodule_visible($cm->id, $value); break; + case 'duplicate': + require_capability('moodle/course:manageactivities', $modcontext); + require_capability('moodle/backup:backuptargetimport', $modcontext); + require_capability('moodle/restore:restoretargetimport', $modcontext); + if (!course_allowed_module($course, $cm->modname)) { + throw new moodle_exception('No permission to create that activity'); + } + $sr = optional_param('sr', null, PARAM_INT); + $result = mod_duplicate_activity($course, $cm, $sr); + echo json_encode($result); + break; + case 'groupmode': require_capability('moodle/course:manageactivities', $modcontext); set_coursemodule_groupmode($cm->id, $value); diff --git a/course/yui/toolboxes/toolboxes.js b/course/yui/toolboxes/toolboxes.js index d30c41a950a..1d98ba76b51 100644 --- a/course/yui/toolboxes/toolboxes.js +++ b/course/yui/toolboxes/toolboxes.js @@ -265,6 +265,10 @@ YUI.add('moodle-course-toolboxes', function(Y) { // The user is deleting the activity. this.delete_with_confirmation(ev, node, activity, action); break; + case 'duplicate' : + // The user is duplicating the activity. + this.duplicate(ev, node, activity, action); + break; case 'hide' : case 'show' : // The user is changing the visibility of the activity. @@ -416,6 +420,48 @@ YUI.add('moodle-course-toolboxes', function(Y) { } }, + /** + * Duplicates the activity + * + * @protected + * @method duplicate + * @param {EventFacade} ev The event that was fired. + * @param {Node} button The button that triggered this action. + * @param {Node} activity The activity node that this action will be performed on. + * @return Boolean + */ + duplicate : function(ev, button, activity) { + // Prevent the default button action + ev.preventDefault(); + + // Get the element we're working on + var element = activity; + + // Add the lightbox. + var section = activity.ancestor(M.course.format.get_section_selector(Y)), + lightbox = M.util.add_lightbox(Y, section).show(); + + // Build and send the request. + var data = { + 'class' : 'resource', + 'field' : 'duplicate', + 'id' : Y.Moodle.core_course.util.cm.getId(element), + 'sr' : button.getData('sr') + }; + this.send_request(data, lightbox, function(response) { + var newcm = Y.Node.create(response.fullcontent); + + // Append to the section? + activity.insert(newcm, 'after'); + Y.use('moodle-course-coursebase', function() { + M.course.coursebase.invoke_function('setup_for_resource', newcm); + }); + if (M.core.actionmenu && M.core.actionmenu.newDOMNode) { + M.core.actionmenu.newDOMNode(newcm); + } + }); + }, + /** * Changes the visibility of this activity or resource. *