Commit 367a6c44 authored by Robert Lyon's avatar Robert Lyon Committed by Cecilia Vela Gurovic

Bug 1784778: Adding signoff feature to peer assessment plugin

Includes:

- Allow a blocktype plugin to add a <div> to the toolbar area of a
viewed page.

- Display the verify/signoff options that can be updated by the
correct user.

- Fixing some styles

behatnotneeded

Change-Id: I6d225a2f4a89aa586d0422770b07b55503f2904b
Signed-off-by: Robert Lyon's avatarRobert Lyon <robertl@catalyst.net.nz>
parent 1ab0d9a2
......@@ -21,3 +21,5 @@ $string['draft'] = 'Save draft';
$string['publish'] = 'Publish';
$string['savepublishhelp'] = '<p><strong>Save draft:</strong> Only you can view it. While your assessment is in draft status, you can make changes.</p>
<p><strong>Publish:</strong> The person for whom you are giving the peer assessment can see your assessment. Everybody else who has access to the portfolio can view it as well, unless the portfolio also contains the signoff block and is not signed off by the portfolio owner. You cannot revert a published assessment to draft status.</p>';
$string['signoff'] = 'Signed off';
$string['verify'] = 'Verified';
......@@ -84,7 +84,9 @@ class PluginBlocktypePeerassessment extends MaharaCoreBlocktype {
}
else {
$smarty->assign('editing', $editing);
$smarty->assign('noassessment', get_string('nopeerassessment', 'blocktype.peerassessment/peerassessment'));
if ($feedback->count = 0) {
$smarty->assign('noassessment', get_string('nopeerassessment', 'blocktype.peerassessment/peerassessment'));
}
}
$html = $smarty->fetch('blocktype:peerassessment:peerassessment.tpl');
return $html;
......@@ -140,6 +142,26 @@ class PluginBlocktypePeerassessment extends MaharaCoreBlocktype {
);
}
public static function get_instance_toolbars(BlockInstance $bi) {
global $USER;
$view = $bi->get_view();
safe_require('artefact', 'peerassessment');
$smarty = smarty_core();
$smarty->assign('WWWROOT', get_config('wwwroot'));
$smarty->assign('view', $view->get('id'));
$smarty->assign('verifiable', ArtefactTypePeerassessment::is_verifiable($view));
$smarty->assign('signable', ArtefactTypePeerassessment::is_signable($view));
$smarty->assign('verified', ArtefactTypePeerassessment::is_verified($view));
$smarty->assign('signoff', ArtefactTypePeerassessment::is_signed_off($view));
return array(
array(
'toolbarhtml' => $smarty->fetch('blocktype:peerassessment:verifyform.tpl')
)
);
}
public static function delete_instance(BlockInstance $instance) {
$id = $instance->get('id');
require_once('embeddedimage.php');
......
<?php
/**
*
* @package mahara
* @subpackage artefact-peerassessment
* @author Catalyst IT Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
*/
define('INTERNAL', 1);
define('JSON', 1);
require(dirname(dirname(dirname(__FILE__))) . '/init.php');
require_once(get_config('libroot') . 'view.php');
safe_require('artefact', 'peerassessment');
safe_require('blocktype', 'peerassessment');
$blockid = param_integer('block', null);
$viewid = param_integer('view', null);
$signoff = param_integer('signoff', null);
$verify = param_integer('verify', null);
if (empty($viewid) && !empty($blockid)) {
// try to find view from blockid
$bi = new ArtefactTypePeerassessment($blockid);
$viewid = $bi->get_view()->get('id');
}
// Does the view have a peerassessment block
if (!$blocks = get_records_sql_array("SELECT id FROM {block_instance} WHERE view = ? AND blocktype = ?", array($viewid, 'peerassessment'))) {
json_reply('local', get_string('wrongblocktype', 'view'));
}
$view = new View($viewid);
$signable = ArtefactTypePeerassessment::is_signable($view);
$verifiable = ArtefactTypePeerassessment::is_verifiable($view);
$data = new stdClass();
if ($signable && $signoff !== null) {
$currentstate = ArtefactTypePeerassessment::is_signed_off($view);
$newstate = 1 - (int)$currentstate;
execute_sql("UPDATE {view_signoff_verify} SET signoff = ? WHERE view = ?", array($newstate, $viewid));
if ($currentstate == 1) {
// we are removing sign-off so we should also remove verify as well
execute_sql("UPDATE {view_signoff_verify} SET verified = ? WHERE view = ?", array(0, $viewid));
$data->verify_change = 1;
}
$data->signoff_newstate = $newstate;
}
else if ($verifiable && !empty($verify)) {
$currentstate = ArtefactTypePeerassessment::is_verified($view);
$newstate = 1;
if ((int)$currentstate != $newstate) {
execute_sql("UPDATE {view_signoff_verify} SET verified = ? WHERE view = ?", array($newstate, $viewid));
}
$data->verify_newstate = $newstate;
}
else {
// No rights to do what is requested
json_reply('local', get_string('wrongassessmentviewrequest', 'artefact.peerassessment'));
}
json_reply(false, array('message' => get_string('assessmentviewupdated', 'artefact.peerassessment'),
'data' => $data));
......@@ -24,6 +24,8 @@ $string['assessmentsubmitteddraft'] = 'Peer assessment saved as draft';
$string['reallydeletethisassessment'] = 'Delete this peer assessment';
$string['thisassessmentisprivate'] = 'Saved as draft';
$string['assessmentremoved'] = 'Peer assessment deleted';
$string['assessmentviewupdated'] = 'Peer assessment status updated';
$string['wrongassessmentviewrequest'] = 'You do not have permission to perform the requested action';
// peer assessment notifications
$string['deletednotificationsubject'] = 'Peer assessment on page "%s" deleted';
......
......@@ -238,6 +238,7 @@ class ArtefactTypePeerassessment extends ArtefactType {
if ($new) {
insert_record('artefact_peer_assessment', $data);
ensure_record_exists('view_signoff_verify', (object) array('view' => $this->get('view')), (object) array('view' => $this->get('view')), 'id', true);
}
else {
update_record('artefact_peer_assessment', $data, 'assessment');
......@@ -343,7 +344,7 @@ class ArtefactTypePeerassessment extends ArtefactType {
}
$userid = $USER->get('id');
$viewid = $view->get('id');
$canedit = $USER->can_peer_assess($view);
$canedit = ($USER->can_peer_assess($view) && !self::is_signed_off($view));
$owner = $view->get('owner');
$isowner = $userid && $userid == $owner;
......@@ -362,12 +363,14 @@ class ArtefactTypePeerassessment extends ArtefactType {
$where = 'pa.view = ? ';
// select assessments that are published
// or select assessments where the user is the author, published or not
$where.= 'AND ( (pa.private = 0) ';
$where.= ' OR (a.author = ?))';
// If the view is signed off, select public assessments
// or if viewing as page owner, select public assessments
// or if viewing as assessment author, select assessments owned by author
$where.= 'AND ((vsv.signoff = 1 AND pa.private = 0) ';
$where.= ' OR (a.author = ?)';
$where.= ' OR (pa.private = 0 AND a.owner = ?))';
$values = array((int)$viewid, (int)$userid, $block);
$values = array($viewid, $userid, $userid, $block);
$result->count = count_records_sql('
SELECT COUNT(*)
......@@ -375,6 +378,8 @@ class ArtefactTypePeerassessment extends ArtefactType {
{artefact} a
JOIN {artefact_peer_assessment} pa
ON a.id = pa.assessment
JOIN {view_signoff_verify} vsv
ON vsv.view = pa.view
LEFT JOIN {artefact} p
ON a.parent = p.id
WHERE ' . $where . '
......@@ -399,6 +404,7 @@ class ArtefactTypePeerassessment extends ArtefactType {
$ids = get_column_sql('
SELECT a.id
FROM {artefact} a JOIN {artefact_peer_assessment} pa ON a.id = pa.assessment
JOIN {view_signoff_verify} vsv ON vsv.view = pa.view
LEFT JOIN {artefact} p ON a.parent = p.id
WHERE ' . $where . '
AND pa.block = ?
......@@ -429,6 +435,7 @@ class ArtefactTypePeerassessment extends ArtefactType {
u.deleted, u.profileicon, u.urlid, p.id AS parent, p.author AS parentauthor
FROM {artefact} a
INNER JOIN {artefact_peer_assessment} pa ON a.id = pa.assessment
JOIN {view_signoff_verify} vsv ON vsv.view = pa.view
LEFT JOIN {artefact} p
ON a.parent = p.id
LEFT JOIN {usr} u ON a.author = u.id
......@@ -445,6 +452,44 @@ class ArtefactTypePeerassessment extends ArtefactType {
return $result;
}
public static function is_signable(View $view) {
global $USER;
$signable = false;
if ($view->get('owner')) {
$signable = ($view->get('owner') == $USER->get('id')) ? true : false;
}
return $signable;
}
public static function is_signed_off(View $view) {
if (!$view->get('owner')) {
return false;
}
return (bool)get_field_sql("SELECT signoff FROM {view_signoff_verify} WHERE view = ? LIMIT 1", array($view->get('id')));
}
public static function is_verifiable(View $view) {
global $USER;
if (!$view->get('owner')) {
return false;
}
$verifiable = get_field_sql("SELECT va.usr FROM {view_access} va
JOIN {usr_roles} ur ON ur.role = va.role
WHERE ur.see_block_content = ?
AND va.view = ? AND va.usr = ?
LIMIT 1", array(1, $view->get('id'), $USER->get('id')));
return (bool)$verifiable;
}
public static function is_verified(View $view) {
if (!$view->get('owner')) {
return false;
}
return (bool)get_field_sql("SELECT verified FROM {view_signoff_verify} WHERE view = ? LIMIT 1", array($view->get('id')));
}
public static function count_assessments($viewids=null) {
if (!empty($viewids)) {
return get_records_sql_assoc('
......
......@@ -188,6 +188,15 @@ abstract class PluginBlocktype extends Plugin implements IPluginBlocktype {
return array();
}
/**
* This function must be implemented in the subclass if it requires
* toolbar options for the view. It returns an array of button <a> tags and/or
* html to display in the toobar area.
*/
public static function get_instance_toolbars(BlockInstance $instance) {
return array();
}
/**
* This function must be implemented in the subclass if it requires
* css file outside of sass compiled css. It returns an array of css files, either local
......
......@@ -2551,6 +2551,52 @@ class View {
);
}
/**
* Returns a list of toolbar code based on the blockinstances present in the view.
*/
public function get_all_blocktype_toolbar() {
global $CFG;
$buttons = array();
$toolbarhtml = array();
$view_data = $this->get_row_datastructure();
$loadajax = false;
foreach ($view_data as $row_data) {
foreach($row_data as $column) {
foreach($column['blockinstances'] as $blockinstance) {
$pluginname = $blockinstance->get('blocktype');
if (!safe_require_plugin('blocktype', $pluginname)) {
continue;
}
$classname = generate_class_name('blocktype', $pluginname);
$instanceinfo = call_static_method(
$classname,
'get_instance_toolbars',
$blockinstance
);
foreach($instanceinfo as $info) {
if (is_array($info)) {
if (isset($info['buttons'])) {
$buttons[] = $info['buttons'];
}
if (isset($info['toolbarhtml'])) {
$toolbarhtml[] = $info['toolbarhtml'];
}
}
else if (is_string($info)) {
$buttons[] = $info;
}
}
}
}
}
return array(
'buttons' => array_unique($buttons), // @TODO - make a way to add in abutton to toolbar
'toolbarhtml' => array_unique($toolbarhtml)
);
}
/**
* Returns a list of required css files.
*/
......
<div id="verifyform" class="toolbarhtml">
<div>
{str tag=signoff section=blocktype.peerassessment/peerassessment}
{if $signable}
<a href="#" id="signoff">
<span class="icon {if $signoff}icon-check-circle completed {else}icon-circle incomplete{/if} icon-lg"></span>
</a>
{elseif $signoff}
<span class="icon icon-check-circle completed icon-lg"></span>
{else}
<span class="icon icon-circle dot disabled icon-lg"></span>
{/if}
</div>
<div>
{str tag=verify section=blocktype.peerassessment/peerassessment}
{if $verifiable && $signoff}
<a href="#" id="verify">
<span class="icon {if $verified}icon-check-circle completed {else}icon-circle incomplete{/if} icon-lg"></span>
</a>
{elseif $verified}
<span class="icon icon-check-circle completed icon-lg"></span>
{else}
<span class="icon icon-circle dot disabled icon-lg"></span>
{/if}
</div>
</div>
<script type="application/javascript">
$(function() {
$('#signoff').on('click', function(event) {
event.preventDefault();
event.stopPropagation();
sendjsonrequest('{$WWWROOT}artefact/peerassessment/completion.json.php', { 'view': '{$view}', 'signoff': 1 }, 'POST', function (data) {
if (data.data) {
if (data.data.signoff_newstate) {
$('#signoff span.icon').addClass('icon-check-circle completed').removeClass('icon-circle incomplete');
}
else {
$('#signoff span.icon').addClass('icon-circle incomplete').removeClass('icon-check-circle completed');
}
if (data.data.verify_change) {
$('#signoff').parent().next().find('span.icon').addClass('icon-circle dot disabled').removeClass('icon-check-circle completed');
}
}
});
});
$('#verify').on('click', function(event) {
event.preventDefault();
event.stopPropagation();
sendjsonrequest('{$WWWROOT}artefact/peerassessment/completion.json.php', { 'view': '{$view}', 'verify': 1 }, 'POST', function (data) {
if (data.data) {
if (data.data.verify_newstate) {
$('#verify span.icon').addClass('icon-check-circle completed').removeClass('icon-circle incomplete');
}
}
});
});
});
</script>
\ No newline at end of file
......@@ -607,6 +607,15 @@
}
}
div.toolbarhtml {
margin-top: 5px;
clear: both;
float: right;
div {
text-align: right;
}
}
.assessment-item-buttons {
.assessbtn + .assessbtn {
button {
......
......@@ -170,7 +170,8 @@ div.statusheader{
}
div.statusheader,
th.statusheader {
th.statusheader,
div.toolbarhtml {
.icon {
font-size: 1.2em;
&.begun {
......@@ -189,6 +190,15 @@ th.statusheader {
font-size: 0.8em;
color: scale-color($gray-light, $lightness: 28%);
}
&.dot.disabled {
font-size: 1em;
color: scale-color($gray-light, $lightness: 28%);
cursor: $cursor-disabled;
// Override dot icon with minus icon
&:before {
content: "\f068";
}
}
}
}
......
......@@ -123,6 +123,11 @@
{/if}
</ul>
</div>
<div class="btn-group-top-below">
{if $toolbarhtml}
{$toolbarhtml|safe}
{/if}
</div>
<div class="with-heading text-small">
{include file=author.tpl}
......
......@@ -371,6 +371,10 @@ if ($collection) {
}
}
$blocktype_toolbar = $view->get_all_blocktype_toolbar();
if (!empty($blocktype_toolbar['toolbarhtml'])) {
$smarty->assign('toolbarhtml', join("\n", $blocktype_toolbar['toolbarhtml']));
}
$smarty->assign('canremove', $can_edit);
$smarty->assign('INLINEJAVASCRIPT', $javascript . $inlinejs);
$smarty->assign('viewid', $viewid);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment