Commit 7d63f2d4 authored by Richard Mansfield's avatar Richard Mansfield
Browse files

Allow anonymous feedback by logged-out users with a view access token

parent 0d9bdd5a
// The list of existing feedback.
var feedbacklist = new TableRenderer('feedbacktable', 'getfeedback.json.php', []);
feedbacklist.limit = 10;
feedbacklist.rowfunction = function(r, n, d) {
var td = TD(null);
td.innerHTML = r.message;
if (r.attachid && r.ownedbythisuser) {
appendChildNodes(td, DIV(null, get_string('feedbackattachmessage')));
}
var publicPrivate = null;
if (r.ispublic == 1) {
var makePrivate = null;
if (r.ownedbythisuser) {
makePrivateLink = A({'href': ''}, get_string('makeprivate'));
connect(makePrivateLink, 'onclick', function (e) {
sendjsonrequest(
'changefeedback.json.php',
r,
'POST',
function (data) {
if (!data.error) {
replaceChildNodes(makePrivateLink.parentNode, get_string('thisfeedbackisprivate'));
}
}
);
e.stop();
});
makePrivate = [' - ', makePrivateLink];
}
publicPrivate = SPAN(null, get_string('thisfeedbackispublic'), makePrivate);
}
else {
publicPrivate = get_string('thisfeedbackisprivate');
}
var attachment = null;
if (r.attachid) {
attachment = [' | ', get_string('attachment'), ': ', A({'href':config.wwwroot + 'artefact/file/download.php?file=' + r.attachid}, r.attachtitle), ' (', r.attachsize, ')'];
}
if (r.author) {
var icon = DIV({'class': 'icon'}, A({'href': config.wwwroot + 'user/view.php?id=' + r.author}, IMG({'src': config.wwwroot + 'thumb.php?type=profileicon&id=' + r.author + '&maxsize=20', 'valign': 'middle'})));
var authorname = A({'href': config.wwwroot + 'user/view.php?id=' + r.author}, r.name);
}
else {
var icon = null;
var authorname = r.name;
}
appendChildNodes(td, DIV({'class': 'details'}, icon, authorname, ' | ', r.date, ' | ', publicPrivate, attachment));
return TR({'class': 'r' + (n % 2)}, td);
};
feedbacklist.emptycontent = get_string('nopublicfeedback');
addLoadEvent(function () {
hideElement('add_feedback_form');
if ($('add_feedback_link')) {
connect('add_feedback_link', 'onclick', function(e) {
e.stop();
hideElement('objection_form');
$('add_feedback_form').reset();
showElement('add_feedback_form');
return false;
});
}
connect('cancel_add_feedback_form_submit', 'onclick', function (e) {
e.stop();
hideElement('add_feedback_form');
return false;
});
hideElement('objection_form');
if ($('objection_link')) {
connect('objection_link', 'onclick', function(e) {
e.stop();
hideElement('add_feedback_form');
$('objection_form').reset();
showElement('objection_form');
return false;
});
}
connect('cancel_objection_form_submit', 'onclick', function (e) {
e.stop();
hideElement('objection_form');
return false;
});
if ($('toggle_watchlist_link')) {
connect('toggle_watchlist_link', 'onclick', function (e) {
e.stop();
sendjsonrequest('togglewatchlist.json.php', {'view': feedbacklist.view}, 'POST', function(data) {
$('toggle_watchlist_link').innerHTML = data.newtext;
});
});
}
});
......@@ -117,6 +117,7 @@ $string['makepublic'] = 'Make public';
$string['nopublicfeedback'] = 'No public feedback';
$string['notifysiteadministrator'] = 'Notify site administrator';
$string['placefeedback'] = 'Place feedback';
$string['placefeedbacknotallowed'] = 'You are not allowed to place feedback on this View';
$string['print'] = 'Print';
$string['thisfeedbackispublic'] = 'This feedback is public';
$string['thisfeedbackisprivate'] = 'This feedback is private';
......
......@@ -632,7 +632,8 @@
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" />
<FIELD NAME="view" TYPE="int" LENGTH="10" NOTNULL="true" />
<FIELD NAME="author" TYPE="int" LENGTH="10" NOTNULL="true" />
<FIELD NAME="author" TYPE="int" LENGTH="10" NOTNULL="false" />
<FIELD NAME="authorname" TYPE="text" NOTNULL="false" />
<FIELD NAME="message" TYPE="text" NOTNULL="true" />
<FIELD NAME="attachment" TYPE="int" LENGTH="10" NOTNULL="false" />
<FIELD NAME="ctime" TYPE="datetime" NOTNULL="true" />
......@@ -650,7 +651,8 @@
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" />
<FIELD NAME="artefact" TYPE="int" LENGTH="10" NOTNULL="true" />
<FIELD NAME="view" TYPE="int" LENGTH="10" NOTNULL="true" />
<FIELD NAME="author" TYPE="int" LENGTH="10" NOTNULL="true" />
<FIELD NAME="author" TYPE="int" LENGTH="10" NOTNULL="false" />
<FIELD NAME="authorname" TYPE="text" NOTNULL="false" />
<FIELD NAME="message" TYPE="text" NOTNULL="false" />
<FIELD NAME="ctime" TYPE="datetime" NOTNULL="true" />
<FIELD NAME="public" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" />
......
......@@ -1422,6 +1422,28 @@ function xmldb_core_upgrade($oldversion=0) {
create_table($table);
}
if ($oldversion < 2008102400) {
// Feedback can be left by anon users with a view token, so feedback author must be nullable
$table = new XMLDBTable('view_feedback');
$field = new XMLDBField('author');
$field->setAttributes(XMLDB_TYPE_INTEGER, 10, XMLDB_UNSIGNED);
change_field_notnull($table, $field);
$key = new XMLDBKEY('authorfk');
$key->setAttributes(XMLDB_KEY_FOREIGN, array('author'), 'usr', array('id'));
add_key($table, $key);
$table = new XMLDBTable('artefact_feedback');
$field = new XMLDBField('author');
$field->setAttributes(XMLDB_TYPE_INTEGER, 10, XMLDB_UNSIGNED);
change_field_notnull($table, $field);
$key = new XMLDBKEY('authorfk');
$key->setAttributes(XMLDB_KEY_FOREIGN, array('author'), 'usr', array('id'));
add_key($table, $key);
table_column('view_feedback', null, 'authorname', 'text', null, null, null, '');
table_column('artefact_feedback', null, 'authorname', 'text', null, null, null, '');
}
return $status;
}
......
......@@ -27,7 +27,7 @@
defined('INTERNAL') || die();
$config = new StdClass;
$config->version = 2008102200;
$config->version = 2008102400;
$config->release = '1.1.0beta2dev';
$config->minupgradefrom = 2007080700;
$config->minupgraderelease = '0.8.0 (release tag 0.8.0_RELEASE)';
......
......@@ -2178,6 +2178,128 @@ function createview_cancel_submit(Pieform $form, $values) {
}
function add_feedback_form($attachments=false) {
global $USER;
$form = array(
'name' => 'add_feedback_form',
'method' => 'post',
'plugintype' => 'core',
'pluginname' => 'view',
'elements' => array(),
);
if (!$USER->is_logged_in()) {
$form['elements']['authorname'] = array(
'type' => 'text',
'title' => get_string('name'),
);
}
$form['elements']['message'] = array(
'type' => 'textarea',
'title' => get_string('message'),
'rows' => 5,
'cols' => 80,
);
$form['elements']['ispublic'] = array(
'type' => 'checkbox',
'title' => get_string('makepublic', 'view'),
);
if ($attachments) {
$form['elements']['attachment'] = array(
'type' => 'file',
'title' => get_string('attachfile', 'view'),
);
}
$form['elements']['submit'] = array(
'type' => 'submitcancel',
'value' => array(get_string('placefeedback', 'view'), get_string('cancel')),
);
return $form;
}
function add_feedback_form_validate(Pieform $form, $values) {
global $USER, $view;
if (!$USER->get('id')) {
if (empty($values['authorname'])) {
$form->set_error(get_string('pleaseenteryourname', 'view'));
}
$token = get_cookie('viewaccess:'.$view->get('id'));
if (!$token || get_view_from_token($token) != $view->get('id')) {
$form->set_error(get_string('placefeedbacknotallowed', 'view'));
}
}
}
function add_feedback_form_submit(Pieform $form, $values) {
global $view, $artefact, $SESSION, $USER;
$data = new StdClass;
$data->view = $view->get('id');
if ($artefact) {
$data->artefact = $artefact->get('id');
$table = 'artefact_feedback';
}
else {
$table = 'view_feedback';
}
$data->message = $values['message'];
$data->public = (int) $values['ispublic'];
$data->author = $USER->get('id');
if (!$data->author) {
unset($data->author);
$data->authorname = $values['authorname'];
}
$data->ctime = db_format_timestamp(time());
insert_record($table, $data, 'id', true);
require_once('activity.php');
activity_occurred('feedback', $data);
$SESSION->add_ok_msg(get_string('feedbacksubmitted', 'view'));
if ($artefact) {
redirect(get_config('wwwroot') . 'view/artefact.php?artefact=' . $artefact->get('id') . '&view='.$view->get('id'));
}
redirect(get_config('wwwroot') . 'view/view.php?id='.$view->get('id'));
}
function objection_form() {
$form = array(
'name' => 'objection_form',
'method' => 'post',
'plugintype' => 'core',
'pluginname' => 'view',
'elements' => array(),
);
$form['elements']['message'] = array(
'type' => 'textarea',
'title' => get_string('complaint', 'view'),
'rows' => 5,
'cols' => 80,
);
$form['elements']['submit'] = array(
'type' => 'submitcancel',
'value' => array(get_string('notifysiteadministrator', 'view'), get_string('cancel')),
);
return $form;
}
function objection_form_submit(Pieform $form, $values) {
global $USER, $SESSION, $view, $artefact;
require_once('activity.php');
$data = new StdClass;
$data->view = $view->get('id');
$data->message = $values['message'];
$data->reporter = $USER->get('id');
if ($artefact) {
$data->artefact = $artefact->get('id');
}
activity_occurred('objectionable', $data);
$SESSION->add_ok_msg(get_string('reportsent', 'view'));
if ($artefact) {
redirect(get_config('wwwroot') . 'view/artefact.php?artefact=' . $artefact->get('id') . '&view='.$view->get('id'));
}
redirect(get_config('wwwroot') . 'view/view.php?id='.$view->get('id'));
}
/**
* display format for author names in views - firstname
*/
......
......@@ -558,6 +558,16 @@ function jsstrings() {
'cancel',
),
),
'feedbacklist' => array(
'view' => array(
'feedbackattachmessage',
'makeprivate',
'thisfeedbackisprivate',
'thisfeedbackispublic',
'attachment',
'nopublicfeedback',
),
),
);
}
......
......@@ -18,13 +18,17 @@
</div>
<div id="publicfeedback">
<table id="feedbacktable" class="fullwidth">
<thead>
<tr><th>{str tag="feedback" section="view"}</th></tr>
</thead>
</table>
<table id="feedbacktable" class="fullwidth">
<thead>
<tr><th>{str tag="feedback" section="view"}</th></tr>
</thead>
</table>
</div>
<div id="viewmenu"></div>
<div id="viewmenu">
{include file="view/viewmenu.tpl"}
</div>
<div>{$addfeedbackform}</div>
<div>{$objectionform}</div>
{include file="columnfullend.tpl"}
......
......@@ -30,7 +30,11 @@
</thead>
</table>
</div>
<div id="viewmenu"></div>
<div id="viewmenu">
{include file="view/viewmenu.tpl"}
</div>
<div>{$addfeedbackform}</div>
<div>{$objectionform}</div>
</div>
{include file="columnfullend.tpl"}
......
{if $LOGGEDIN || $anonfeedback}
<a id="add_feedback_link" href="">{str tag=placefeedback section=view}</a> |
{/if}
{if $LOGGEDIN}
<a id="objection_link" href="">{str tag=reportobjectionablematerial section=view}</a> |
{/if}
<a href="" onclick="window.print(); return false;">{str tag=print section=view}</a>
{if $LOGGEDIN}
| <a id="toggle_watchlist_link" href="">{if $viewbeingwatched}{str tag=removefromwatchlist section=view}{else}{str tag=addtowatchlist section=view}{/if}</a>
| {contextualhelp plugintype='core' pluginname='view' section='viewmenu'}
{/if}
......@@ -87,218 +87,17 @@ $artefactpath[] = array(
'title' => $artefact->display_title(),
);
$getstring = quotestrings(array(
'mahara' => array('message', 'cancel'),
'view' => array('makepublic', 'placefeedback', 'complaint',
'feedbackonthisartefactwillbeprivate', 'notifysiteadministrator',
'nopublicfeedback', 'reportobjectionablematerial', 'print',
'thisfeedbackispublic', 'thisfeedbackisprivate', 'attachment',
'makeprivate')
));
$getstring['feedbackattachmessage'] = "'(" . get_string('feedbackattachmessage', 'view', get_string('feedbackattachdirname', 'view')) . ")'";
// Safari doesn't seem to like these inputs to be called 'public', so call them 'ispublic' instead.
$feedbackisprivate = !$artefact->public_feedback_allowed();
if (!empty($feedbackisprivate)) {
$makepublic = "TR(null, INPUT({'type':'hidden','name':'ispublic','value':'false'}), TD({'colspan':2}, "
. $getstring['feedbackonthisartefactwillbeprivate'] . ")),";
}
else {
$makepublic = "TR(null, TH(null, LABEL(null, " . $getstring['makepublic'] . " ), "
. "INPUT({'type':'checkbox', 'class':'checkbox', 'name':'ispublic'}))),";
}
// Feedback
$javascript = <<<EOF
var view = {$viewid};
var artefact = {$artefactid};
function feedbackform() {
if ($('menuform')) {
removeElement('menuform');
}
var form = FORM({'id':'menuform','method':'post'});
submitfeedback = function () {
var data = {'view':view,
'public':form.ispublic.checked,
'message':form.message.value};
if (artefact) {
data.artefact = artefact;
}
sendjsonrequest('addfeedback.json.php', data, 'POST', function () {
removeElement('menuform');
feedbacklist.doupdate();
});
return false;
}
appendChildNodes(form,
TABLE({'border':0, 'cellspacing':0, 'id':'feedback'},
TBODY(null,
TR(null, TH(null, LABEL(null, {$getstring['message']}))),
TR(null, TD(null, TEXTAREA({'rows':5, 'cols':80, 'name':'message'}))),
{$makepublic}
TR(null, TD(null,
INPUT({'type':'button', 'class':'button',
'value':{$getstring['placefeedback']},
'onclick':'submitfeedback();'}),
INPUT({'type':'button', 'class':'button', 'value':{$getstring['cancel']},
'onclick':"removeElement('menuform');"}))))));
appendChildNodes('viewmenu', DIV(null, form));
form.message.focus();
return false;
}
function objectionform() {
if ($('menuform')) {
removeElement('menuform');
}
var form = FORM({'id':'menuform','method':'post'});
submitobjection = function () {
var data = {'view':view, 'message':form.message.value};
if (artefact) {
data.artefact = artefact;
}
sendjsonrequest('objectionable.json.php', data, 'POST', function () { removeElement('menuform'); });
return false;
}
appendChildNodes(form,
TABLE({'border':0, 'cellspacing':0, 'id':'objection'},
TBODY(null,
TR(null, TH(null, LABEL(null, {$getstring['complaint']}))),
TR(null, TD(null, TEXTAREA({'rows':5, 'cols':80, 'name':'message'}))),
TR(null, TD(null,
INPUT({'type':'button', 'class':'button',
'value':{$getstring['notifysiteadministrator']},
'onclick':'submitobjection();'}),
INPUT({'type':'button', 'class':'button', 'value':{$getstring['cancel']},
'onclick':"removeElement('menuform');"}))))));
appendChildNodes('viewmenu', DIV(null, form));
form.message.focus();
return false;
}
function view_menu() {
if (config.loggedin) {
appendChildNodes('viewmenu',
A({'href':'', 'onclick':"return feedbackform();"},
{$getstring['placefeedback']}), ' | ',
A({'href':'', 'onclick':'return objectionform();'},
{$getstring['reportobjectionablematerial']}), ' | '
);
}
appendChildNodes('viewmenu',
A({'href':'', 'onclick':'window.print();return false;'},
{$getstring['print']})
);
var helpIcon = contextualHelpIcon(null, null, 'core', 'view', null, 'viewmenu');
appendChildNodes('viewmenu', ' ', helpIcon);
}
addLoadEvent(view_menu);
// The list of existing feedback.
var feedbacklist = new TableRenderer(
'feedbacktable',
'getfeedback.json.php',
[/*
function (r) {
var td = TD(null);
td.innerHTML = r.message;
if (r.attachid && r.ownedbythisuser) {
appendChildNodes(td, DIV(null, {$getstring['feedbackattachmessage']}));
return td;
}
return td;
},
'name',
'date',
function (r) {
if (r.ispublic == 1) {
var makePrivate = null;
if (r.ownedbythisuser) {
makePrivate = A({'href': ''}, get_string('makeprivate'));
connect(makePrivate, 'onclick', function (e) {
sendjsonrequest(
'changefeedback.json.php',
r,
'POST',
function (data) {
if (!data.error) {
replaceChildNodes(makePrivate.parentNode, '(' + get_string('private') + ')');
}
}
);
e.stop();
});
}
return TD(null, '(' + get_string('public') + ') ', makePrivate);
}
return TD(null, '(' + get_string('private') + ')');
},
function (r) {
if (r.attachid) {
return TD(null, A({'href':config.wwwroot + 'artefact/file/download.php?file=' + r.attachid},
r.attachtitle));
}
return TD(null);
}
*/]
);
feedbacklist.rowfunction = function(r, n, d) {
var td = TD(null);
td.innerHTML = r.message;
if (r.attachid && r.ownedbythisuser) {
appendChildNodes(td, DIV(null, {$getstring['feedbackattachmessage']}));
}
var publicPrivate = null;
if (r.ispublic == 1) {
var makePrivate = null;
if (r.ownedbythisuser) {
makePrivateLink = A({'href': ''}, {$getstring['makeprivate']});
connect(makePrivateLink, 'onclick', function (e) {
sendjsonrequest(
'changefeedback.json.php',
r,
'POST',
function (data) {
if (!data.error) {
replaceChildNodes(makePrivateLink.parentNode, {$getstring['thisfeedbackisprivate']});
}
}
);
e.stop();
});
makePrivate = [' - ', makePrivateLink];
}
publicPrivate = SPAN(null, {$getstring['thisfeedbackispublic']}, makePrivate);
}
else {
publicPrivate = {$getstring['thisfeedbackisprivate']};
}
var icon = A({'href': config.wwwroot + 'user/view.php?id=' + r.author}, IMG({'src': config.wwwroot + 'thumb.php?type=profileicon&id=' + r.author + '&maxsize=20', 'valign': 'middle'}));
appendChildNodes(td, DIV({'class': 'details'}, DIV({'class': 'icon'}, icon), A({'href': config.wwwroot + 'user/view.php?id=' + r.author}, r.name), ' | ', r.date, ' | ', publicPrivate));
return TR({'class': 'r' + (n % 2)}, td);
};
feedbacklist.limit = 10;
feedbacklist.view = view;
feedbacklist.artefact = artefact;
feedbacklist.statevars.push('view','artefact');
feedbacklist.emptycontent = {$getstring['nopublicfeedback']};
feedbacklist.view = {$viewid};
feedbacklist.artefact = {$artefactid};
feedbacklist.statevars.push('view', 'artefact');
feedbacklist.updateOnLoad();
EOF;
$smarty = smarty(
array('tablerenderer'),
array('mahara', 'tablerenderer', 'feedbacklist'),
array('<link rel="stylesheet" type="text/css" href="' . get_config('wwwroot') . 'theme/views.css">'),
array(),
array(
......@@ -322,6 +121,9 @@ else if ($view->get('group')) {
}
$smarty->assign('ownername', $view->formatted_owner());
$smarty->assign('addfeedbackform', pieform(add_feedback_form(false)));
$smarty->assign('objectionform', pieform(objection_form()));
$smarty->assign('anonfeedback', !$USER->is_logged_in() && $viewid == get_view_from_token(get_cookie('viewaccess:'.$viewid)));
$smarty->display('view/artefact.tpl');
......
......@@ -53,7 +53,7 @@ if ($artefact) {
. ($public ? ' AND (public = 1 OR author = ' . $userid . ')' : ''));
$feedback = get_records_sql_array('
SELECT
id, author, ctime, message, public
id, author, authorname, ctime, message, public
FROM {artefact_feedback}