Saving a form as a public template
authorDavid Mudrak <david@moodle.com>
Tue, 18 Oct 2011 23:17:43 +0000 (01:17 +0200)
committerDavid Mudrak <david@moodle.com>
Tue, 18 Oct 2011 23:17:43 +0000 (01:17 +0200)
There is a new API method get_definition_copy() that is expected to
return the definition structure as if the form was written from scratch
via the editor. Such a prepared structure is passed to
a controller's update_definition() method in the new target area.

The same mechanism will be used for copying definitions from a shared
area to a normal gradable area.

grade/grading/form/lib.php
grade/grading/form/rubric/lib.php
grade/grading/lib.php
grade/grading/manage.php
grade/grading/renderer.php
grade/grading/yui/manage/manage.js [new file with mode: 0644]
lang/en/grading.php
pix/b/bookmark-new.png [new file with mode: 0644]
pix/b/document-edit.png [new file with mode: 0644]
theme/standard/style/grade.css

index 59bbc36..f0663aa 100644 (file)
@@ -183,6 +183,36 @@ abstract class gradingform_controller {
         return $this->definition;
     }
 
+    /**
+     * Returns the form definition suitable for cloning into another area
+     *
+     * @param gradingform_controller $target the controller of the new copy
+     * @return stdClass definition structure to pass to the target's {@link update_definition()}
+     */
+    public function get_definition_copy(gradingform_controller $target) {
+
+        if (get_class($this) != get_class($target)) {
+            throw new coding_exception('The source and copy controller mismatch');
+        }
+
+        if ($target->is_form_defined()) {
+            throw new coding_exception('The target controller already contains a form definition');
+        }
+
+        $old = $this->get_definition();
+        // keep our id
+        $new = new stdClass();
+        $new->copiedfromid = $old->id;
+        $new->name = $old->name;
+        // once we support files embedded into the description, we will want to
+        // relink them into the new file area here (that is why we accept $target)
+        $new->description = $old->description;
+        $new->descriptionformat = $old->descriptionformat;
+        $new->options = $old->options;
+
+        return $new;
+    }
+
     /**
      * Saves the defintion data into the database
      *
index 0a293c6..67298ca 100644 (file)
@@ -233,6 +233,39 @@ class gradingform_rubric_controller extends gradingform_controller {
         return $properties;
     }
 
+    /**
+     * Returns the form definition suitable for cloning into another area
+     *
+     * @see parent::get_definition_copy()
+     * @param gradingform_controller $target the controller of the new copy
+     * @return stdClass definition structure to pass to the target's {@link update_definition()}
+     */
+    public function get_definition_copy(gradingform_controller $target) {
+
+        $new = parent::get_definition_copy($target);
+        $old = $this->get_definition();
+        $new->rubric_criteria = array();
+        $newcritid = 1;
+        $newlevid = 1;
+        foreach ($old->rubric_criteria as $oldcritid => $oldcrit) {
+            unset($oldcrit['id']);
+            if (isset($oldcrit['levels'])) {
+                foreach ($oldcrit['levels'] as $oldlevid => $oldlev) {
+                    unset($oldlev['id']);
+                    $oldcrit['levels']['NEWID'.$newlevid] = $oldlev;
+                    unset($oldcrit['levels'][$oldlevid]);
+                    $newlevid++;
+                }
+            } else {
+                $oldcrit['levels'] = array();
+            }
+            $new->rubric_criteria['NEWID'.$newcritid] = $oldcrit;
+            $newcritid++;
+        }
+
+        return $new;
+    }
+
     public function get_grading($raterid, $itemid) {
         global $DB;
         $sql = "SELECT f.id, f.criterionid, f.levelid, f.remark, f.remarkformat
@@ -250,6 +283,7 @@ class gradingform_rubric_controller extends gradingform_controller {
             }
             // TODO: remarks
         }
+        $rs->close();
         return $grading;
     }
 
index 6ad77b6..ca36655 100644 (file)
@@ -155,13 +155,26 @@ class grading_manager {
     public function get_component_title() {
 
         $this->ensure_isset(array('context', 'component'));
-        list($context, $course, $cm) = get_context_info_array($this->get_context()->id);
 
-        if (!empty($cm->name)) {
-            $title = $cm->name;
+        if ($this->get_context()->contextlevel == CONTEXT_SYSTEM) {
+            if ($this->get_component() == 'core_grading') {
+                $title = ''; // we are in the bank UI
+            } else {
+                throw new coding_exception('Unsupported component at the system context');
+            }
+
+        } else if ($this->get_context()->contextlevel >= CONTEXT_COURSE) {
+            list($context, $course, $cm) = get_context_info_array($this->get_context()->id);
+
+            if (!empty($cm->name)) {
+                $title = $cm->name;
+            } else {
+                debugging('Gradable areas are currently supported at the course module level only', DEBUG_DEVELOPER);
+                $title = $this->get_component();
+            }
+
         } else {
-            debugging('Gradable areas are currently supported at the course module level only', DEBUG_DEVELOPER);
-            $title = $this->get_component();
+            throw new coding_exception('Unsupported gradable area context level');
         }
 
         return $title;
@@ -174,10 +187,22 @@ class grading_manager {
      */
     public function get_area_title() {
 
-        $this->ensure_isset(array('context', 'component', 'area'));
-        $areas = $this->get_available_areas();
+        if ($this->get_context()->contextlevel == CONTEXT_SYSTEM) {
+            return '';
+
+        } else if ($this->get_context()->contextlevel >= CONTEXT_COURSE) {
+            $this->ensure_isset(array('context', 'component', 'area'));
+            $areas = $this->get_available_areas();
+            if (array_key_exists($this->get_area(), $areas)) {
+                return $areas[$this->get_area()];
+            } else {
+                debugging('Unknown area!');
+                return '???';
+            }
 
-        return $areas[$this->get_area()];
+        } else {
+            throw new coding_exception('Unsupported context level');
+        }
     }
 
     /**
@@ -240,6 +265,7 @@ class grading_manager {
         // require_once($CFG->dirroot.'/mod/assignment/lib.php');
         // return assignment_gradable_area_list();
 
+        // todo - what to return for bank areas in the system context
         // todo - hardcoded list for now
         return array('submission' => 'Submissions');
     }
@@ -465,6 +491,29 @@ class grading_manager {
         return new moodle_url('/grade/grading/manage.php', $params);
     }
 
+    /**
+     * Creates a new shared area to hold a grading form template
+     *
+     * Shared area are implemented as virtual gradable areas at the system level context
+     * with the component set to core_grading and unique random area name.
+     *
+     * @param string $method the name of the plugin we create the area for
+     * @return int the new area id
+     */
+    public function create_shared_area($method) {
+        global $DB;
+
+        // generate some unique random name for the new area
+        $name = $method . '_' . sha1(rand().uniqid($method, true));
+        // create new area record
+        $area = array(
+            'contextid'     => get_system_context()->id,
+            'component'     => 'core_grading',
+            'areaname'      => $name,
+            'activemethod'  => $method);
+        return $DB->insert_record('grading_areas', $area);
+    }
+
     ////////////////////////////////////////////////////////////////////////////
 
     /**
index 1fff61c..f761a4f 100644 (file)
 require_once(dirname(dirname(dirname(__FILE__))).'/config.php');
 require_once($CFG->dirroot.'/grade/grading/lib.php');
 
+// identify gradable area by its id
 $areaid     = optional_param('areaid', null, PARAM_INT);
+// alternatively the context, component and areaname must be provided
 $contextid  = optional_param('contextid', null, PARAM_INT);
 $component  = optional_param('component', null, PARAM_COMPONENT);
 $area       = optional_param('area', null, PARAM_AREA);
+// keep the caller's URL so that we know where to send the user finally
 $returnurl  = optional_param('returnurl', null, PARAM_LOCALURL);
+// active method selector
 $setmethod  = optional_param('setmethod', null, PARAM_PLUGIN);
+// publish the given form definition as a new template in the forms bank
+$shareform  = optional_param('shareform', null, PARAM_INT);
+// consider the required action as confirmed
+$confirmed  = optional_param('confirmed', false, PARAM_BOOL);
+// a message to display, typically a previous action's result
+$message    = optional_param('message', null, PARAM_NOTAGS);
 
 if (!is_null($areaid)) {
     // get manager by id
@@ -47,17 +57,30 @@ if (!is_null($areaid)) {
 // currently active method
 $method = $manager->get_active_method();
 
-list($context, $course, $cm) = get_context_info_array($manager->get_context()->id);
+if ($manager->get_context()->contextlevel == CONTEXT_SYSTEM) {
+    // this is a shared area in the forms bank, redirect the user
+    $params = array('areaid' =>$areaid);
+    if (!is_null($returnurl)) {
+        $params['returnurl'] = $returnurl;
+    }
+    redirect(new moodle_url('/grade/grading/bank.php', $params));
+
+} else if ($manager->get_context()->contextlevel >= CONTEXT_COURSE) {
+    list($context, $course, $cm) = get_context_info_array($manager->get_context()->id);
+
+    if (is_null($returnurl)) {
+        $returnurl = new moodle_url('/course/view.php', array('id' => $course->id));
+    } else {
+        $returnurl = new moodle_url($returnurl);
+    }
+
+    require_login($course, true, $cm);
+    require_capability('moodle/grade:managegradingforms', $context);
 
-if (is_null($returnurl)) {
-    $returnurl = new moodle_url('/course/view.php', array('id' => $course->id));
 } else {
-    $returnurl = new moodle_url($returnurl);
+    throw new coding_exception('Unsupported gradable area context level');
 }
 
-require_login($course, true, $cm);
-require_capability('moodle/grade:managegradingforms', $context);
-
 $PAGE->set_url($manager->get_management_url($returnurl));
 navigation_node::override_active_url($manager->get_management_url());
 $PAGE->set_title(get_string('gradingmanagement', 'core_grading'));
@@ -75,7 +98,35 @@ if (!empty($setmethod)) {
     redirect($PAGE->url);
 }
 
+// publish the form as a template
+if (!empty($shareform)) {
+    require_capability('moodle/grade:sharegradingforms', get_system_context());
+    $controller = $manager->get_controller($method);
+    $definition = $controller->get_definition();
+    if (!$confirmed) {
+        // let the user confirm they understand what they are doing (haha ;-)
+        echo $output->header();
+        echo $output->confirm(get_string('manageactionshareconfirm', 'core_grading', s($definition->name)),
+            new moodle_url($PAGE->url, array('shareform' => $shareform, 'confirmed' => 1)),
+            $PAGE->url);
+        echo $output->footer();
+        die();
+    } else {
+        require_sesskey();
+        $newareaid = $manager->create_shared_area($method);
+        $targetarea = get_grading_manager($newareaid);
+        $targetcontroller = $targetarea->get_controller($method);
+        $targetcontroller->update_definition($controller->get_definition_copy($targetcontroller));
+        redirect(new moodle_url($PAGE->url, array('message' => get_string('manageactionsharedone', 'core_grading'))));
+    }
+}
+
 echo $output->header();
+
+if (!empty($message)) {
+    echo $output->management_message($message);
+}
+
 echo $output->heading(get_string('gradingmanagementtitle', 'core_grading', array(
     'component' => $manager->get_component_title(), 'area' => $manager->get_area_title())));
 
@@ -88,10 +139,15 @@ if (!empty($method)) {
     // display relevant actions
     echo $output->container_start('actions');
     if ($controller->is_form_defined()) {
+        $definition = $controller->get_definition();
         echo $output->management_action_icon($controller->get_editor_url($returnurl),
-            get_string('manageactionedit', 'core_grading'), 'b/document-properties');
+            get_string('manageactionedit', 'core_grading'), 'b/document-edit');
         echo $output->management_action_icon($PAGE->url,
             get_string('manageactiondelete', 'core_grading'), 'b/edit-delete');
+        if (has_capability('moodle/grade:sharegradingforms', get_system_context())) {
+            echo $output->management_action_icon(new moodle_url($PAGE->url, array('shareform' => $definition->id)),
+                get_string('manageactionshare', 'core_grading'), 'b/bookmark-new');
+        }
     } else {
         echo $output->management_action_icon($controller->get_editor_url($returnurl),
             get_string('manageactionnew', 'core_grading'), 'b/document-new');
index 173f07b..526f4a5 100644 (file)
@@ -66,6 +66,18 @@ class core_grading_renderer extends plugin_renderer_base {
         return html_writer::link($url, $img . $txt, array('class' => 'action'));
     }
 
+    /**
+     * Renders a message for the user, typically as an action result
+     *
+     * @param string $message
+     * @return string
+     */
+    public function management_message($message) {
+        $this->page->requires->strings_for_js(array('clicktoclose'), 'core_grading');
+        $this->page->requires->yui_module('moodle-core_grading-manage', 'M.core_grading.init_manage');
+        return $this->output->box(format_string($message).html_writer::tag('span', ''), 'message', 'actionresultmessagebox');
+    }
+
     /**
      * Renders the common information about the form definition
      *
diff --git a/grade/grading/yui/manage/manage.js b/grade/grading/yui/manage/manage.js
new file mode 100644 (file)
index 0000000..2d1ecfb
--- /dev/null
@@ -0,0 +1,48 @@
+/**
+ * YUI module for advanced grading methods - the manage page
+ *
+ * @author David Mudrak <david@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+YUI.add('moodle-core_grading-manage', function(Y) {
+
+    var MANAGE = function() {
+        MANAGE.superclass.constructor.apply(this, arguments);
+    }
+
+    Y.extend(MANAGE, Y.Base, {
+
+        initializer : function(config) {
+            this.setup_messagebox();
+        },
+
+        setup_messagebox : function() {
+            Y.one('#actionresultmessagebox span').setContent(M.util.get_string('clicktoclose', 'core_grading'));
+            Y.one('#actionresultmessagebox').on('click', function(e) {
+                e.halt();
+                var box = e.currentTarget;
+                var anim = new Y.Anim({
+                    node: box,
+                    from: { opacity: 1 },
+                    to: { opacity: 0 },
+                });
+                anim.run();
+                anim.on('end', function() {
+                    var box = this.get('node'); // this === anim
+                    box.remove(true);
+                });
+            });
+        }
+
+    }, {
+        NAME : 'grading_manage_page',
+        ATTRS : { }
+    });
+
+    M.core_grading = M.core_grading || {};
+
+    M.core_grading.init_manage = function(config) {
+        return new MANAGE(config);
+    }
+
+}, '@VERSION@', { requires:['base', 'anim'] });
index 57c2446..4e5479a 100644 (file)
@@ -29,6 +29,7 @@ defined('MOODLE_INTERNAL') || die();
 $string['activemethodinfo'] = '\'{$a->method}\' is selected as the active grading method for the \'{$a->area}\' area';
 $string['activemethodinfonone'] = 'There is no advanced grading method selected for the \'{$a->area}\' area. Simple direct grading will be used.';
 $string['changeactivemethod'] = 'Change active grading method to';
+$string['clicktoclose'] = 'click to close';
 $string['exc_gradingformelement'] = 'Unable to instantiate grading form element';
 $string['formnotavailable'] = 'Advanced grading method was selected to use but the grading form is not available yet. You may need to define it first via a link in the Settings block.';
 $string['gradingmanagement'] = 'Advanced grading';
@@ -37,10 +38,13 @@ $string['gradingmethod'] = 'Grading method';
 $string['gradingmethod_help'] = 'Choose the advanced grading method that should be used for calculating grades in the given context.
 
 To disable advance grading and switch back to the default grading mechanism, choose \'Simple direct grading\'.';
-$string['gradingmethods'] = 'Grading methods';
 $string['gradingmethodnone'] = 'Simple direct grading';
-$string['manageactionclone'] = 'Create new grading form from template';
+$string['gradingmethods'] = 'Grading methods';
+$string['manageactionclone'] = 'Create new grading form from a template';
 $string['manageactiondelete'] = 'Remove the currently defined form';
 $string['manageactionedit'] = 'Edit the current form definition';
 $string['manageactionnew'] = 'Define new grading form from scratch';
+$string['manageactionshare'] = 'Publish the form as a new template';
+$string['manageactionshareconfirm'] = 'You are going to save a copy of the grading form \'{$a}\' as a new public template. Other users at your site will be able to create new grading forms in their activities from that template. Note that users are able to reuse their own grading forms in other activities even if the forms were not saved as template.';
+$string['manageactionsharedone'] = 'The form was successfully saved as a template';
 $string['noitemid'] = 'Grading not possible. The graded item does not exist.';
diff --git a/pix/b/bookmark-new.png b/pix/b/bookmark-new.png
new file mode 100644 (file)
index 0000000..2d494b4
Binary files /dev/null and b/pix/b/bookmark-new.png differ
diff --git a/pix/b/document-edit.png b/pix/b/document-edit.png
new file mode 100644 (file)
index 0000000..7b53f8f
Binary files /dev/null and b/pix/b/document-edit.png differ
index a8a47e2..4fb48d5 100644 (file)
@@ -41,4 +41,7 @@ td.grade div.overridden {background-color: #DDDDDD;}
 #page-grade-grading-manage .actions {text-align:center;}
 #page-grade-grading-manage .action {display:inline-block;width: 150px;background-color:#EEE;border:2px solid #CCC;
     margin:0.5em;padding:0.5em;text-align:center;-moz-border-radius:5px}
-#page-grade-grading-manage .action:hover {text-decoration:none;background-color:#F6F6F6;
\ No newline at end of file
+#page-grade-grading-manage .action:hover {text-decoration:none;background-color:#F6F6F6;}
+#page-grade-grading-manage #actionresultmessagebox {background-color:#D2EBFF;width:60%;margin:1em auto 1em auto;text-align:center;
+    padding:0.5em;border:2px solid #CCC;text-align:center;-moz-border-radius:5px;position:relative}
+#page-grade-grading-manage #actionresultmessagebox span {position:absolute;right:0px;top:-1.2em;color:#666;font-size:80%}