Commit 9b48e8c4 authored by Richard Mansfield's avatar Richard Mansfield Committed by Gerrit Code Review
Browse files

Merge changes...

Merge changes I308cf44f,I21f8636e,I5214c5b0,I81b44928,I8725e566,I3ec7d13d,I3ec49f59,Ia1611a6a,I3f8a15de,I7e5e5ff8,Id2ef0453

* changes:
  Allow update of group note permissions (bug #736665)
  Show artefact owner when selecting html artefacts (bug #736665)
  When configuring a read-only textbox, allow copying (bug #736665)
  Display textbox notes as read-only in config form (bug #736665)
  Return sanitised copy of description from artefactchooser (bug #736665)
  Get editable status along with artefactchooser artefacts (bug #736665)
  Stop users from editing note text in the wrong context (bug #736665)
  Allow editing of a textbox block when artefact not found (bug #736665)
  User can_publish_artefact method too restrictive (bug #865911)
  Use artefact method to get user's edit/publish permissions
  Allow use of group_user_access when logged out
parents 2f0f941d ee14f8f5
......@@ -33,3 +33,5 @@ $string['blockcontent'] = 'Block Content';
$string['usecontentfromanothertextbox'] = 'Use content from another text box';
$string['textusedinotherblocks'] = 'If you edit the text of this block, it will also be changed in %s other block(s) where it appears.';
$string['managealltextboxcontent'] = 'Manage all textbox content';
$string['readonlymessage'] = 'The text you have selected is not editable on this page.';
$string['makeacopy'] = 'Make a copy';
<h3>Reusable textbox content</h3>
<p>It is possible to include the same textbox content in several
blocks, and these blocks can appear on different pages. Updating the
text in one block will also change it in all other pages in which it
appears. You can see a list of all the pages on which a particular
note is contained in your Notes area under the Content menu.</p>
<p>When you are editing a group or institution page, remember that
other group or institution members may have inserted this text inside
their own pages. If you edit it here, it will be updated on their
pages too.</p>
<p>You can also make a copy of the selected note, and then
when you edit it, the changes will only affect this page.</p>
<h3>Read-only textbox content</h3>
<p>You can sometimes include a textbox note on a page even when the
note is owned by someone other than the page's owner. For example,
you may have permission to include notes belonging to one of your
groups inside your own personal page.</p>
<p>If you include such content, it will be updated automatically on
your page whenever it is changed by the owner. If you want to update
it yourself, you must do that through a page with the same owner as
the note.</p>
<p>Alternatively, you may take your own copy of this content, and then
when you edit it, the changes will only affect this page.</p>
......@@ -100,7 +100,8 @@ class PluginBlocktypeTextbox extends PluginBlocktype {
'selectone' => true,
'selectjscallback' => 'updateTextContent',
'getblocks' => true,
'returnfields' => array('id', 'title', 'description'),
'ownerinfo' => true,
'returnfields' => array('id', 'title', 'description', 'safedescription', 'editable'),
'artefacttypes' => array('html'),
'template' => 'artefact:internal:html-artefactchooser-element.tpl',
);
......@@ -113,25 +114,38 @@ class PluginBlocktypeTextbox extends PluginBlocktype {
$blockid = $instance->get('id');
return <<<EOF
function updateTextContent(a) {
tinyMCE.activeEditor.setContent(a.description);
setNodeAttribute('instconf_title', 'value', a.title);
var blockcountmsg = $('instconf_otherblocksmsg_container');
if (blockcountmsg && $('textbox_blockcount')) {
var otherblockcount = 0;
if (a.blocks && a.blocks.length > 0) {
for (var i = 0; i < a.blocks.length; i++) {
if (a.blocks[i] != {$blockid}) {
otherblockcount++;
tinyMCE.activeEditor.setContent(a.description);
$('instconf_textreadonly_display').innerHTML = a.safedescription;
$('instconf_makecopy').checked = false;
if (a.editable == 1) {
addElementClass('instconf_textreadonly_container', 'hidden');
addElementClass('instconf_readonlymsg_container', 'hidden');
removeElementClass('instconf_text_container', 'hidden');
var blockcountmsg = $('instconf_otherblocksmsg_container');
if (blockcountmsg && $('textbox_blockcount')) {
var otherblockcount = 0;
if (a.blocks && a.blocks.length > 0) {
for (var i = 0; i < a.blocks.length; i++) {
if (a.blocks[i] != {$blockid}) {
otherblockcount++;
}
}
}
if (otherblockcount) {
replaceChildNodes('textbox_blockcount', otherblockcount);
removeElementClass(blockcountmsg, 'hidden');
}
else {
addElementClass(blockcountmsg, 'hidden');
}
}
if (otherblockcount) {
replaceChildNodes('textbox_blockcount', otherblockcount);
removeElementClass(blockcountmsg, 'hidden');
}
else {
addElementClass(blockcountmsg, 'hidden');
}
}
else {
addElementClass('instconf_text_container', 'hidden');
addElementClass('instconf_otherblocksmsg_container', 'hidden');
removeElementClass('instconf_textreadonly_container', 'hidden');
removeElementClass('instconf_readonlymsg_container', 'hidden');
}
}
connect('chooseartefactlink', 'onclick', function(e) {
......@@ -139,6 +153,21 @@ connect('chooseartefactlink', 'onclick', function(e) {
toggleElementClass('hidden', 'instconf_artefactid_container');
toggleElementClass('hidden', 'instconf_managenotes_container');
});
forEach(getElementsByTagAndClassName('a', 'copytextboxnote', 'instconf'), function(link) {
connect(link, 'onclick', function(e) {
e.stop();
forEach(getElementsByTagAndClassName('input', 'radio', 'artefactid_data'), function(i) {
if (i.checked) {
i.checked = false;
}
});
$('instconf_makecopy').checked = true;
addElementClass('instconf_textreadonly_container', 'hidden');
addElementClass('instconf_readonlymsg_container', 'hidden');
addElementClass('instconf_otherblocksmsg_container', 'hidden');
removeElementClass('instconf_text_container', 'hidden');
});
});
EOF;
}
......@@ -147,26 +176,44 @@ EOF;
}
public static function instance_config_form($instance) {
global $USER;
$instance->set('artefactplugin', 'internal');
$configdata = $instance->get('configdata');
if (!$height = get_config('blockeditorheight')) {
$cfheight = param_integer('cfheight', 0);
$height = $cfheight ? $cfheight * 0.7 : 150;
}
$otherblockcount = 0;
$readonly = false;
$text = '';
$view = $instance->get_view();
if (!empty($configdata['artefactid'])) {
if ($blocks = get_column('view_artefact', 'block', 'artefact', $configdata['artefactid'])) {
$blocks = array_unique($blocks);
$otherblockcount = count($blocks) - 1;
}
$artefactid = $configdata['artefactid'];
$text = $instance->get_artefact_instance($configdata['artefactid'])->get('description');
try {
$artefact = $instance->get_artefact_instance($artefactid);
$readonly = $artefact->get('owner') !== $view->get('owner')
|| $artefact->get('group') !== $view->get('group')
|| $artefact->get('institution') !== $view->get('institution')
|| !$USER->can_edit_artefact($artefact);
$text = $artefact->get('description');
if ($blocks = get_column('view_artefact', 'block', 'artefact', $artefactid)) {
$blocks = array_unique($blocks);
$otherblockcount = count($blocks) - 1;
}
}
catch (ArtefactNotFoundException $e) {
unset($artefactid);
}
}
$otherblocksmsg = '<span id="textbox_blockcount">' . $otherblockcount . '</span>';
$otherblocksmsg = get_string('textusedinotherblocks', 'blocktype.internal/textbox', $otherblocksmsg);
$view = $instance->get_view();
$manageurl = get_config('wwwroot') . 'artefact/internal/notes.php';
if ($group = $view->get('group')) {
$manageurl .= '?group=' . $group;
......@@ -179,17 +226,39 @@ EOF;
// Add a message whenever this text appears in some other block
'otherblocksmsg' => array(
'type' => 'html',
'class' => $otherblockcount ? '' : 'hidden',
'value' => '<div class="message info">' . $otherblocksmsg . '</div>',
'class' => 'message info' . (($otherblockcount && !$readonly) ? '' : ' hidden'),
'value' => $otherblocksmsg
. ' <a class="copytextboxnote nojs-hidden-inline" href="">' . get_string('makeacopy', 'blocktype.internal/textbox') . '</a>',
'help' => true,
),
// Add a message whenever this text cannot be edited here
'readonlymsg' => array(
'type' => 'html',
'class' => 'message info' . ($readonly ? '' : ' hidden'),
'value' => get_string('readonlymessage', 'blocktype.internal/textbox')
. ' <a class="copytextboxnote nojs-hidden-inline" href="">' . get_string('makeacopy', 'blocktype.internal/textbox') . '</a>',
'help' => true,
),
'text' => array(
'type' => 'wysiwyg',
'class' => $readonly ? 'hidden' : '',
'title' => get_string('blockcontent', 'blocktype.internal/textbox'),
'width' => '100%',
'height' => $height . 'px',
'defaultvalue' => isset($text) ? $text : '',
'defaultvalue' => $text,
'rules' => array('maxlength' => 65536),
),
'textreadonly' => array(
'type' => 'html',
'class' => $readonly ? '' : 'hidden',
'width' => '100%',
'value' => '<div id="instconf_textreadonly_display">' . $text . '</div>',
),
'makecopy' => array(
'type' => 'checkbox',
'class' => 'hidden',
'defaultvalue' => false,
),
'chooseartefact' => array(
'type' => 'html',
'class' => 'nojs-hidden-block',
......@@ -210,12 +279,12 @@ EOF;
public static function instance_config_save($values, $instance) {
global $USER;
$data = array();
$view = $instance->get_view();
foreach (array('owner', 'group', 'institution') as $f) {
$data[$f] = $view->get($f);
}
if (empty($values['artefactid'])) {
$view = $instance->get_view();
foreach (array('owner', 'group', 'institution') as $f) {
$data[$f] = $view->get($f);
}
if (empty($values['artefactid']) || $values['makecopy']) {
// The artefact title will be the same as the block title when the
// artefact is first created, or, if there's no block title, generate
// 'Note (1)', 'Note (2)', etc. After that, the artefact title can't
......@@ -229,17 +298,30 @@ EOF;
else {
$title = $values['title'];
}
$artefact = new ArtefactTypeHtml(0, $data);
$artefact->set('title', $title);
$artefact->set('description', $values['text']);
}
else {
$artefact = new ArtefactTypeHtml((int)$values['artefactid']);
$artefact = new ArtefactTypeHtml((int)$values['artefactid'], $data);
if (!$USER->can_edit_artefact($artefact)) {
throw new AccessDeniedException(get_string('accessdenied', 'error'));
}
if (!$USER->can_publish_artefact($artefact)) {
throw new AccessDeniedException(get_string('nopublishpermissiononartefact', 'mahara', hsc($artefact->get('title'))));
}
if (isset($title)) {
$artefact->set('title', $title);
// Stop users from editing html artefacts whose owner is not the same as the
// view owner, even if they would normally be allowed to edit the artefact.
// It's too confusing. Html artefacts with other owners *can* be included in
// the view read-only, provided the artefact has the correct republish
// permission.
if ($artefact->get('owner') === $data['owner']
&& $artefact->get('group') === $data['group']
&& $artefact->get('institution') === $data['institution']
&& $USER->can_edit_artefact($artefact)) {
$artefact->set('description', $values['text']);
}
}
$artefact->set('description', $values['text']);
$artefact->commit();
$values['artefactid'] = $artefact->get('id');
......@@ -247,7 +329,11 @@ EOF;
unset($values['text']);
unset($values['otherblocksmsg']);
unset($values['readonlymsg']);
unset($values['textreadonly']);
unset($values['makecopy']);
unset($values['chooseartefact']);
unset($values['managenotes']);
// Pass back a list of any other blocks that need to be rendered
// due to this change.
......
......@@ -70,6 +70,13 @@ $form = pieform(array(
'title' => get_string('allowcomments', 'artefact.comment'),
'defaultvalue' => $artefact->get('allowcomments'),
),
'perms' => array(
'type' => 'rolepermissions',
'title' => get_string('Permissions'),
'defaultvalue' => $artefact->get('rolepermissions'),
'group' => $group,
'ignore' => !$group,
),
'submit' => array(
'type' => 'submitcancel',
'value' => array(get_string('save'), get_string('cancel')),
......@@ -88,6 +95,10 @@ function editnote_submit(Pieform $form, array $values) {
$artefact->set('title', $values['title']);
$artefact->set('description', $values['description']);
$artefact->set('allowcomments', (int) $values['allowcomments']);
if (isset($values['perms'])) {
$artefact->set('rolepermissions', $values['perms']);
$artefact->set('dirty', true);
}
$artefact->commit();
$SESSION->add_ok_msg(get_string('noteupdated', 'artefact.internal'));
redirect($goto);
......
......@@ -3,7 +3,8 @@
{$formcontrols|safe}
</td>
<th><label for="{$elementname}_{$artefact->id}">{$artefact->title|str_shorten_text:60:true}</label></th>
<td>{if $artefact->ownerurl}({str tag=by section=view} <a href="{$artefact->ownerurl}">{$artefact->ownername}</a>){/if}</td>
</tr>
<tr>
<td>{$artefact->description|str_shorten_html:80:true|strip_tags|safe}</td>
<td colspan=2>{$artefact->description|str_shorten_html:80:true|strip_tags|safe}</td>
</tr>
......@@ -852,6 +852,10 @@ abstract class ArtefactType {
}
}
public function role_has_permission($role, $permission) {
return $this->rolepermissions[$role]->{$permission};
}
public function copy_data() {
$ignore = array(
'dirty' => 1,
......@@ -1598,3 +1602,49 @@ function artefact_get_types_from_filter($filter) {
return $contenttype_artefacttype[$filter];
}
/**
* Given a list of artefact ids, return a name and url for the thing that
* owns each artefact, suitable for display.
*
* @param array $ids list of artefact ids
*
* @return array list of StdClass objects, each containing a name & url property
*/
function artefact_get_owner_info($ids) {
$data = get_records_sql_assoc('
SELECT
a.id AS aid, a.owner, a.group, a.institution,
u.id, u.username, u.firstname, u.lastname, u.preferredname, u.email,
g.name AS groupname,
i.displayname
FROM
{artefact} a
LEFT JOIN {usr} u ON a.owner = u.id
LEFT JOIN {group} g ON a.group = g.id
LEFT JOIN {institution} i ON a.institution = i.name
WHERE
a.id IN (' . join(',', array_fill(0, count($ids), '?')) . ')',
$ids
);
$wwwroot = get_config('wwwroot');
foreach ($data as &$d) {
if ($d->institution == 'mahara') {
$name = get_config('sitename');
}
else if ($d->institution) {
$name = $d->displayname;;
$url = 'institution/index.php?institution=' . $d->institution;
}
else if ($d->group) {
$name = $d->groupname;;
$url = 'group/view.php?id=' . $d->group;
}
else {
$name = display_name($d);
$url = 'user/view.php?id=' . $d->id;
}
$d = (object) array('name' => $name, 'url' => $wwwroot . $url);
}
return $data;
}
......@@ -912,38 +912,56 @@ class User {
public function can_edit_artefact($a) {
if ($this->get('admin')
|| ($this->get('id') and $this->get('id') == $a->get('owner'))
|| ($a->get('group') && group_user_access($a->get('group'), $this->get('id')) && $this->get('id') and $this->get('id') == $a->get('author'))
|| ($a->get('institution') and $this->is_institutional_admin($a->get('institution')))) {
return true;
}
$group = $a->get('group');
if ($group) {
return count_records_sql("SELECT COUNT(*) FROM {artefact_access_role} ar
INNER JOIN {group_member} g ON ar.role = g.role
WHERE ar.artefact = ? AND g.member = ? AND ar.can_edit = 1 AND g.group = ?", array($a->get('id'), $this->get('id'), $group));
/*
require_once(get_config('docroot') . 'lib/group.php');
$role = group_user_access($group, $this->get('id'));
if ($role) {
$aperms = $a->get('rolepermissions');
return $aperms->{$role}->edit;
} */
if (!$group = $a->get('group')) {
return false;
}
return false;
require_once('group.php');
if (!$role = group_user_access($group, $this->id)) {
return false;
}
if ($role == 'admin') {
return true;
}
if ($this->id == $a->get('author')) {
return true;
}
return $a->role_has_permission($role, 'edit');
}
public function can_publish_artefact($a) {
if (($this->get('id') and $this->get('id') == $a->get('owner'))
|| ($a->get('institution') and $this->is_institutional_admin($a->get('institution')))) {
if (($this->get('id') and $this->get('id') == $a->get('owner'))) {
return true;
}
$group = $a->get('group');
if ($group) {
return count_records_sql("SELECT COUNT(*) FROM {artefact_access_role} ar
INNER JOIN {group_member} g ON ar.role = g.role
WHERE ar.artefact = ? AND g.member = ? AND ar.can_publish = 1 AND g.group = ?", array($a->get('id'), $this->get('id'), $group));
if ($i = $a->get('institution')) {
if ($i == 'mahara') {
return $this->get('admin');
}
return $this->in_institution($i);
}
return false;
if (!$group = $a->get('group')) {
return false;
}
require_once('group.php');
if (!$role = group_user_access($group, $this->id)) {
return false;
}
if ($role == 'admin') {
return true;
}
if ($this->id == $a->get('author')) {
return true;
}
return $a->role_has_permission($role, 'republish');
}
public function can_edit_view($v) {
......
......@@ -639,6 +639,7 @@ $string['Permissions'] = 'Permissions';
$string['republish'] = 'Publish';
$string['view'] = 'Page';
$string['artefactnotpublishable'] = 'Artefact %s is not publishable in page %s';
$string['nopublishpermissiononartefact'] = 'You don\'t have permission to publish %s';
$string['belongingto'] = 'Belonging to';
$string['allusers'] = 'All users';
......
<?php
/**
* Mahara: Electronic portfolio, weblog, resume builder and social networking
* Copyright (C) 2011 Catalyst IT Ltd and others; see:
* http://wiki.mahara.org/Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package mahara
* @subpackage form-element
* @author Richard Mansfield
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
*
*/
// Pieform element for editing group artefact permissions
function pieform_element_rolepermissions(Pieform $form, $element) {/*{{{*/
$value = $form->get_value($element);
$roles = group_get_role_info($element['group']);
$permissions = array_keys(get_object_vars($value['member']));
$result = '<table class="editpermissions"><tbody>';
$result .= '<tr><th>' . get_string('Role', 'group') . '</th>';
foreach ($permissions as $p) {
$result .= '<th>' . get_string('filepermission.' . $p, 'artefact.file') . '</th>';
}
$result .= '</tr>';
$prefix = $form->get_name() . '_' . $element['name'] . '_p';
foreach ($roles as $r) {
$result .= '<tr>';
$result .= '<td>' . hsc($r->display) . '</td>';
foreach ($permissions as $p) {
$inputname = $prefix . '_' . $r->name . '_' . $p;
$result .= '<td><input type="checkbox" class="permission" name="' . hsc($inputname) . '"';
if ($r->name == 'admin') {
$result .= ' checked disabled';
}
else if ($value[$r->name]->$p) {
$result .= ' checked';
}
$result .= '/></td>';
}
$result .= '</tr>';
}
$result .= '</tbody></table>';
return $result;
}/*}}}*/
function pieform_element_rolepermissions_get_value(Pieform $form, $element) {/*{{{*/
if (isset($element['value'])) {
return $element['value'];
}
if (isset($element['defaultvalue'])) {
$value = $element['defaultvalue'];
}
else {
$value = group_get_default_artefact_permissions($element['group']);
}
if ($form->is_submitted()) {
$global = ($form->get_property('method') == 'get') ? $_GET : $_POST;
$prefix = $form->get_name() . '_' . $element['name'] . '_p';
foreach ($value as $r => $perms) {
foreach (array_keys(get_object_vars($perms)) as $p) {
if ($r != 'admin') {
$value[$r]->$p = param_boolean($prefix . '_' . $r . '_' . $p);
}
}
}
}
return $value;
}/*}}}*/
......@@ -43,7 +43,7 @@ defined('INTERNAL') || die();
function group_user_access($groupid, $userid=null, $refresh=null) {
static $result;
if (!is_logged_in()) {
if (empty($userid) && !is_logged_in()) {
return false;
}
......
......@@ -2173,6 +2173,20 @@ class View {
$returnartefacts = array();
$result = '';
if ($artefacts) {
if (!empty($data['ownerinfo'])) {
require_once(get_config('docroot') . 'artefact/lib.php');
$userid = ($group || $institution) ? null : $USER->get('id');
foreach (artefact_get_owner_info(array_keys($artefacts)) as $k => $v) {
if ($artefacts[$k]->owner !== $userid
|| $artefacts[$k]->group !== $group
|| $artefacts[$k]->institution !== $institution) {
$artefacts[$k]->ownername = $v->name;
$artefacts[$k]->ownerurl = $v->url;
}
}
}