Commit eb3ec9c1 authored by Robert Lyon's avatar Robert Lyon
Browse files

Bug 1409546: Annotation feedback on form



When you click on the hollow circle (rather than dot) on the matrix
screen you will get an annotation feedback form in the modal dock
rather than the create annotation form.

Change-Id: Id9c73a2a3a379b2700c4196726a50179be81974f
Signed-off-by: Robert Lyon's avatarRobert Lyon <robertl@catalyst.net.nz>
parent bc2ad0c5
......@@ -112,3 +112,4 @@ $string['private'] = 'Private';
$string['public'] = 'Public';
$string['enteredon'] = 'entered on';
$string['noreflectionentryfound'] = "Cannot find reflection entry for annotation.";
$string['nofeedback'] = "There is no feedback for this annotation yet.";
\ No newline at end of file
......@@ -627,7 +627,7 @@ class ArtefactTypeAnnotationfeedback extends ArtefactType {
$options->view = ''; // viewid that the annotation is linked to.
$options->block = ''; // blockid that the annotation lives in.
$options->export = false;
$options->export = 0;
$sortorder = get_user_institution_comment_sort_order();
$options->sort = (!empty($sortorder)) ? $sortorder : 'earliest';
return $options;
......@@ -797,7 +797,7 @@ class ArtefactTypeAnnotationfeedback extends ArtefactType {
if (isset($data->showcomment) && $data->showcomment == $item->id) {
$item->highlight = 1;
}
$is_export_preview = param_integer('export', 0);
$is_export_preview = param_integer('export', $data->export);
if ($item->deletedby) {
$item->deletedmessage = $deletedmessage[$item->deletedby];
}
......@@ -932,15 +932,159 @@ class ArtefactTypeAnnotationfeedback extends ArtefactType {
return $newest[0];
}
/**
* Fetching the annotations for an artefact to display on a matrix
*
* @param object $annotationartefact The annotation artefact to display feedbacks for.
* @param object $view The view on which the annotation artefact is linked to.
* @param int $blockid The id of the block instance that connects the artefact to the view
* @param boolean $listonly Only return the list and not the form
*
*/
public function get_annotation_feedback_for_matrix($annotationartefact, $view, $blockid, $listonly = false) {
$options = ArtefactTypeAnnotationfeedback::get_annotation_feedback_options();
$options->limit = 0;
$options->view = $view->get('id');
$options->annotation = $annotationartefact->get('id');
$options->block = $blockid;
$options->export = 1;
$annotationfeedback = ArtefactTypeAnnotationfeedback::get_annotation_feedback($options);
$annotationfeedbackcount = isset($annotationfeedback->count) ? $annotationfeedback->count : 0;
if ($listonly) {
return array($annotationfeedbackcount, $annotationfeedback);
}
$smarty = smarty_core();
$smarty->assign('blockid', $blockid);
$smarty->assign('annotationfeedbackcount', $annotationfeedbackcount);
$smarty->assign('annotationfeedback', $annotationfeedback);
if ($annotationartefact->get('allowcomments')) {
$form = ArtefactTypeAnnotationfeedback::add_annotation_feedback_form(false, $annotationartefact->get('approvecomments'), $annotationartefact, $view, null, $blockid);
// Replace the submit/cancel with just a submit button
$submit = array(
'type' => 'submit',
'value' => get_string('placeannotationfeedback', 'artefact.annotation'),
'class' => 'btn-default'
);
$form['elements']['submit'] = $submit;
// Remove the 'assessment' option as we want that independent of submitting feedback here
unset($form['elements']['assessment']);
$addannotationfeedbackform = pieform($form);
$smarty->assign('addannotationfeedbackform', $addannotationfeedbackform);
}
else {
// The user has switched off annotation feedback. Don't create the add annotation feedback form.
$smarty->assign('addannotationfeedbackform', null);
}
$render = $smarty->fetch('artefact:annotation:annotationfeedbackmatrix.tpl');
return array($annotationfeedbackcount, $render);
}
/**
* Saving the annotation feedback via the matrix dock
*
* @param object $annotationartefact The annotation artefact to add feedback to.
* @param object $view The view the annotation artefact is on
* @param int $blockid The id of the block instance that connects the artefact to the view
* @param string $message The feedback message
* @param boolean $ispublic Whether it is a public message or not
*/
public function save_matrix_feedback($annotationartefact, $view, $blockid, $message, $ispublic = true) {
global $USER;
if (!is_object($annotationartefact) || !is_object($view) || empty($message)) {
throw new MaharaException(get_string('annotationinformationerror', 'artefact.annotation'));
}
$data = (object) array(
'title' => get_string('Annotation', 'artefact.annotation'),
'description' => $message,
'onannotation' => $annotationartefact->get('id'),
);
$data->view = $view->get('id');
$data->owner = $view->get('owner');
$data->group = $view->get('group');
$data->institution = $view->get('institution');
if ($author = $USER->get('id')) {
$anonymous = false;
$data->author = $author;
}
else {
$anonymous = true;
$data->authorname = $values['authorname'];
}
// @TODO deal with moderation
// if (isset($values['moderate']) && $values['ispublic'] && !$USER->can_edit_view($view)) {
// $data->private = 1;
// $data->requestpublic = 'author';
// $moderated = true;
// }
// else {
$data->private = (int) !$ispublic;
$moderated = false;
// }
$private = $data->private;
$annotationfeedback = new ArtefactTypeAnnotationfeedback(0, $data);
db_begin();
$annotationfeedback->commit();
if (isset($data->requestpublic) && $data->requestpublic === 'author' && $data->owner) {
$arg = $author ? display_name($USER, null, true) : $data->authorname;
$moderatemsg = (object) array(
'subject' => false,
'message' => false,
'strings' => (object) array(
'subject' => (object) array(
'key' => 'makepublicrequestsubject',
'section' => 'artefact.annotation',
'args' => array(),
),
'message' => (object) array(
'key' => 'makepublicrequestbyauthormessage',
'section' => 'artefact.annotation',
'args' => array(hsc($arg)),
),
'urltext' => (object) array(
'key' => 'Annotation',
'section' => 'artefact.annotation',
),
),
'users' => array($data->owner),
'url' => $url,
);
}
require_once('activity.php');
$data = (object) array(
'annotationfeedbackid' => $annotationfeedback->get('id'),
'annotationid' => $annotationartefact->get('id'),
'viewid' => $view->get('id'),
);
activity_occurred('annotationfeedback', $data, 'artefact', 'annotation');
if (isset($moderatemsg)) {
activity_occurred('maharamessage', $moderatemsg);
}
db_commit();
list($count, $newlist) = self::get_annotation_feedback_for_matrix($annotationartefact, $view, $blockid, true);
return $newlist->tablerows;
}
/**
* Fetching the annotations for an artefact to display on a view
*
* @param object $annotationartefact The annotation artefact to display feedbacks for.
* @param object $view The view on which the annotation artefact is linked to.
* @param int $blockid The id of the block instance that connects the artefact to the view
* @param int @annotationscountonview The number annotations alread on the view. If one is already
* on there, don't add the add_annotation_feedback_form as it's already been
* created.
* @param bool $html Whether to return the information rendered as html or not
* @param bool $editing Whether we are view edit mode or not
*/
......@@ -1102,6 +1246,33 @@ class ArtefactTypeAnnotationfeedback extends ArtefactType {
'cols' => 80,
'rules' => array('maxlength' => 8192),
);
if (isset($view)) {
$collection = $view->get('collection');
if (is_object($collection) && $collection->get('framework')) {
foreach ($view->get_artefact_metadata() as $metadata) {
if ($metadata->id === $annotation->get('id')) {
$evidence = get_record('framework_evidence', 'annotation', $metadata->block);
$defaultval = $evidence->state;
$form['elements']['assessment'] = array(
'type' => 'select',
'title' => get_string('assessment', 'module.framework'),
'options' => array(
'0' => get_string('begun','module.framework'),
'1' => get_string('incomplete','module.framework'),
'2' => get_string('partialcomplete','module.framework'),
'3' => get_string('completed','module.framework'),
),
'defaultvalue' => $defaultval,
'width' => '280px',
);
$form['elements']['evidence'] = array(
'type' => 'hidden',
'value' => $evidence->id,
);
}
}
}
}
$form['elements']['ispublic'] = array(
'type' => 'switchbox',
'title' => get_string('makepublic', 'artefact.annotation'),
......@@ -1593,6 +1764,18 @@ function add_annotation_feedback_form_submit(Pieform $form, $values) {
db_begin();
$annotationfeedback->commit();
if (!empty($values['evidence']) && !empty($values['assessment'])) {
$reviewer = null;
if ((int) $values['assessment'] === Framework::EVIDENCE_COMPLETED) {
$reviewer = $USER->get('id');
}
$fordb = array('mtime' => db_format_timestamp(time()),
'state' => $values['assessment'],
'reviewer' => $reviewer,
);
// update row
update_record('framework_evidence', (object) $fordb, (object) array('id' => $values['evidence']));
}
$url = $annotation->get_view_url($view->get('id'), true, false);
$goto = get_config('wwwroot') . $url;
......
......@@ -27,3 +27,9 @@ $string['standard'] = 'Standard';
$string['standarddesc'] = 'Select the standard this evidence addresses. You can type into the box to search the standards.';
$string['annotationclash'] = 'There is already an annotation block on the page for this standard';
$string['needtoactivate'] = 'The annotation plugin needs activation. Please ask your site administrator to activate it.';
$string['studentannotation'] = "Annotation:";
$string['assessment'] = 'Assessment';
$string['begun'] = 'Ready for assessment';
$string['incomplete'] = 'Doesn\'t meet the standard';
$string['partialcomplete'] = 'Partially meets the standard';
$string['completed'] = 'Meets the standard';
......@@ -383,10 +383,10 @@ class Framework {
*/
public static function get_state_array($state) {
return array(
'begun' => ((int) $state === Self::EVIDENCE_BEGUN),
'incomplete' => ((int) $state === Self::EVIDENCE_INCOMPLETE),
'partialcomplete' => ((int) $state === Self::EVIDENCE_PARTIALCOMPLETE),
'completed' => ((int) $state === Self::EVIDENCE_COMPLETED),
'begun' => ((int) $state === Self::EVIDENCE_BEGUN ? 1 : 0),
'incomplete' => ((int) $state === Self::EVIDENCE_INCOMPLETE ? 1 : 0),
'partialcomplete' => ((int) $state === Self::EVIDENCE_PARTIALCOMPLETE ? 1 : 0),
'completed' => ((int) $state === Self::EVIDENCE_COMPLETED ? 1 : 0),
);
}
......@@ -424,7 +424,8 @@ class Framework {
}
/**
* Get the evidence state for the framework
* Add/update an annotation block on a view via the framework matrix page.
* This hooks into using the annotation block's config form.
*
* @param int $annotationid
*
......@@ -514,6 +515,7 @@ class Framework {
if (!empty($element)) {
$fordb['element'] = $element;
}
$fordb['reviewer'] = ($completed === 1) ? $reviewer : null;
update_record('framework_evidence', (object) $fordb, (object) array('id' => $id));
}
else {
......@@ -569,6 +571,77 @@ class Framework {
}
}
}
/**
* Add/update an annotation status form on the framework matrix page.
* This uses a feedback style config form with some extra bits.
*/
public function annotation_feedback_form($data) {
require_once(get_config('docroot') . 'blocktype/lib.php');
$annotation = new BlockInstance($data->annotation);
$configdata = $annotation->get('configdata');
if (empty($configdata['artefactid'])) {
return false;
}
safe_require('artefact', 'file');
$artefactid = $configdata['artefactid'];
$artefact = $annotation->get_artefact_instance($artefactid);
$view = $annotation->get_view();
$text = $artefact->get('description');
$collection = $view->get('collection');
$evidence = get_record('framework_evidence', 'annotation', $annotation->get('id'));
if (!is_object($collection) || !$collection->get('framework')) {
return false;
}
$form = array(
'name' => 'annotationfeedback',
'jsform' => true,
'renderer' => 'div',
'plugintype' => 'module',
'pluginname' => 'framework',
'jssuccesscallback' => 'updateAnnotation',
'elements' => array(
'annotation' => array(
'type' => 'html',
'title' => get_string('studentannotation', 'module.framework'),
'value' => $text,
),
'assessment' => array(
'type' => 'select',
'title' => get_string('assessment', 'module.framework'),
'options' => array(
'0' => get_string('begun','module.framework'),
'1' => get_string('incomplete','module.framework'),
'2' => get_string('partialcomplete','module.framework'),
'3' => get_string('completed','module.framework'),
),
'defaultvalue' => $evidence->state,
'width' => '280px',
'class' => 'top-line',
),
'submitcancel' => array(
'type' => 'submitcancel',
'class' => 'btn-default',
'value' => array(get_string('save'), get_string('cancel')),
'goto' => get_string('docroot') . 'module/framework/matrix.php?id=' . $collection->get('id'),
),
),
);
$content = pieform($form);
list($feedbackcount, $annotationfeedback) = ArtefactTypeAnnotationfeedback::get_annotation_feedback_for_matrix($artefact, $view, $annotation->get('id'));
$content .= $annotationfeedback;
$return = array(
'content' => $content,
'js' => 'function updateAnnotation(form, data) { formSuccess(form, data); }',
'css' => '',
'title' => $annotation->get_title(),
);
return $return;
}
}
class FrameworkNotFoundException extends NotFoundException {}
......@@ -194,6 +194,10 @@ jQuery(function($) {
editmatrix_update(params);
}
tinyMCE.execCommand('mceRemoveEditor', false, "instconf_text");
feedbacktextarea = $("#addfeedbackmatrix textarea");
if (feedbacktextarea.length) {
tinyMCE.execCommand('mceRemoveEditor', false, feedbacktextarea.attr('id'));
}
dock.hide();
});
cancelbutton = newpagemodal.find('.submitcancel.cancel');
......@@ -206,10 +210,20 @@ jQuery(function($) {
editmatrix_update(params);
}
tinyMCE.execCommand('mceRemoveEditor', false, "instconf_text");
feedbacktextarea = $("#addfeedbackmatrix textarea");
if (feedbacktextarea.length) {
tinyMCE.execCommand('mceRemoveEditor', false, feedbacktextarea.attr('id'));
}
dock.hide();
});
tinyMCE.idCounter=0;
tinyMCE.execCommand('mceAddEditor', false, "instconf_text");
if ($("#instconf_text").length) {
tinyMCE.execCommand('mceAddEditor', false, "instconf_text");
}
if ($("#addfeedbackmatrix").length) {
textareaid = $("#addfeedbackmatrix textarea").attr('id');
tinyMCE.execCommand('mceAddEditor', false, textareaid);
}
// Only allow the point selected to be active in the 'Standard' dropdown
$("#instconf_smartevidence option:not(:selected)").prop('disabled', true);
$("#instconf_smartevidence").select2();
......@@ -223,7 +237,7 @@ jQuery(function($) {
$("#instconf_smartevidence").on('change', function() {
show_se_desc($(this).val());
});
// When we are saving the annotation block config form
$('#instconf').on('submit', function(se) {
se.preventDefault();
var sdata = $("#instconf :input").serializeArray();
......@@ -243,9 +257,48 @@ jQuery(function($) {
values['option'] = params.option;
values['action'] = 'update';
editmatrix_update(values);
dock.hide();
});
// When we are saving the annotation feedback form - changing the evidence status
$('#annotationfeedback').on('submit', function(se) {
se.preventDefault();
var sdata = $("#annotationfeedback :input").serializeArray();
var values = {};
sdata.forEach(function(item, index) {
values[item.name] = item.value;
});
values['framework'] = params.framework;
values['view'] = params.view;
values['option'] = params.option;
values['action'] = 'evidence';
editmatrix_update(values);
tinyMCE.execCommand('mceRemoveEditor', false, "instconf_text");
dock.hide();
});
// When we are saving the annotation feedback form - adding new feedback
$('#addfeedbackmatrix').on('submit', function(se) {
se.preventDefault();
var sdata = $("#addfeedbackmatrix :input").serializeArray();
var values = {};
sdata.forEach(function(item, index) {
values[item.name] = item.value;
});
textareaid = $("#addfeedbackmatrix textarea").attr('id');
if (values['message'].length == 0) {
// add error message
$("#" + textareaid).parent().append('<div class="errmsg"><span>' +
get_string_ajax('annotationfeedbackempty', 'artefact.annotation') +
'</span></div>');
}
else {
values['framework'] = params.framework;
values['view'] = params.view;
values['option'] = params.option;
values['action'] = 'feedback';
editmatrix_update(values);
}
});
});
});
......@@ -258,6 +311,14 @@ jQuery(function($) {
.data('option', results.data.option)
.data('view', results.data.view).empty();
}
if (results.data.tablerows) {
if ($("#matrixfeedbacklist").has(".annotationfeedbacktable").length == 0) {
$("#matrixfeedbacklist").html('<ul class="annotationfeedbacktable list-group list-group-lite list-unstyled"></div>');
}
$("#matrixfeedbacklist .annotationfeedbacktable").html(results.data.tablerows);
textareaid = $("#addfeedbackmatrix textarea").attr('id');
tinyMCE.get(textareaid).setContent('');
}
});
}
// Setup
......
......@@ -31,6 +31,7 @@ $action = param_alphanum('action', 'form');
$evidence = get_record('framework_evidence', 'framework', $framework, 'element', $option, 'view', $view);
if ($action == 'update') {
// When we click a dot on the matrix and add an annotation
require_once(get_config('docroot') . 'blocktype/lib.php');
$title = param_alphanumext('title', 'Annotation');
$text = param_variable('text', '');
......@@ -42,6 +43,7 @@ if ($action == 'update') {
$values = array('title' => $title,
'text' => $text,
'tags' => $tags,
'allowfeedback' => $allowfeedback,
'retractable' => $retractable,
'retractedonload' => 0,
);
......@@ -55,6 +57,7 @@ if ($action == 'update') {
$bi->set('configdata', $values);
$bi->set('title', $title);
$bi->commit();
if ($evidence) {
$id = Framework::save_evidence($evidence->id, null, null, null, $bi->get('id'));
$message = get_string('matrixpointupdated', 'module.framework');
......@@ -65,6 +68,46 @@ if ($action == 'update') {
}
$class = 'icon icon-circle-o danger';
$data = (object) array('id' => $id,
'class' => $class,
'view' => $view,
'option' => $option
);
json_reply(false, array('message' => $message, 'data' => $data));
}
else if ($action == 'evidence') {
global $USER;
// When we click on one of the begun/ready/completed symbols and submit that form
if (!$evidence->id) {
// problem need to return error
}
$begun = $ready = $completed = 0;
$reviewer = null;
$assessment = param_alpha('assessment', 'begun');
switch ($assessment) {
case 'completed':
$begun = $ready = $completed = 1;
$reviewer = $USER->get('id');
break;
case 'ready':
$begun = $ready = 1;
break;
default:
$begun = 1;
}
$id = Framework::save_evidence($evidence->id, null, null, null, $evidence->annotation, $assessment, $USER->get('id'));
$message = get_string('matrixpointupdated', 'module.framework');
if ($completed) {
$class = 'icon icon-circle success';
}
else if ($ready) {
$class = 'icon icon-adjust warning';
}
else {
$class = 'icon icon-circle-o danger';
}
$data = (object) array('id' => $id,
'class' => $class,
......@@ -73,7 +116,20 @@ if ($action == 'update') {
);
json_reply(false, array('message' => $message, 'data' => $data));
}
if ($action == 'delete') {
else if ($action == 'feedback') {
$annotationid = param_integer('annotationid');
$annotation = new ArtefactTypeAnnotation((int) $annotationid);
$blockid = param_integer('blockid');
$message = param_variable('message');
$ispublic = param_boolean('ispublic');
require_once(get_config('libroot') . 'view.php');
$view = new View($view);
$newlist = ArtefactTypeAnnotationfeedback::save_matrix_feedback($annotation, $view, $blockid, $message, $ispublic);
$message = get_string('annotationfeedbacksubmitted', 'artefact.annotation');
$data = (object) array('id' => $evidence->id, 'tablerows' => $newlist);
json_reply(false, array('message' => $message, 'data' => $data));
}
else if ($action == 'delete') {
// Clean up partial annotation block instance
require_once(get_config('docroot') . 'blocktype/lib.php');
$blockid = param_integer('blockconfig', 0);
......@@ -100,7 +156,13 @@ else {
'partialcomplete' => $states['partialcomplete'],
'completed' => $states['completed'],
);
$form = Framework::annotation_config_form($params);
if ($evidence && !empty($params->begun)) {
// There is an annotation in play
$form = Framework::annotation_feedback_form($params);
}
else {
$form = Framework::annotation_config_form($params);
}
$data = (object) array('form' => $form);
json_reply(false, (object) array('message' => $message, 'data' => $data));
}
\ No newline at end of file
<div id="annotationfeedback_{$blockid}" class="annotation-feedback matrix">
<div class="modal-header modal-section">
{str tag="Annotationfeedback" section="artefact.annotation"}
</div>
{if $addannotationfeedbackform}
<div id="addfeedbackmatrix">
{str tag="placeannotationfeedback" section="artefact.annotation"}
{$addannotationfeedbackform|safe}
</div>
{/if}
<div id="matrixfeedbacklist">
{if $annotationfeedbackcount}
<ul id="annotationfeedbacktable_{$blockid}" class="annotationfeedbacktable list-group list-group-lite list-unstyled">
{$annotationfeedback->tablerows|safe}
</ul>
{else}
<div class="form-group"><span class="description">{str tag='nofeedback' section='artefact.annotation'}</span></div>
{/if}
</div>
</div>
......@@ -48,6 +48,11 @@ $width-sm: 90%;
.list-group.flush {
margin: -15px;
}
.matrix {