}
}
$CFG->directorypermissions = $chmod;
+$CFG->filepermissions = ($CFG->directorypermissions & 0666);
+$CFG->umaskpermissions = (($CFG->directorypermissions & 0777) ^ 0777);
//We need wwwroot before we test dataroot
$wwwroot = clean_param($options['wwwroot'], PARAM_URL);
<ON_CHECK message="settingfileuploads" />
</FEEDBACK>
</PHP_SETTING>
+ <PHP_SETTING name="opcache.enable" value="1" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="opcacherecommended" />
+ </FEEDBACK>
+ </PHP_SETTING>
</PHP_SETTINGS>
</MOODLE>
</COMPATIBILITY_MATRIX>
$setting->set_updatedcallback('js_reset_all_caches');
$temp->add($setting);
$temp->add(new admin_setting_configcheckbox('modchooserdefault', new lang_string('modchooserdefault', 'admin'), new lang_string('configmodchooserdefault', 'admin'), 1));
+ $temp->add(new admin_setting_configcheckbox('modeditingmenu', new lang_string('modeditingmenu', 'admin'), new lang_string('modeditingmenu_desc', 'admin'), 1));
+ $temp->add(new admin_setting_configcheckbox('blockeditingmenu', new lang_string('blockeditingmenu', 'admin'), new lang_string('blockeditingmenu_desc', 'admin'), 1));
$ADMIN->add('appearance', $temp);
// link to tag management interface
* @return bool false is any error occured.
*/
public function prepare() {
- global $DB;
+ global $DB, $SITE;
$this->prepared = true;
// Validate the shortname.
$this->error('courseexistsanduploadnotallowed',
new lang_string('courseexistsanduploadnotallowed', 'tool_uploadcourse'));
return false;
+ } else if ($this->can_update()) {
+ // We can never allow for any front page changes!
+ if ($this->shortname == $SITE->shortname) {
+ $this->error('cannotupdatefrontpage', new lang_string('cannotupdatefrontpage', 'tool_uploadcourse'));
+ return false;
+ }
}
} else {
if (!$this->can_create()) {
if ($exists) {
$missingonly = ($updatemode === tool_uploadcourse_processor::UPDATE_MISSING_WITH_DATA_OR_DEFAUTLS);
$coursedata = $this->get_final_update_data($coursedata, $usedefaults, $missingonly);
+
+ // Make sure we are not trying to mess with the front page, though we should never get here!
+ if ($coursedata['id'] == $SITE->id) {
+ $this->error('cannotupdatefrontpage', new lang_string('cannotupdatefrontpage', 'tool_uploadcourse'));
+ return false;
+ }
+
$this->do = self::DO_UPDATE;
} else {
$coursedata = $this->get_final_create_data($coursedata);
$this->error('errorwhilerestoringcourse', new lang_string('errorwhilerestoringthecourse', 'tool_uploadcourse'));
}
$rc->destroy();
+ unset($rc); // File logging is a mess, we can only try to rely on gc to close handles.
}
// Proceed with enrolment data.
*/
public static function clean_restore_content() {
global $CFG;
+
+ // There are some sloppy unclosed file handles in backup/restore code,
+ // let's hope somebody unset all controllers before calling this
+ // and destroy magic will close all remaining open file handles,
+ // otherwise Windows will fail deleting the directory.
+ gc_collect_cycles();
+
if (!empty($CFG->keeptempdirectoriesonbackup)) {
$cache = cache::make('tool_uploadcourse', 'helper');
$backupids = (array) $cache->get('backupids');
$string['cannotrenamecoursenotexist'] = 'Cannot rename a course that does not exist';
$string['cannotrenameidnumberconflict'] = 'Cannot rename the course, the ID number conflicts with an existing course';
$string['cannotrenameshortnamealreadyinuse'] = 'Cannot rename the course, the shortname is already used';
+$string['cannotupdatefrontpage'] = 'It is forbidden to modify the front page';
$string['canonlyrenameinupdatemode'] = 'Can only rename a course when update is allowed';
$string['canonlyresetcourseinupdatemode'] = 'Can only reset a course in update mode';
$string['couldnotresolvecatgorybyid'] = 'Could not resolve category by ID';
}
public function test_create_bad_category() {
+ global $DB;
$this->resetAfterTest(true);
// Ensure fails when category cannot be resolved upon creation.
$this->assertFalse($co->prepare());
$this->assertArrayHasKey('couldnotresolvecatgorybyid', $co->get_errors());
+ // Ensure fails when category is 0 on create.
+ $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
+ $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
+ $data = array('shortname' => 'c1', 'summary' => 'summary', 'fullname' => 'FN', 'category' => '0');
+ $co = new tool_uploadcourse_course($mode, $updatemode, $data);
+ $this->assertFalse($co->prepare());
+ $this->assertArrayHasKey('missingmandatoryfields', $co->get_errors());
+
// Ensure fails when category cannot be resolved upon update.
$c1 = $this->getDataGenerator()->create_course();
$mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
$co = new tool_uploadcourse_course($mode, $updatemode, $data);
$this->assertFalse($co->prepare());
$this->assertArrayHasKey('couldnotresolvecatgorybyid', $co->get_errors());
+
+ // Ensure does not update the category when it is 0.
+ $c1 = $this->getDataGenerator()->create_course();
+ $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
+ $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
+ $data = array('shortname' => $c1->shortname, 'category' => '0');
+ $co = new tool_uploadcourse_course($mode, $updatemode, $data);
+ $this->assertTrue($co->prepare());
+ $this->assertEmpty($co->get_errors());
+ $this->assertEmpty($co->get_statuses());
+ $co->proceed();
+ $this->assertEquals($c1->category, $DB->get_field('course', 'category', array('id' => $c1->id)));
+
+ // Ensure does not update the category when it is set to 0 in the defaults.
+ $c1 = $this->getDataGenerator()->create_course();
+ $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
+ $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_OR_DEFAUTLS;
+ $data = array('shortname' => $c1->shortname);
+ $defaults = array('category' => '0');
+ $co = new tool_uploadcourse_course($mode, $updatemode, $data, $defaults);
+ $this->assertTrue($co->prepare());
+ $this->assertEmpty($co->get_errors());
+ $this->assertEmpty($co->get_statuses());
+ $co->proceed();
+ $this->assertEquals($c1->category, $DB->get_field('course', 'category', array('id' => $c1->id)));
}
public function test_enrolment_data() {
$this->assertArrayHasKey('courseshortnameincremented', $co->get_statuses());
}
+ public function test_mess_with_frontpage() {
+ global $SITE;
+ $this->resetAfterTest(true);
+
+ // Updating the front page.
+ $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
+ $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
+ $data = array('shortname' => $SITE->shortname, 'idnumber' => 'NewIDN');
+ $importoptions = array();
+ $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
+ $this->assertFalse($co->prepare());
+ $this->assertArrayHasKey('cannotupdatefrontpage', $co->get_errors());
+
+ // Updating the front page.
+ $mode = tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE;
+ $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
+ $data = array('shortname' => $SITE->shortname, 'idnumber' => 'NewIDN');
+ $importoptions = array();
+ $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
+ $this->assertFalse($co->prepare());
+ $this->assertArrayHasKey('cannotupdatefrontpage', $co->get_errors());
+
+ // Generating a shortname should not be allowed in update mode, and so we cannot update the front page.
+ $mode = tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE;
+ $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
+ $data = array('idnumber' => 'NewIDN', 'fullname' => 'FN', 'category' => 1);
+ $importoptions = array('shortnametemplate' => $SITE->shortname);
+ $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
+ $this->assertFalse($co->prepare());
+ $this->assertArrayHasKey('cannotgenerateshortnameupdatemode', $co->get_errors());
+
+ // Renaming to the front page should not be allowed.
+ $c1 = $this->getDataGenerator()->create_course();
+ $mode = tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE;
+ $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
+ $data = array('shortname' => $c1->shortname, 'fullname' => 'FN', 'idnumber' => 'NewIDN', 'rename' => $SITE->shortname);
+ $importoptions = array('canrename' => true);
+ $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
+ $this->assertFalse($co->prepare());
+ $this->assertArrayHasKey('cannotrenameshortnamealreadyinuse', $co->get_errors());
+
+ }
+
}
$this->assertTrue(isset($result['backup_destination']));
$c1backupfile = $result['backup_destination']->copy_content_to_temp();
$bc->destroy();
+ unset($bc); // File logging is a mess, we can only try to rely on gc to close handles.
// Creating backup file.
$bc = new backup_controller(backup::TYPE_1COURSE, $c2->id, backup::FORMAT_MOODLE,
$this->assertTrue(isset($result['backup_destination']));
$c2backupfile = $result['backup_destination']->copy_content_to_temp();
$bc->destroy();
+ unset($bc); // File logging is a mess, we can only try to rely on gc to close handles.
// Checking restore dir.
$dir = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null);
* @return array type=>name
*/
function uu_supported_auths() {
- // only following plugins are guaranteed to work properly
- $whitelist = array('manual', 'nologin', 'none', 'email');
+ // Get all the enabled plugins.
$plugins = get_enabled_auth_plugins();
$choices = array();
foreach ($plugins as $plugin) {
- if (!in_array($plugin, $whitelist)) {
+ $objplugin = get_auth_plugin($plugin);
+ // If the plugin can not be manually set skip it.
+ if (!$objplugin->can_be_manually_set()) {
continue;
}
$choices[$plugin] = get_string('pluginname', "auth_{$plugin}");
return true;
}
+ /**
+ * Returns true if plugin can be manually set.
+ *
+ * @return bool
+ */
+ function can_be_manually_set() {
+ return true;
+ }
+
/**
* Prints a form for configuring this authentication plugin.
*
return !empty($this->config->stdchangepassword);
}
+ /**
+ * Returns true if plugin can be manually set.
+ *
+ * @return bool
+ */
+ function can_be_manually_set() {
+ return true;
+ }
+
/**
* Returns true if plugin allows signup and user creation.
*
return true;
}
+ /**
+ * Returns true if plugin can be manually set.
+ *
+ * @return bool
+ */
+ function can_be_manually_set() {
+ return true;
+ }
+
/**
* Prints a form for configuring this authentication plugin.
*
return false;
}
+ /**
+ * Returns true if plugin can be manually set.
+ *
+ * @return bool
+ */
+ function can_be_manually_set() {
+ return true;
+ }
}
return true;
}
+ /**
+ * Returns true if plugin can be manually set.
+ *
+ * @return bool
+ */
+ function can_be_manually_set() {
+ return true;
+ }
+
/**
* Prints a form for configuring this authentication plugin.
*
+++ /dev/null
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle 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.
-//
-// Moodle 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 Moodle. If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Tests for auth.
- *
- * @package core_auth
- * @copyright 2013 Frédéric Massart
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-global $CFG;
-require_once($CFG->libdir . '/authlib.php');
-
-/**
- * Auth testcase class.
- *
- * @package core_auth
- * @copyright 2013 Frédéric Massart
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class auth_testcase extends advanced_testcase {
-
- public function test_user_loggedin_event() {
- global $USER;
- $this->resetAfterTest(true);
- $this->setAdminUser();
-
- $sink = $this->redirectEvents();
- $user = clone($USER);
- login_attempt_valid($user);
- $events = $sink->get_events();
- $sink->close();
-
- $this->assertCount(1, $events);
- $event = reset($events);
- $this->assertInstanceOf('\core_auth\event\user_loggedin', $event);
- $this->assertEquals('user', $event->objecttable);
- $this->assertEquals('2', $event->objectid);
- $this->assertEquals(context_system::instance()->id, $event->contextid);
- $this->assertEquals($user, $event->get_record_snapshot('user', 2));
- }
-
- public function test_user_loggedin_event_exceptions() {
- try {
- $event = \core_auth\event\user_loggedin::create(array('objectid' => 1));
- $this->fail('\core_auth\event\user_loggedin requires other[\'username\']');
- } catch(Exception $e) {
- $this->assertInstanceOf('coding_exception', $e);
- }
-
- try {
- $event = \core_auth\event\user_loggedin::create(array('other' => array('username' => 'test')));
- $this->fail('\core_auth\event\user_loggedin requires objectid');
- } catch(Exception $e) {
- $this->assertInstanceOf('coding_exception', $e);
- }
- }
-
-}
This files describes API changes in /auth/* - plugins,
information provided here is intended especially for developers.
+=== 2.6 ===
+
+* can_be_manually_set() - This function was introduced in the base class and returns false by default. If overriden by
+ an authentication plugin to return true, the authentication plugin will be able to be manually set for users. For example,
+ when bulk uploading users you will be able to select it as the authentication method they use.
=== 2.4 ===
/*
* controller tests (all)
*/
-class backup_controller_testcase extends advanced_testcase {
+class core_backup_controller_testcase extends advanced_testcase {
protected $moduleid; // course_modules id used for testing
protected $sectionid; // course_sections id used for testing
require_once($CFG->dirroot . '/backup/converter/moodle1/lib.php');
-class moodle1_converter_testcase extends advanced_testcase {
+class core_backup_moodle1_converter_testcase extends advanced_testcase {
/** @var string the name of the directory containing the unpacked Moodle 1.9 backup */
protected $tempdir;
// Get all the block_position objects pending to match
$params = array('backupid' => $this->get_restoreid(), 'itemname' => 'block_position');
- $rs = $DB->get_recordset('backup_ids_temp', $params, '', 'itemid');
+ $rs = $DB->get_recordset('backup_ids_temp', $params, '', 'itemid, info');
// Process block positions, creating them or accumulating for final step
foreach($rs as $posrec) {
- // Get the complete position object (stored as info)
- $position = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'block_position', $posrec->itemid)->info;
+ // Get the complete position object out of the info field.
+ $position = backup_controller_dbops::decode_backup_temp_info($posrec->info);
// If position is for one already mapped (known) contextid
// process it now, creating the position, else nothing to
// do, position finally discarded
// Get all the module_availability objects to process
$params = array('backupid' => $this->get_restoreid(), 'itemname' => 'module_availability');
- $rs = $DB->get_recordset('backup_ids_temp', $params, '', 'itemid');
+ $rs = $DB->get_recordset('backup_ids_temp', $params, '', 'itemid, info');
// Process availabilities, creating them if everything matches ok
foreach($rs as $availrec) {
$allmatchesok = true;
// Get the complete availabilityobject
- $availability = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'module_availability', $availrec->itemid)->info;
+ $availability = backup_controller_dbops::decode_backup_temp_info($availrec->info);
// Map the sourcecmid if needed and possible
if (!empty($availability->sourcecmid)) {
$newcm = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'course_module', $availability->sourcecmid);
// Iterate over aliases in the queue.
foreach ($rs as $record) {
- $info = unserialize(base64_decode($record->info));
+ $info = restore_dbops::decode_backup_temp_info($record->info);
// Try to pick a repository instance that should serve the alias.
$repository = $this->choose_repository($info);
$source = null;
foreach ($candidates as $candidate) {
- $candidateinfo = unserialize(base64_decode($candidate->info));
+ $candidateinfo = backup_controller_dbops::decode_backup_temp_info($candidate->info);
if ($candidateinfo->filename === $reference['filename']
and $candidateinfo->filepath === $reference['filepath']
and !is_null($candidate->newcontextid)
method is not available anymore. Temp tables must be created
inline always.
+* Using the info field from backup_ids_temp or backup_files_temp
+ must now go via backup_controller_dbops::decode_backup_temp_info() and
+ backup_controller_dbops::encode_backup_temp_info(). The implementation
+ of the encoding has changed. These new functions encapsulate any future
+ changes to the encoding.
+
=== 2.5 ===
* New optional param $sortby in backup set_source_table() allows to
$dbman->drop_table($table); // And drop it
}
+ /**
+ * Decode the info field from backup_ids_temp or backup_files_temp.
+ *
+ * @param mixed $info The info field data to decode, may be an object or a simple integer.
+ * @return mixed The decoded information. For simple types it returns, for complex ones we decode.
+ */
+ public static function decode_backup_temp_info($info) {
+ // We encode all data except null.
+ if ($info != null) {
+ if (extension_loaded('zlib')) {
+ return unserialize(gzuncompress(base64_decode($info)));
+ } else {
+ return unserialize(base64_decode($info));
+ }
+ }
+ return $info;
+ }
+
+ /**
+ * Encode the info field for backup_ids_temp or backup_files_temp.
+ *
+ * @param mixed $info string The info field data to encode.
+ * @return string An encoded string of data or null if the input is null.
+ */
+ public static function encode_backup_temp_info($info) {
+ // We encode if there is any information to keep the translations simpler.
+ if ($info != null) {
+ // We compress if possible. It reduces db, network and memory storage. The saving is greater than CPU compression cost.
+ // Compression level 1 is chosen has it produces good compression with the smallest possible overhead, see MDL-40618.
+ if (extension_loaded('zlib')) {
+ return base64_encode(gzcompress(serialize($info), 1));
+ } else {
+ return base64_encode(serialize($info));
+ }
+ }
+ return $info;
+ }
+
/**
* Given one type and id from controller, return the corresponding courseid
*/
$problems = array(); // To store warnings/errors
// Get loaded roles from backup_ids
- $rs = $DB->get_recordset('backup_ids_temp', array('backupid' => $restoreid, 'itemname' => 'role'), '', 'itemid');
+ $rs = $DB->get_recordset('backup_ids_temp', array('backupid' => $restoreid, 'itemname' => 'role'), '', 'itemid, info');
foreach ($rs as $recrole) {
// If the rolemappings->modified flag is set, that means that we are coming from
// manually modified mappings (by UI), so accept those mappings an put them to backup_ids
// Else, we haven't any info coming from UI, let's calculate the mappings, matching
// in multiple ways and checking permissions. Note mapping to 0 means "skip"
} else {
- $role = (object)self::get_backup_ids_record($restoreid, 'role', $recrole->itemid)->info;
+ $role = (object)backup_controller_dbops::decode_backup_temp_info($recrole->info);
$match = self::get_best_assignable_role($role, $courseid, $userid, $samesite);
// Send match to backup_ids
self::set_backup_ids_record($restoreid, 'role', $recrole->itemid, $match);
// Build the rolemappings element for controller
unset($role->id);
unset($role->nameincourse);
- unset($role->nameincourse);
$role->targetroleid = $match;
$rolemappings->mappings[$recrole->itemid] = $role;
// Prepare warning if no match found
global $DB;
$results = array();
- $qcats = $DB->get_records_sql("SELECT itemid, parentitemid AS contextid
+ $qcats = $DB->get_recordset_sql("SELECT itemid, parentitemid AS contextid, info
FROM {backup_ids_temp}
WHERE backupid = ?
AND itemname = 'question_category'", array($restoreid));
foreach ($qcats as $qcat) {
// If this qcat context haven't been acummulated yet, do that
if (!isset($results[$qcat->contextid])) {
- $temprec = self::get_backup_ids_record($restoreid, 'question_category', $qcat->itemid);
+ $info = backup_controller_dbops::decode_backup_temp_info($qcat->info);
// Filter by contextlevel if necessary
- if (is_null($contextlevel) || $contextlevel == $temprec->info->contextlevel) {
- $results[$qcat->contextid] = $temprec->info->contextlevel;
+ if (is_null($contextlevel) || $contextlevel == $info->contextlevel) {
+ $results[$qcat->contextid] = $info->contextlevel;
}
}
}
+ $qcats->close();
// Sort by value (contextlevel from CONTEXT_SYSTEM downto CONTEXT_MODULE)
asort($results);
return $results;
global $DB;
$results = array();
- $qcats = $DB->get_records_sql("SELECT itemid
+ $qcats = $DB->get_recordset_sql("SELECT itemid, info
FROM {backup_ids_temp}
WHERE backupid = ?
AND itemname = 'question_category'
AND parentitemid = ?", array($restoreid, $contextid));
foreach ($qcats as $qcat) {
- $temprec = self::get_backup_ids_record($restoreid, 'question_category', $qcat->itemid);
- $results[$qcat->itemid] = $temprec->info;
+ $results[$qcat->itemid] = backup_controller_dbops::decode_backup_temp_info($qcat->info);
}
+ $qcats->close();
+
return $results;
}
global $DB;
$results = array();
- $qs = $DB->get_records_sql("SELECT itemid
+ $qs = $DB->get_recordset_sql("SELECT itemid, info
FROM {backup_ids_temp}
WHERE backupid = ?
AND itemname = 'question'
AND parentitemid = ?", array($restoreid, $qcatid));
foreach ($qs as $q) {
- $temprec = self::get_backup_ids_record($restoreid, 'question', $q->itemid);
- $results[$q->itemid] = $temprec->info;
+ $results[$q->itemid] = backup_controller_dbops::decode_backup_temp_info($q->info);
}
+ $qs->close();
return $results;
}
$basepath = $basepath . '/files/';// Get backup file pool base
$rs = $DB->get_recordset_sql($sql, $params);
foreach ($rs as $rec) {
- $file = (object)unserialize(base64_decode($rec->info));
+ $file = (object)backup_controller_dbops::decode_backup_temp_info($rec->info);
// ignore root dirs (they are created automatically)
if ($file->filepath == '/' && $file->filename == '.') {
$themes = get_list_of_themes(); // Get themes for quick search later
// Iterate over all the included users with newitemid = 0, have to create them
- $rs = $DB->get_recordset('backup_ids_temp', array('backupid' => $restoreid, 'itemname' => 'user', 'newitemid' => 0), '', 'itemid, parentitemid');
+ $rs = $DB->get_recordset('backup_ids_temp', array('backupid' => $restoreid, 'itemname' => 'user', 'newitemid' => 0), '', 'itemid, parentitemid, info');
foreach ($rs as $recuser) {
- $user = (object)self::get_backup_ids_record($restoreid, 'user', $recuser->itemid)->info;
+ $user = (object)backup_controller_dbops::decode_backup_temp_info($recuser->info);
// if user lang doesn't exist here, use site default
if (!array_key_exists($user->lang, $languages)) {
}
// Iterate over all the included users
- $rs = $DB->get_recordset('backup_ids_temp', array('backupid' => $restoreid, 'itemname' => 'user'), '', 'itemid');
+ $rs = $DB->get_recordset('backup_ids_temp', array('backupid' => $restoreid, 'itemname' => 'user'), '', 'itemid, info');
foreach ($rs as $recuser) {
- $user = (object)self::get_backup_ids_record($restoreid, 'user', $recuser->itemid)->info;
+ $user = (object)backup_controller_dbops::decode_backup_temp_info($recuser->info);
// Find the correct mnethostid for user before performing any further check
if (empty($user->mnethosturl) || $user->mnethosturl === $CFG->wwwroot) {
global $DB;
// Store external files info in `info` field
- $filerec->info = base64_encode(serialize($filerec)); // Serialize the whole rec in info
+ $filerec->info = backup_controller_dbops::encode_backup_temp_info($filerec); // Encode the whole record into info.
$filerec->backupid = $restoreid;
$DB->insert_record('backup_files_temp', $filerec);
}
$extrarecord['parentitemid'] = $parentitemid;
}
if ($info != null) {
- $extrarecord['info'] = base64_encode(serialize($info));
+ $extrarecord['info'] = backup_controller_dbops::encode_backup_temp_info($info);
}
self::set_backup_ids_cached($restoreid, $itemname, $itemid, $extrarecord);
public static function get_backup_ids_record($restoreid, $itemname, $itemid) {
$dbrec = self::get_backup_ids_cached($restoreid, $itemname, $itemid);
+ // We must test if info is a string, as the cache stores info in object form.
if ($dbrec && isset($dbrec->info) && is_string($dbrec->info)) {
- $dbrec->info = unserialize(base64_decode($dbrec->info));
+ $dbrec->info = backup_controller_dbops::decode_backup_temp_info($dbrec->info);
}
return $dbrec;
// Get the course context
$coursectx = context_course::instance($courseid);
// Get all the mapped roles we have
- $rs = $DB->get_recordset('backup_ids_temp', array('backupid' => $restoreid, 'itemname' => 'role'), '', 'itemid');
+ $rs = $DB->get_recordset('backup_ids_temp', array('backupid' => $restoreid, 'itemname' => 'role'), '', 'itemid, info, newitemid');
foreach ($rs as $recrole) {
- // Get the complete temp_ids record
- $role = (object)self::get_backup_ids_record($restoreid, 'role', $recrole->itemid);
+ $info = backup_controller_dbops::decode_backup_temp_info($recrole->info);
// If it's one mapped role and we have one name for it
- if (!empty($role->newitemid) && !empty($role->info['nameincourse'])) {
+ if (!empty($recrole->newitemid) && !empty($info['nameincourse'])) {
// If role name doesn't exist, add it
$rolename = new stdclass();
- $rolename->roleid = $role->newitemid;
+ $rolename->roleid = $recrole->newitemid;
$rolename->contextid = $coursectx->id;
if (!$DB->record_exists('role_names', (array)$rolename)) {
- $rolename->name = $role->info['nameincourse'];
+ $rolename->name = $info['nameincourse'];
$DB->insert_record('role_names', $rolename);
}
}
// Drop and check it doesn't exists anymore
backup_controller_dbops::drop_backup_ids_temp_table('testingid');
$this->assertFalse($dbman->table_exists('backup_ids_temp'));
+
+ // Test encoding/decoding of backup_ids_temp,backup_files_temp encode/decode functions.
+ // We need to handle both objects and data elements.
+ $object = new stdClass();
+ $object->item1 = 10;
+ $object->item2 = 'a String';
+ $testarray = array($object, 10, null, 'string', array('a' => 'b', 1 => 1));
+ foreach ($testarray as $item) {
+ $encoded = backup_controller_dbops::encode_backup_temp_info($item);
+ $decoded = backup_controller_dbops::decode_backup_temp_info($encoded);
+ $this->assertEquals($item, $decoded);
+ }
}
/**
And I add a "Database" to section "1" and I fill the form with:
| Name | Test database name |
| Description | Test database description |
- When I click on "Duplicate" "link" in the "#section-1" "css_element"
+ And I click on "Actions" "link" in the "Test database name" activity
+ When I click on "Duplicate" "link" in the "Test database name" activity
And I press "Continue"
And I press "Edit the new copy"
And I fill the moodle form with:
}
$options = array(
- 'FRESH_CONNECT' => true,
+ 'FRESH_CONNECT' => true,
'RETURNTRANSFER' => true,
- 'FORBID_REUSE' => true,
- 'HEADER' => 0,
+ 'FORBID_REUSE' => true,
+ 'HEADER' => 0,
+ 'HTTPHEADER' => array('Expect:'),
'CONNECTTIMEOUT' => 3,
);
global $CFG;
require_once($CFG->libdir . '/badgeslib.php');
-class badges_testcase extends advanced_testcase {
+class core_badgeslib_testcase extends advanced_testcase {
protected $badgeid;
protected function setUp() {
$deleteicon = $this->find('css', '.comment-delete a img', $deleteexception, $commentnode);
$deleteicon->click();
- // Yes confirm.
- $confirmnode = $this->find('xpath', "//div[@class='comment-delete-confirm']/descendant::a[contains(., '" . get_string('yes') . "')]");
- $confirmnode->click();
-
// Wait for the AJAX request.
$this->getSession()->wait(4 * 1000, false);
}
),
'clonepermissionsfrom' => 'moodle/my:manageblocks'
+ ),
+
+ 'block/course_overview:addinstance' => array(
+ 'riskbitmask' => RISK_SPAM | RISK_XSS,
+
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_BLOCK,
+ 'archetypes' => array(
+ 'manager' => CAP_ALLOW
+ ),
+
+ 'clonepermissionsfrom' => 'moodle/site:manageblocks'
)
);
function block_course_overview_get_overviews($courses) {
$htmlarray = array();
if ($modules = get_plugin_list_with_function('mod', 'print_overview')) {
- foreach ($modules as $fname) {
- $fname($courses,$htmlarray);
+ // Split courses list into batches with no more than MAX_MODINFO_CACHE_SIZE courses in one batch.
+ // Otherwise we exceed the cache limit in get_fast_modinfo() and rebuild it too often.
+ if (defined('MAX_MODINFO_CACHE_SIZE') && MAX_MODINFO_CACHE_SIZE > 0 && count($courses) > MAX_MODINFO_CACHE_SIZE) {
+ $batches = array_chunk($courses, MAX_MODINFO_CACHE_SIZE, true);
+ } else {
+ $batches = array($courses);
+ }
+ foreach ($batches as $courses) {
+ foreach ($modules as $fname) {
+ $fname($courses, $htmlarray);
+ }
}
}
return $htmlarray;
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2013050100; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version = 2013073000; // The current plugin version (Date: YYYYMMDDXX)
$plugin->requires = 2013050100; // Requires this Moodle version
$plugin->component = 'block_course_overview'; // Full name of the plugin (used for diagnostics)
Then I should see "cat1" in the "div.block_navigation .type_system" "css_element"
And I should see "cat3" in the "div.block_navigation .type_system" "css_element"
And I should not see "cat2" in the "div.block_navigation .type_system" "css_element"
- When I expand "cat3" node
- Then I should see "cat31" in the "div.block_navigation .type_system" "css_element"
+ And I expand "cat3" node
+ And I wait "2" seconds
+ And I should see "cat31" in the "div.block_navigation .type_system" "css_element"
And I should see "cat33" in the "div.block_navigation .type_system" "css_element"
And I should not see "cat32" in the "div.block_navigation .type_system" "css_element"
- When I expand "cat31" node
- Then I should see "c31" in the "div.block_navigation .type_system" "css_element"
- When I expand "cat33" node
+ And I expand "cat31" node
+ And I wait "2" seconds
+ And I should see "c31" in the "div.block_navigation .type_system" "css_element"
+ And I expand "cat33" node
+ And I wait "2" seconds
And I should see "c331" in the "div.block_navigation .type_system" "css_element"
And I should not see "c332" in the "div.block_navigation .type_system" "css_element"
return $this->content;
}
-/// slow & hacky editing mode
+ // Slow & hacky editing mode.
+ /** @var core_course_renderer $courserenderer */
$courserenderer = $this->page->get_renderer('core', 'course');
$ismoving = ismoving($course->id);
course_create_sections_if_missing($course, 0);
$strcancel= get_string('cancel');
$stractivityclipboard = $USER->activitycopyname;
}
- /// Casting $course->modinfo to string prevents one notice when the field is null
+ // Casting $course->modinfo to string prevents one notice when the field is null.
$editbuttons = '';
if ($ismoving) {
if (!$ismoving) {
$actions = course_get_cm_edit_actions($mod, -1);
$editbuttons = html_writer::tag('div',
- $courserenderer->course_section_cm_edit_actions($actions),
- array('class' => 'buttons'));
+ $courserenderer->course_section_cm_edit_actions($actions, $mod, array('donotenhance' => true)),
+ array('class' => 'buttons')
+ );
} else {
$editbuttons = '';
}
}
-/// slow & hacky editing mode
+ // Slow & hacky editing mode.
+ /** @var core_course_renderer $courserenderer */
$courserenderer = $this->page->get_renderer('core', 'course');
$ismoving = ismoving($course->id);
$modinfo = get_fast_modinfo($course);
$strcancel= get_string('cancel');
$stractivityclipboard = $USER->activitycopyname;
}
- /// Casting $course->modinfo to string prevents one notice when the field is null
+ // Casting $course->modinfo to string prevents one notice when the field is null.
$editbuttons = '';
if ($ismoving) {
if (!$ismoving) {
$actions = course_get_cm_edit_actions($mod, -1);
$editbuttons = '<br />'.
- $courserenderer->course_section_cm_edit_actions($actions);
+ $courserenderer->course_section_cm_edit_actions($actions, $mod);
} else {
$editbuttons = '';
}
/**
* Inserts this entry in the database. Access control checks must be done by calling code.
* TODO Set the publishstate correctly
- * @param mform $form Used for attachments
* @return void
*/
public function add() {
if (!empty($CFG->useblogassociations)) {
$this->add_associations();
- add_to_log(SITEID, 'blog', 'add', 'index.php?userid='.$this->userid.'&entryid='.$this->id, $this->subject);
}
tag_set('post', $this->id, $this->tags);
- events_trigger('blog_entry_added', $this);
+
+ // Trigger an event for the new entry.
+ $event = \core\event\blog_entry_created::create(array('objectid' => $this->id,
+ 'userid' => $this->userid,
+ 'other' => array ("subject" => $this->subject)
+ ));
+ $event->set_custom_data($this);
+ $event->trigger();
}
/**
And I follow "Save comment"
And I wait "4" seconds
When I click on ".comment-delete a" "css_element"
- And I click on "Yes" "link"
And I wait "4" seconds
Then I should not see "$My own >nasty< \"string\"!"
And I follow "Blog post from user 1"
/**
* Test functions that rely on the DB tables
*/
-class bloglib_testcase extends advanced_testcase {
+class core_bloglib_testcase extends advanced_testcase {
private $courseid; // To store important ids to be used in tests
private $cmid;
$blog_headers = blog_get_headers($this->courseid);
$this->assertNotEquals($blog_headers['heading'], '');
}
+
+ /**
+ * Test various blog related events.
+ */
+ public function test_blog_entry_events() {
+ global $USER;
+
+ $this->setAdminUser();
+ $this->resetAfterTest();
+
+ // Create a blog entry.
+ $blog = new blog_entry();
+ $blog->summary = "This is summary of blog";
+ $blog->subject = "Subject of blog";
+ $states = blog_entry::get_applicable_publish_states();
+ $blog->publishstate = reset($states);
+ $sink = $this->redirectEvents();
+ $blog->add();
+ $events = $sink->get_events();
+ $event = reset($events);
+ $sitecontext = context_system::instance();
+
+ // Validate event data.
+ $this->assertInstanceOf('\core\event\blog_entry_created', $event);
+ $this->assertEquals($sitecontext->id, $event->contextid);
+ $this->assertEquals($blog->id, $event->objectid);
+ $this->assertEquals($USER->id, $event->userid);
+ $this->assertEquals("post", $event->objecttable);
+ }
}
$this->definition = $definition;
$hash = preg_replace('#[^a-zA-Z0-9]+#', '_', $this->definition->get_id());
$this->path = $this->filestorepath.'/'.$hash;
- make_writable_directory($this->path);
+ make_writable_directory($this->path, false);
if ($this->prescan && $definition->get_mode() !== self::MODE_REQUEST) {
$this->prescan = false;
}
return $this->path . '/' . $key . '.cache';
} else {
// We are using a single subdirectory to achieve 1 level.
- $subdir = substr($key, 0, 3);
+ // We suffix the subdir so it does not clash with any windows
+ // reserved filenames like 'con'.
+ $subdir = substr($key, 0, 3) . '-cache';
$dir = $this->path . '/' . $subdir;
if ($create) {
// Create the directory. This function does it recursivily!
- make_writable_directory($dir);
+ make_writable_directory($dir, false);
}
return $dir . '/' . $key . '.cache';
}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle 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.
+//
+// Moodle 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 Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * PHPunit tests for the cache API and in particular things in locallib.php
+ *
+ * This file is part of Moodle's cache API, affectionately called MUC.
+ * It contains the components that are requried in order to use caching.
+ *
+ * @package core
+ * @category cache
+ * @copyright 2012 Sam Hemelryk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+// Include the necessary evils.
+global $CFG;
+require_once($CFG->dirroot.'/cache/locallib.php');
+require_once($CFG->dirroot.'/cache/tests/fixtures/lib.php');
+
+
+/**
+ * PHPunit tests for the cache API and in particular the cache_administration_helper
+ *
+ * @copyright 2012 Sam Hemelryk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_cache_administration_helper_testcase extends advanced_testcase {
+
+ /**
+ * Set things back to the default before each test.
+ */
+ public function setUp() {
+ parent::setUp();
+ cache_factory::reset();
+ cache_config_phpunittest::create_default_configuration();
+ }
+
+ /**
+ * Final task is to reset the cache system
+ */
+ public static function tearDownAfterClass() {
+ parent::tearDownAfterClass();
+ cache_factory::reset();
+ }
+
+ /**
+ * Test the numerous summaries the helper can produce.
+ */
+ public function test_get_summaries() {
+ // First the preparation.
+ $config = cache_config_writer::instance();
+ $this->assertTrue($config->add_store_instance('summariesstore', 'file'));
+ $config->set_definition_mappings('core/eventinvalidation', array('summariesstore'));
+ $this->assertTrue($config->set_mode_mappings(array(
+ cache_store::MODE_APPLICATION => array('summariesstore'),
+ cache_store::MODE_SESSION => array('default_session'),
+ cache_store::MODE_REQUEST => array('default_request'),
+ )));
+
+ $storesummaries = cache_administration_helper::get_store_instance_summaries();
+ $this->assertInternalType('array', $storesummaries);
+ $this->assertArrayHasKey('summariesstore', $storesummaries);
+ $summary = $storesummaries['summariesstore'];
+ // Check the keys
+ $this->assertArrayHasKey('name', $summary);
+ $this->assertArrayHasKey('plugin', $summary);
+ $this->assertArrayHasKey('default', $summary);
+ $this->assertArrayHasKey('isready', $summary);
+ $this->assertArrayHasKey('requirementsmet', $summary);
+ $this->assertArrayHasKey('mappings', $summary);
+ $this->assertArrayHasKey('modes', $summary);
+ $this->assertArrayHasKey('supports', $summary);
+ // Check the important/known values
+ $this->assertEquals('summariesstore', $summary['name']);
+ $this->assertEquals('file', $summary['plugin']);
+ $this->assertEquals(0, $summary['default']);
+ $this->assertEquals(1, $summary['isready']);
+ $this->assertEquals(1, $summary['requirementsmet']);
+ $this->assertEquals(1, $summary['mappings']);
+
+ $definitionsummaries = cache_administration_helper::get_definition_summaries();
+ $this->assertInternalType('array', $definitionsummaries);
+ $this->assertArrayHasKey('core/eventinvalidation', $definitionsummaries);
+ $summary = $definitionsummaries['core/eventinvalidation'];
+ // Check the keys
+ $this->assertArrayHasKey('id', $summary);
+ $this->assertArrayHasKey('name', $summary);
+ $this->assertArrayHasKey('mode', $summary);
+ $this->assertArrayHasKey('component', $summary);
+ $this->assertArrayHasKey('area', $summary);
+ $this->assertArrayHasKey('mappings', $summary);
+ // Check the important/known values
+ $this->assertEquals('core/eventinvalidation', $summary['id']);
+ $this->assertInstanceOf('lang_string', $summary['name']);
+ $this->assertEquals(cache_store::MODE_APPLICATION, $summary['mode']);
+ $this->assertEquals('core', $summary['component']);
+ $this->assertEquals('eventinvalidation', $summary['area']);
+ $this->assertInternalType('array', $summary['mappings']);
+ $this->assertContains('summariesstore', $summary['mappings']);
+
+ $pluginsummaries = cache_administration_helper::get_store_plugin_summaries();
+ $this->assertInternalType('array', $pluginsummaries);
+ $this->assertArrayHasKey('file', $pluginsummaries);
+ $summary = $pluginsummaries['file'];
+ // Check the keys
+ $this->assertArrayHasKey('name', $summary);
+ $this->assertArrayHasKey('requirementsmet', $summary);
+ $this->assertArrayHasKey('instances', $summary);
+ $this->assertArrayHasKey('modes', $summary);
+ $this->assertArrayHasKey('supports', $summary);
+ $this->assertArrayHasKey('canaddinstance', $summary);
+
+ $locksummaries = cache_administration_helper::get_lock_summaries();
+ $this->assertInternalType('array', $locksummaries);
+ $this->assertTrue(count($locksummaries) > 0);
+
+ $mappings = cache_administration_helper::get_default_mode_stores();
+ $this->assertInternalType('array', $mappings);
+ $this->assertCount(3, $mappings);
+ $this->assertArrayHasKey(cache_store::MODE_APPLICATION, $mappings);
+ $this->assertInternalType('array', $mappings[cache_store::MODE_APPLICATION]);
+ $this->assertContains('summariesstore', $mappings[cache_store::MODE_APPLICATION]);
+
+ $potentials = cache_administration_helper::get_definition_store_options('core', 'eventinvalidation');
+ $this->assertInternalType('array', $potentials); // Currently used, suitable, default
+ $this->assertCount(3, $potentials);
+ $this->assertArrayHasKey('summariesstore', $potentials[0]);
+ $this->assertArrayHasKey('summariesstore', $potentials[1]);
+ $this->assertArrayHasKey('default_application', $potentials[1]);
+ }
+
+ /**
+ * Test instantiating an add store form.
+ */
+ public function test_get_add_store_form() {
+ $form = cache_administration_helper::get_add_store_form('file');
+ $this->assertInstanceOf('moodleform', $form);
+
+ try {
+ $form = cache_administration_helper::get_add_store_form('somethingstupid');
+ $this->fail('You should not be able to create an add form for a store plugin that does not exist.');
+ } catch (moodle_exception $e) {
+ $this->assertInstanceOf('coding_exception', $e, 'Needs to be: ' .get_class($e)." ::: ".$e->getMessage());
+ }
+ }
+
+ /**
+ * Test instantiating a form to edit a store instance.
+ */
+ public function test_get_edit_store_form() {
+ $config = cache_config_writer::instance();
+ $this->assertTrue($config->add_store_instance('summariesstore', 'file'));
+
+ $form = cache_administration_helper::get_edit_store_form('file', 'summariesstore');
+ $this->assertInstanceOf('moodleform', $form);
+
+ try {
+ $form = cache_administration_helper::get_edit_store_form('somethingstupid', 'moron');
+ $this->fail('You should not be able to create an edit form for a store plugin that does not exist.');
+ } catch (moodle_exception $e) {
+ $this->assertInstanceOf('coding_exception', $e);
+ }
+
+ try {
+ $form = cache_administration_helper::get_edit_store_form('file', 'blisters');
+ $this->fail('You should not be able to create an edit form for a store plugin that does not exist.');
+ } catch (moodle_exception $e) {
+ $this->assertInstanceOf('coding_exception', $e);
+ }
+ }
+
+ /**
+ * Test the hash_key functionality.
+ */
+ public function test_hash_key() {
+ global $CFG;
+
+ $currentdebugging = $CFG->debug;
+
+ $CFG->debug = E_ALL;
+
+ // First with simplekeys
+ $instance = cache_config_phpunittest::instance(true);
+ $instance->phpunit_add_definition('phpunit/hashtest', array(
+ 'mode' => cache_store::MODE_APPLICATION,
+ 'component' => 'phpunit',
+ 'area' => 'hashtest',
+ 'simplekeys' => true
+ ));
+ $factory = cache_factory::instance();
+ $definition = $factory->create_definition('phpunit', 'hashtest');
+
+ $result = cache_helper::hash_key('test', $definition);
+ $this->assertEquals('test-'.$definition->generate_single_key_prefix(), $result);
+
+ try {
+ cache_helper::hash_key('test/test', $definition);
+ $this->fail('Invalid key was allowed, you should see this.');
+ } catch (coding_exception $e) {
+ $this->assertEquals('test/test', $e->debuginfo);
+ }
+
+ // Second without simple keys
+ $instance->phpunit_add_definition('phpunit/hashtest2', array(
+ 'mode' => cache_store::MODE_APPLICATION,
+ 'component' => 'phpunit',
+ 'area' => 'hashtest2',
+ 'simplekeys' => false
+ ));
+ $definition = $factory->create_definition('phpunit', 'hashtest2');
+
+ $result = cache_helper::hash_key('test', $definition);
+ $this->assertEquals(sha1($definition->generate_single_key_prefix().'-test'), $result);
+
+ $result = cache_helper::hash_key('test/test', $definition);
+ $this->assertEquals(sha1($definition->generate_single_key_prefix().'-test/test'), $result);
+
+ $CFG->debug = $currentdebugging;
+ }
+}
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class cache_phpunit_tests extends advanced_testcase {
+class core_cache_testcase extends advanced_testcase {
/**
* Set things back to the default before each test.
}
}
+ /**
+ * Tests for cache keys that would break on windows.
+ */
+ public function test_windows_nasty_keys() {
+ $instance = cache_config_phpunittest::instance();
+ $instance->phpunit_add_definition('phpunit/windowskeytest', array(
+ 'mode' => cache_store::MODE_APPLICATION,
+ 'component' => 'phpunit',
+ 'area' => 'windowskeytest',
+ 'simplekeys' => true,
+ 'simpledata' => true
+ ));
+ $cache = cache::make('phpunit', 'windowskeytest');
+ $this->assertTrue($cache->set('contest', 'test data 1'));
+ $this->assertEquals('test data 1', $cache->get('contest'));
+ }
+
/**
* Tests the default application cache
*/
* @param cache_loader $cache
*/
protected function run_on_cache(cache_loader $cache) {
- $key = 'testkey';
+ $key = 'contestkey';
$datascalars = array('test data', null);
- $dataarray = array('test' => 'data', 'part' => 'two');
+ $dataarray = array('contest' => 'data', 'part' => 'two');
$dataobject = (object)$dataarray;
foreach ($datascalars as $datascalar) {
// OK data added, data invalidated, and invalidation time has been set.
// Now we need to manually add back the data and adjust the invalidation time.
$hash = md5(cache_store::MODE_APPLICATION.'/phpunit/eventinvalidationtest/'.$CFG->wwwroot.'phpunit');
- $timefile = $CFG->dataroot."/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/las/lastinvalidation-$hash.cache";
+ $timefile = $CFG->dataroot."/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/las-cache/lastinvalidation-$hash.cache";
// Make sure the file is correct.
$this->assertTrue(file_exists($timefile));
$timecont = serialize(cache::now() - 60); // Back 60sec in the past to force it to re-invalidate.
file_put_contents($timefile, $timecont);
$this->assertTrue(file_exists($timefile));
- $datafile = $CFG->dataroot."/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/tes/testkey1-$hash.cache";
+ $datafile = $CFG->dataroot."/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/tes-cache/testkey1-$hash.cache";
$datacont = serialize("test data 1");
make_writable_directory(dirname($datafile));
file_put_contents($datafile, $datacont);
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class cache_config_writer_phpunit_tests extends advanced_testcase {
+class core_cache_config_writer_testcase extends advanced_testcase {
/**
* Set things back to the default before each test.
}
}
}
-
-/**
- * PHPunit tests for the cache API and in particular the cache_administration_helper
- *
- * @copyright 2012 Sam Hemelryk
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class cache_administration_helper_phpunit_tests extends advanced_testcase {
-
- /**
- * Set things back to the default before each test.
- */
- public function setUp() {
- parent::setUp();
- cache_factory::reset();
- cache_config_phpunittest::create_default_configuration();
- }
-
- /**
- * Final task is to reset the cache system
- */
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- cache_factory::reset();
- }
-
- /**
- * Test the numerous summaries the helper can produce.
- */
- public function test_get_summaries() {
- // First the preparation.
- $config = cache_config_writer::instance();
- $this->assertTrue($config->add_store_instance('summariesstore', 'file'));
- $config->set_definition_mappings('core/eventinvalidation', array('summariesstore'));
- $this->assertTrue($config->set_mode_mappings(array(
- cache_store::MODE_APPLICATION => array('summariesstore'),
- cache_store::MODE_SESSION => array('default_session'),
- cache_store::MODE_REQUEST => array('default_request'),
- )));
-
- $storesummaries = cache_administration_helper::get_store_instance_summaries();
- $this->assertInternalType('array', $storesummaries);
- $this->assertArrayHasKey('summariesstore', $storesummaries);
- $summary = $storesummaries['summariesstore'];
- // Check the keys
- $this->assertArrayHasKey('name', $summary);
- $this->assertArrayHasKey('plugin', $summary);
- $this->assertArrayHasKey('default', $summary);
- $this->assertArrayHasKey('isready', $summary);
- $this->assertArrayHasKey('requirementsmet', $summary);
- $this->assertArrayHasKey('mappings', $summary);
- $this->assertArrayHasKey('modes', $summary);
- $this->assertArrayHasKey('supports', $summary);
- // Check the important/known values
- $this->assertEquals('summariesstore', $summary['name']);
- $this->assertEquals('file', $summary['plugin']);
- $this->assertEquals(0, $summary['default']);
- $this->assertEquals(1, $summary['isready']);
- $this->assertEquals(1, $summary['requirementsmet']);
- $this->assertEquals(1, $summary['mappings']);
-
- $definitionsummaries = cache_administration_helper::get_definition_summaries();
- $this->assertInternalType('array', $definitionsummaries);
- $this->assertArrayHasKey('core/eventinvalidation', $definitionsummaries);
- $summary = $definitionsummaries['core/eventinvalidation'];
- // Check the keys
- $this->assertArrayHasKey('id', $summary);
- $this->assertArrayHasKey('name', $summary);
- $this->assertArrayHasKey('mode', $summary);
- $this->assertArrayHasKey('component', $summary);
- $this->assertArrayHasKey('area', $summary);
- $this->assertArrayHasKey('mappings', $summary);
- // Check the important/known values
- $this->assertEquals('core/eventinvalidation', $summary['id']);
- $this->assertInstanceOf('lang_string', $summary['name']);
- $this->assertEquals(cache_store::MODE_APPLICATION, $summary['mode']);
- $this->assertEquals('core', $summary['component']);
- $this->assertEquals('eventinvalidation', $summary['area']);
- $this->assertInternalType('array', $summary['mappings']);
- $this->assertContains('summariesstore', $summary['mappings']);
-
- $pluginsummaries = cache_administration_helper::get_store_plugin_summaries();
- $this->assertInternalType('array', $pluginsummaries);
- $this->assertArrayHasKey('file', $pluginsummaries);
- $summary = $pluginsummaries['file'];
- // Check the keys
- $this->assertArrayHasKey('name', $summary);
- $this->assertArrayHasKey('requirementsmet', $summary);
- $this->assertArrayHasKey('instances', $summary);
- $this->assertArrayHasKey('modes', $summary);
- $this->assertArrayHasKey('supports', $summary);
- $this->assertArrayHasKey('canaddinstance', $summary);
-
- $locksummaries = cache_administration_helper::get_lock_summaries();
- $this->assertInternalType('array', $locksummaries);
- $this->assertTrue(count($locksummaries) > 0);
-
- $mappings = cache_administration_helper::get_default_mode_stores();
- $this->assertInternalType('array', $mappings);
- $this->assertCount(3, $mappings);
- $this->assertArrayHasKey(cache_store::MODE_APPLICATION, $mappings);
- $this->assertInternalType('array', $mappings[cache_store::MODE_APPLICATION]);
- $this->assertContains('summariesstore', $mappings[cache_store::MODE_APPLICATION]);
-
- $potentials = cache_administration_helper::get_definition_store_options('core', 'eventinvalidation');
- $this->assertInternalType('array', $potentials); // Currently used, suitable, default
- $this->assertCount(3, $potentials);
- $this->assertArrayHasKey('summariesstore', $potentials[0]);
- $this->assertArrayHasKey('summariesstore', $potentials[1]);
- $this->assertArrayHasKey('default_application', $potentials[1]);
- }
-
- /**
- * Test instantiating an add store form.
- */
- public function test_get_add_store_form() {
- $form = cache_administration_helper::get_add_store_form('file');
- $this->assertInstanceOf('moodleform', $form);
-
- try {
- $form = cache_administration_helper::get_add_store_form('somethingstupid');
- $this->fail('You should not be able to create an add form for a store plugin that does not exist.');
- } catch (moodle_exception $e) {
- $this->assertInstanceOf('coding_exception', $e, 'Needs to be: ' .get_class($e)." ::: ".$e->getMessage());
- }
- }
-
- /**
- * Test instantiating a form to edit a store instance.
- */
- public function test_get_edit_store_form() {
- $config = cache_config_writer::instance();
- $this->assertTrue($config->add_store_instance('summariesstore', 'file'));
-
- $form = cache_administration_helper::get_edit_store_form('file', 'summariesstore');
- $this->assertInstanceOf('moodleform', $form);
-
- try {
- $form = cache_administration_helper::get_edit_store_form('somethingstupid', 'moron');
- $this->fail('You should not be able to create an edit form for a store plugin that does not exist.');
- } catch (moodle_exception $e) {
- $this->assertInstanceOf('coding_exception', $e);
- }
-
- try {
- $form = cache_administration_helper::get_edit_store_form('file', 'blisters');
- $this->fail('You should not be able to create an edit form for a store plugin that does not exist.');
- } catch (moodle_exception $e) {
- $this->assertInstanceOf('coding_exception', $e);
- }
- }
-
- /**
- * Test the hash_key functionality.
- */
- public function test_hash_key() {
- global $CFG;
-
- $currentdebugging = $CFG->debug;
-
- $CFG->debug = E_ALL;
-
- // First with simplekeys
- $instance = cache_config_phpunittest::instance(true);
- $instance->phpunit_add_definition('phpunit/hashtest', array(
- 'mode' => cache_store::MODE_APPLICATION,
- 'component' => 'phpunit',
- 'area' => 'hashtest',
- 'simplekeys' => true
- ));
- $factory = cache_factory::instance();
- $definition = $factory->create_definition('phpunit', 'hashtest');
-
- $result = cache_helper::hash_key('test', $definition);
- $this->assertEquals('test-'.$definition->generate_single_key_prefix(), $result);
-
- try {
- cache_helper::hash_key('test/test', $definition);
- $this->fail('Invalid key was allowed, you should see this.');
- } catch (coding_exception $e) {
- $this->assertEquals('test/test', $e->debuginfo);
- }
-
- // Second without simple keys
- $instance->phpunit_add_definition('phpunit/hashtest2', array(
- 'mode' => cache_store::MODE_APPLICATION,
- 'component' => 'phpunit',
- 'area' => 'hashtest2',
- 'simplekeys' => false
- ));
- $definition = $factory->create_definition('phpunit', 'hashtest2');
-
- $result = cache_helper::hash_key('test', $definition);
- $this->assertEquals(sha1($definition->generate_single_key_prefix().'-test'), $result);
-
- $result = cache_helper::hash_key('test/test', $definition);
- $this->assertEquals(sha1($definition->generate_single_key_prefix().'-test/test'), $result);
-
- $CFG->debug = $currentdebugging;
- }
-}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.5
*/
-class core_calendar_external_testcase extends externallib_advanced_testcase {
+class core_calendar_externallib_testcase extends externallib_advanced_testcase {
/**
* Tests set up
* @copyright 2012 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class cohort_testcase extends advanced_testcase {
+class core_cohort_cohortlib_testcase extends advanced_testcase {
public function test_cohort_add_cohort() {
global $DB;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
require_once($CFG->dirroot . '/cohort/externallib.php');
-class core_cohort_external_testcase extends externallib_advanced_testcase {
+class core_cohort_externallib_testcase extends externallib_advanced_testcase {
/**
* Test create_cohorts
}, this);
}
scope.toggle_textarea(false);
- CommentHelper.confirmoverlay = new Y.Overlay({
-bodyContent: '<div class="comment-delete-confirm"><a href="#" id="confirmdelete-'+this.client_id+'">'+M.str.moodle.yes+'</a> <a href="#" id="canceldelete-'+this.client_id+'">'+M.str.moodle.no+'</a></div>',
- visible: false
- });
- CommentHelper.confirmoverlay.render(document.body);
},
post: function() {
var ta = Y.one('#dlg-content-'+this.client_id);
dodelete: function(id) { // note: delete is a reserved word in javascript, chrome and safary do not like it at all here!
var scope = this;
var params = {'commentid': id};
- scope.cancel_delete();
function remove_dom(type, anim, cmt) {
cmt.remove();
}
if (commentid[1]) {
Y.Event.purgeElement('#'+theid, false, 'click');
}
- node.on('click', function(e, node) {
+ node.on('click', function(e) {
e.preventDefault();
- var width = CommentHelper.confirmoverlay.bodyNode.getStyle('width');
- var re = new RegExp("(\\d+).*", "i");
- var result = width.match(re);
- if (result[1]) {
- width = Number(result[1]);
- } else {
- width = 0;
+ if (commentid[1]) {
+ scope.dodelete(commentid[1]);
}
- //CommentHelper.confirmoverlay.set('xy', [e.pageX-(width/2), e.pageY]);
- CommentHelper.confirmoverlay.set('xy', [e.pageX-width-5, e.pageY]);
- CommentHelper.confirmoverlay.set('visible', true);
- Y.one('#canceldelete-'+scope.client_id).on('click', function(e) {
- e.preventDefault();
- scope.cancel_delete();
- });
- Y.Event.purgeElement('#confirmdelete-'+scope.client_id, false, 'click');
- Y.one('#confirmdelete-'+scope.client_id).on('click', function(e) {
- e.preventDefault();
- if (commentid[1]) {
- scope.dodelete(commentid[1]);
- }
- });
- }, scope, node);
+ });
+ // Also handle space/enter key.
+ node.on('key', function(e) {
+ e.preventDefault();
+ if (commentid[1]) {
+ scope.dodelete(commentid[1]);
+ }
+ }, '13,32');
+ // 13 and 32 are the keycodes for space and enter.
}
);
},
- cancel_delete: function() {
- CommentHelper.confirmoverlay.set('visible', false);
- },
register_pagination: function() {
var scope = this;
// page buttons
if (ta) {
//toggle_textarea.apply(ta, [false]);
//// reset textarea size
- ta.on('click', function() {
+ ta.on('focus', function() {
this.toggle_textarea(true);
}, this);
//ta.onkeypress = function() {
* @param sectionnumber the number of the selected course section
*/
add_editing: function(elementid) {
+ var node = Y.one('#' + elementid);
YUI().use('moodle-course-coursebase', function(Y) {
- M.course.coursebase.invoke_function('setup_for_resource', '#' + elementid);
+ M.course.register_new_module(node);
});
+ if (M.core.actionmenu && M.core.actionmenu.newDOMNode) {
+ M.core.actionmenu.newDOMNode(node);
+ }
}
};
$resp->content = $mod->get_content();
$resp->elementid = 'module-'.$mod->id;
$actions = course_get_cm_edit_actions($mod, 0, $mod->sectionnum);
- $resp->commands = ' '. $courserenderer->course_section_cm_edit_actions($actions);
+ $resp->commands = ' '. $courserenderer->course_section_cm_edit_actions($actions, $mod);
$resp->onclick = $mod->get_on_click();
$resp->visible = $mod->visible;
- // if using groupings, then display grouping name
+ // If using groupings, then display grouping name.
if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', $this->context)) {
$groupings = groups_get_all_groupings($this->course->id);
$resp->groupingname = format_string($groupings[$mod->groupingid]->name);
Overview of this plugin type at http://docs.moodle.org/dev/Course_formats
+=== 2.6 ===
+
+* core_course_renderer::course_section_cm_edit_actions has two new optional arguments and now uses and action_menu component.
+* core_course_renderer::course_section_cm has been altered to call core_course_renderer::course_section_cm_edit_actions with the two new arguments
+* An additional course renderer function has been created which allows you to
+ specify the wrapper for a course module within a section (e.g. the <li>). This can be
+ found in core_course_renderer::course_section_cm_list_item().
+
=== 2.5 ===
* Functions responsible for output in course/lib.php are deprecated, the code is moved to
$editcaps = array('moodle/course:manageactivities', 'moodle/course:activityvisibility', 'moodle/role:assign');
$dupecaps = array('moodle/backup:backuptargetimport', 'moodle/restore:restoretargetimport');
- // no permission to edit anything
+ // No permission to edit anything.
if (!has_any_capability($editcaps, $modcontext) and !has_all_capabilities($dupecaps, $coursecontext)) {
return array();
}
}
$actions = array();
- // AJAX edit title
+ // AJAX edit title.
if ($mod->has_view() && $hasmanageactivities &&
(($mod->course == $COURSE->id && course_ajax_enabled($COURSE)) ||
($mod->course == SITEID && course_ajax_enabled($SITE)))) {
// we will not display link if we are on some other-course page (where we should not see this module anyway)
- $actions['title'] = new action_link(
+ $actions['title'] = new action_menu_link_secondary(
new moodle_url($baseurl, array('update' => $mod->id)),
new pix_icon('t/editstring', $str->edittitle, 'moodle', array('class' => 'iconsmall visibleifjs', 'title' => '')),
- null,
- array('class' => 'editing_title', 'title' => $str->edittitle)
+ $str->edittitle,
+ array('class' => 'editing_title', 'data-action' => 'edittitle')
);
}
- // leftright
+ // Indent.
if ($hasmanageactivities) {
if (right_to_left()) { // Exchange arrows on RTL
$rightarrow = 't/left';
$leftarrow = 't/left';
}
+ $hiddenclass = 'hidden';
if ($indent > 0) {
- $actions['moveleft'] = new action_link(
- new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '-1')),
- new pix_icon($leftarrow, $str->moveleft, 'moodle', array('class' => 'iconsmall', 'title' => '')),
- null,
- array('class' => 'editing_moveleft', 'title' => $str->moveleft)
- );
+ $hiddenclass = '';
}
+ $actions['moveleft'] = new action_menu_link_secondary(
+ new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '-1')),
+ new pix_icon($leftarrow, $str->moveleft, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+ $str->moveleft,
+ array('class' => 'editing_moveleft ' . $hiddenclass, 'data-action' => 'moveleft')
+ );
+ $hiddenclass = 'hidden';
if ($indent >= 0) {
- $actions['moveright'] = new action_link(
- new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '1')),
- new pix_icon($rightarrow, $str->moveright, 'moodle', array('class' => 'iconsmall', 'title' => '')),
- null,
- array('class' => 'editing_moveright', 'title' => $str->moveright)
- );
+ $hiddenclass = '';
}
+ $actions['moveright'] = new action_menu_link_secondary(
+ new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '1')),
+ new pix_icon($rightarrow, $str->moveright, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+ $str->moveright,
+ array('class' => 'editing_moveright ' . $hiddenclass, 'data-action' => 'moveright')
+ );
}
- // move
+ // Move.
if ($hasmanageactivities) {
- $actions['move'] = new action_link(
+ $actions['move'] = new action_menu_link_primary(
new moodle_url($baseurl, array('copy' => $mod->id)),
new pix_icon('t/move', $str->move, 'moodle', array('class' => 'iconsmall', 'title' => '')),
- null,
- array('class' => 'editing_move', 'title' => $str->move)
+ $str->move,
+ array('class' => 'editing_move status', 'data-action' => 'move')
);
}
- // Update
+ // Update.
if ($hasmanageactivities) {
- $actions['update'] = new action_link(
+ $actions['update'] = new action_menu_link_secondary(
new moodle_url($baseurl, array('update' => $mod->id)),
new pix_icon('t/edit', $str->update, 'moodle', array('class' => 'iconsmall', 'title' => '')),
- null,
- array('class' => 'editing_update', 'title' => $str->update)
+ $str->update,
+ array('class' => 'editing_update', 'data-action' => 'update')
);
}
// Duplicate (require both target import caps to be able to duplicate and backup2 support, see modduplicate.php)
- // note that restoring on front page is never allowed
+ // Note that restoring on front page is never allowed.
if ($mod->course != SITEID && has_all_capabilities($dupecaps, $coursecontext) &&
plugin_supports('mod', $mod->modname, FEATURE_BACKUP_MOODLE2)) {
- $actions['duplicate'] = new action_link(
+ $actions['duplicate'] = new action_menu_link_secondary(
new moodle_url($baseurl, array('duplicate' => $mod->id)),
new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall', 'title' => '')),
- null,
- array('class' => 'editing_duplicate', 'title' => $str->duplicate)
+ $str->duplicate,
+ array('class' => 'editing_duplicate', 'data-action' => 'duplicate')
);
}
- // Delete
+ // Delete.
if ($hasmanageactivities) {
- $actions['delete'] = new action_link(
+ $actions['delete'] = new action_menu_link_secondary(
new moodle_url($baseurl, array('delete' => $mod->id)),
new pix_icon('t/delete', $str->delete, 'moodle', array('class' => 'iconsmall', 'title' => '')),
- null,
- array('class' => 'editing_delete', 'title' => $str->delete)
+ $str->delete,
+ array('class' => 'editing_delete', 'data-action' => 'delete')
);
}
- // hideshow
+ // Hide/Show.
if (has_capability('moodle/course:activityvisibility', $modcontext)) {
if ($mod->visible) {
- $actions['hide'] = new action_link(
+ $actions['hide'] = new action_menu_link_primary(
new moodle_url($baseurl, array('hide' => $mod->id)),
new pix_icon('t/hide', $str->hide, 'moodle', array('class' => 'iconsmall', 'title' => '')),
- null,
- array('class' => 'editing_hide', 'title' => $str->hide)
+ $str->hide,
+ array('class' => 'editing_hide', 'data-action' => 'hide')
);
} else {
- $actions['show'] = new action_link(
+ $actions['show'] = new action_menu_link_primary(
new moodle_url($baseurl, array('show' => $mod->id)),
new pix_icon('t/show', $str->show, 'moodle', array('class' => 'iconsmall', 'title' => '')),
- null,
- array('class' => 'editing_show', 'title' => $str->show)
+ $str->show,
+ array('class' => 'editing_show', 'data-action' => 'show')
);
}
}
- // groupmode
+ // Groupmode.
if ($hasmanageactivities and plugin_supports('mod', $mod->modname, FEATURE_GROUPS, 0)) {
if ($mod->coursegroupmodeforce) {
$modgroupmode = $mod->coursegroupmode;
$modgroupmode = $mod->groupmode;
}
if ($modgroupmode == SEPARATEGROUPS) {
- $groupmode = NOGROUPS;
+ $nextgroupmode = VISIBLEGROUPS;
$grouptitle = $str->groupsseparate;
$forcedgrouptitle = $str->forcedgroupsseparate;
$actionname = 'groupsseparate';
$groupimage = 't/groups';
} else if ($modgroupmode == VISIBLEGROUPS) {
- $groupmode = SEPARATEGROUPS;
+ $nextgroupmode = NOGROUPS;
$grouptitle = $str->groupsvisible;
$forcedgrouptitle = $str->forcedgroupsvisible;
$actionname = 'groupsvisible';
$groupimage = 't/groupv';
} else {
- $groupmode = VISIBLEGROUPS;
+ $nextgroupmode = SEPARATEGROUPS;
$grouptitle = $str->groupsnone;
$forcedgrouptitle = $str->forcedgroupsnone;
$actionname = 'groupsnone';
$groupimage = 't/groupn';
}
if (!$mod->coursegroupmodeforce) {
- $actions[$actionname] = new action_link(
- new moodle_url($baseurl, array('id' => $mod->id, 'groupmode' => $groupmode)),
+ $actions[$actionname] = new action_menu_link_primary(
+ new moodle_url($baseurl, array('id' => $mod->id, 'groupmode' => $nextgroupmode)),
new pix_icon($groupimage, $grouptitle, 'moodle', array('class' => 'iconsmall', 'title' => '')),
- null,
- array('class' => 'editing_'. $actionname, 'title' => $grouptitle)
+ $grouptitle,
+ array('class' => 'editing_'. $actionname, 'data-action' => $actionname, 'data-nextgroupmode' => $nextgroupmode)
);
} else {
- $actions[$actionname] = new pix_icon($groupimage, $forcedgrouptitle, 'moodle', array('title' => $forcedgrouptitle, 'class' => 'iconsmall'));
+ $actions[$actionname] = new pix_icon($groupimage, $forcedgrouptitle, 'moodle', array('title' => '', 'class' => 'iconsmall'));
}
}
- // Assign
+ // Assign.
if (has_capability('moodle/role:assign', $modcontext)){
- $actions['assign'] = new action_link(
+ $actions['assign'] = new action_menu_link_secondary(
new moodle_url('/admin/roles/assign.php', array('contextid' => $modcontext->id)),
new pix_icon('t/assignroles', $str->assign, 'moodle', array('class' => 'iconsmall', 'title' => '')),
- null,
- array('class' => 'editing_assign', 'title' => $str->assign)
+ $str->assign,
+ array('class' => 'editing_assign', 'data-action' => 'assignroles')
);
}
*
* @see course_get_cm_edit_actions()
*
- * @param array $actions array of action_link or pix_icon objects
+ * @param action_link[] $actions Array of action_link objects
+ * @param cm_info $mod The module we are displaying actions for.
+ * @param array $displayoptions additional display options:
+ * ownerselector => A JS/CSS selector that can be used to find an cm node.
+ * If specified the owning node will be given the class 'action-menu-shown' when the action
+ * menu is being displayed.
+ * constraintselector => A JS/CSS selector that can be used to find the parent node for which to constrain
+ * the action menu to when it is being displayed.
+ * donotenhance => If set to true the action menu that gets displayed won't be enhanced by JS.
* @return string
*/
- public function course_section_cm_edit_actions($actions) {
- $output = html_writer::start_tag('span', array('class' => 'commands'));
+ public function course_section_cm_edit_actions($actions, cm_info $mod = null, $displayoptions = array()) {
+ global $CFG;
+
+ if (empty($actions)) {
+ return '';
+ }
+
+ if (isset($displayoptions['ownerselector'])) {
+ $ownerselector = $displayoptions['ownerselector'];
+ } else if ($mod) {
+ $ownerselector = '#module-'.$mod->id;
+ } else {
+ debugging('You should upgrade your call to '.__FUNCTION__.' and provide $mod', DEBUG_DEVELOPER);
+ $ownerselector = 'li.activity';
+ }
+
+ if (isset($displayoptions['constraintselector'])) {
+ $constraint = $displayoptions['constraintselector'];
+ } else {
+ $constraint = '.course-content';
+ }
+
+ $menu = new action_menu();
+ $menu->set_owner_selector($ownerselector);
+ $menu->set_contraint($constraint);
+ $menu->set_alignment(action_menu::TL, action_menu::TR);
+ if (isset($CFG->modeditingmenu) && !$CFG->modeditingmenu || !empty($displayoptions['donotenhance'])) {
+ $menu->do_not_enhance();
+ }
foreach ($actions as $action) {
- if ($action instanceof renderable) {
- $output .= $this->output->render($action);
- } else {
- $output .= $action;
+ if ($action instanceof action_menu_link) {
+ $action->add_class('cm-edit-action');
}
+ $menu->add($action);
}
- $output .= html_writer::end_tag('span');
- return $output;
+ $menu->attributes['class'] .= ' section-cm-edit-actions commands';
+ return $this->render($menu);
}
/**
return $output;
}
$content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
- $conditionalhidden = $this->is_cm_conditionally_hidden($mod);
- $accessiblebutdim = !$mod->visible || $conditionalhidden;
+ if ($this->page->user_is_editing()) {
+ // In editing mode, when an item is conditionally hidden from some users
+ // we show it as greyed out.
+ $conditionalhidden = $this->is_cm_conditionally_hidden($mod);
+ $dim = !$mod->visible || $conditionalhidden;
+ } else {
+ // When not in editing mode, we only show item as hidden if it is
+ // actually not available to the user
+ $conditionalhidden = false;
+ $dim = !$mod->uservisible;
+ }
$textclasses = '';
$accesstext = '';
- if ($accessiblebutdim) {
+ if ($dim) {
$textclasses .= ' dimmed_text';
if ($conditionalhidden) {
$textclasses .= ' conditionalhidden';
return '';
}
+ /**
+ * Renders HTML to display one course module for display within a section.
+ *
+ * This function calls:
+ * {@link core_course_renderer::course_section_cm()}
+ *
+ * @param stdClass $course
+ * @param completion_info $completioninfo
+ * @param cm_info $mod
+ * @param int|null $sectionreturn
+ * @param array $displayoptions
+ * @return String
+ */
+ public function course_section_cm_list_item($course, &$completioninfo, cm_info $mod, $sectionreturn, $displayoptions = array()) {
+ $output = '';
+ if ($modulehtml = $this->course_section_cm($course, $completioninfo, $mod, $sectionreturn, $displayoptions)) {
+ $modclasses = 'activity ' . $mod->modname . ' modtype_' . $mod->modname . ' ' . $mod->get_extra_classes();
+ $output .= html_writer::tag('li', $modulehtml, array('class' => $modclasses, 'id' => 'module-' . $mod->id));
+ }
+ return $output;
+ }
+
/**
* Renders HTML to display one course module in a course section
*
if ($this->page->user_is_editing()) {
$editactions = course_get_cm_edit_actions($mod, $mod->indent, $sectionreturn);
- $output .= ' '. $this->course_section_cm_edit_actions($editactions);
+ $output .= ' '. $this->course_section_cm_edit_actions($editactions, $mod, $displayoptions);
$output .= $mod->get_after_edit_icons();
}
continue;
}
- if ($modulehtml = $this->course_section_cm($course,
+ if ($modulehtml = $this->course_section_cm_list_item($course,
$completioninfo, $mod, $sectionreturn, $displayoptions)) {
$moduleshtml[$modnumber] = $modulehtml;
}
}
}
+ $sectionoutput = '';
if (!empty($moduleshtml) || $ismoving) {
-
- $output .= html_writer::start_tag('ul', array('class' => 'section img-text'));
-
foreach ($moduleshtml as $modnumber => $modulehtml) {
if ($ismoving) {
$movingurl = new moodle_url('/course/mod.php', array('moveto' => $modnumber, 'sesskey' => sesskey()));
- $output .= html_writer::tag('li', html_writer::link($movingurl, $this->output->render($movingpix)),
+ $sectionoutput .= html_writer::tag('li', html_writer::link($movingurl, $this->output->render($movingpix)),
array('class' => 'movehere', 'title' => $strmovefull));
}
- $mod = $modinfo->cms[$modnumber];
- $modclasses = 'activity '. $mod->modname. ' modtype_'.$mod->modname. ' '. $mod->get_extra_classes();
- $output .= html_writer::start_tag('li', array('class' => $modclasses, 'id' => 'module-'. $mod->id));
- $output .= $modulehtml;
- $output .= html_writer::end_tag('li');
+ $sectionoutput .= $modulehtml;
}
if ($ismoving) {
$movingurl = new moodle_url('/course/mod.php', array('movetosection' => $section->id, 'sesskey' => sesskey()));
- $output .= html_writer::tag('li', html_writer::link($movingurl, $this->output->render($movingpix)),
+ $sectionoutput .= html_writer::tag('li', html_writer::link($movingurl, $this->output->render($movingpix)),
array('class' => 'movehere', 'title' => $strmovefull));
}
-
- $output .= html_writer::end_tag('ul'); // .section
}
+ // Always output the section module list.
+ $output .= html_writer::tag('ul', $sectionoutput, array('class' => 'section img-text'));
+
return $output;
}
When I indent right "Test glossary name" activity
Then "#section-1 li.glossary div.mod-indent-1" "css_element" should exists
And I indent right "Test glossary name" activity
- And "//li[@id='section-1']/descendant::li[contains(concat(' ', @class, ' '), ' glossary ')]/descendant::a[@title='Move left']" "xpath_element" should exists
+ And "//li[@id='section-1']/descendant::li[contains(concat(' ', @class, ' '), ' glossary ')]/descendant::a[not(contains(concat(' ', @class, ' '), ' hidden '))]/descendant::span[normalize-space(.)='Move left']" "xpath_element" should exists
And "#section-1 li.glossary div.mod-indent-2" "css_element" should exists
And I reload the page
And "#section-1 li.glossary div.mod-indent-2" "css_element" should exists
And I indent left "Test glossary name" activity
And "#section-1 li.glossary div.mod-indent-2" "css_element" should not exists
And "#section-1 li.glossary div.mod-indent-1" "css_element" should not exists
- And "//li[@id='section-1']/descendant::li[contains(concat(' ', @class, ' '), ' glossary ')]/descendant::a[@title='Move left']" "xpath_element" should not exists
+ And "//li[@id='section-1']/descendant::li[contains(concat(' ', @class, ' '), ' glossary ')]/descendant::a[not(contains(concat(' ', @class, ' '), ' hidden '))]/descendant::span[normalize-space(.)='Move left']" "xpath_element" should not exists
}
// Adding chr(10) to save changes.
+ $activity = $this->escape($activityname);
return array(
- new Given('I click on "' . get_string('edittitle') . '" "link" in the "' . $this->escape($activityname) .'" activity'),
+ new Given('I click on "' . get_string('actions', 'moodle') . '" "link" in the "' . $activity . '" activity'),
+ new Given('I click on "' . get_string('edittitle') . '" "link" in the "' . $activity .'" activity'),
new Given('I fill in "title" with "' . $this->escape($newactivityname) . chr(10) . '"'),
new Given('I wait "2" seconds')
);
*/
public function i_indent_right_activity($activityname) {
- $steps = array(
- new Given('I click on "' . get_string('moveright') . '" "link" in the "' . $this->escape($activityname) . '" activity')
- );
+ $steps = array();
+ $activity = $this->escape($activityname);
+ if ($this->running_javascript()) {
+ $steps[] = new Given('I click on "' . get_string('actions', 'moodle') . '" "link" in the "' . $activity . '" activity');
+ }
+ $steps[] = new Given('I click on "' . get_string('moveright') . '" "link" in the "' . $activity . '" activity');
if ($this->running_javascript()) {
$steps[] = new Given('I wait "2" seconds');
*/
public function i_indent_left_activity($activityname) {
- $steps = array(
- new Given('I click on "' . get_string('moveleft') . '" "link" in the "' . $this->escape($activityname) . '" activity')
- );
+ $steps = array();
+ $activity = $this->escape($activityname);
+ if ($this->running_javascript()) {
+ $steps[] = new Given('I click on "' . get_string('actions', 'moodle') . '" "link" in the "' . $activity . '" activity');
+ }
+ $steps[] = new Given('I click on "' . get_string('moveleft') . '" "link" in the "' . $activity . '" activity');
if ($this->running_javascript()) {
$steps[] = new Given('I wait "2" seconds');
* @param string $activityname
*/
public function i_duplicate_activity($activityname) {
- return array(
- new Given('I click on "' . get_string('duplicate') . '" "link" in the "' . $this->escape($activityname) . '" activity'),
- new Given('I press "' . get_string('continue') .'"'),
- new Given('I press "' . get_string('duplicatecontcourse') .'"')
- );
+ $steps = array();
+ $activity = $this->escape($activityname);
+ if ($this->running_javascript()) {
+ $steps[] = new Given('I click on "' . get_string('actions', 'moodle') . '" "link" in the "' . $activity . '" activity');
+ }
+ $steps[] = new Given('I click on "' . get_string('duplicate') . '" "link" in the "' . $activity . '" activity');
+ $steps[] = new Given('I press "' . get_string('continue') .'"');
+ $steps[] = new Given('I press "' . get_string('duplicatecontcourse') .'"');
+ return $steps;
}
/**
* @param TableNode $data
*/
public function i_duplicate_activity_editing_the_new_copy_with($activityname, TableNode $data) {
- return array(
- new Given('I click on "' . get_string('duplicate') . '" "link" in the "' . $this->escape($activityname) . '" activity'),
- new Given('I press "' . get_string('continue') .'"'),
- new Given('I press "' . get_string('duplicatecontedit') . '"'),
- new Given('I fill the moodle form with:', $data),
- new Given('I press "' . get_string('savechangesandreturntocourse') . '"')
- );
+ $steps = array();
+ $activity = $this->escape($activityname);
+ if ($this->running_javascript()) {
+ $steps[] = new Given('I click on "' . get_string('actions', 'moodle') . '" "link" in the "' . $activity . '" activity');
+ }
+ $steps[] = new Given('I click on "' . get_string('duplicate') . '" "link" in the "' . $activity . '" activity');
+ $steps[] = new Given('I press "' . get_string('continue') .'"');
+ $steps[] = new Given('I press "' . get_string('duplicatecontedit') . '"');
+ $steps[] = new Given('I fill the moodle form with:', $data);
+ $steps[] = new Given('I press "' . get_string('savechangesandreturntocourse') . '"');
+ return $steps;
}
/**
And I should see "Turn editing on"
And "Turn editing on" "button" should exists
And I turn editing mode on
+ And I click on "Actions" "link" in the ".block_recent_activity" "css_element"
And I click on "Delete Recent activity block" "link"
And I press "Yes"
And "#section-2" "css_element" <should_see_other_sections> exists
And "#section-2" "css_element" <should_see_other_sections> exists
And I indent left "Test forum name 1" activity
And "#section-2" "css_element" <should_see_other_sections> exists
+ And I click on "Actions" "link" in the "Test forum name 1" activity
And I click on "Update" "link" in the "Test forum name 1" activity
And I should see "Updating Forum"
And I should see "Display description on course page"
And I press "Save and return to course"
And "#section-2" "css_element" <should_see_other_sections> exists
+ And I click on "Actions" "link" in the "Test forum name 1" activity
And I click on "Hide" "link" in the "Test forum name 1" activity
And "#section-2" "css_element" <should_see_other_sections> exists
And I duplicate "Test forum name 2" activity editing the new copy with:
And I should see "Turn editing on"
And "Turn editing on" "button" should exists
And I turn editing mode on
+ And I click on "Actions" "link" in the ".block_recent_activity" "css_element"
And I click on "Delete Recent activity block" "link"
And I press "Yes"
And "#section-2" "css_element" <should_see_other_sections> exists
And "#section-2" "css_element" <should_see_other_sections> exists
And I indent left "Test forum name 1" activity
And "#section-2" "css_element" <should_see_other_sections> exists
+ And I click on "Actions" "link" in the "Test forum name 1" activity
And I click on "Update" "link" in the "Test forum name 1" activity
And I should see "Updating Forum"
And I should see "Display description on course page"
And I press "Save and return to course"
And "#section-2" "css_element" <should_see_other_sections> exists
+ And I click on "Actions" "link" in the "Test forum name 1" activity
And I click on "Hide" "link" in the "Test forum name 1" activity
And "#section-2" "css_element" <should_see_other_sections> exists
And I delete "Test forum name 1" activity
global $CFG;
require_once($CFG->dirroot.'/course/lib.php');
-class courselib_testcase extends advanced_testcase {
+class core_course_courselib_testcase extends advanced_testcase {
/**
* Set forum specific test values for calling create_module().
global $CFG;
require_once($CFG->dirroot.'/course/lib.php');
-class courserequest_testcase extends advanced_testcase {
+class core_course_courserequest_testcase extends advanced_testcase {
public function test_create_request() {
global $DB, $USER;
* @copyright 2012 Jerome Mouneyrac
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class core_course_external_testcase extends externallib_advanced_testcase {
+class core_course_externallib_testcase extends externallib_advanced_testcase {
/**
* Tests set up
var CSS = {
ACTIVITY : 'activity',
- COMMANDSPAN : 'span.commands',
+ COMMANDSPAN : '.commands',
CONTENT : 'content',
COURSECONTENT : 'course-content',
EDITINGMOVE : 'editing_move',
YUI.add('moodle-course-toolboxes', function(Y) {
- WAITICON = {'pix':"i/loading_small",'component':'moodle'};
- // The CSS selectors we use
+
+ // The following properties contain common strings.
+ // We separate them out here because when this JS is minified the content is less as
+ // Variables get compacted to single/double characters and the full length of the string
+ // exists only once.
+
+ // The CSS classes we use.
var CSS = {
- ACTIVITYLI : 'li.activity',
- COMMANDSPAN : 'span.commands',
- SPINNERCOMMANDSPAN : 'span.commands',
- CONTENTAFTERLINK : 'div.contentafterlink',
- DELETE : 'a.editing_delete',
+ ACTIVITYINSTANCE : 'activityinstance',
+ AVAILABILITYINFODIV : 'div.availabilityinfo',
+ CONDITIONALHIDDEN : 'conditionalhidden',
DIMCLASS : 'dimmed',
DIMMEDTEXT : 'dimmed_text',
- EDITTITLE : 'a.editing_title',
- EDITTITLECLASS : 'edittitle',
- GENERICICONCLASS : 'iconsmall',
- GROUPSNONE : 'a.editing_groupsnone',
- GROUPSSEPARATE : 'a.editing_groupsseparate',
- GROUPSVISIBLE : 'a.editing_groupsvisible',
- HIDE : 'a.editing_hide',
- HIGHLIGHT : 'a.editing_highlight',
- INSTANCENAME : 'span.instancename',
- LIGHTBOX : 'lightbox',
+ EDITINSTRUCTIONS : 'editinstructions',
+ HIDE : 'hide',
MODINDENTCOUNT : 'mod-indent-',
- MODINDENTDIV : 'div.mod-indent',
MODINDENTHUGE : 'mod-indent-huge',
MODULEIDPREFIX : 'module-',
- MOVELEFT : 'a.editing_moveleft',
- MOVELEFTCLASS : 'editing_moveleft',
- MOVERIGHT : 'a.editing_moveright',
- PAGECONTENT : 'div#page-content',
- RIGHTSIDE : '.right',
SECTIONHIDDENCLASS : 'hidden',
SECTIONIDPREFIX : 'section-',
+ SHOW : 'editing_show',
+ TITLEEDITOR : 'titleeditor'
+ },
+ // The CSS selectors we use.
+ SELECTOR = {
+ ACTIONLINKTEXT : '.actionlinktext',
+ ACTIVITYACTION : 'a.cm-edit-action[data-action]',
+ ACTIVITYFORM : 'form.'+CSS.ACTIVITYINSTANCE,
+ ACTIVITYICON : 'img.activityicon',
+ ACTIVITYLI : 'li.activity',
+ ACTIVITYTITLE : 'input[name=title]',
+ COMMANDSPAN : '.commands',
+ CONTENTAFTERLINK : 'div.contentafterlink',
+ HIDE : 'a.editing_hide',
+ HIGHLIGHT : 'a.editing_highlight',
+ INSTANCENAME : 'span.instancename',
+ MODINDENTDIV : 'div.mod-indent',
+ PAGECONTENT : 'div#page-content',
SECTIONLI : 'li.section',
- SHOW : 'a.editing_show',
- SHOWHIDE : 'a.editing_showhide',
- CONDITIONALHIDDEN : 'conditionalhidden',
- AVAILABILITYINFODIV : 'div.availabilityinfo',
- SHOWCLASS : 'editing_show',
- HIDECLASS : 'hide'
- };
+ SHOW : 'a.'+CSS.SHOW,
+ SHOWHIDE : 'a.editing_showhide'
+ },
+ BODY = Y.one(document.body);
/**
* The toolbox classes
}
Y.extend(TOOLBOX, Y.Base, {
- /**
- * Toggle the visibility and availability for the specified
- * resource show/hide button
- */
- toggle_hide_resource_ui : function(button) {
- var element = button.ancestor(CSS.ACTIVITYLI);
- var hideicon = button.one('img');
-
- var dimarea;
- var toggle_class;
- if (this.get_instance_name(element) == null) {
- toggle_class = CSS.DIMMEDTEXT;
- dimarea = element.all(CSS.MODINDENTDIV + ' > div').item(1);
- } else {
- toggle_class = CSS.DIMCLASS;
- dimarea = element.one('a');
- }
-
- var status = '';
- var value;
- if (button.hasClass(CSS.SHOWCLASS)) {
- status = 'hide';
- value = 1;
- } else {
- status = 'show';
- value = 0;
- }
- // Update button info.
- var newstring = M.util.get_string(status, 'moodle');
- hideicon.setAttrs({
- 'alt' : newstring,
- 'src' : M.util.image_url('t/' + status)
- });
- button.set('title', newstring);
- button.set('className', 'editing_'+status);
-
- // If activity is conditionally hidden, then don't toggle.
- if (!dimarea.hasClass(CSS.CONDITIONALHIDDEN)) {
- // Change the UI.
- dimarea.toggleClass(toggle_class);
- // We need to toggle dimming on the description too.
- element.all(CSS.CONTENTAFTERLINK).toggleClass(CSS.DIMMEDTEXT);
- }
- // Toggle availablity info for conditional activities.
- var availabilityinfo = element.one(CSS.AVAILABILITYINFODIV);
-
- if (availabilityinfo) {
- availabilityinfo.toggleClass(CSS.HIDECLASS);
- }
- return value;
- },
/**
* Send a request using the REST API
*
* @return string|null Instance name
*/
get_instance_name : function(target) {
- if (target.one(CSS.INSTANCENAME)) {
- return target.one(CSS.INSTANCENAME).get('firstChild').get('data');
+ if (target.one(SELECTOR.INSTANCENAME)) {
+ return target.one(SELECTOR.INSTANCENAME).get('firstChild').get('data');
}
return null;
},
}
);
-
+ /**
+ * Resource and activity toolbox class.
+ *
+ * This class is responsible for managing AJAX interactions with activities and resources
+ * when viewing a course in editing mode.
+ *
+ * @namespace M.course.toolbox
+ * @class ResourceToolbox
+ * @constructor
+ */
var RESOURCETOOLBOX = function() {
RESOURCETOOLBOX.superclass.constructor.apply(this, arguments);
}
Y.extend(RESOURCETOOLBOX, TOOLBOX, {
- // Variables
+ /**
+ * No groups are being used.
+ * @static
+ * @const GROUPS_NONE
+ * @type Number
+ */
GROUPS_NONE : 0,
+ /**
+ * Separate groups are being used.
+ * @static
+ * @const GROUPS_SEPARATE
+ * @type Number
+ */
GROUPS_SEPARATE : 1,
+ /**
+ * Visible groups are being used.
+ * @static
+ * @const GROUPS_VISIBLE
+ * @type Number
+ */
GROUPS_VISIBLE : 2,
+ /**
+ * Events that were added when editing a title.
+ * These should all be detached when editing is complete.
+ * @property edittitleevents
+ * @type {Event[]}
+ * @protected
+ */
+ edittitleevents : [],
+
/**
* Initialize the resource toolbox
*
- * Updates all span.commands with relevant handlers and other required changes
+ * For each activity the commands are updated and a reference to the activity is attached.
+ * This way it doesn't matter where the commands are going to called from they have a reference to the
+ * activity that they relate to.
+ * This is essential as some of the actions are displayed in an actionmenu which removes them from the
+ * page flow.
+ *
+ * This function also creates a single event delegate to manage all AJAX actions for all activities on
+ * the page.
+ *
+ * @method initializer
*/
initializer : function(config) {
- this.setup_for_resource();
M.course.coursebase.register_module(this);
-
- var prefix = CSS.ACTIVITYLI + ' ' + CSS.COMMANDSPAN + ' ';
- Y.delegate('click', this.edit_resource_title, CSS.PAGECONTENT, prefix + CSS.EDITTITLE, this);
- Y.delegate('click', this.move_left, CSS.PAGECONTENT, prefix + CSS.MOVELEFT, this);
- Y.delegate('click', this.move_right, CSS.PAGECONTENT, prefix + CSS.MOVERIGHT, this);
- Y.delegate('click', this.delete_resource, CSS.PAGECONTENT, prefix + CSS.DELETE, this);
- Y.delegate('click', this.toggle_hide_resource, CSS.PAGECONTENT, prefix + CSS.HIDE, this);
- Y.delegate('click', this.toggle_hide_resource, CSS.PAGECONTENT, prefix + CSS.SHOW, this);
- Y.delegate('click', this.toggle_groupmode, CSS.PAGECONTENT, prefix + CSS.GROUPSNONE, this);
- Y.delegate('click', this.toggle_groupmode, CSS.PAGECONTENT, prefix + CSS.GROUPSSEPARATE, this);
- Y.delegate('click', this.toggle_groupmode, CSS.PAGECONTENT, prefix + CSS.GROUPSVISIBLE, this);
+ Y.all(SELECTOR.ACTIVITYLI).each(function(activity){
+ activity.setData('toolbox', this);
+ activity.all(SELECTOR.COMMANDSPAN+ ' ' + SELECTOR.ACTIVITYACTION).each(function(){
+ this.setData('activity', activity);
+ });
+ }, this);
+ Y.delegate('click', this.handle_data_action, BODY, SELECTOR.ACTIVITYACTION, this);
},
/**
- * Update any span.commands within the scope of the specified
- * selector with AJAX equivelants
+ * Handles the delegation event. When this is fired someone has triggered an action.
*
- * @param baseselector The selector to limit scope to
- * @return void
+ * Note not all actions will result in an AJAX enhancement.
+ *
+ * @protected
+ * @method handle_data_action
+ * @param {EventFacade} ev The event that was triggered.
+ * @returns {boolean}
*/
- setup_for_resource : function(baseselector) {
- if (!baseselector) {
- var baseselector = CSS.PAGECONTENT + ' ' + CSS.ACTIVITYLI;
+ handle_data_action : function(ev) {
+ // We need to get the anchor element that triggered this event.
+ var node = ev.target;
+ if (!node.test('a')) {
+ node = node.ancestor(SELECTOR.ACTIVITYACTION);
}
- Y.all(baseselector).each(this._setup_for_resource, this);
- },
- _setup_for_resource : function(toolboxtarget) {
- toolboxtarget = Y.one(toolboxtarget);
-
- // Set groupmode attribute for use by this.toggle_groupmode()
- var groups;
- groups = toolboxtarget.all(CSS.COMMANDSPAN + ' ' + CSS.GROUPSNONE);
- groups.setAttribute('groupmode', this.GROUPS_NONE);
-
- groups = toolboxtarget.all(CSS.COMMANDSPAN + ' ' + CSS.GROUPSSEPARATE);
- groups.setAttribute('groupmode', this.GROUPS_SEPARATE);
+ // From the anchor we can get both the activity (added during initialisation) and the action being
+ // performed (added by the UI as a data attribute).
+ var action = node.getData('action'),
+ activity = node.getData('activity');
+ if (!node.test('a') || !action || !activity) {
+ // It wasn't a valid action node.
+ return;
+ }
- groups = toolboxtarget.all(CSS.COMMANDSPAN + ' ' + CSS.GROUPSVISIBLE);
- groups.setAttribute('groupmode', this.GROUPS_VISIBLE);
- },
- move_left : function(e) {
- this.move_leftright(e, -1);
- },
- move_right : function(e) {
- this.move_leftright(e, 1);
+ // Switch based upon the action and do the desired thing.
+ switch (action) {
+ case 'edittitle' :
+ // The user wishes to edit the title of the event.
+ this.edit_title(ev, node, activity, action);
+ break;
+ case 'moveleft' :
+ case 'moveright' :
+ // The user changing the indent of the activity.
+ this.change_indent(ev, node, activity, action);
+ break;
+ case 'delete' :
+ // The user is deleting the activity.
+ this.delete_with_confirmation(ev, node, activity, action);
+ break;
+ case 'hide' :
+ case 'show' :
+ // The user is changing the visibility of the activity.
+ this.change_visibility(ev, node, activity, action);
+ break;
+ case 'groupsseparate' :
+ case 'groupsvisible' :
+ case 'groupsnone' :
+ // The user is changing the group mode.
+ callback = 'change_groupmode';
+ this.change_groupmode(ev, node, activity, action);
+ break;
+ case 'move' :
+ case 'update' :
+ case 'duplicate' :
+ case 'assignroles' :
+ default:
+ // Nothing to do here!
+ break;
+ }
},
- move_leftright : function(e, direction) {
+
+ /**
+ * Change the indent of the activity or resource.
+ *
+ * @protected
+ * @method change_indent
+ * @param {EventFacade} ev The event that was fired.
+ * @param {Node} button The button that triggered this action.
+ * @param {Node} activity The activity node that this action will be performed on.
+ * @param {String} action The action that has been requested. Will be 'moveleft' or 'moveright'.
+ */
+ change_indent : function(ev, button, activity, action) {
// Prevent the default button action
- e.preventDefault();
+ ev.preventDefault();
- // Get the element we're working on
- var element = e.target.ancestor(CSS.ACTIVITYLI);
+ var direction = (action === 'moveleft') ? -1 : 1;
// And we need to determine the current and new indent level
- var indentdiv = element.one(CSS.MODINDENTDIV);
+ var indentdiv = activity.one(SELECTOR.MODINDENTDIV);
var indent = indentdiv.getAttribute('class').match(/mod-indent-(\d{1,})/);
if (indent) {
'class' : 'resource',
'field' : 'indent',
'value' : newindent,
- 'id' : this.get_element_id(element)
+ 'id' : this.get_element_id(activity)
};
- var spinner = M.util.add_spinner(Y, element.one(CSS.SPINNERCOMMANDSPAN));
+ var commands = activity.one(SELECTOR.COMMANDSPAN);
+ var spinner = M.util.add_spinner(Y, commands).setStyles({
+ position: 'absolute',
+ top: 0
+ });
+ if (BODY.hasClass('dir-ltr')) {
+ spinner.setStyle('left', '100%');
+ } else {
+ spinner.setStyle('right', '100%');
+ }
this.send_request(data, spinner);
- // Handle removal/addition of the moveleft button
+ // Handle removal/addition of the moveleft button.
if (newindent == 0) {
- element.one(CSS.MOVELEFT).remove();
+ button.addClass('hidden');
} else if (newindent == 1 && oldindent == 0) {
- this.add_moveleft(element);
+ button.ancestor('.menu').one('[data-action=moveleft]').removeClass('hidden');
}
// Handle massive indentation to match non-ajax display
indentdiv.removeClass(CSS.MODINDENTHUGE);
}
},
- delete_resource : function(e) {
+
+ /**
+ * Deletes the given activity or resource after confirmation.
+ *
+ * @protected
+ * @method delete_with_confirmation
+ * @param {EventFacade} ev The event that was fired.
+ * @param {Node} button The button that triggered this action.
+ * @param {Node} activity The activity node that this action will be performed on.
+ * @return Boolean
+ */
+ delete_with_confirmation : function(ev, button, activity) {
// Prevent the default button action
- e.preventDefault();
+ ev.preventDefault();
// Get the element we're working on
- var element = e.target.ancestor(CSS.ACTIVITYLI);
+ var element = activity
// Create confirm string (different if element has or does not have name)
var confirmstring = '';
'id' : this.get_element_id(element)
};
this.send_request(data);
+ if (M.core.actionmenu && M.core.actionmenu.instance) {
+ M.core.actionmenu.instance.hideMenu();
+ }
},
- toggle_hide_resource : function(e) {
+
+ /**
+ * Changes the visibility of this activity or resource.
+ *
+ * @protected
+ * @method change_visibility
+ * @param {EventFacade} ev The event that was fired.
+ * @param {Node} button The button that triggered this action.
+ * @param {Node} activity The activity node that this action will be performed on.
+ * @param {String} action The action that has been requested.
+ * @return Boolean
+ */
+ change_visibility : function(ev, button, activity, action) {
// Prevent the default button action
- e.preventDefault();
+ ev.preventDefault();
// Return early if the current section is hidden
- var section = e.target.ancestor(M.course.format.get_section_selector(Y));
+ var section = activity.ancestor(M.course.format.get_section_selector(Y));
if (section && section.hasClass(CSS.SECTIONHIDDENCLASS)) {
return;
}
// Get the element we're working on
- var element = e.target.ancestor(CSS.ACTIVITYLI);
-
- var button = e.target.ancestor('a', true);
-
- var value = this.toggle_hide_resource_ui(button);
+ var element = activity;
+ var value = this.handle_resource_dim(button, activity, action);
// Send the request
var data = {
'value' : value,
'id' : this.get_element_id(element)
};
- var spinner = M.util.add_spinner(Y, element.one(CSS.SPINNERCOMMANDSPAN));
+ var spinner = M.util.add_spinner(Y, element.one(SELECTOR.COMMANDSPAN));
this.send_request(data, spinner);
return false; // Need to return false to stop the delegate for the new state firing
},
- toggle_groupmode : function(e) {
- // Prevent the default button action
- e.preventDefault();
- // Get the element we're working on
- var element = e.target.ancestor(CSS.ACTIVITYLI);
+ /**
+ * Handles the UI aspect of dimming the activity or resource.
+ *
+ * @protected
+ * @method handle_resource_dim
+ * @param {Node} button The button that triggered the action.
+ * @param {Node} activity The activity node that this action will be performed on.
+ * @param {String} status Whether the activity was shown or hidden.
+ * @returns {number} 1 if we were changing to visible, 0 if we were hiding.
+ */
+ handle_resource_dim : function(button, activity, status) {
+ var toggleclass = CSS.DIMCLASS,
+ dimarea = activity.one('a'),
+ availabilityinfo = activity.one(CSS.AVAILABILITYINFODIV),
+ newstatus = (status === 'hide') ? 'show' : 'hide',
+ newstring = M.util.get_string(newstatus, 'moodle');
- var button = e.target.ancestor('a', true);
- var icon = button.one('img');
+ // Update button info.
+ button.one('img').setAttrs({
+ 'alt' : newstring,
+ 'src' : M.util.image_url('t/' + newstatus)
+ });
+ button.set('title', newstring);
+ button.replaceClass('editing_'+status, 'editing_'+newstatus)
+ button.setData('action', newstatus);
+
+ // If activity is conditionally hidden, then don't toggle.
+ if (this.get_instance_name(activity) == null) {
+ toggleclass = CSS.DIMMEDTEXT;
+ dimarea = activity.all(SELECTOR.MODINDENTDIV + ' > div').item(1);
+ }
+ if (!dimarea.hasClass(CSS.CONDITIONALHIDDEN)) {
+ // Change the UI.
+ dimarea.toggleClass(toggleclass);
+ // We need to toggle dimming on the description too.
+ activity.all(SELECTOR.CONTENTAFTERLINK).toggleClass(CSS.DIMMEDTEXT);
+ }
+ // Toggle availablity info for conditional activities.
+ if (availabilityinfo) {
+ availabilityinfo.toggleClass(CSS.HIDE);
+ }
+ return (status === 'hide') ? 0 : 1;
+ },
+
+ /**
+ * Changes the groupmode of the activity to the next groupmode in the sequence.
+ *
+ * @protected
+ * @method change_groupmode
+ * @param {EventFacade} ev The event that was fired.
+ * @param {Node} button The button that triggered this action.
+ * @param {Node} activity The activity node that this action will be performed on.
+ * @param {String} action The action that has been requested.
+ * @return Boolean
+ */
+ change_groupmode : function(ev, button, activity, action) {
+ // Prevent the default button action.
+ ev.preventDefault();
// Current Mode
- var groupmode = button.getAttribute('groupmode');
- groupmode++;
- if (groupmode > 2) {
- groupmode = 0;
- }
- button.setAttribute('groupmode', groupmode);
-
- var newtitle = '';
- var iconsrc = '';
- switch (groupmode) {
- case this.GROUPS_NONE:
- newtitle = 'groupsnone';
- iconsrc = M.util.image_url('t/groupn');
- break;
- case this.GROUPS_SEPARATE:
- newtitle = 'groupsseparate';
- iconsrc = M.util.image_url('t/groups');
- break;
- case this.GROUPS_VISIBLE:
- newtitle = 'groupsvisible';
- iconsrc = M.util.image_url('t/groupv');
- break;
+ var groupmode = parseInt(button.getData('nextgroupmode'), 10),
+ newtitle = '',
+ iconsrc = '',
+ newtitlestr,
+ data,
+ spinner,
+ nextgroupmode = groupmode + 1;
+
+ if (nextgroupmode > 2) {
+ nextgroupmode = 0;
}
- newtitle = M.util.get_string('clicktochangeinbrackets', 'moodle',
- M.util.get_string(newtitle, 'moodle'));
+
+ if (groupmode === this.GROUPS_NONE) {
+ newtitle = 'groupsnone';
+ iconsrc = M.util.image_url('t/groupn', 'moodle');
+ } else if (groupmode === this.GROUPS_SEPARATE) {
+ newtitle = 'groupsseparate';
+ iconsrc = M.util.image_url('t/groups', 'moodle');
+ } else if (groupmode === this.GROUPS_VISIBLE) {
+ newtitle = 'groupsvisible';
+ iconsrc = M.util.image_url('t/groupv', 'moodle');
+ }
+ newtitlestr = M.util.get_string(newtitle, 'moodle'),
+ newtitlestr = M.util.get_string('clicktochangeinbrackets', 'moodle', newtitlestr);
// Change the UI
- icon.setAttrs({
- 'alt' : newtitle,
+ button.one('img').setAttrs({
+ 'alt' : newtitlestr,
'src' : iconsrc
});
- button.setAttribute('title', newtitle);
+ button.setAttribute('title', newtitlestr).setData('action', newtitle).setData('nextgroupmode', nextgroupmode);
// And send the request
- var data = {
+ data = {
'class' : 'resource',
'field' : 'groupmode',
'value' : groupmode,
- 'id' : this.get_element_id(element)
+ 'id' : this.get_element_id(activity)
};
- var spinner = M.util.add_spinner(Y, element.one(CSS.SPINNERCOMMANDSPAN));
+
+ spinner = M.util.add_spinner(Y, activity.one(SELECTOR.COMMANDSPAN));
this.send_request(data, spinner);
return false; // Need to return false to stop the delegate for the new state firing
},
- /**
- * Add the moveleft button
- * This is required after moving left from an initial position of 0
- *
- * @param target The encapsulating <li> element
- */
- add_moveleft : function(target) {
- var left_string = M.util.get_string('moveleft', 'moodle');
- var moveimage = 't/left'; // ltr mode
- if ( Y.one(document.body).hasClass('dir-rtl') ) {
- moveimage = 't/right';
- } else {
- moveimage = 't/left';
- }
- var newicon = Y.Node.create('<img />')
- .addClass(CSS.GENERICICONCLASS)
- .setAttrs({
- 'src' : M.util.image_url(moveimage, 'moodle'),
- 'alt' : left_string
- });
- var moveright = target.one(CSS.MOVERIGHT);
- var newlink = moveright.getAttribute('href').replace('indent=1', 'indent=-1');
- var anchor = new Y.Node.create('<a />')
- .setStyle('cursor', 'pointer')
- .addClass(CSS.MOVELEFTCLASS)
- .setAttribute('href', newlink)
- .setAttribute('title', left_string);
- anchor.appendChild(newicon);
- moveright.insert(anchor, 'before');
- },
+
/**
* Edit the title for the resource
+ *
+ * @protected
+ * @method edit_title
+ * @param {EventFacade} ev The event that was fired.
+ * @param {Node} button The button that triggered this action.
+ * @param {Node} activity The activity node that this action will be performed on.
+ * @param {String} action The action that has been requested.
+ * @return Boolean
*/
- edit_resource_title : function(e) {
+ edit_title : function(ev, button, activity) {
// Get the element we're working on
- var element = e.target.ancestor(CSS.ACTIVITYLI);
- var elementdiv = element.one('div');
- var instancename = element.one(CSS.INSTANCENAME);
- var currenttitle = instancename.get('firstChild');
- var oldtitle = currenttitle.get('data');
- var titletext = oldtitle;
- var editbutton = element.one('a.' + CSS.EDITTITLECLASS + ' img');
-
- // Handle events for edit_resource_title
- var listenevents = [];
- var thisevent;
-
- // Grab the anchor so that we can swap it with the edit form
- var anchor = instancename.ancestor('a');
+ var activityid = this.get_element_id(activity),
+ instancename = activity.one(SELECTOR.INSTANCENAME),
+ currenttitle = instancename.get('firstChild'),
+ oldtitle = currenttitle.get('data'),
+ titletext = oldtitle,
+ thisevent,
+ anchor = instancename.ancestor('a'),// Grab the anchor so that we can swap it with the edit form.
+ data = {
+ 'class' : 'resource',
+ 'field' : 'gettitle',
+ 'id' : activityid
+ },
+ response = this.send_request(data);
- var data = {
- 'class' : 'resource',
- 'field' : 'gettitle',
- 'id' : this.get_element_id(element)
- };
+ if (M.core.actionmenu && M.core.actionmenu.instance) {
+ M.core.actionmenu.instance.hideMenu();
+ }
// Try to retrieve the existing string from the server
- var response = this.send_request(data, editbutton);
if (response.instancename) {
titletext = response.instancename;
}
// Create the editor and submit button
- var editor = Y.Node.create('<input />')
- .setAttrs({
- 'name' : 'title',
- 'value' : titletext,
- 'autocomplete' : 'off',
- 'aria-describedby' : 'id_editinstructions',
- 'maxLength' : '255'
- })
- .addClass('titleeditor');
- var editform = Y.Node.create('<form />')
- .addClass('activityinstance')
- .setAttribute('action', '#');
- var editinstructions = Y.Node.create('<span />')
- .addClass('editinstructions')
- .setAttrs({'id' : 'id_editinstructions'})
+ var editform = Y.Node.create('<form class="'+CSS.ACTIVITYINSTANCE+'" action="#" />');
+ var editinstructions = Y.Node.create('<span class="'+CSS.EDITINSTRUCTIONS+'" id="id_editinstructions" />')
.set('innerHTML', M.util.get_string('edittitleinstructions', 'moodle'));
- var activityicon = element.one('img.activityicon').cloneNode();
+ var editor = Y.Node.create('<input name="title" type="text" class="'+CSS.TITLEEDITOR+'" />').setAttrs({
+ 'value' : titletext,
+ 'autocomplete' : 'off',
+ 'aria-describedby' : 'id_editinstructions',
+ 'maxLength' : '255'
+ })
// Clear the existing content and put the editor in
- currenttitle.set('data', '');
- editform.appendChild(activityicon);
+ editform.appendChild(activity.one(SELECTOR.ACTIVITYICON).cloneNode());
editform.appendChild(editor);
+ editform.setData('anchor', anchor);
anchor.replace(editform);
- elementdiv.appendChild(editinstructions);
- e.preventDefault();
+ activity.one('div').appendChild(editinstructions);
+ ev.preventDefault();
// Focus and select the editor text
editor.focus().select();
- // Handle removal of the editor
- var clear_edittitle = function() {
- // Detach all listen events to prevent duplicate triggers
- var thisevent;
- while (thisevent = listenevents.shift()) {
- thisevent.detach();
- }
+ // Cancel the edit if we lose focus or the escape key is pressed.
+ thisevent = editor.on('blur', this.edit_title_cancel, this, activity, false);
+ this.edittitleevents.push(thisevent);
+ thisevent = editor.on('key', this.edit_title_cancel, 'esc', this, activity, true);
+ this.edittitleevents.push(thisevent);
- if (editinstructions) {
- // Convert back to anchor and remove instructions
- editform.replace(anchor);
- editinstructions.remove();
- editinstructions = null;
+ // Handle form submission.
+ thisevent = editform.on('submit', this.edit_title_submit, this, activity, oldtitle);
+ this.edittitleevents.push(thisevent);
+ },
+
+ /**
+ * Handles the submit event when editing the activity or resources title.
+ *
+ * @protected
+ * @method edit_title_submit
+ * @param {EventFacade} ev The event that triggered this.
+ * @param {Node} activity The activity whose title we are altering.
+ * @param {String} originaltitle The original title the activity or resource had.
+ */
+ edit_title_submit : function(ev, activity, originaltitle) {
+ // We don't actually want to submit anything
+ ev.preventDefault();
+
+ var newtitle = Y.Lang.trim(activity.one(SELECTOR.ACTIVITYFORM + ' ' + SELECTOR.ACTIVITYTITLE).get('value'));
+ this.edit_title_clear(activity);
+ var spinner = M.util.add_spinner(Y, activity.one(SELECTOR.INSTANCENAME));
+ if (newtitle != null && newtitle != "" && newtitle != originaltitle) {
+ var data = {
+ 'class' : 'resource',
+ 'field' : 'updatetitle',
+ 'title' : newtitle,
+ 'id' : this.get_element_id(activity)
+ };
+ var response = this.send_request(data, spinner);
+ if (response.instancename) {
+ activity.one(SELECTOR.INSTANCENAME).setContent(response.instancename);
}
}
+ },
- // Handle cancellation of the editor
- var cancel_edittitle = function(e) {
- clear_edittitle();
-
- // Set the title and anchor back to their previous settings
- currenttitle.set('data', oldtitle);
- };
+ /**
+ * Handles the cancel event when editing the activity or resources title.
+ *
+ * @protected
+ * @method edit_title_cancel
+ * @param {EventFacade} ev The event that triggered this.
+ * @param {Node} activity The activity whose title we are altering.
+ * @param {Boolean} preventdefault If true we should prevent the default action from occuring.
+ */
+ edit_title_cancel : function(ev, activity, preventdefault) {
+ if (preventdefault) {
+ ev.preventDefault();
+ }
+ this.edit_title_clear(activity);
+ },
- // Cancel the edit if we lose focus or the escape key is pressed
- thisevent = editor.on('blur', cancel_edittitle);
- listenevents.push(thisevent);
- thisevent = Y.one('document').on('keydown', function(e) {
- if (e.keyCode === 27) {
- e.preventDefault();
- cancel_edittitle(e);
- }
- });
- listenevents.push(thisevent);
-
- // Handle form submission
- thisevent = editform.on('submit', function(e) {
- // We don't actually want to submit anything
- e.preventDefault();
-
- // Clear the edit title boxes
- clear_edittitle();
-
- // We only accept strings which have valid content
- var newtitle = Y.Lang.trim(editor.get('value'));
- if (newtitle != null && newtitle != "" && newtitle != titletext) {
- var data = {
- 'class' : 'resource',
- 'field' : 'updatetitle',
- 'title' : newtitle,
- 'id' : this.get_element_id(element)
- };
- var response = this.send_request(data, editbutton);
- if (response.instancename) {
- currenttitle.set('data', response.instancename);
- }
- } else {
- // Invalid content. Set the title back to it's original contents
- currenttitle.set('data', oldtitle);
- }
- }, this);
- listenevents.push(thisevent);
+ /**
+ * Handles clearing the editing UI and returning things to the original state they were in.
+ *
+ * @protected
+ * @method edit_title_clear
+ * @param {Node} activity The activity whose title we were altering.
+ */
+ edit_title_clear : function(activity) {
+ // Detach all listen events to prevent duplicate triggers
+ var thisevent;
+ while (thisevent = this.edittitleevents.shift()) {
+ thisevent.detach();
+ }
+ var editform = activity.one(SELECTOR.ACTIVITYFORM),
+ instructions = activity.one('#id_editinstructions');
+ if (editform) {
+ editform.replace(editform.getData('anchor'));
+ }
+ if (instructions) {
+ instructions.remove();
+ }
},
+
/**
* Set the visibility of the current resource (identified by the element)
* to match the hidden parameter (this is not a toggle).
* Only changes the visibility in the browser (no ajax update).
+ *
+ * @public This method is used by other modules.
+ * @method set_visibility_resource_ui
* @param args An object with 'element' being the A node containing the resource
- * and 'visible' being the state that the visiblity should be set to.
- * @return void
+ * and 'visible' being the state that the visibility should be set to.
*/
set_visibility_resource_ui: function(args) {
- var element = args.element;
- var shouldbevisible = args.visible;
- var buttonnode = element.one(CSS.SHOW);
- var visible = (buttonnode === null);
+ var element = args.element,
+ shouldbevisible = args.visible,
+ buttonnode = element.one(SELECTOR.SHOW),
+ visible = (buttonnode === null),
+ status = 'hide';
if (visible) {
- buttonnode = element.one(CSS.HIDE);
+ buttonnode = element.one(SELECTOR.HIDE);
+ status = 'show'
}
if (visible != shouldbevisible) {
- this.toggle_hide_resource_ui(buttonnode);
+ this.handle_resource_dim(buttonnode, buttonnode.getData('activity'), status);
}
}
}, {
M.course.coursebase.register_module(this);
// Section Highlighting
- Y.delegate('click', this.toggle_highlight, CSS.PAGECONTENT, CSS.SECTIONLI + ' ' + CSS.HIGHLIGHT, this);
+ Y.delegate('click', this.toggle_highlight, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.HIGHLIGHT, this);
// Section Visibility
- Y.delegate('click', this.toggle_hide_section, CSS.PAGECONTENT, CSS.SECTIONLI + ' ' + CSS.SHOWHIDE, this);
+ Y.delegate('click', this.toggle_hide_section, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.SHOWHIDE, this);
},
/**
* Update any section areas within the scope of the specified
setup_for_section : function(baseselector) {
// Left here for potential future use - not currently needed due to YUI delegation in initializer()
/*if (!baseselector) {
- var baseselector = CSS.PAGECONTENT;
+ var baseselector = SELECTOR.PAGECONTENT;
}
Y.all(baseselector).each(this._setup_for_section, this);*/
// The value to submit
var value;
// The status text for strings and images
- var status;
+ var status,
+ oldstatus;
if (!section.hasClass(CSS.SECTIONHIDDENCLASS)) {
section.addClass(CSS.SECTIONHIDDENCLASS);
value = 0;
status = 'show';
-
+ oldstatus = 'hide';
} else {
section.removeClass(CSS.SECTIONHIDDENCLASS);
value = 1;
status = 'hide';
+ oldstatus = 'show';
}
var newstring = M.util.get_string(status + 'fromothers', 'format_' + this.get('format'));
var response = this.send_request(data, lightbox);
- var activities = section.all(CSS.ACTIVITYLI);
+ var activities = section.all(SELECTOR.ACTIVITYLI);
activities.each(function(node) {
- if (node.one(CSS.SHOW)) {
- var button = node.one(CSS.SHOW);
+ if (node.one(SELECTOR.SHOW)) {
+ var button = node.one(SELECTOR.SHOW);
} else {
- var button = node.one(CSS.HIDE);
+ var button = node.one(SELECTOR.HIDE);
}
var activityid = this.get_element_id(node);
if (Y.Array.indexOf(response.resourcestotoggle, activityid) != -1) {
- this.toggle_hide_resource_ui(button);
+ node.getData('toolbox').handle_resource_dim(button, node, oldstatus);
}
}, this);
},
// Set the current highlighted item text
var old_string = M.util.get_string('markthistopic', 'moodle');
- Y.one(CSS.PAGECONTENT)
- .all(M.course.format.get_section_selector(Y) + '.current ' + CSS.HIGHLIGHT)
+ Y.one(SELECTOR.PAGECONTENT)
+ .all(M.course.format.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT)
.set('title', old_string);
- Y.one(CSS.PAGECONTENT)
- .all(M.course.format.get_section_selector(Y) + '.current ' + CSS.HIGHLIGHT + ' img')
+ Y.one(SELECTOR.PAGECONTENT)
+ .all(M.course.format.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT + ' img')
.set('alt', old_string)
.set('src', M.util.image_url('i/marker'));
// Remove the highlighting from all sections
- var allsections = Y.one(CSS.PAGECONTENT).all(M.course.format.get_section_selector(Y))
+ var allsections = Y.one(SELECTOR.PAGECONTENT).all(M.course.format.get_section_selector(Y))
.removeClass('current');
// Then add it if required to the selected section
});
M.course = M.course || {};
-
+ M.course.resource_toolbox = null;
M.course.init_resource_toolbox = function(config) {
- return new RESOURCETOOLBOX(config);
+ M.course.resource_toolbox = new RESOURCETOOLBOX(config);
+ return M.course.resource_toolbox;
};
M.course.init_section_toolbox = function(config) {
return new SECTIONTOOLBOX(config);
};
+ M.course.register_new_module = function(module) {
+ if (typeof module === 'string') {
+ module = Y.one(module);
+ }
+ if (M.course.resource_toolbox !== null) {
+ module.setData('toolbox', M.course.resource_toolbox);
+ module.all(SELECTOR.COMMANDSPAN+ ' ' + SELECTOR.ACTIVITYACTION).each(function(){
+ this.setData('activity', module);
+ });
+ }
+ }
+
},
'@VERSION@', {
requires : ['base', 'node', 'io', 'moodle-course-coursebase']
if (strpos($userrole->component, 'enrol_') === 0) {
$plugin = substr($userrole->component, 6);
if (isset($plugins[$plugin])) {
- $changeable = !$plugin[$plugin]->roles_protected();
+ $changeable = !$plugins[$plugin]->roles_protected();
}
}
}
$context = context_course::instance($instance->courseid);
- if (!$parentcontext = context_course::instance($instance->customint1, IGNORE_MISSING)) {
- // linking to missing course is not possible
- role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_meta'));
- return;
- }
-
// list of enrolments in parent course (we ignore meta enrols in parents completely)
list($enabled, $params) = $DB->get_in_or_equal(explode(',', $CFG->enrol_plugins_enabled), SQL_PARAMS_NAMED, 'e');
$params['userid'] = $userid;
return;
}
- if (!enrol_is_enabled('meta')) {
- if ($ue) {
- role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_meta'));
- }
+ if (!$parentcontext = context_course::instance($instance->customint1, IGNORE_MISSING)) {
+ // Weird, we should not get here.
return;
}
$ue->status = $parentstatus;
}
+ $unenrolaction = $plugin->get_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES);
+
// only active users in enabled instances are supposed to have roles (we can reassign the roles any time later)
if ($ue->status != ENROL_USER_ACTIVE or $instance->status != ENROL_INSTANCE_ENABLED) {
- if ($roles) {
+ if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND) {
+ // Always keep the roles.
+ } else if ($roles) {
role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_meta', 'itemid'=>$instance->id));
}
return;
}
}
+ if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND) {
+ // Always keep the roles.
+ return;
+ }
+
// remove roles
foreach ($roles as $rid) {
if (!isset($parentroles[$rid])) {
*/
protected static function user_not_supposed_to_be_here($instance, $ue, context_course $context, $plugin) {
if (!$ue) {
- // not enrolled yet - simple!
+ // Not enrolled yet - simple!
return;
}
$userid = $ue->userid;
- $unenrolaction = $plugin->get_config('unenrolaction', ENROL_EXT_REMOVED_UNENROL);
+ $unenrolaction = $plugin->get_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES);
if ($unenrolaction == ENROL_EXT_REMOVED_UNENROL) {
- // purges grades, group membership, preferences, etc. - admins were warned!
+ // Purges grades, group membership, preferences, etc. - admins were warned!
$plugin->unenrol_user($instance, $userid);
- return;
- } else { // ENROL_EXT_REMOVED_SUSPENDNOROLES
- // just suspend users and remove all roles (we can reassign the roles any time later)
+ } else if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND) {
if ($ue->status != ENROL_USER_SUSPENDED) {
$plugin->update_user_enrol($instance, $userid, ENROL_USER_SUSPENDED);
- role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_meta', 'itemid'=>$instance->id));
}
- return;
+
+ } else if ($unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
+ if ($ue->status != ENROL_USER_SUSPENDED) {
+ $plugin->update_user_enrol($instance, $userid, ENROL_USER_SUSPENDED);
+ }
+ role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_meta', 'itemid'=>$instance->id));
+
+ } else {
+ debugging('Unknown unenrol action '.$unenrolaction);
}
}
* @return bool success
*/
public static function user_unenrolled($ue) {
-
- // keep unenrolling even if plugin disabled
+ if (!enrol_is_enabled('meta')) {
+ // This is slow, let enrol_meta_sync() deal with disabled plugin.
+ return true;
+ }
if ($ue->enrol === 'meta') {
// prevent circular dependencies - we can not sync meta enrolments recursively
public static function course_deleted($course) {
global $DB;
- // NOTE: do not test if plugin enabled, we want to keep disabling instances with invalid course links
+ if (!enrol_is_enabled('meta')) {
+ // This is slow, let enrol_meta_sync() deal with disabled plugin.
+ return true;
+ }
// does anything want to sync with this parent?
if (!$enrols = $DB->get_records('enrol', array('customint1'=>$course->id, 'enrol'=>'meta'), 'courseid ASC, id ASC')) {
}
$plugin = enrol_get_plugin('meta');
+ $unenrolaction = $plugin->get_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES);
- // hack the DB info for all courses first
- foreach ($enrols as $enrol) {
- $enrol->customint1 = 0;
- $enrol->status = ENROL_INSTANCE_DISABLED;
- $DB->update_record('enrol', $enrol);
- $context = context_course::instance($enrol->courseid);
- role_unassign_all(array('contextid'=>$context->id, 'component'=>'enrol_meta', 'itemid'=>$enrol->id));
+ if ($unenrolaction == ENROL_EXT_REMOVED_UNENROL) {
+ // Simple, just delete this instance which purges all enrolments,
+ // admins were warned that this is risky setting!
+ foreach ($enrols as $enrol) {
+ $plugin->delete_instance($enrol);
+ }
+ return true;
}
- // now trigger sync for each instance and purge caches
foreach ($enrols as $enrol) {
- $plugin->update_status($enrol, ENROL_INSTANCE_DISABLED);
+ $enrol->customint = 0;
+ $DB->update_record('enrol', $enrol);
+
+ if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND or $unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
+ // This makes all enrolments suspended very quickly.
+ $plugin->update_status($enrol, ENROL_INSTANCE_DISABLED);
+ }
+ if ($unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
+ $context = context_course::instance($enrol->courseid);
+ role_unassign_all(array('contextid'=>$context->id, 'component'=>'enrol_meta', 'itemid'=>$enrol->id));
+ }
}
return true;
$meta = enrol_get_plugin('meta');
- $unenrolaction = $meta->get_config('unenrolaction', ENROL_EXT_REMOVED_UNENROL);
+ $unenrolaction = $meta->get_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES);
$skiproles = $meta->get_config('nosyncroleids', '');
$skiproles = empty($skiproles) ? array() : explode(',', $skiproles);
$syncall = $meta->get_config('syncall', 1);
if ($verbose) {
mtrace(" unenrolling: $ue->userid ==> $instance->courseid");
}
- continue;
- } else { // ENROL_EXT_REMOVED_SUSPENDNOROLES
- // just disable and ignore any changes
+ } else if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND) {
+ if ($ue->status != ENROL_USER_SUSPENDED) {
+ $meta->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
+ if ($verbose) {
+ mtrace(" suspending: $ue->userid ==> $instance->courseid");
+ }
+ }
+
+ } else if ($unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
if ($ue->status != ENROL_USER_SUSPENDED) {
$meta->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
$context = context_course::instance($instance->courseid);
- role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$context->id, 'component'=>'enrol_meta'));
+ role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$context->id, 'component'=>'enrol_meta', 'itemid'=>$instance->id));
if ($verbose) {
mtrace(" suspending and removing all roles: $ue->userid ==> $instance->courseid");
}
}
- continue;
}
}
$rs->close();
} else {
$notignored = "";
}
+
$sql = "SELECT ra.roleid, ra.userid, ra.contextid, ra.itemid, e.courseid
FROM {role_assignments} ra
JOIN {enrol} e ON (e.id = ra.itemid AND ra.component = 'enrol_meta' AND e.enrol = 'meta' $onecourse)
LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = ra.userid AND ue.status = :activeuser)
WHERE pra.id IS NULL OR ue.id IS NULL OR e.status <> :enabledinstance";
- $rs = $DB->get_recordset_sql($sql, $params);
- foreach($rs as $ra) {
- role_unassign($ra->roleid, $ra->userid, $ra->contextid, 'enrol_meta', $ra->itemid);
- if ($verbose) {
- mtrace(" unassigning role: $ra->userid ==> $ra->courseid as ".$allroles[$ra->roleid]->shortname);
+ if ($unenrolaction != ENROL_EXT_REMOVED_SUSPEND) {
+ $rs = $DB->get_recordset_sql($sql, $params);
+ foreach($rs as $ra) {
+ role_unassign($ra->roleid, $ra->userid, $ra->contextid, 'enrol_meta', $ra->itemid);
+ if ($verbose) {
+ mtrace(" unassigning role: $ra->userid ==> $ra->courseid as ".$allroles[$ra->roleid]->shortname);
+ }
}
+ $rs->close();
}
- $rs->close();
// kick out or suspend users without synced roles if syncall disabled
$settings->add(new admin_setting_configcheckbox('enrol_meta/syncall', get_string('syncall', 'enrol_meta'), get_string('syncall_desc', 'enrol_meta'), 1));
$options = array(
- ENROL_EXT_REMOVED_UNENROL => get_string('extremovedunenrol', 'enrol'),
- ENROL_EXT_REMOVED_SUSPENDNOROLES => get_string('extremovedsuspendnoroles', 'enrol'));
+ ENROL_EXT_REMOVED_UNENROL => get_string('extremovedunenrol', 'core_enrol'),
+ ENROL_EXT_REMOVED_SUSPEND => get_string('extremovedsuspend', 'core_enrol'),
+ ENROL_EXT_REMOVED_SUSPENDNOROLES => get_string('extremovedsuspendnoroles', 'core_enrol'),
+ );
$settings->add(new admin_setting_configselect('enrol_meta/unenrolaction', get_string('extremovedaction', 'enrol'), get_string('extremovedaction_help', 'enrol'), ENROL_EXT_REMOVED_SUSPENDNOROLES, $options));
}
}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle 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.
+//
+// Moodle 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 Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Meta enrolment sync functional test.
+ *
+ * @package enrol_meta
+ * @category phpunit
+ * @copyright 2013 Petr Skoda {@link http://skodak.org}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+class enrol_meta_plugin_testcase extends advanced_testcase {
+
+ protected function enable_plugin() {
+ $enabled = enrol_get_plugins(true);
+ $enabled['meta'] = true;
+ $enabled = array_keys($enabled);
+ set_config('enrol_plugins_enabled', implode(',', $enabled));
+ }
+
+ protected function disable_plugin() {
+ $enabled = enrol_get_plugins(true);
+ unset($enabled['meta']);
+ $enabled = array_keys($enabled);
+ set_config('enrol_plugins_enabled', implode(',', $enabled));
+ }
+
+ protected function is_meta_enrolled($user, $enrol, $role = null) {
+ global $DB;
+
+ if (!$DB->record_exists('user_enrolments', array('enrolid'=>$enrol->id, 'userid'=>$user->id))) {
+ return false;
+ }
+
+ if ($role === null) {
+ return true;
+ }
+
+ return $this->has_role($user, $enrol, $role);
+ }
+
+ protected function has_role($user, $enrol, $role) {
+ global $DB;
+
+ $context = context_course::instance($enrol->courseid);
+
+ if ($role === false) {
+ if ($DB->record_exists('role_assignments', array('contextid'=>$context->id, 'userid'=>$user->id, 'component'=>'enrol_meta', 'itemid'=>$enrol->id))) {
+ return false;
+ }
+ } else if (!$DB->record_exists('role_assignments', array('contextid'=>$context->id, 'userid'=>$user->id, 'roleid'=>$role->id, 'component'=>'enrol_meta', 'itemid'=>$enrol->id))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function test_sync() {
+ global $CFG, $DB;
+
+ $this->resetAfterTest(true);
+
+ $metalplugin = enrol_get_plugin('meta');
+ $manplugin = enrol_get_plugin('manual');
+
+ $user1 = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+ $user3 = $this->getDataGenerator()->create_user();
+ $user4 = $this->getDataGenerator()->create_user();
+ $user5 = $this->getDataGenerator()->create_user();
+
+ $course1 = $this->getDataGenerator()->create_course();
+ $course2 = $this->getDataGenerator()->create_course();
+ $course3 = $this->getDataGenerator()->create_course();
+ $course4 = $this->getDataGenerator()->create_course();
+ $manual1 = $DB->get_record('enrol', array('courseid'=>$course1->id, 'enrol'=>'manual'), '*', MUST_EXIST);
+ $manual2 = $DB->get_record('enrol', array('courseid'=>$course2->id, 'enrol'=>'manual'), '*', MUST_EXIST);
+ $manual3 = $DB->get_record('enrol', array('courseid'=>$course3->id, 'enrol'=>'manual'), '*', MUST_EXIST);
+ $manual4 = $DB->get_record('enrol', array('courseid'=>$course4->id, 'enrol'=>'manual'), '*', MUST_EXIST);
+
+ $student = $DB->get_record('role', array('shortname'=>'student'));
+ $teacher = $DB->get_record('role', array('shortname'=>'teacher'));
+ $manager = $DB->get_record('role', array('shortname'=>'manager'));
+
+ $this->disable_plugin();
+
+ $this->getDataGenerator()->enrol_user($user1->id, $course1->id, $student->id);
+ $this->getDataGenerator()->enrol_user($user2->id, $course1->id, $student->id);
+ $this->getDataGenerator()->enrol_user($user3->id, $course1->id, 0);
+ $this->getDataGenerator()->enrol_user($user4->id, $course1->id, $teacher->id);
+ $this->getDataGenerator()->enrol_user($user5->id, $course1->id, $manager->id);
+
+ $this->getDataGenerator()->enrol_user($user1->id, $course2->id, $student->id);
+ $this->getDataGenerator()->enrol_user($user2->id, $course2->id, $teacher->id);
+
+ $this->assertEquals(7, $DB->count_records('user_enrolments'));
+ $this->assertEquals(6, $DB->count_records('role_assignments'));
+
+ set_config('syncall', 0, 'enrol_meta');
+ set_config('nosyncroleids', $manager->id, 'enrol_meta');
+
+ require_once($CFG->dirroot.'/enrol/meta/locallib.php');
+
+ enrol_meta_sync(null, false);
+ $this->assertEquals(7, $DB->count_records('user_enrolments'));
+ $this->assertEquals(6, $DB->count_records('role_assignments'));
+
+ $this->enable_plugin();
+ enrol_meta_sync(null, false);
+ $this->assertEquals(7, $DB->count_records('user_enrolments'));
+ $this->assertEquals(6, $DB->count_records('role_assignments'));
+
+ $e1 = $metalplugin->add_instance($course3, array('customint1'=>$course1->id));
+ $e2 = $metalplugin->add_instance($course3, array('customint1'=>$course2->id));
+ $e3 = $metalplugin->add_instance($course4, array('customint1'=>$course2->id));
+ $enrol1 = $DB->get_record('enrol', array('id'=>$e1));
+ $enrol2 = $DB->get_record('enrol', array('id'=>$e2));
+ $enrol3 = $DB->get_record('enrol', array('id'=>$e3));
+
+ enrol_meta_sync($course4->id, false);
+ $this->assertEquals(9, $DB->count_records('user_enrolments'));
+ $this->assertEquals(8, $DB->count_records('role_assignments'));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol3, $student));
+ $this->assertTrue($this->is_meta_enrolled($user2, $enrol3, $teacher));
+
+ enrol_meta_sync(null, false);
+ $this->assertEquals(14, $DB->count_records('user_enrolments'));
+ $this->assertEquals(13, $DB->count_records('role_assignments'));
+
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, $student));
+ $this->assertTrue($this->is_meta_enrolled($user2, $enrol1, $student));
+ $this->assertFalse($this->is_meta_enrolled($user3, $enrol1));
+ $this->assertTrue($this->is_meta_enrolled($user4, $enrol1, $teacher));
+ $this->assertFalse($this->is_meta_enrolled($user5, $enrol1));
+
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol2, $student));
+ $this->assertTrue($this->is_meta_enrolled($user2, $enrol2, $teacher));
+
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol3, $student));
+ $this->assertTrue($this->is_meta_enrolled($user2, $enrol3, $teacher));
+
+ set_config('syncall', 1, 'enrol_meta');
+ enrol_meta_sync(null, false);
+ $this->assertEquals(16, $DB->count_records('user_enrolments'));
+ $this->assertEquals(13, $DB->count_records('role_assignments'));
+
+ $this->assertTrue($this->is_meta_enrolled($user3, $enrol1, false));
+ $this->assertTrue($this->is_meta_enrolled($user5, $enrol1, false));
+
+ $this->assertEquals(16, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->disable_plugin();
+ $manplugin->unenrol_user($manual1, $user1->id);
+ $manplugin->unenrol_user($manual2, $user1->id);
+
+ $this->assertEquals(14, $DB->count_records('user_enrolments'));
+ $this->assertEquals(11, $DB->count_records('role_assignments'));
+ $this->assertEquals(14, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+
+ $this->enable_plugin();
+
+ set_config('unenrolaction', ENROL_EXT_REMOVED_SUSPEND, 'enrol_meta');
+ enrol_meta_sync($course4->id, false);
+ $this->assertEquals(14, $DB->count_records('user_enrolments'));
+ $this->assertEquals(11, $DB->count_records('role_assignments'));
+ $this->assertEquals(13, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol3, $student));
+ $this->assertTrue($DB->record_exists('user_enrolments', array('enrolid'=>$enrol3->id, 'status'=>ENROL_USER_SUSPENDED, 'userid'=>$user1->id)));
+
+ enrol_meta_sync(null, false);
+ $this->assertEquals(14, $DB->count_records('user_enrolments'));
+ $this->assertEquals(11, $DB->count_records('role_assignments'));
+ $this->assertEquals(11, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, $student));
+ $this->assertTrue($DB->record_exists('user_enrolments', array('enrolid'=>$enrol1->id, 'status'=>ENROL_USER_SUSPENDED, 'userid'=>$user1->id)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol2, $student));
+ $this->assertTrue($DB->record_exists('user_enrolments', array('enrolid'=>$enrol2->id, 'status'=>ENROL_USER_SUSPENDED, 'userid'=>$user1->id)));
+
+ set_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES, 'enrol_meta');
+ enrol_meta_sync($course4->id, false);
+ $this->assertEquals(14, $DB->count_records('user_enrolments'));
+ $this->assertEquals(10, $DB->count_records('role_assignments'));
+ $this->assertEquals(11, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol3, false));
+ $this->assertTrue($DB->record_exists('user_enrolments', array('enrolid'=>$enrol3->id, 'status'=>ENROL_USER_SUSPENDED, 'userid'=>$user1->id)));
+
+ enrol_meta_sync(null, false);
+ $this->assertEquals(14, $DB->count_records('user_enrolments'));
+ $this->assertEquals(8, $DB->count_records('role_assignments'));
+ $this->assertEquals(11, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, false));
+ $this->assertTrue($DB->record_exists('user_enrolments', array('enrolid'=>$enrol1->id, 'status'=>ENROL_USER_SUSPENDED, 'userid'=>$user1->id)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol2, false));
+ $this->assertTrue($DB->record_exists('user_enrolments', array('enrolid'=>$enrol2->id, 'status'=>ENROL_USER_SUSPENDED, 'userid'=>$user1->id)));
+
+ set_config('unenrolaction', ENROL_EXT_REMOVED_UNENROL, 'enrol_meta');
+ enrol_meta_sync($course4->id, false);
+ $this->assertEquals(13, $DB->count_records('user_enrolments'));
+ $this->assertEquals(8, $DB->count_records('role_assignments'));
+ $this->assertEquals(11, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertFalse($this->is_meta_enrolled($user1, $enrol3));
+
+ enrol_meta_sync(null, false);
+ $this->assertEquals(11, $DB->count_records('user_enrolments'));
+ $this->assertEquals(8, $DB->count_records('role_assignments'));
+ $this->assertEquals(11, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertFalse($this->is_meta_enrolled($user1, $enrol1));
+ $this->assertFalse($this->is_meta_enrolled($user1, $enrol2));
+
+
+ // Now try sync triggered by events.
+
+ set_config('syncall', 1, 'enrol_meta');
+
+ $this->getDataGenerator()->enrol_user($user1->id, $course1->id, $student->id);
+ $this->assertEquals(13, $DB->count_records('user_enrolments'));
+ $this->assertEquals(10, $DB->count_records('role_assignments'));
+ $this->assertEquals(13, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, $student));
+ enrol_meta_sync(null, false);
+ $this->assertEquals(13, $DB->count_records('user_enrolments'));
+ $this->assertEquals(10, $DB->count_records('role_assignments'));
+ $this->assertEquals(13, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, $student));
+
+ $manplugin->unenrol_user($manual1, $user1->id);
+ $this->assertEquals(11, $DB->count_records('user_enrolments'));
+ $this->assertEquals(8, $DB->count_records('role_assignments'));
+ $this->assertEquals(11, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertFalse($this->is_meta_enrolled($user1, $enrol1));
+ enrol_meta_sync(null, false);
+ $this->assertEquals(11, $DB->count_records('user_enrolments'));
+ $this->assertEquals(8, $DB->count_records('role_assignments'));
+ $this->assertEquals(11, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertFalse($this->is_meta_enrolled($user1, $enrol1));
+
+ $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 0);
+ $this->assertEquals(13, $DB->count_records('user_enrolments'));
+ $this->assertEquals(8, $DB->count_records('role_assignments'));
+ $this->assertEquals(13, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, false));
+ enrol_meta_sync(null, false);
+ $this->assertEquals(13, $DB->count_records('user_enrolments'));
+ $this->assertEquals(8, $DB->count_records('role_assignments'));
+ $this->assertEquals(13, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, false));
+
+ $manplugin->unenrol_user($manual1, $user1->id);
+ $this->assertEquals(11, $DB->count_records('user_enrolments'));
+ $this->assertEquals(8, $DB->count_records('role_assignments'));
+ $this->assertEquals(11, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertFalse($this->is_meta_enrolled($user1, $enrol1));
+ enrol_meta_sync(null, false);
+ $this->assertEquals(11, $DB->count_records('user_enrolments'));
+ $this->assertEquals(8, $DB->count_records('role_assignments'));
+ $this->assertEquals(11, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertFalse($this->is_meta_enrolled($user1, $enrol1));
+
+ set_config('syncall', 0, 'enrol_meta');
+ enrol_meta_sync(null, false);
+ $this->assertEquals(9, $DB->count_records('user_enrolments'));
+ $this->assertEquals(8, $DB->count_records('role_assignments'));
+ $this->assertEquals(9, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertFalse($this->is_meta_enrolled($user1, $enrol1));
+
+ $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 0);
+ $this->assertEquals(10, $DB->count_records('user_enrolments'));
+ $this->assertEquals(8, $DB->count_records('role_assignments'));
+ $this->assertEquals(10, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertFalse($this->is_meta_enrolled($user1, $enrol1, $student));
+ enrol_meta_sync(null, false);
+ $this->assertEquals(10, $DB->count_records('user_enrolments'));
+ $this->assertEquals(8, $DB->count_records('role_assignments'));
+ $this->assertEquals(10, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertFalse($this->is_meta_enrolled($user1, $enrol1, $student));
+
+ role_assign($teacher->id, $user1->id, context_course::instance($course1->id)->id);
+ $this->assertEquals(11, $DB->count_records('user_enrolments'));
+ $this->assertEquals(10, $DB->count_records('role_assignments'));
+ $this->assertEquals(11, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, $teacher));
+ enrol_meta_sync(null, false);
+ $this->assertEquals(11, $DB->count_records('user_enrolments'));
+ $this->assertEquals(10, $DB->count_records('role_assignments'));
+ $this->assertEquals(11, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, $teacher));
+
+ role_unassign($teacher->id, $user1->id, context_course::instance($course1->id)->id);
+ $this->assertEquals(10, $DB->count_records('user_enrolments'));
+ $this->assertEquals(8, $DB->count_records('role_assignments'));
+ $this->assertEquals(10, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertFalse($this->is_meta_enrolled($user1, $enrol1, $student));
+ enrol_meta_sync(null, false);
+ $this->assertEquals(10, $DB->count_records('user_enrolments'));
+ $this->assertEquals(8, $DB->count_records('role_assignments'));
+ $this->assertEquals(10, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertFalse($this->is_meta_enrolled($user1, $enrol1, $student));
+
+ $manplugin->unenrol_user($manual1, $user1->id);
+ $this->assertEquals(9, $DB->count_records('user_enrolments'));
+ $this->assertEquals(8, $DB->count_records('role_assignments'));
+ $this->assertEquals(9, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertFalse($this->is_meta_enrolled($user1, $enrol1));
+
+ set_config('syncall', 1, 'enrol_meta');
+ set_config('unenrolaction', ENROL_EXT_REMOVED_SUSPEND, 'enrol_meta');
+ enrol_meta_sync(null, false);
+ $this->assertEquals(11, $DB->count_records('user_enrolments'));
+ $this->assertEquals(8, $DB->count_records('role_assignments'));
+ $this->assertEquals(11, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+
+ $this->getDataGenerator()->enrol_user($user1->id, $course1->id, $student->id);
+ $this->assertEquals(13, $DB->count_records('user_enrolments'));
+ $this->assertEquals(10, $DB->count_records('role_assignments'));
+ $this->assertEquals(13, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, $student));
+ enrol_meta_sync(null, false);
+ $this->assertEquals(13, $DB->count_records('user_enrolments'));
+ $this->assertEquals(10, $DB->count_records('role_assignments'));
+ $this->assertEquals(13, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, $student));
+
+ $manplugin->update_user_enrol($manual1, $user1->id, ENROL_USER_SUSPENDED);
+ $this->assertEquals(13, $DB->count_records('user_enrolments'));
+ $this->assertEquals(10, $DB->count_records('role_assignments'));
+ $this->assertEquals(11, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, $student));
+ enrol_meta_sync(null, false);
+ $this->assertEquals(13, $DB->count_records('user_enrolments'));
+ $this->assertEquals(10, $DB->count_records('role_assignments'));
+ $this->assertEquals(11, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, $student));
+
+ $manplugin->unenrol_user($manual1, $user1->id);
+ $this->assertEquals(12, $DB->count_records('user_enrolments'));
+ $this->assertEquals(9, $DB->count_records('role_assignments'));
+ $this->assertEquals(11, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, $student));
+ enrol_meta_sync(null, false);
+ $this->assertEquals(12, $DB->count_records('user_enrolments'));
+ $this->assertEquals(9, $DB->count_records('role_assignments'));
+ $this->assertEquals(11, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, $student));
+
+ $this->getDataGenerator()->enrol_user($user1->id, $course1->id, $student->id);
+ $this->assertEquals(13, $DB->count_records('user_enrolments'));
+ $this->assertEquals(10, $DB->count_records('role_assignments'));
+ $this->assertEquals(13, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, $student));
+ enrol_meta_sync(null, false);
+ $this->assertEquals(13, $DB->count_records('user_enrolments'));
+ $this->assertEquals(10, $DB->count_records('role_assignments'));
+ $this->assertEquals(13, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, $student));
+
+ set_config('syncall', 1, 'enrol_meta');
+ set_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES, 'enrol_meta');
+ enrol_meta_sync(null, false);
+ $this->assertEquals(13, $DB->count_records('user_enrolments'));
+ $this->assertEquals(10, $DB->count_records('role_assignments'));
+ $this->assertEquals(13, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+
+ $this->getDataGenerator()->enrol_user($user1->id, $course1->id, $student->id);
+ $this->assertEquals(13, $DB->count_records('user_enrolments'));
+ $this->assertEquals(10, $DB->count_records('role_assignments'));
+ $this->assertEquals(13, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, $student));
+ enrol_meta_sync(null, false);
+ $this->assertEquals(13, $DB->count_records('user_enrolments'));
+ $this->assertEquals(10, $DB->count_records('role_assignments'));
+ $this->assertEquals(13, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, $student));
+
+ $manplugin->unenrol_user($manual1, $user1->id);
+ $this->assertEquals(12, $DB->count_records('user_enrolments'));
+ $this->assertEquals(8, $DB->count_records('role_assignments'));
+ $this->assertEquals(11, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, false));
+ enrol_meta_sync(null, false);
+ $this->assertEquals(12, $DB->count_records('user_enrolments'));
+ $this->assertEquals(8, $DB->count_records('role_assignments'));
+ $this->assertEquals(11, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, false));
+
+ $this->getDataGenerator()->enrol_user($user1->id, $course1->id, $student->id);
+ $this->assertEquals(13, $DB->count_records('user_enrolments'));
+ $this->assertEquals(10, $DB->count_records('role_assignments'));
+ $this->assertEquals(13, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, $student));
+ enrol_meta_sync(null, false);
+ $this->assertEquals(13, $DB->count_records('user_enrolments'));
+ $this->assertEquals(10, $DB->count_records('role_assignments'));
+ $this->assertEquals(13, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ $this->assertTrue($this->is_meta_enrolled($user1, $enrol1, $student));
+
+
+ set_config('unenrolaction', ENROL_EXT_REMOVED_UNENROL, 'enrol_meta');
+ enrol_meta_sync(null, false);
+ $this->assertEquals(13, $DB->count_records('user_enrolments'));
+ $this->assertEquals(10, $DB->count_records('role_assignments'));
+ $this->assertEquals(13, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+
+ delete_course($course1, false);
+ $this->assertEquals(3, $DB->count_records('user_enrolments'));
+ $this->assertEquals(3, $DB->count_records('role_assignments'));
+ $this->assertEquals(3, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ enrol_meta_sync(null, false);
+ $this->assertEquals(3, $DB->count_records('user_enrolments'));
+ $this->assertEquals(3, $DB->count_records('role_assignments'));
+ $this->assertEquals(3, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+
+ delete_course($course2, false);
+ $this->assertEquals(0, $DB->count_records('user_enrolments'));
+ $this->assertEquals(0, $DB->count_records('role_assignments'));
+ $this->assertEquals(0, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+ enrol_meta_sync(null, false);
+ $this->assertEquals(0, $DB->count_records('user_enrolments'));
+ $this->assertEquals(0, $DB->count_records('role_assignments'));
+ $this->assertEquals(0, $DB->count_records('user_enrolments', array('status'=>ENROL_USER_ACTIVE)));
+
+ delete_course($course3, false);
+ delete_course($course4, false);
+
+ }
+}
* @copyright 2012 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class core_enrol_testcase extends advanced_testcase {
+class core_enrollib_testcase extends advanced_testcase {
public function test_enrol_get_all_users_courses() {
global $DB, $CFG;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.4
*/
-class core_enrol_external_testcase extends externallib_advanced_testcase {
+class core_enrol_externallib_testcase extends externallib_advanced_testcase {
/**
* Test get_enrolled_users
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.4
*/
-class core_role_external_testcase extends externallib_advanced_testcase {
+class core_enrol_role_external_testcase extends externallib_advanced_testcase {
/**
* Tests set up
*/
private function fm_js_template_mkdir() {
$rv = '
-<div class="filemanager fp-mkdir-dlg">
+<div class="filemanager fp-mkdir-dlg" role="dialog" aria-live="assertive" aria-labelledby="fp-mkdir-dlg-title">
<div class="fp-mkdir-dlg-text">
- <label>' . get_string('newfoldername', 'repository') . '</label><br/>
+ <label id="fp-mkdir-dlg-title">' . get_string('newfoldername', 'repository') . '</label><br/>
<input type="text" />
</div>
<button class="{!}fp-dlg-butcreate">'.get_string('makeafolder').'</button>
*/
private function fp_js_template_message() {
$rv = '
-<div class="file-picker fp-msg">
- <p class="{!}fp-msg-text"></p>
+<div class="file-picker fp-msg" role="alertdialog" aria-live="assertive" aria-labelledby="fp-msg-labelledby">
+ <p class="{!}fp-msg-text" id="fp-msg-labelledby"></p>
<button class="{!}fp-msg-butok">'.get_string('ok').'</button>
</div>';
return preg_replace('/\{\!\}/', '', $rv);
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
require_once($CFG->dirroot . '/files/externallib.php');
-class test_external_files extends advanced_testcase {
+class core_files_externallib_testcase extends advanced_testcase {
/*
* Test core_files_external::upload().
$mform->addElement('text', 'iprestriction', get_string('keyiprestriction', 'userkey'), array('size'=>80));
$mform->addHelpButton('iprestriction', 'keyiprestriction', 'userkey');
$mform->setDefault('iprestriction', getremoteaddr()); // own IP - just in case somebody does not know what user key is
+ $mform->setType('iprestriction', PARAM_RAW_TRIMMED);
$mform->addElement('date_time_selector', 'validuntil', get_string('keyvaliduntil', 'userkey'), array('optional'=>true));
$mform->addHelpButton('validuntil', 'keyvaliduntil', 'userkey');
$mform->setDefault('validuntil', time()+3600*24*7); // only 1 week default duration - just in case somebody does not know what user key is
+ $mform->setType('validuntil', PARAM_INT);
$mform->disabledIf('iprestriction', 'key', 'noteq', 1);
$mform->disabledIf('validuntil', 'key', 'noteq', 1);
$mform->addElement('static', 'value', get_string('keyvalue', 'userkey'));
$mform->addElement('text', 'iprestriction', get_string('keyiprestriction', 'userkey'), array('size'=>80));
+ $mform->setType('iprestriction', PARAM_RAW_TRIMMED);
+
$mform->addElement('date_time_selector', 'validuntil', get_string('keyvaliduntil', 'userkey'), array('optional'=>true));
+ $mform->setType('validuntil', PARAM_INT);
$mform->addHelpButton('iprestriction', 'keyiprestriction', 'userkey');
$mform->addHelpButton('validuntil', 'keyvaliduntil', 'userkey');
* @copyright 2011 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class grading_manager_testcase extends advanced_testcase {
+class core_grade_grading_manager_testcase extends advanced_testcase {
public function test_basic_instantiation() {
$manager1 = get_grading_manager();
$mform->addHelpButton('iprestriction', 'keyiprestriction', 'userkey');
$mform->addHelpButton('validuntil', 'keyvaliduntil', 'userkey');
+ $mform->setType('iprestriction', PARAM_RAW_TRIMMED);
$mform->addElement('hidden','id');
$mform->setType('id', PARAM_INT);
$string['pluginname'] = 'User report';
$string['user:view'] = 'View your own grade report';
+$string['tablesummary'] = 'The table is arranged as a list of graded items including categories of graded items. When items are in a category they will be indicated as such.';
/// Process those items that have scores associated
if ($type == 'item' or $type == 'categoryitem' or $type == 'courseitem') {
+ $header_row = "row_{$eid}_{$this->user->id}";
+ $header_cat = "cat_{$grade_object->categoryid}_{$this->user->id}";
+
if (! $grade_grade = grade_grade::fetch(array('itemid'=>$grade_object->id,'userid'=>$this->user->id))) {
$grade_grade = new grade_grade();
$grade_grade->userid = $this->user->id;
$cm = $instances[$grade_object->iteminstance];
if (!$cm->uservisible) {
// Further checks are required to determine whether the activity is entirely hidden or just greyed out.
- if ($cm->is_user_access_restricted_by_group() || $cm->is_user_access_restricted_by_conditional_access()) {
+ if ($cm->is_user_access_restricted_by_group() || $cm->is_user_access_restricted_by_conditional_access() ||
+ $cm->is_user_access_restricted_by_capability()) {
$hide = true;
}
}
} else {
$class .= ($type == 'categoryitem' or $type == 'courseitem') ? " ".$alter."d$depth baggb" : " item b1b";
}
+ if ($type == 'categoryitem' or $type == 'courseitem') {
+ $header_cat = "cat_{$grade_object->iteminstance}_{$this->user->id}";
+ }
/// Name
$data['itemname']['content'] = $fullname;
$data['itemname']['class'] = $class;
$data['itemname']['colspan'] = ($this->maxdepth - $depth);
+ $data['itemname']['celltype'] = 'th';
+ $data['itemname']['id'] = $header_row;
/// Actual Grade
$gradeval = $grade_grade->finalgrade;
if ($this->showweight) {
$data['weight']['class'] = $class;
$data['weight']['content'] = '-';
+ $data['weight']['headers'] = "$header_cat $header_row weight";
// has a weight assigned, might be extra credit