Merge branch 'MDL-50664-master' of git://github.com/ryanwyllie/moodle

This commit is contained in:
Dan Poltawski
2015-09-22 10:39:33 +01:00
13 changed files with 422 additions and 10 deletions
@@ -43,7 +43,7 @@ class backup_data_activity_structure_step extends backup_activity_structure_step
'requiredentries', 'requiredentriestoview', 'maxentries', 'rssarticles',
'singletemplate', 'listtemplate', 'listtemplateheader', 'listtemplatefooter',
'addtemplate', 'rsstemplate', 'rsstitletemplate', 'csstemplate',
'jstemplate', 'asearchtemplate', 'approval', 'scale',
'jstemplate', 'asearchtemplate', 'approval', 'manageapproved', 'scale',
'assessed', 'assesstimestart', 'assesstimefinish', 'defaultsort',
'defaultsortdir', 'editany', 'notification'));
+2 -1
View File
@@ -152,7 +152,7 @@ class mod_data_external extends external_api {
$additionalfields = array('maxentries', 'rssarticles', 'singletemplate', 'listtemplate',
'listtemplateheader', 'listtemplatefooter', 'addtemplate', 'rsstemplate', 'rsstitletemplate',
'csstemplate', 'jstemplate', 'asearchtemplate', 'approval', 'scale', 'assessed', 'assesstimestart',
'csstemplate', 'jstemplate', 'asearchtemplate', 'approval', 'manageapproved', 'scale', 'assessed', 'assesstimestart',
'assesstimefinish', 'defaultsort', 'defaultsortdir', 'editany', 'notification');
// This is for avoid a long repetitive list.
@@ -212,6 +212,7 @@ class mod_data_external extends external_api {
'jstemplate' => new external_value(PARAM_RAW, 'jstemplate field', VALUE_OPTIONAL),
'asearchtemplate' => new external_value(PARAM_RAW, 'asearchtemplate field', VALUE_OPTIONAL),
'approval' => new external_value(PARAM_BOOL, 'approval field', VALUE_OPTIONAL),
'manageapproved' => new external_value(PARAM_BOOL, 'manageapproved field', VALUE_OPTIONAL),
'scale' => new external_value(PARAM_INT, 'scale field', VALUE_OPTIONAL),
'assessed' => new external_value(PARAM_INT, 'assessed field', VALUE_OPTIONAL),
'assesstimestart' => new external_value(PARAM_INT, 'assesstimestart field', VALUE_OPTIONAL),
+1
View File
@@ -31,6 +31,7 @@
<FIELD NAME="jstemplate" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="asearchtemplate" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="approval" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="manageapproved" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
<FIELD NAME="scale" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="assessed" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="assesstimestart" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+15
View File
@@ -153,5 +153,20 @@ function xmldb_data_upgrade($oldversion) {
// Moodle v2.9.0 release upgrade line.
// Put any upgrade step following this.
if ($oldversion < 2015092200) {
// Define field manageapproved to be added to data.
$table = new xmldb_table('data');
$field = new xmldb_field('manageapproved', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '1', 'approval');
// Conditionally launch add field manageapproved.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Data savepoint reached.
upgrade_mod_savepoint(true, 2015092200, 'data');
}
return true;
}
+1 -1
View File
@@ -112,7 +112,7 @@ $groupmode = groups_get_activity_groupmode($cm);
if (!has_capability('mod/data:manageentries', $context)) {
if ($rid) {
// User is editing an existing record
if (!data_isowner($rid) || data_in_readonly_period($data)) {
if (!data_user_can_manage_entry($record, $data, $context)) {
print_error('noaccess','data');
}
} else if (!data_user_can_add_entry($data, $currentgroup, $groupmode, $context)) {
+2
View File
@@ -215,6 +215,8 @@ $string['latlongotherfields'] = 'Other fields';
$string['list'] = 'View list';
$string['listtemplate'] = 'List template';
$string['longitude'] = 'Longitude';
$string['manageapproved'] = 'Allow editing of approved entries';
$string['manageapproved_help'] = 'If disabled, approved entries are not editable and deletable by its owner. This setting only takes effect if approval required is set to yes. Default is yes.';
$string['mapexistingfield'] = 'Map to {$a}';
$string['mapnewfield'] = 'Create a new field';
$string['mappingwarning'] = 'All old fields not mapped to a new field will be lost and all data in that field will be removed.';
+40 -4
View File
@@ -1230,9 +1230,6 @@ function data_print_template($template, $records, $data, $search='', $page=0, $r
}
$jumpurl = new moodle_url($jumpurl, array('page' => $page, 'sesskey' => sesskey()));
// Check whether this activity is read-only at present
$readonly = data_in_readonly_period($data);
foreach ($records as $record) { // Might be just one for the single template
// Replacing tags
@@ -1250,7 +1247,7 @@ function data_print_template($template, $records, $data, $search='', $page=0, $r
// Replacing special tags (##Edit##, ##Delete##, ##More##)
$patterns[]='##edit##';
$patterns[]='##delete##';
if ($canmanageentries || (!$readonly && data_isowner($record->id))) {
if (data_user_can_manage_entry($record, $data, $context)) {
$replacement[] = '<a href="'.$CFG->wwwroot.'/mod/data/edit.php?d='
.$data->id.'&amp;rid='.$record->id.'&amp;sesskey='.sesskey().'"><img src="'.$OUTPUT->pix_url('t/edit') . '" class="iconsmall" alt="'.get_string('edit').'" title="'.get_string('edit').'" /></a>';
$replacement[] = '<a href="'.$CFG->wwwroot.'/mod/data/view.php?d='
@@ -2173,6 +2170,44 @@ function data_user_can_add_entry($data, $currentgroup, $groupmode, $context = nu
}
}
/**
* Check whether the current user is allowed to manage the given record considering manageentries capability,
* data_in_readonly_period() result, ownership (determined by data_isowner()) and manageapproved setting.
* @param mixed $record record object or id
* @param object $data data object
* @param object $context context object
* @return bool returns true if the user is allowd to edit the entry, false otherwise
*/
function data_user_can_manage_entry($record, $data, $context) {
global $DB;
if (has_capability('mod/data:manageentries', $context)) {
return true;
}
// Check whether this activity is read-only at present.
$readonly = data_in_readonly_period($data);
if (!$readonly) {
// Get record object from db if just id given like in data_isowner.
// ...done before calling data_isowner() to avoid querying db twice.
if (!is_object($record)) {
if (!$record = $DB->get_record('data_records', array('id' => $record))) {
return false;
}
}
if (data_isowner($record)) {
if ($data->approval && $record->approved) {
return $data->manageapproved == 1;
} else {
return true;
}
}
}
return false;
}
/**
* Check whether the specified database activity is currently in a read-only period
*
@@ -3314,6 +3349,7 @@ function data_presets_generate_xml($course, $cm, $data) {
'maxentries',
'rssarticles',
'approval',
'manageapproved',
'defaultsortdir'
);
+5
View File
@@ -32,6 +32,11 @@ class mod_data_mod_form extends moodleform_mod {
$mform->addElement('selectyesno', 'approval', get_string('requireapproval', 'data'));
$mform->addHelpButton('approval', 'requireapproval', 'data');
$mform->addElement('selectyesno', 'manageapproved', get_string('manageapproved', 'data'));
$mform->addHelpButton('manageapproved', 'manageapproved', 'data');
$mform->setDefault('manageapproved', 1);
$mform->disabledIf('manageapproved', 'approval', 'eq', 0);
$mform->addElement('selectyesno', 'comments', get_string('allowcomments', 'data'));
$countoptions = array(0=>get_string('none'))+
@@ -0,0 +1,91 @@
@mod @mod_data
Feature: Users can edit approved entries in database activities
In order to control whether approved database entries can be changed
As a teacher
I need to be able to enable or disable management of approved entries
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And I log in as "teacher1"
And I follow "Course 1"
And I turn editing mode on
@javascript
Scenario: Students can manage their approved entries to a database
# Create database activity and allow editing of
# approved entries.
And I add a "Database" to section "1" and I fill the form with:
| Name | Test database name |
| Description | Test |
| id_approval | Yes |
| id_manageapproved | Yes |
And I add a "Text input" field to "Test database name" database and I fill the form with:
| Field name | Test field name |
| Field description | Test field description |
# To generate the default templates.
And I follow "Templates"
And I log out
# Add an entry as a student.
And I log in as "student1"
And I follow "Course 1"
And I add an entry to "Test database name" database with:
| Test field name | Student entry |
And I press "Save and view"
And I log out
# Approve the student's entry as a teacher.
And I log in as "teacher1"
And I follow "Course 1"
And I follow "Test database name"
And I follow "Approve"
And I log out
# Make sure the student can still edit their entry after it's approved.
When I log in as "student1"
And I follow "Course 1"
And I follow "Test database name"
Then I should see "Student entry"
And "Edit" "link" should exist
@javascript
Scenario: Students can not manage their approved entries to a database
# Create database activity and don't allow editing of
# approved entries.
And I add a "Database" to section "1" and I fill the form with:
| Name | Test database name |
| Description | Test |
| id_approval | Yes |
| id_manageapproved | No |
And I add a "Text input" field to "Test database name" database and I fill the form with:
| Field name | Test field name |
| Field description | Test field description |
# To generate the default templates.
And I follow "Templates"
And I log out
# Add an entry as a student.
And I log in as "student1"
And I follow "Course 1"
And I add an entry to "Test database name" database with:
| Test field name | Student entry |
And I press "Save and view"
And I log out
# Approve the student's entry as a teacher.
And I log in as "teacher1"
And I follow "Course 1"
And I follow "Test database name"
And I follow "Approve"
And I log out
# Make sure the student isn't able to edit their entry after it's approved.
When I log in as "student1"
And I follow "Course 1"
And I follow "Test database name"
Then I should see "Student entry"
And "Edit" "link" should not exist
+1 -1
View File
@@ -144,7 +144,7 @@ class mod_data_external_testcase extends externallib_advanced_testcase {
$additionalfields = array('maxentries', 'rssarticles', 'singletemplate', 'listtemplate',
'listtemplateheader', 'listtemplatefooter', 'addtemplate', 'rsstemplate', 'rsstitletemplate',
'csstemplate', 'jstemplate', 'asearchtemplate', 'approval', 'scale', 'assessed', 'assesstimestart',
'assesstimefinish', 'defaultsort', 'defaultsortdir', 'editany', 'notification');
'assesstimefinish', 'defaultsort', 'defaultsortdir', 'editany', 'notification', 'manageapproved');
foreach ($additionalfields as $field) {
if ($field == 'approval' or $field == 'editany') {
+261
View File
@@ -232,6 +232,267 @@ class mod_data_lib_testcase extends advanced_testcase {
$this->assertEventContextNotUsed($event);
}
/**
* Checks that data_user_can_manage_entry will return true if the user
* has the mod/data:manageentries capability.
*/
public function test_data_user_can_manage_entry_return_true_with_capability() {
$this->resetAfterTest();
$testdata = $this->create_user_test_data();
$user = $testdata['user'];
$course = $testdata['course'];
$roleid = $testdata['roleid'];
$context = $testdata['context'];
$record = $testdata['record'];
$data = new stdClass();
$this->setUser($user);
assign_capability('mod/data:manageentries', CAP_ALLOW, $roleid, $context);
$this->assertTrue(data_user_can_manage_entry($record, $data, $context),
'data_user_can_manage_entry() returns true if the user has mod/data:manageentries capability');
}
/**
* Checks that data_user_can_manage_entry will return false if the data
* is set to readonly.
*/
public function test_data_user_can_manage_entry_return_false_readonly() {
$this->resetAfterTest();
$testdata = $this->create_user_test_data();
$user = $testdata['user'];
$course = $testdata['course'];
$roleid = $testdata['roleid'];
$context = $testdata['context'];
$record = $testdata['record'];
$data = new stdClass();
// Causes readonly mode to be enable.
$now = time();
$data->timeviewfrom = $now;
$data->timeviewto = $now;
$this->setUser($user);
// Need to make sure they don't have this capability in order to fall back to
// the other checks.
assign_capability('mod/data:manageentries', CAP_PROHIBIT, $roleid, $context);
$this->assertFalse(data_user_can_manage_entry($record, $data, $context),
'data_user_can_manage_entry() returns false if the data is read only');
}
/**
* Checks that data_user_can_manage_entry will return false if the record
* can't be found in the database.
*/
public function test_data_user_can_manage_entry_return_false_no_record() {
$this->resetAfterTest();
$testdata = $this->create_user_test_data();
$user = $testdata['user'];
$course = $testdata['course'];
$roleid = $testdata['roleid'];
$context = $testdata['context'];
$record = $testdata['record'];
$data = new stdClass();
// Causes readonly mode to be disabled.
$now = time();
$data->timeviewfrom = $now + 100;
$data->timeviewto = $now - 100;
$this->setUser($user);
// Need to make sure they don't have this capability in order to fall back to
// the other checks.
assign_capability('mod/data:manageentries', CAP_PROHIBIT, $roleid, $context);
// Pass record id instead of object to force DB lookup.
$this->assertFalse(data_user_can_manage_entry(1, $data, $context),
'data_user_can_manage_entry() returns false if the record cannot be found');
}
/**
* Checks that data_user_can_manage_entry will return false if the record
* isn't owned by the user.
*/
public function test_data_user_can_manage_entry_return_false_not_owned_record() {
$this->resetAfterTest();
$testdata = $this->create_user_test_data();
$user = $testdata['user'];
$course = $testdata['course'];
$roleid = $testdata['roleid'];
$context = $testdata['context'];
$record = $testdata['record'];
$data = new stdClass();
// Causes readonly mode to be disabled.
$now = time();
$data->timeviewfrom = $now + 100;
$data->timeviewto = $now - 100;
// Make sure the record isn't owned by this user.
$record->userid = $user->id + 1;
$this->setUser($user);
// Need to make sure they don't have this capability in order to fall back to
// the other checks.
assign_capability('mod/data:manageentries', CAP_PROHIBIT, $roleid, $context);
$this->assertFalse(data_user_can_manage_entry($record, $data, $context),
'data_user_can_manage_entry() returns false if the record isnt owned by the user');
}
/**
* Checks that data_user_can_manage_entry will return true if the data
* doesn't require approval.
*/
public function test_data_user_can_manage_entry_return_true_data_no_approval() {
$this->resetAfterTest();
$testdata = $this->create_user_test_data();
$user = $testdata['user'];
$course = $testdata['course'];
$roleid = $testdata['roleid'];
$context = $testdata['context'];
$record = $testdata['record'];
$data = new stdClass();
// Causes readonly mode to be disabled.
$now = time();
$data->timeviewfrom = $now + 100;
$data->timeviewto = $now - 100;
// The record doesn't need approval.
$data->approval = false;
// Make sure the record is owned by this user.
$record->userid = $user->id;
$this->setUser($user);
// Need to make sure they don't have this capability in order to fall back to
// the other checks.
assign_capability('mod/data:manageentries', CAP_PROHIBIT, $roleid, $context);
$this->assertTrue(data_user_can_manage_entry($record, $data, $context),
'data_user_can_manage_entry() returns true if the record doesnt require approval');
}
/**
* Checks that data_user_can_manage_entry will return true if the record
* isn't yet approved.
*/
public function test_data_user_can_manage_entry_return_true_record_unapproved() {
$this->resetAfterTest();
$testdata = $this->create_user_test_data();
$user = $testdata['user'];
$course = $testdata['course'];
$roleid = $testdata['roleid'];
$context = $testdata['context'];
$record = $testdata['record'];
$data = new stdClass();
// Causes readonly mode to be disabled.
$now = time();
$data->timeviewfrom = $now + 100;
$data->timeviewto = $now - 100;
// The record needs approval.
$data->approval = true;
// Make sure the record is owned by this user.
$record->userid = $user->id;
// The record hasn't yet been approved.
$record->approved = false;
$this->setUser($user);
// Need to make sure they don't have this capability in order to fall back to
// the other checks.
assign_capability('mod/data:manageentries', CAP_PROHIBIT, $roleid, $context);
$this->assertTrue(data_user_can_manage_entry($record, $data, $context),
'data_user_can_manage_entry() returns true if the record is not yet approved');
}
/**
* Checks that data_user_can_manage_entry will return the 'manageapproved'
* value if the record has already been approved.
*/
public function test_data_user_can_manage_entry_return_manageapproved() {
$this->resetAfterTest();
$testdata = $this->create_user_test_data();
$user = $testdata['user'];
$course = $testdata['course'];
$roleid = $testdata['roleid'];
$context = $testdata['context'];
$record = $testdata['record'];
$data = new stdClass();
// Causes readonly mode to be disabled.
$now = time();
$data->timeviewfrom = $now + 100;
$data->timeviewto = $now - 100;
// The record needs approval.
$data->approval = true;
// Can the user managed approved records?
$data->manageapproved = false;
// Make sure the record is owned by this user.
$record->userid = $user->id;
// The record has been approved.
$record->approved = true;
$this->setUser($user);
// Need to make sure they don't have this capability in order to fall back to
// the other checks.
assign_capability('mod/data:manageentries', CAP_PROHIBIT, $roleid, $context);
$canmanageentry = data_user_can_manage_entry($record, $data, $context);
// Make sure the result of the check is what ever the manageapproved setting
// is set to.
$this->assertEquals($data->manageapproved, $canmanageentry,
'data_user_can_manage_entry() returns the manageapproved setting on approved records');
}
/**
* Helper method to create a set of test data for data_user_can_manage tests
*
* @return array contains user, course, roleid, module, context and record
*/
private function create_user_test_data() {
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$roleid = $this->getDataGenerator()->create_role();
$record = new stdClass();
$record->name = "test name";
$record->intro = "test intro";
$record->comments = 1;
$record->course = $course->id;
$record->userid = $user->id;
$module = $this->getDataGenerator()->create_module('data', $record);
$cm = get_coursemodule_from_instance('data', $module->id, $course->id);
$context = context_module::instance($module->cmid);
$this->getDataGenerator()->role_assign($roleid, $user->id, $context->id);
return array(
'user' => $user,
'course' => $course,
'roleid' => $roleid,
'module' => $module,
'context' => $context,
'record' => $record
);
}
/**
* Tests for mod_data_rating_can_see_item_ratings().
*
+1 -1
View File
@@ -24,7 +24,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2015051100; // The current module version (Date: YYYYMMDDXX)
$plugin->version = 2015092200; // The current module version (Date: YYYYMMDDXX)
$plugin->requires = 2015050500; // Requires this Moodle version
$plugin->component = 'mod_data'; // Full name of the plugin (used for diagnostics)
$plugin->cron = 0;
+1 -1
View File
@@ -373,7 +373,7 @@
/// Delete any requested records
if ($delete && confirm_sesskey() && ($canmanageentries or data_isowner($delete))) {
if ($delete && confirm_sesskey() && (data_user_can_manage_entry($delete, $data, $context))) {
if ($confirm = optional_param('confirm',0,PARAM_INT)) {
if (data_delete_record($delete, $data, $course->id, $cm->id)) {
echo $OUTPUT->notification(get_string('recorddeleted','data'), 'notifysuccess');