--- /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/>.
+
+/**
+ * This script fixed incorrectly deleted users.
+ *
+ * @package core
+ * @subpackage cli
+ * @copyright 2013 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', true);
+
+require(__DIR__.'/../../config.php');
+require_once($CFG->libdir.'/clilib.php');
+
+// Get cli options.
+list($options, $unrecognized) = cli_get_params(
+ array(
+ 'courses' => false,
+ 'fix' => false,
+ 'help' => false
+ ),
+ array(
+ 'h' => 'help',
+ 'c' => 'courses',
+ 'f' => 'fix'
+ )
+);
+
+if ($options['help'] || empty($options['courses'])) {
+ $help =
+"Checks and fixes that course modules and sections reference each other correctly.
+
+Compares DB fields course_sections.sequence and course_modules.section
+checking that:
+- course_sections.sequence contains each module id not more than once in the course
+- for each moduleid from course_sections.sequence the field course_modules.section
+ refers to the same section id (this means course_sections.sequence is more
+ important if they are different)
+- each module in the course is present in one of course_sections.sequence
+- section sequences do not contain non-existing course modules
+
+If there are any mismatches, the message is displayed. If --fix is specified,
+the records in DB are corrected.
+
+This script may run for a long time on big systems if called for all courses.
+
+Avoid executing the script when another user may simultaneously edit any of the
+courses being checked (recommended to run in mainenance mode).
+
+Options:
+-c, --courses List courses that need to be checked (comma-separated
+ values or * for all). Required
+-f, --fix Fix the mismatches in DB. If not specified check only and
+ report problems to STDERR
+-h, --help Print out this help
+
+Example:
+\$sudo -u www-data /usr/bin/php admin/cli/fix_course_sequence.php --courses=*
+\$sudo -u www-data /usr/bin/php admin/cli/fix_course_sequence.php --courses=2,3,4 --fix
+";
+
+ echo $help;
+ die;
+}
+
+$courseslist = preg_split('/\s*,\s*/', $options['courses'], -1, PREG_SPLIT_NO_EMPTY);
+if (in_array('*', $courseslist)) {
+ $where = '';
+ $params = array();
+} else {
+ list($sql, $params) = $DB->get_in_or_equal($courseslist, SQL_PARAMS_NAMED, 'id');
+ $where = 'WHERE id '. $sql;
+}
+$coursescount = $DB->get_field_sql('SELECT count(id) FROM {course} '. $where, $params);
+
+if (!$coursescount) {
+ cli_error('No courses found');
+}
+echo "Checking $coursescount courses...\n\n";
+
+require_once($CFG->dirroot. '/course/lib.php');
+
+$problems = array();
+$courses = $DB->get_fieldset_sql('SELECT id FROM {course} '. $where, $params);
+foreach ($courses as $courseid) {
+ $errors = course_integrity_check($courseid, null, null, true, empty($options['fix']));
+ if ($errors) {
+ if (!empty($options['fix'])) {
+ // Reset the course cache to make sure cache is recalculated next time the course is viewed.
+ rebuild_course_cache($courseid, true);
+ }
+ foreach ($errors as $error) {
+ cli_problem($error);
+ }
+ $problems[] = $courseid;
+ } else {
+ echo "Course [$courseid] is OK\n";
+ }
+}
+if (!count($problems)) {
+ echo "\n...All courses are OK\n";
+} else {
+ if (!empty($options['fix'])) {
+ echo "\n...Found and fixed ".count($problems)." courses with problems". "\n";
+ } else {
+ echo "\n...Found ".count($problems)." courses with problems. To fix run:\n";
+ echo "\$sudo -u www-data /usr/bin/php admin/cli/fix_course_sequence.php --courses=".join(',', $problems)." --fix". "\n";
+ }
+}
\ No newline at end of file
}
require('../config.php');
+
+// Invalidate the cache of version.php in any circumstances to help core_component
+// detecting if the version has changed and component cache should be reset.
+if (function_exists('opcache_invalidate')) {
+ opcache_invalidate($CFG->dirroot . '/version.php', true);
+}
+// Make sure the component cache gets rebuilt if necessary, any method that
+// indirectly calls the protected init() method is good here.
+core_component::get_core_subsystems();
+
require_once($CFG->libdir.'/adminlib.php'); // various admin-only functions
require_once($CFG->libdir.'/upgradelib.php'); // general upgrade/install related functions
require_once($CFG->libdir.'/pluginlib.php'); // available updates notifications
SET visibleold=visible, visible=0
WHERE module=?";
$DB->execute($sql, array($module->id));
- // clear the course modinfo cache for courses
- // where we just uninstalld something
- $sql = "UPDATE {course}
- SET modinfo=''
- WHERE id IN (SELECT DISTINCT course
+ // Increment course.cacherev for courses where we just made something invisible.
+ // This will force cache rebuilding on the next request.
+ increment_revision_number('course', 'cacherev',
+ "id IN (SELECT DISTINCT course
FROM {course_modules}
- WHERE visibleold=1 AND module=?)";
- $DB->execute($sql, array($module->id));
+ WHERE visibleold=1 AND module=?)",
+ array($module->id));
admin_get_root(true, false); // settings not required - only pages
}
}
$DB->set_field("modules", "visible", "1", array("id"=>$module->id)); // Show main module
$DB->set_field('course_modules', 'visible', '1', array('visibleold'=>1, 'module'=>$module->id)); // Get the previous saved visible state for the course module.
- // clear the course modinfo cache for courses
- // where we just made something visible
- $sql = "UPDATE {course}
- SET modinfo = ''
- WHERE id IN (SELECT DISTINCT course
+ // Increment course.cacherev for courses where we just made something visible.
+ // This will force cache rebuilding on the next request.
+ increment_revision_number('course', 'cacherev',
+ "id IN (SELECT DISTINCT course
FROM {course_modules}
- WHERE visible=1 AND module=?)";
- $DB->execute($sql, array($module->id));
+ WHERE visible=1 AND module=?)",
+ array($module->id));
admin_get_root(true, false); // settings not required - only pages
}
'moodle/badges:createbadge',
'moodle/badges:manageglobalsettings',
'moodle/badges:awardbadge',
+ 'moodle/badges:configurecriteria',
'moodle/badges:configuremessages',
'moodle/badges:configuredetails',
'moodle/badges:deletebadge'), $systemcontext))) {
'moodle/badges:viewawarded',
'moodle/badges:createbadge',
'moodle/badges:awardbadge',
+ 'moodle/badges:configurecriteria',
'moodle/badges:configuremessages',
'moodle/badges:configuredetails',
'moodle/badges:deletebadge'
$string['stepsdefinitionsfilters'] = 'Steps definitions';
$string['stepsdefinitionstype'] = 'Type';
$string['theninfo'] = 'Then. Checkings to ensure the outcomes are the expected ones';
+$string['unknownexceptioninfo'] = 'There was a problem with Selenium or the browser, try to upgrade Selenium to the latest version. Error: ';
$string['viewsteps'] = 'Filter';
$string['wheninfo'] = 'When. Actions that provokes an event';
$string['wrongbehatsetup'] = 'Something is wrong with behat setup, ensure:<ul>
// test pgtIou parameter for proxy mode (https connection
// in background from CAS server to the php server)
if ($authCAS != 'CAS' && !isset($_GET['pgtIou'])) {
- $PAGE->set_url('/auth/cas/auth.php');
+ $PAGE->set_url('/login/index.php');
$PAGE->navbar->add($CASform);
$PAGE->set_title("$site->fullname: $CASform");
$PAGE->set_heading($site->fullname);
$string['auth_dbfieldpass_key'] = 'Password field';
$string['auth_dbfielduser'] = 'Name of the field containing usernames';
$string['auth_dbfielduser_key'] = 'Username field';
-$string['auth_dbhost'] = 'The computer hosting the database server.';
+$string['auth_dbhost'] = 'The computer hosting the database server. Use a system DSN entry if using ODBC.';
$string['auth_dbhost_key'] = 'Host';
$string['auth_dbchangepasswordurl_key'] = 'Password-change URL';
$string['auth_dbinsertuser'] = 'Inserted user {$a->name} id {$a->id}';
$string['auth_dbinsertuserduplicate'] = 'Error inserting user {$a->username} - user with this username was already created through \'{$a->auth}\' plugin.';
$string['auth_dbinsertusererror'] = 'Error inserting user {$a}';
-$string['auth_dbname'] = 'Name of the database itself';
+$string['auth_dbname'] = 'Name of the database itself. Leave empty if using an ODBC DSN.';
$string['auth_dbname_key'] = 'DB name';
$string['auth_dbpass'] = 'Password matching the above username';
$string['auth_dbpass_key'] = 'Password';
}
$mnethostlogssql = "
SELECT
- mhostlogs.remoteid, mhostlogs.time, mhostlogs.userid, mhostlogs.ip,
- mhostlogs.course, mhostlogs.module, mhostlogs.cmid, mhostlogs.action,
- mhostlogs.url, mhostlogs.info, mhostlogs.username, c.fullname as coursename,
- c.modinfo
+ l.id as remoteid, l.time, l.userid, l.ip, l.course, l.module, l.cmid,
+ l.action, l.url, l.info, u.username
FROM
- (
- SELECT
- l.id as remoteid, l.time, l.userid, l.ip, l.course, l.module, l.cmid,
- l.action, l.url, l.info, u.username
- FROM
- {user} u
- INNER JOIN {log} l on l.userid = u.id
- WHERE
- u.mnethostid = ?
- AND l.id > ?
- ORDER BY remoteid ASC
- LIMIT 500
- ) mhostlogs
- INNER JOIN {course} c on c.id = mhostlogs.course
- ORDER by mhostlogs.remoteid ASC";
+ {user} u
+ INNER JOIN {log} l on l.userid = u.id
+ WHERE
+ u.mnethostid = ?
+ AND l.id > ?
+ AND l.course IS NOT NULL
+ ORDER by l.id ASC
+ LIMIT 500";
$mnethostlogs = $DB->get_records_sql($mnethostlogssql, array($mnethostid, $mnet_request->response['last log id']));
$processedlogs = array();
foreach($mnethostlogs as $hostlog) {
- // Extract the name of the relevant module instance from the
- // course modinfo if possible.
- if (!empty($hostlog->modinfo) && !empty($hostlog->cmid)) {
- $modinfo = unserialize($hostlog->modinfo);
- unset($hostlog->modinfo);
- $modulearray = array();
- foreach($modinfo as $module) {
- $modulearray[$module->cm] = $module->name;
+ try {
+ // Get impersonalised course information. If it is cached there will be no DB queries.
+ $modinfo = get_fast_modinfo($hostlog->course, -1);
+ $hostlog->coursename = $modinfo->get_course()->fullname;
+ if (!empty($hostlog->cmid) && isset($modinfo->cms[$hostlog->cmid])) {
+ $hostlog->resource_name = $modinfo->cms[$hostlog->cmid]->name;
+ } else {
+ $hostlog->resource_name = '';
}
- $hostlog->resource_name = $modulearray[$hostlog->cmid];
- } else {
- $hostlog->resource_name = '';
+ } catch (moodle_exception $e) {
+ // Course not found
+ continue;
}
$processedlogs[] = array (
if ($this->status != backup::STATUS_NEED_PRECHECK) {
throw new restore_controller_exception('cannot_precheck_wrong_status', $this->status);
}
+ // Basic/initial prevention against time/memory limits
+ set_time_limit(1 * 60 * 60); // 1 hour for 1 course initially granted
+ raise_memory_limit(MEMORY_EXTRA);
$this->precheck = restore_prechecks_helper::execute_prechecks($this, $droptemptablesafter);
if (!array_key_exists('errors', $this->precheck)) { // No errors, can be executed
$this->set_status(backup::STATUS_AWAITING);
}
}
-$outcome = $restore->process();
$heading = $course->fullname;
$PAGE->set_title($heading.': '.$restore->get_stage_name());
$renderer = $PAGE->get_renderer('core','backup');
echo $OUTPUT->header();
+
+// Prepare a progress bar which can display optionally during long-running
+// operations while setting up the UI.
+$slowprogress = new core_backup_display_progress_if_slow();
+// Depending on the code branch above, $restore may be a restore_ui or it may
+// be a restore_ui_independent_stage. Either way, this function exists.
+$restore->set_progress_reporter($slowprogress);
+$outcome = $restore->process();
+
if (!$restore->is_independent() && $restore->enforce_changed_dependencies()) {
debugging('Your settings have been altered due to unmet dependencies', DEBUG_DEVELOPER);
}
require_once($CFG->dirroot . '/backup/util/progress/core_backup_progress.class.php');
require_once($CFG->dirroot . '/backup/util/progress/core_backup_null_progress.class.php');
require_once($CFG->dirroot . '/backup/util/progress/core_backup_display_progress.class.php');
+require_once($CFG->dirroot . '/backup/util/progress/core_backup_display_progress_if_slow.class.php');
require_once($CFG->dirroot . '/backup/util/factories/backup_factory.class.php');
require_once($CFG->dirroot . '/backup/util/factories/restore_factory.class.php');
require_once($CFG->dirroot . '/backup/util/helper/backup_helper.class.php');
echo html_writer::end_div();
}
+ /**
+ * When progress is updated, updates the bar.
+ *
+ * @see core_backup_progress::update_progress()
+ */
public function update_progress() {
// If finished...
if (!$this->is_in_progress_section()) {
--- /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/>.
+
+/**
+ * Progress handler that uses a standard Moodle progress bar to display
+ * progress. Same as core_backup_display_progress, but the bar does not
+ * appear until a certain time has elapsed, and disappears automatically
+ * after it finishes.
+ *
+ * The bar can be re-used, i.e. if you end all sections it will disappear,
+ * but if you start all sections, a new bar will be output.
+ *
+ * @package core_backup
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_backup_display_progress_if_slow extends core_backup_display_progress {
+ /**
+ * @var int Waits this many seconds before displaying progress bar
+ */
+ const DEFAULT_DISPLAY_DELAY = 5;
+
+ /**
+ * @var int Number in the next id to use
+ */
+ private static $nextid = 1;
+
+ /**
+ * @var string HTML id for containing div
+ */
+ protected $id;
+
+ /**
+ * @var int Time at which the progress bar should display (if it isn't yet)
+ */
+ protected $starttime;
+
+ /**
+ * Constructs the progress reporter. This will not output HTML just yet,
+ * until the required delay time expires.
+ *
+ * @param int $delay Delay time (default 5 seconds)
+ */
+ public function __construct($delay = self::DEFAULT_DISPLAY_DELAY) {
+ // Set start time based on delay.
+ $this->starttime = time() + $delay;
+ parent::__construct(false);
+ }
+
+ /**
+ * Adds a div around the parent display so it can be hidden.
+ *
+ * @see core_backup_display_progress::start_html()
+ */
+ public function start_html() {
+ $this->id = 'core_backup_display_progress_if_slow' . self::$nextid;
+ self::$nextid++;
+ echo html_writer::start_div('', array('id' => $this->id));
+ parent::start_html();
+ }
+
+ /**
+ * When progress is updated, after a certain time, starts actually displaying
+ * the progress bar.
+ *
+ * @see core_backup_progress::update_progress()
+ */
+ public function update_progress() {
+ // If we haven't started yet, consider starting.
+ if ($this->starttime) {
+ if (time() > $this->starttime) {
+ $this->starttime = 0;
+ } else {
+ // Do nothing until start time.
+ return;
+ }
+ }
+
+ // We have started, so handle as default.
+ parent::update_progress();
+ }
+
+ /**
+ * Finishes parent display then closes div and hides it.
+ *
+ * @see core_backup_display_progress::end_html()
+ */
+ public function end_html() {
+ parent::end_html();
+ echo html_writer::end_div();
+ echo html_writer::script('document.getElementById("' . $this->id .
+ '").style.display = "none"');
+ }
+}
*/
protected $stage = null;
+ /**
+ * @var core_backup_progress Progress indicator (where there is no controller)
+ */
+ protected $progressreporter = null;
+
/**
* String mappings to the above stages
* @var array
public function get_restoreid() {
return $this->controller->get_restoreid();
}
+
+ /**
+ * Gets the progress reporter object in use for this restore UI.
+ *
+ * IMPORTANT: This progress reporter is used only for UI progress that is
+ * outside the restore controller. The restore controller has its own
+ * progress reporter which is used for progress during the main restore.
+ * Use the restore controller's progress reporter to report progress during
+ * a restore operation, not this one.
+ *
+ * This extra reporter is necessary because on some restore UI screens,
+ * there are long-running tasks even though there is no restore controller
+ * in use.
+ *
+ * @return core_backup_null_progress
+ */
+ public function get_progress_reporter() {
+ if (!$this->progressreporter) {
+ $this->progressreporter = new core_backup_null_progress();
+ }
+ return $this->progressreporter;
+ }
+
+ /**
+ * Sets the progress reporter that will be returned by get_progress_reporter.
+ *
+ * @param core_backup_progress $progressreporter Progress reporter
+ */
+ public function set_progress_reporter(core_backup_progress $progressreporter) {
+ $this->progressreporter = $progressreporter;
+ }
+
/**
* Executes the restore plan
* @return bool
* no use for the restore controller.
*/
abstract class restore_ui_independent_stage {
+ /**
+ * @var core_backup_progress Optional progress reporter
+ */
+ private $progressreporter;
+
abstract public function __construct($contextid);
abstract public function process();
abstract public function display(core_backup_renderer $renderer);
abstract public function get_stage();
+
+ /**
+ * Gets the progress reporter object in use for this restore UI stage.
+ *
+ * IMPORTANT: This progress reporter is used only for UI progress that is
+ * outside the restore controller. The restore controller has its own
+ * progress reporter which is used for progress during the main restore.
+ * Use the restore controller's progress reporter to report progress during
+ * a restore operation, not this one.
+ *
+ * This extra reporter is necessary because on some restore UI screens,
+ * there are long-running tasks even though there is no restore controller
+ * in use. There is a similar function in restore_ui. but that class is not
+ * used on some stages.
+ *
+ * @return core_backup_null_progress
+ */
+ public function get_progress_reporter() {
+ if (!$this->progressreporter) {
+ $this->progressreporter = new core_backup_null_progress();
+ }
+ return $this->progressreporter;
+ }
+
+ /**
+ * Sets the progress reporter that will be returned by get_progress_reporter.
+ *
+ * @param core_backup_progress $progressreporter Progress reporter
+ */
+ public function set_progress_reporter(core_backup_progress $progressreporter) {
+ $this->progressreporter = $progressreporter;
+ }
+
/**
* Gets an array of progress bar items that can be displayed through the restore renderer.
* @return array Array of items for the progress bar
*
* This is the first stage, it is independent.
*/
-class restore_ui_stage_confirm extends restore_ui_independent_stage {
+class restore_ui_stage_confirm extends restore_ui_independent_stage implements file_progress {
+
protected $contextid;
protected $filename = null;
protected $filepath = null;
protected $details;
+
+ /**
+ * @var bool True if we have started reporting progress
+ */
+ protected $startedprogress = false;
+
public function __construct($contextid) {
$this->contextid = $contextid;
$this->filename = required_param('filename', PARAM_FILE);
$this->filepath = restore_controller::get_tempdir_name($this->contextid, $USER->id);
$fb = get_file_packer();
- return ($fb->extract_to_pathname("$CFG->tempdir/backup/".$this->filename, "$CFG->tempdir/backup/$this->filepath/"));
+ $result = $fb->extract_to_pathname("$CFG->tempdir/backup/".$this->filename,
+ "$CFG->tempdir/backup/$this->filepath/", null, $this);
+
+ // If any progress happened, end it.
+ if ($this->startedprogress) {
+ $this->get_progress_reporter()->end_progress();
+ }
+ return $result;
+ }
+
+ /**
+ * Implementation for file_progress interface to display unzip progress.
+ *
+ * @param int $progress Current progress
+ * @param int $max Max value
+ */
+ public function progress($progress = file_progress::INDETERMINATE, $max = file_progress::INDETERMINATE) {
+ $reporter = $this->get_progress_reporter();
+
+ // Start tracking progress if necessary.
+ if (!$this->startedprogress) {
+ $reporter->start_progress('extract_file_to_dir',
+ ($max == file_progress::INDETERMINATE) ? core_backup_progress::INDETERMINATE : $max);
+ $this->startedprogress = true;
+ }
+
+ // Pass progress through to whatever handles it.
+ $reporter->progress(
+ ($progress == file_progress::INDETERMINATE) ? core_backup_progress::INDETERMINATE : $progress);
}
/**
if ($this->stageform === null) {
$form = new restore_schema_form($this, $PAGE->url);
$tasks = $this->ui->get_tasks();
- $content = '';
$courseheading = false;
+
+ $allsettings = array();
foreach ($tasks as $task) {
if (!($task instanceof restore_root_task)) {
if (!$courseheading) {
$form->add_heading('coursesettings', get_string('coursesettings', 'backup'));
$courseheading = true;
}
- // First add each setting
- foreach ($task->get_settings() as $setting) {
- $form->add_setting($setting, $task);
- }
- // The add all the dependencies
+ // Put each setting into an array of settings to add. Adding
+ // a setting individually is a very slow operation, so we add
+ // them all in a batch later on.
foreach ($task->get_settings() as $setting) {
- $form->add_dependencies($setting);
+ $allsettings[] = array($setting, $task);
}
} else if ($this->ui->enforce_changed_dependencies()) {
// Only show these settings if dependencies changed them.
}
}
}
+
+ // Actually add all the settings that we put in the array.
+ $form->add_settings($allsettings);
+
+ // Add the dependencies for all the settings.
+ foreach ($allsettings as $settingtask) {
+ $form->add_dependencies($settingtask[0]);
+ }
+
$this->stageform = $form;
}
return $this->stageform;
class restore_ui_stage_review extends restore_ui_stage {
/**
* Constructs the stage
- * @param backup_ui $ui
+ * @param restore_ui $ui
*/
public function __construct($ui, array $params=null) {
$this->stage = restore_ui::STAGE_REVIEW;
$content = '';
$courseheading = false;
- foreach ($this->ui->get_tasks() as $task) {
+ $progress = $this->ui->get_progress_reporter();
+ $tasks = $this->ui->get_tasks();
+ $progress->start_progress('initialise_stage_form', count($tasks));
+ $done = 1;
+ foreach ($tasks as $task) {
if ($task instanceof restore_root_task) {
// If its a backup root add a root settings heading to group nicely
$form->add_heading('rootsettings', get_string('rootsettings', 'backup'));
foreach ($task->get_settings() as $setting) {
$form->add_fixed_setting($setting, $task);
}
+ // Update progress.
+ $progress->progress($done++);
}
+ $progress->end_progress();
$this->stageform = $form;
}
return $this->stageform;
require_capability('moodle/badges:createbadge', $context);
$cloneid = $badge->make_clone();
- redirect(new moodle_url('/badges/edit.php', array('id' => $cloneid, 'action' => 'details')));
+ // If a user can edit badge details, they will be redirected to the edit page.
+ if (has_capability('moodle/badges:configuredetails', $context)) {
+ redirect(new moodle_url('/badges/edit.php', array('id' => $cloneid, 'action' => 'details')));
+ }
+ redirect(new moodle_url('/badges/overview.php', array('id' => $cloneid)));
}
if ($activate) {
$context = $badge->get_context();
$navurl = new moodle_url('/badges/index.php', array('type' => $badge->type));
+require_capability('moodle/badges:configurecriteria', $context);
+
if ($badge->type == BADGE_TYPE_COURSE) {
if (empty($CFG->badges_allowcoursebadges)) {
print_error('coursebadgesdisabled', 'badges');
if ((($update == BADGE_CRITERIA_AGGREGATION_ALL) || ($update == BADGE_CRITERIA_AGGREGATION_ANY))) {
require_sesskey();
- require_capability('moodle/badges:configurecriteria', $context);
$obj = new stdClass();
$obj->id = $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->id;
$obj->method = $update;
$context = $badge->get_context();
$navurl = new moodle_url('/badges/index.php', array('type' => $badge->type));
-require_capability('moodle/badges:configuredetails', $context);
+if ($action == 'message') {
+ require_capability('moodle/badges:configuremessages', $context);
+} else {
+ require_capability('moodle/badges:configuredetails', $context);
+}
if ($badge->type == BADGE_TYPE_COURSE) {
if (empty($CFG->badges_allowcoursebadges)) {
$newbadge = new badge($newid);
badges_process_badge_image($newbadge, $form->save_temp_file('image'));
- redirect(new moodle_url('/badges/criteria.php', array('id' => $newid)));
+ // If a user can configure badge criteria, they will be redirected to the criteria page.
+ if (has_capability('moodle/badges:configurecriteria', $PAGE->context)) {
+ redirect(new moodle_url('/badges/criteria.php', array('id' => $newid)));
+ }
+ redirect(new moodle_url('/badges/overview.php', array('id' => $newid)));
}
echo $OUTPUT->header();
$limit = block_course_overview_get_max_user_courses();
- $courses = enrol_get_my_courses('id, shortname, fullname, modinfo, sectioncache');
+ $courses = enrol_get_my_courses();
$site = get_site();
if (array_key_exists($site->id,$courses)) {
--- /dev/null
+/* RSS Feeds
+-------------------------*/
+.block_rss_client .list li:first-child {
+ border-top-width: 0;
+}
+.block_rss_client .list li {
+ border-top: 1px solid;
+ padding: 5px;
+}
\ No newline at end of file
$strcancel= get_string('cancel');
$stractivityclipboard = $USER->activitycopyname;
}
- // Casting $course->modinfo to string prevents one notice when the field is null.
$editbuttons = '';
if ($ismoving) {
$strcancel= get_string('cancel');
$stractivityclipboard = $USER->activitycopyname;
}
- // Casting $course->modinfo to string prevents one notice when the field is null.
$editbuttons = '';
if ($ismoving) {
}
require_once('edit_form.php');
-$summaryoptions = array('subdirs'=>false, 'maxfiles'=> 99, 'maxbytes'=>$CFG->maxbytes, 'trusttext'=>true, 'context'=>$sitecontext);
+$summaryoptions = array('maxfiles'=> 99, 'maxbytes'=>$CFG->maxbytes, 'trusttext'=>true, 'context'=>$sitecontext,
+ 'subdirs'=>file_area_contains_subdirs($sitecontext, 'blog', 'post', $entry->id));
$attachmentoptions = array('subdirs'=>false, 'maxfiles'=> 99, 'maxbytes'=>$CFG->maxbytes);
$blogeditform = new blog_edit_form(null, compact('entry', 'summaryoptions', 'attachmentoptions', 'sitecontext', 'courseid', 'modid'));
tag_set('post', $this->id, $this->tags);
// 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 = \core\event\blog_entry_created::create(array(
+ 'objectid' => $this->id,
+ 'relateduserid' => $this->userid,
+ 'other' => array('subject' => $this->subject)
+ ));
$event->set_custom_data($this);
$event->trigger();
}
/**
* Updates this entry in the database. Access control checks must be done by calling code.
*
- * @param mform $form Used for attachments
+ * @param array $params Entry parameters.
+ * @param moodleform $form Used for attachments.
+ * @param array $summaryoptions Summary options.
+ * @param array $attachmentoptions Attachment options.
+ *
* @return void
*/
public function edit($params=array(), $form=null, $summaryoptions=array(), $attachmentoptions=array()) {
- global $CFG, $USER, $DB, $PAGE;
+ global $CFG, $DB;
$sitecontext = context_system::instance();
$entry = $this;
$entry->lastmodified = time();
- // Update record
+ // Update record.
$DB->update_record('post', $entry);
tag_set('post', $entry->id, $entry->tags);
- add_to_log(SITEID, 'blog', 'update', 'index.php?userid='.$USER->id.'&entryid='.$entry->id, $entry->subject);
- events_trigger('blog_entry_edited', $entry);
+ $event = \core\event\blog_entry_updated::create(array(
+ 'objectid' => $entry->id,
+ 'relateduserid' => $entry->userid,
+ 'other' => array('subject' => $entry->subject)
+ ));
+ $event->set_custom_data($entry);
+ $event->trigger();
}
/**
$DB->delete_records('post', array('id' => $this->id));
tag_set('post', $this->id, array());
- $event = \core\event\blog_entry_deleted::create(array('objectid' => $this->id,
- 'userid' => $this->userid,
- 'other' => array("record" => (array)$record)
- ));
+ $event = \core\event\blog_entry_deleted::create(array(
+ 'objectid' => $this->id,
+ 'relateduserid' => $this->userid,
+ 'other' => array('record' => (array) $record)
+ ));
$event->add_record_snapshot("post", $record);
$event->set_custom_data($this);
$event->trigger();
require_once($CFG->dirroot . '/blog/locallib.php');
require_once($CFG->dirroot . '/blog/lib.php');
-
/**
* Test functions that rely on the DB tables
*/
/**
* Test various blog related events.
*/
- public function test_blog_entry_events() {
- global $USER, $DB;
+ public function test_blog_entry_created_event() {
+ global $USER;
$this->setAdminUser();
$this->resetAfterTest();
- // Create a blog entry.
+ // Create a blog entry for another user as Admin.
+ $sink = $this->redirectEvents();
$blog = new blog_entry();
- $blog->summary = "This is summary of blog";
$blog->subject = "Subject of blog";
+ $blog->userid = $this->userid;
$states = blog_entry::get_applicable_publish_states();
$blog->publishstate = reset($states);
- $sink = $this->redirectEvents();
$blog->add();
$events = $sink->get_events();
+ $sink->close();
$event = reset($events);
$sitecontext = context_system::instance();
$this->assertEquals($sitecontext->id, $event->contextid);
$this->assertEquals($blog->id, $event->objectid);
$this->assertEquals($USER->id, $event->userid);
+ $this->assertEquals($this->userid, $event->relateduserid);
$this->assertEquals("post", $event->objecttable);
+ $arr = array(SITEID, 'blog', 'add', 'index.php?userid=' . $this->userid . '&entryid=' . $blog->id, $blog->subject);
+ $this->assertEventLegacyLogData($arr, $event);
+ $this->assertEquals("blog_entry_added", $event->get_legacy_eventname());
+ $this->assertEventLegacyData($blog, $event);
+ }
+
+ /**
+ * Tests for event blog_entry_updated.
+ */
+ public function test_blog_entry_updated_event() {
+ global $USER;
+
+ $this->setAdminUser();
+ $this->resetAfterTest();
+ $sitecontext = context_system::instance();
- // Delete a blog entry.
+ // Edit a blog entry as Admin.
+ $blog = new blog_entry($this->postid);
+ $sink = $this->redirectEvents();
+ $blog->summary_editor = array('text' => 'Something', 'format' => FORMAT_MOODLE);
+ $blog->edit(array(), null, array(), array());
+ $events = $sink->get_events();
+ $event = array_pop($events);
+ $sink->close();
+
+ // Validate event data.
+ $this->assertInstanceOf('\core\event\blog_entry_updated', $event);
+ $this->assertEquals($sitecontext->id, $event->contextid);
+ $this->assertEquals($blog->id, $event->objectid);
+ $this->assertEquals($USER->id, $event->userid);
+ $this->assertEquals($this->userid, $event->relateduserid);
+ $this->assertEquals("post", $event->objecttable);
+ $this->assertEquals("blog_entry_edited", $event->get_legacy_eventname());
+ $this->assertEventLegacyData($blog, $event);
+ $arr = array (SITEID, 'blog', 'update', 'index.php?userid=' . $this->userid . '&entryid=' . $blog->id, $blog->subject);
+ $this->assertEventLegacyLogData($arr, $event);
+ }
+
+ /**
+ * Tests for event blog_entry_deleted.
+ */
+ public function test_blog_entry_deleted_event() {
+ global $USER, $DB;
+
+ $this->setAdminUser();
+ $this->resetAfterTest();
+ $sitecontext = context_system::instance();
+
+ // Delete a user blog entry as Admin.
+ $blog = new blog_entry($this->postid);
+ $sink = $this->redirectEvents();
$record = $DB->get_record('post', array('id' => $blog->id));
$blog->delete();
$events = $sink->get_events();
$event = array_pop($events);
+ $sink->close();
// Validate event data.
$this->assertInstanceOf('\core\event\blog_entry_deleted', $event);
- $this->assertEquals(context_system::instance()->id, $event->contextid);
+ $this->assertEquals($sitecontext->id, $event->contextid);
$this->assertEquals($blog->id, $event->objectid);
$this->assertEquals($USER->id, $event->userid);
+ $this->assertEquals($this->userid, $event->relateduserid);
$this->assertEquals("post", $event->objecttable);
$this->assertEquals($record, $event->get_record_snapshot("post", $blog->id));
$this->assertSame('blog_entry_deleted', $event->get_legacy_eventname());
-
+ $arr = array(SITEID, 'blog', 'delete', 'index.php?userid=' . $blog->userid, 'deleted blog entry with entry id# ' .
+ $blog->id);
+ $this->assertEventLegacyLogData($arr, $event);
+ $this->assertEventLegacyData($blog, $event);
}
}
+
*/
public function set_identifiers(array $identifiers = array()) {
foreach ($this->requireidentifiers as $identifier) {
- if (!array_key_exists($identifier, $identifiers)) {
+ if (!isset($identifiers[$identifier])) {
throw new coding_exception('Identifier required for cache has not been provided: '.$identifier);
}
}
*/
public function create_cache_from_definition($component, $area, array $identifiers = array(), $aggregate = null) {
$definitionname = $component.'/'.$area;
- if (array_key_exists($definitionname, $this->cachesfromdefinitions)) {
+ if (isset($this->cachesfromdefinitions[$definitionname])) {
$cache = $this->cachesfromdefinitions[$definitionname];
$cache->set_identifiers($identifiers);
return $cache;
if ($aggregate) {
$id .= '::'.$aggregate;
}
- if (!array_key_exists($id, $this->definitions)) {
+ if (!isset($this->definitions[$id])) {
// This is the first time this definition has been requested.
if ($this->is_initialising()) {
// We're initialising the cache right now. Don't try to create another config instance.
--- /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/>.
+
+namespace core_calendar;
+
+/**
+ * Defines functions used by calendar type plugins.
+ *
+ * This library provides a unified interface for calendar types.
+ *
+ * @package core_calendar
+ * @copyright 2008 onwards Foodle Group {@link http://foodle.org}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class type_base {
+
+ /**
+ * Returns a list of all the possible days for all months.
+ *
+ * This is used to generate the select box for the days
+ * in the date selector elements. Some months contain more days
+ * than others so this function should return all possible days as
+ * we can not predict what month will be chosen (the user
+ * may have JS turned off and we need to support this situation in
+ * Moodle).
+ *
+ * @return array the days
+ */
+ public abstract function get_days();
+
+ /**
+ * Returns a list of all the names of the months.
+ *
+ * @return array the month names
+ */
+ public abstract function get_months();
+
+ /**
+ * Returns the minimum year of the calendar.
+ *
+ * @return int the minumum year
+ */
+ public abstract function get_min_year();
+
+ /**
+ * Returns the maximum year of the calendar.
+ *
+ * @return int the max year
+ */
+ public abstract function get_max_year();
+
+ /**
+ * Returns a formatted string that represents a date in user time.
+ *
+ * @param int $date the timestamp in UTC, as obtained from the database
+ * @param string $format strftime format
+ * @param int|float|string $timezone the timezone to use
+ * {@link http://docs.moodle.org/dev/Time_API#Timezone}
+ * @param bool $fixday if true then the leading zero from %d is removed,
+ * if false then the leading zero is maintained
+ * @param bool $fixhour if true then the leading zero from %I is removed,
+ * if false then the leading zero is maintained
+ * @return string the formatted date/time
+ */
+ public abstract function timestamp_to_date_string($date, $format, $timezone, $fixday, $fixhour);
+
+ /**
+ * Given a $time timestamp in GMT (seconds since epoch), returns an array that represents
+ * the date in user time.
+ *
+ * @param int $time timestamp in GMT
+ * @param float|int|string $timezone the timezone to use to calculate the time
+ * {@link http://docs.moodle.org/dev/Time_API#Timezone}
+ * @return array an array that represents the date in user time
+ */
+ public abstract function timestamp_to_date_array($time, $timezone);
+
+ /**
+ * Provided with a day, month, year, hour and minute in the specific
+ * calendar type convert it into the equivalent Gregorian date.
+ *
+ * @param int $year
+ * @param int $month
+ * @param int $day
+ * @param int $hour
+ * @param int $minute
+ * @return array the converted day, month and year.
+ */
+ public abstract function convert_to_gregorian($year, $month, $day, $hour = 0, $minute = 0);
+
+ /**
+ * Provided with a day, month, year, hour and minute in a Gregorian date
+ * convert it into the specific calendar type date.
+ *
+ * @param int $year
+ * @param int $month
+ * @param int $day
+ * @param int $hour
+ * @param int $minute
+ * @return array the converted day, month and year.
+ */
+ public abstract function convert_from_gregorian($year, $month, $day, $hour = 0, $minute = 0);
+}
--- /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/>.
+
+namespace core_calendar;
+
+/**
+ * Class \core_calendar\type_factory.
+ *
+ * Factory class producing required subclasses of {@link \core_calendar\type_base}.
+ *
+ * @package core_calendar
+ * @copyright 2008 onwards Foodle Group {@link http://foodle.org}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class type_factory {
+
+ /**
+ * Returns an instance of the currently used calendar type.
+ *
+ * @param string|null $type the calendar type to use, if none provided use logic to determine
+ * @return calendartype_* the created calendar_type class
+ * @throws coding_exception if the calendar type file could not be loaded
+ */
+ public static function get_calendar_instance($type = null) {
+ if (is_null($type)) {
+ $type = self::get_calendar_type();
+ }
+
+ $class = "\\calendartype_$type\\structure";
+
+ // Ensure the calendar type exists. It may occur that a user has selected a calendar type, which was then
+ // deleted. If this happens we want to fall back on the Gregorian calendar type.
+ if (!class_exists($class)) {
+ $class = "\\calendartype_gregorian\\structure";
+ }
+
+ return new $class();
+ }
+
+ /**
+ * Returns a list of calendar typess available for use.
+ *
+ * @return array the list of calendar types
+ */
+ public static function get_list_of_calendar_types() {
+ $calendars = array();
+ $calendardirs = \core_component::get_plugin_list('calendartype');
+
+ foreach ($calendardirs as $name => $location) {
+ $calendars[$name] = get_string('name', "calendartype_{$name}");
+ }
+
+ return $calendars;
+ }
+
+ /**
+ * Returns the current calendar type in use.
+ *
+ * @return string the current calendar type being used
+ */
+ public static function get_calendar_type() {
+ global $CFG, $USER, $SESSION, $COURSE;
+
+ if (!empty($COURSE->id) and $COURSE->id != SITEID and !empty($COURSE->calendartype)) { // Course calendartype can override all other settings for this page.
+ $return = $COURSE->calendartype;
+ } else if (!empty($SESSION->calendartype)) { // Session calendartype can override other settings.
+ $return = $SESSION->calendartype;
+ } else if (!empty($USER->calendartype)) {
+ $return = $USER->calendartype;
+ } else if (!empty($CFG->calendartype)) {
+ $return = $CFG->calendartype;
+ } else {
+ $return = 'gregorian';
+ }
+
+ return $return;
+ }
+}
--- /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/>.
+
+/**
+ * This file contains the class that handles testing the calendar type system.
+ *
+ * @package core_calendar
+ * @copyright 2013 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+// The test calendar type.
+require_once($CFG->dirroot . '/calendar/tests/calendartype_test_example.php');
+
+// Used to test the dateselector elements.
+require_once($CFG->libdir . '/form/dateselector.php');
+require_once($CFG->libdir . '/form/datetimeselector.php');
+
+// Used to test the user datetime profile field.
+require_once($CFG->dirroot . '/user/profile/lib.php');
+require_once($CFG->dirroot . '/user/profile/definelib.php');
+require_once($CFG->dirroot . '/user/profile/index_field_form.php');
+
+/**
+ * Unit tests for the calendar type system.
+ *
+ * @package core_calendar
+ * @copyright 2013 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.6
+ */
+class core_calendar_type_testcase extends advanced_testcase {
+
+ /**
+ * The test user.
+ */
+ private $user;
+
+ /**
+ * Test set up.
+ */
+ protected function setUp() {
+ // The user we are going to test this on.
+ $this->user = self::getDataGenerator()->create_user();
+ self::setUser($this->user);
+ }
+
+ /**
+ * Test that setting the calendar type works.
+ */
+ public function test_calendar_type_set() {
+ // We want to reset the test data after this run.
+ $this->resetAfterTest();
+
+ // Test setting it as the 'Test' calendar type.
+ $this->set_calendar_type('test');
+ $this->assertEquals('test', \core_calendar\type_factory::get_calendar_type());
+
+ // Test setting it as the 'Gregorian' calendar type.
+ $this->set_calendar_type('gregorian');
+ $this->assertEquals('gregorian', \core_calendar\type_factory::get_calendar_type());
+ }
+
+ /**
+ * Test that calling core Moodle functions responsible for displaying the date
+ * have the same results as directly calling the same function in the calendar type.
+ */
+ public function test_calendar_type_core_functions() {
+ // We want to reset the test data after this run.
+ $this->resetAfterTest();
+
+ // Test that the core functions reproduce the same results as the Gregorian calendar.
+ $this->core_functions_test('gregorian');
+
+ // Test that the core functions reproduce the same results as the test calendar.
+ $this->core_functions_test('test');
+ }
+
+ /**
+ * Test that dates selected using the date selector elements are being saved as unixtime, and that the
+ * unixtime is being converted back to a valid date to display in the date selector elements for
+ * different calendar types.
+ */
+ public function test_calendar_type_dateselector_elements() {
+ // We want to reset the test data after this run.
+ $this->resetAfterTest();
+
+ // Check converting dates to Gregorian when submitting a date selector element works. Note: the test
+ // calendar is 2 years, 2 months, 2 days, 2 hours and 2 minutes ahead of the Gregorian calendar.
+ $date1 = array();
+ $date1['day'] = 4;
+ $date1['month'] = 7;
+ $date1['year'] = 2013;
+ $date1['hour'] = 0;
+ $date1['minute'] = 0;
+ $date1['timestamp'] = 1372896000;
+ $this->convert_dateselector_to_unixtime_test('dateselector', 'gregorian', $date1);
+
+ $date2 = array();
+ $date2['day'] = 7;
+ $date2['month'] = 9;
+ $date2['year'] = 2015;
+ $date2['hour'] = 0; // The dateselector element does not have hours.
+ $date2['minute'] = 0; // The dateselector element does not have minutes.
+ $date2['timestamp'] = 1372896000;
+ $this->convert_dateselector_to_unixtime_test('dateselector', 'test', $date2);
+
+ $date3 = array();
+ $date3['day'] = 4;
+ $date3['month'] = 7;
+ $date3['year'] = 2013;
+ $date3['hour'] = 23;
+ $date3['minute'] = 15;
+ $date3['timestamp'] = 1372979700;
+ $this->convert_dateselector_to_unixtime_test('datetimeselector', 'gregorian', $date3);
+
+ $date4 = array();
+ $date4['day'] = 7;
+ $date4['month'] = 9;
+ $date4['year'] = 2015;
+ $date4['hour'] = 1;
+ $date4['minute'] = 17;
+ $date4['timestamp'] = 1372979700;
+ $this->convert_dateselector_to_unixtime_test('datetimeselector', 'test', $date4);
+
+ // The date selector element values are set by using the function usergetdate, here we want to check that
+ // the unixtime passed is being successfully converted to the correct values for the calendar type.
+ $this->convert_unixtime_to_dateselector_test('gregorian', $date3);
+ $this->convert_unixtime_to_dateselector_test('test', $date4);
+ }
+
+ /**
+ * Test that the user profile field datetime minimum and maximum year settings are saved as the
+ * equivalent Gregorian years.
+ */
+ public function test_calendar_type_datetime_field_submission() {
+ // We want to reset the test data after this run.
+ $this->resetAfterTest();
+
+ // Create an array with the input values and expected values once submitted.
+ $date = array();
+ $date['inputminyear'] = '1970';
+ $date['inputmaxyear'] = '2013';
+ $date['expectedminyear'] = '1970';
+ $date['expectedmaxyear'] = '2013';
+ $this->datetime_field_submission_test('gregorian', $date);
+
+ // The test calendar is 2 years, 2 months, 2 days in the future, so when the year 1970 is submitted,
+ // the year 1967 should be saved in the DB, as 1/1/1970 converts to 30/10/1967 in Gregorian.
+ $date['expectedminyear'] = '1967';
+ $date['expectedmaxyear'] = '2010';
+ $this->datetime_field_submission_test('test', $date);
+ }
+
+ /**
+ * Test all the core functions that use the calendar type system.
+ *
+ * @param string $type the calendar type we want to test
+ */
+ private function core_functions_test($type) {
+ $this->set_calendar_type($type);
+
+ // Get the calendar.
+ $calendar = \core_calendar\type_factory::get_calendar_instance();
+
+ // Test the userdate function.
+ $this->assertEquals($calendar->timestamp_to_date_string($this->user->timecreated, '', 99, true, true),
+ userdate($this->user->timecreated));
+ }
+
+ /**
+ * Simulates submitting a form with a date selector element and tests that the chosen dates
+ * are converted into unixtime before being saved in DB.
+ *
+ * @param string $element the form element we are testing
+ * @param string $type the calendar type we want to test
+ * @param array $date the date variables
+ */
+ private function convert_dateselector_to_unixtime_test($element, $type, $date) {
+ $this->set_calendar_type($type);
+
+ if ($element == 'dateselector') {
+ $el = new MoodleQuickForm_date_selector('dateselector', null, array('timezone' => 0.0, 'step' => 1));
+ } else {
+ $el = new MoodleQuickForm_date_time_selector('dateselector', null, array('timezone' => 0.0, 'step' => 1));
+ }
+ $el->_createElements();
+ $submitvalues = array('dateselector' => $date);
+
+ $this->assertSame($el->exportValue($submitvalues), array('dateselector' => $date['timestamp']));
+ }
+
+ /**
+ * Test converting dates from unixtime to a date for the calendar type specified.
+ *
+ * @param string $type the calendar type we want to test
+ * @param array $date the date variables
+ */
+ private function convert_unixtime_to_dateselector_test($type, $date) {
+ $this->set_calendar_type($type);
+
+ // Get the calendar.
+ $calendar = \core_calendar\type_factory::get_calendar_instance();
+
+ $usergetdate = $calendar->timestamp_to_date_array($date['timestamp'], 0.0);
+ $comparedate = array(
+ 'minute' => $usergetdate['minutes'],
+ 'hour' => $usergetdate['hours'],
+ 'day' => $usergetdate['mday'],
+ 'month' => $usergetdate['mon'],
+ 'year' => $usergetdate['year'],
+ 'timestamp' => $date['timestamp']
+ );
+
+ $this->assertEquals($comparedate, $date);
+ }
+
+ /**
+ * Test saving the minimum and max year settings for the user datetime field.
+ *
+ * @param string $type the calendar type we want to test
+ * @param array $date the date variables
+ */
+ private function datetime_field_submission_test($type, $date) {
+ $this->set_calendar_type($type);
+
+ // Get the data we are submitting for the form.
+ $formdata = array();
+ $formdata['id'] = 0;
+ $formdata['shortname'] = 'Shortname';
+ $formdata['name'] = 'Name';
+ $formdata['param1'] = $date['inputminyear'];
+ $formdata['param2'] = $date['inputmaxyear'];
+
+ // Mock submitting this.
+ field_form::mock_submit($formdata);
+
+ // Create the user datetime form.
+ $form = new field_form(null, 'datetime');
+
+ // Get the data from the submission.
+ $submissiondata = $form->get_data();
+ // On the user profile field page after get_data, the function define_save is called
+ // in the field base class, which then calls the field's function define_save_preprocess.
+ $field = new profile_define_datetime();
+ $submissiondata = $field->define_save_preprocess($submissiondata);
+
+ // Create an array we want to compare with the date passed.
+ $comparedate = $date;
+ $comparedate['expectedminyear'] = $submissiondata->param1;
+ $comparedate['expectedmaxyear'] = $submissiondata->param2;
+
+ $this->assertEquals($comparedate, $date);
+ }
+
+ /**
+ * Set the calendar type for this user.
+ *
+ * @param string $type the calendar type we want to set
+ */
+ private function set_calendar_type($type) {
+ $this->user->calendartype = $type;
+ session_set_user($this->user);
+ }
+}
--- /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/>.
+
+namespace calendartype_test;
+use \core_calendar\type_base;
+
+/**
+ * Handles calendar functions for the test calendar.
+ *
+ * The test calendar is going to be 2 years, 2 days, 2 hours and 2 minutes
+ * in the future of the Gregorian calendar.
+ *
+ * @package core_calendar
+ * @copyright 2013 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class structure extends type_base {
+
+ /**
+ * Returns a list of all the possible days for all months.
+ *
+ * This is used to generate the select box for the days
+ * in the date selector elements. Some months contain more days
+ * than others so this function should return all possible days as
+ * we can not predict what month will be chosen (the user
+ * may have JS turned off and we need to support this situation in
+ * Moodle).
+ *
+ * @return array the days
+ */
+ public function get_days() {
+ $days = array();
+
+ for ($i = 1; $i <= 31; $i++) {
+ $days[$i] = $i;
+ }
+
+ return $days;
+ }
+
+ /**
+ * Returns a list of all the names of the months.
+ *
+ * @return array the month names
+ */
+ public function get_months() {
+ $months = array();
+
+ for ($i = 1; $i <= 12; $i++) {
+ $months[$i] = $i;
+ }
+
+ return $months;
+ }
+
+ /**
+ * Returns the minimum year of the calendar.
+ *
+ * @return int the minumum year
+ */
+ public function get_min_year() {
+ return 1970;
+ }
+
+ /**
+ * Returns the maximum year of the calendar.
+ *
+ * @return int the max year
+ */
+ public function get_max_year() {
+ return 2050;
+ }
+
+ /**
+ * Returns a formatted string that represents a date in user time.
+ *
+ * @param int $date the timestamp in UTC, as obtained from the database
+ * @param string $format strftime format
+ * @param int|float|string $timezone the timezone to use
+ * {@link http://docs.moodle.org/dev/Time_API#Timezone}
+ * @param bool $fixday if true then the leading zero from %d is removed,
+ * if false then the leading zero is maintained
+ * @param bool $fixhour if true then the leading zero from %I is removed,
+ * if false then the leading zero is maintained
+ * @return string the formatted date/time
+ */
+ public function timestamp_to_date_string($date, $format, $timezone, $fixday, $fixhour) {
+ return '';
+ }
+
+ /**
+ * Given a $time timestamp in GMT (seconds since epoch), returns an array that represents
+ * the date in user time.
+ *
+ * @param int $time timestamp in GMT
+ * @param float|int|string $timezone the timezone to use to calculate the time
+ * {@link http://docs.moodle.org/dev/Time_API#Timezone}
+ * @return array an array that represents the date in user time
+ */
+ public function timestamp_to_date_array($time, $timezone) {
+ $gregoriancalendar = \core_calendar\type_factory::get_calendar_instance('gregorian');
+ $date = $gregoriancalendar->timestamp_to_date_array($time, $timezone);
+ $newdate = $this->convert_from_gregorian($date['year'], $date['mon'], $date['mday'],
+ $date['hours'], $date['minutes']);
+
+ $date['year'] = $newdate['year'];
+ $date['mon'] = $newdate['month'];
+ $date['mday'] = $newdate['day'];
+ $date['hours'] = $newdate['hour'];
+ $date['minutes'] = $newdate['minute'];
+
+ return $date;
+ }
+
+ /**
+ * Provided with a day, month, year, hour and minute
+ * convert it into the equivalent Gregorian date.
+ *
+ * @param int $year
+ * @param int $month
+ * @param int $day
+ * @param int $hour
+ * @param int $minute
+ * @return array the converted day, month, year, hour and minute.
+ */
+ public function convert_to_gregorian($year, $month, $day, $hour = 0, $minute = 0) {
+ $timestamp = make_timestamp($year, $month, $day, $hour, $minute);
+ $date = date('Y/n/j/H/i', strtotime('-2 year, -2 months, -2 days, -2 hours, -2 minutes', $timestamp));
+
+ list($year, $month, $day, $hour, $minute) = explode('/', $date);
+
+ return array('year' => (int) $year,
+ 'month' => (int) $month,
+ 'day' => (int) $day,
+ 'hour' => (int) $hour,
+ 'minute' => (int) $minute);
+
+ }
+
+ /**
+ * Provided with a day, month, year, hour and minute in a Gregorian date
+ * convert it into the specific calendar type date.
+ *
+ * @param int $year
+ * @param int $month
+ * @param int $day
+ * @param int $hour
+ * @param int $minute
+ * @return array the converted day, month, year, hour and minute.
+ */
+ public function convert_from_gregorian($year, $month, $day, $hour = 0, $minute = 0) {
+ $timestamp = make_timestamp($year, $month, $day, $hour, $minute);
+ $date = date('Y/n/j/H/i', strtotime('+2 year, +2 months, +2 days, +2 hours, +2 minutes', $timestamp));
+
+ list($year, $month, $day, $hour, $minute) = explode('/', $date);
+
+ return array('year' => (int) $year,
+ 'month' => (int) $month,
+ 'day' => (int) $day,
+ 'hour' => (int) $hour,
+ 'minute' => (int) $minute);
+ }
+}
--- /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/>.
+
+namespace calendartype_gregorian;
+use core_calendar\type_base;
+
+/**
+ * Handles calendar functions for the gregorian calendar.
+ *
+ * @package calendartype_gregorian
+ * @copyright 2008 onwards Foodle Group {@link http://foodle.org}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class structure extends type_base {
+
+ /**
+ * Returns a list of all the possible days for all months.
+ *
+ * This is used to generate the select box for the days
+ * in the date selector elements. Some months contain more days
+ * than others so this function should return all possible days as
+ * we can not predict what month will be chosen (the user
+ * may have JS turned off and we need to support this situation in
+ * Moodle).
+ *
+ * @return array the days
+ */
+ public function get_days() {
+ $days = array();
+
+ for ($i = 1; $i <= 31; $i++) {
+ $days[$i] = $i;
+ }
+
+ return $days;
+ }
+
+ /**
+ * Returns a list of all the names of the months.
+ *
+ * @return array the month names
+ */
+ public function get_months() {
+ $months = array();
+
+ for ($i = 1; $i <= 12; $i++) {
+ $months[$i] = userdate(gmmktime(12, 0, 0, $i, 15, 2000), '%B');
+ }
+
+ return $months;
+ }
+
+ /**
+ * Returns the minimum year of the calendar.
+ *
+ * @return int the minumum year
+ */
+ public function get_min_year() {
+ return 1900;
+ }
+
+ /**
+ * Returns the maximum year of the calendar.
+ *
+ * @return int the max year
+ */
+ public function get_max_year() {
+ return 2050;
+ }
+
+ /**
+ * Returns a formatted string that represents a date in user time.
+ *
+ * Returns a formatted string that represents a date in user time
+ * <b>WARNING: note that the format is for strftime(), not date().</b>
+ * Because of a bug in most Windows time libraries, we can't use
+ * the nicer %e, so we have to use %d which has leading zeroes.
+ * A lot of the fuss in the function is just getting rid of these leading
+ * zeroes as efficiently as possible.
+ *
+ * If parameter fixday = true (default), then take off leading
+ * zero from %d, else maintain it.
+ *
+ * @param int $date the timestamp in UTC, as obtained from the database
+ * @param string $format strftime format
+ * @param int|float|string $timezone the timezone to use
+ * {@link http://docs.moodle.org/dev/Time_API#Timezone}
+ * @param bool $fixday if true then the leading zero from %d is removed,
+ * if false then the leading zero is maintained
+ * @param bool $fixhour if true then the leading zero from %I is removed,
+ * if false then the leading zero is maintained
+ * @return string the formatted date/time
+ */
+ public function timestamp_to_date_string($date, $format, $timezone, $fixday, $fixhour) {
+ global $CFG;
+
+ if (empty($format)) {
+ $format = get_string('strftimedaydatetime', 'langconfig');
+ }
+
+ if (!empty($CFG->nofixday)) { // Config.php can force %d not to be fixed.
+ $fixday = false;
+ } else if ($fixday) {
+ $formatnoday = str_replace('%d', 'DD', $format);
+ $fixday = ($formatnoday != $format);
+ $format = $formatnoday;
+ }
+
+ // Note: This logic about fixing 12-hour time to remove unnecessary leading
+ // zero is required because on Windows, PHP strftime function does not
+ // support the correct 'hour without leading zero' parameter (%l).
+ if (!empty($CFG->nofixhour)) {
+ // Config.php can force %I not to be fixed.
+ $fixhour = false;
+ } else if ($fixhour) {
+ $formatnohour = str_replace('%I', 'HH', $format);
+ $fixhour = ($formatnohour != $format);
+ $format = $formatnohour;
+ }
+
+ // Add daylight saving offset for string timezones only, as we can't get dst for
+ // float values. if timezone is 99 (user default timezone), then try update dst.
+ if ((99 == $timezone) || !is_numeric($timezone)) {
+ $date += dst_offset_on($date, $timezone);
+ }
+
+ $timezone = get_user_timezone_offset($timezone);
+
+ // If we are running under Windows convert to windows encoding and then back to UTF-8
+ // (because it's impossible to specify UTF-8 to fetch locale info in Win32).
+ if (abs($timezone) > 13) { // Server time.
+ $datestring = date_format_string($date, $format, $timezone);
+ if ($fixday) {
+ $daystring = ltrim(str_replace(array(' 0', ' '), '', strftime(' %d', $date)));
+ $datestring = str_replace('DD', $daystring, $datestring);
+ }
+ if ($fixhour) {
+ $hourstring = ltrim(str_replace(array(' 0', ' '), '', strftime(' %I', $date)));
+ $datestring = str_replace('HH', $hourstring, $datestring);
+ }
+ } else {
+ $date += (int)($timezone * 3600);
+ $datestring = date_format_string($date, $format, $timezone);
+ if ($fixday) {
+ $daystring = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %d', $date)));
+ $datestring = str_replace('DD', $daystring, $datestring);
+ }
+ if ($fixhour) {
+ $hourstring = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %I', $date)));
+ $datestring = str_replace('HH', $hourstring, $datestring);
+ }
+ }
+
+ return $datestring;
+ }
+
+ /**
+ * Given a $time timestamp in GMT (seconds since epoch), returns an array that
+ * represents the date in user time.
+ *
+ * @param int $time Timestamp in GMT
+ * @param float|int|string $timezone offset's time with timezone, if float and not 99, then no
+ * dst offset is applied {@link http://docs.moodle.org/dev/Time_API#Timezone}
+ * @return array an array that represents the date in user time
+ */
+ public function timestamp_to_date_array($time, $timezone) {
+ return usergetdate($time, $timezone);
+ }
+
+ /**
+ * Provided with a day, month, year, hour and minute in a specific
+ * calendar type convert it into the equivalent Gregorian date.
+ *
+ * In this function we don't need to do anything except pass the data
+ * back as an array. This is because the date received is Gregorian.
+ *
+ * @param int $year
+ * @param int $month
+ * @param int $day
+ * @param int $hour
+ * @param int $minute
+ * @return array the converted day, month, year, hour and minute.
+ */
+ public function convert_from_gregorian($year, $month, $day, $hour = 0, $minute = 0) {
+ $date = array();
+ $date['year'] = $year;
+ $date['month'] = $month;
+ $date['day'] = $day;
+ $date['hour'] = $hour;
+ $date['minute'] = $minute;
+
+ return $date;
+ }
+
+ /**
+ * Provided with a day, month, year, hour and minute in a specific
+ * calendar type convert it into the equivalent Gregorian date.
+ *
+ * In this function we don't need to do anything except pass the data
+ * back as an array. This is because the date received is Gregorian.
+ *
+ * @param int $year
+ * @param int $month
+ * @param int $day
+ * @param int $hour
+ * @param int $minute
+ * @return array the converted day, month, year, hour and minute.
+ */
+ public function convert_to_gregorian($year, $month, $day, $hour = 0, $minute = 0) {
+ $date = array();
+ $date['year'] = $year;
+ $date['month'] = $month;
+ $date['day'] = $day;
+ $date['hour'] = $hour;
+ $date['minute'] = $minute;
+
+ return $date;
+ }
+}
--- /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/>.
+
+/**
+ * Strings for component 'calendartype_gregorian', language 'en'.
+ *
+ * @package calendartype_gregorian
+ * @copyright 2008 onwards Foodle Group {@link http://foodle.org}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['name'] = 'Gregorian';
+$string['pluginname'] = 'Gregorian calendar type';
--- /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/>.
+
+/**
+ * Version details.
+ *
+ * @package calendartype_gregorian
+ * @copyright 2008 onwards Foodle Group {@link http://foodle.org}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version = 2013082300; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2012120300; // Requires this Moodle version.
+$plugin->component = 'calendartype_gregorian'; // Full name of the plugin (used for diagnostics).
// config.php file
// $CFG->preventexecpath = true;
//
+// Use the following flag to set userid for noreply user. If not set then moodle will
+// create dummy user and use -ve value as user id.
+// $CFG->noreplyuserid = -10;
+//
+// As of version 2.6 Moodle supports admin to set support user. If not set, all mails
+// will be sent to supportemail.
+// $CFG->supportuserid = -20;
+//
//=========================================================================
// 7. SETTINGS FOR DEVELOPMENT SERVERS - not intended for production use!!!
//=========================================================================
$DB->set_field('course_modules', 'instance', $instanceid, array('id' => $this->cm->id));
// Rebuild the course cache after update action
rebuild_course_cache($this->course->id, true);
- $this->course->modinfo = null; // Otherwise we will just get the old version back again.
$sectionid = course_add_cm_to_section($this->course, $this->cm->id, $this->section);
if (!empty($course)) {
//add context for editor
$editoroptions['context'] = $coursecontext;
+ $editoroptions['subdirs'] = file_area_contains_subdirs($coursecontext, 'course', 'summary', 0);
$course = file_prepare_standard_editor($course, 'summary', $editoroptions, $coursecontext, 'course', 'summary', 0);
if ($overviewfilesoptions) {
file_prepare_standard_filemanager($course, 'overviewfiles', $overviewfilesoptions, $coursecontext, 'course', 'overviewfiles', 0);
} else {
//editor should respect category context if course context is not set.
$editoroptions['context'] = $catcontext;
+ $editoroptions['subdirs'] = 0;
$course = file_prepare_standard_editor($course, 'summary', $editoroptions, null, 'course', 'summary', null);
if ($overviewfilesoptions) {
file_prepare_standard_filemanager($course, 'overviewfiles', $overviewfilesoptions, null, 'course', 'overviewfiles', 0);
require_once($CFG->libdir.'/completionlib.php');
require_once($CFG->libdir. '/coursecatlib.php');
+/**
+ * The form for handling editing a course.
+ */
class course_edit_form extends moodleform {
protected $course;
protected $context;
+ /**
+ * Form definition.
+ */
function definition() {
- global $USER, $CFG, $DB, $PAGE;
+ global $CFG, $PAGE;
$mform = $this->_form;
$PAGE->requires->yui_module('moodle-course-formatchooser', 'M.course.init_formatchooser',
$this->course = $course;
$this->context = $context;
-/// form definition with new course defaults
-//--------------------------------------------------------------------------------
+ // Form definition with new course defaults.
$mform->addElement('header','general', get_string('general', 'form'));
$mform->addElement('hidden', 'returnto', null);
$mform->addElement('select', 'lang', get_string('forcelanguage'), $languages);
$mform->setDefault('lang', $courseconfig->lang);
+ // Multi-Calendar Support - see MDL-18375.
+ $calendartypes = \core_calendar\type_factory::get_list_of_calendar_types();
+ // We do not want to show this option unless there is more than one calendar type to display.
+ if (count($calendartypes) > 1) {
+ $calendars = array();
+ $calendars[''] = get_string('forceno');
+ $calendars += $calendartypes;
+ $mform->addElement('select', 'calendartype', get_string('forcecalendartype', 'calendar'), $calendars);
+ }
+
$options = range(0, 10);
$mform->addElement('select', 'newsitems', get_string('newsitemsnumber'), $options);
$mform->addHelpButton('newsitems', 'newsitemsnumber');
$mform->setDefault('enablecompletion', 0);
}
-//--------------------------------------------------------------------------------
enrol_course_edit_form($mform, $course, $context);
-//--------------------------------------------------------------------------------
$mform->addElement('header','groups', get_string('groupsettingsheader', 'group'));
$choices = array();
$options[0] = get_string('none');
$mform->addElement('select', 'defaultgroupingid', get_string('defaultgrouping', 'group'), $options);
-//--------------------------------------------------------------------------------
-
-/// customizable role names in this course
-//--------------------------------------------------------------------------------
+ // Customizable role names in this course.
$mform->addElement('header','rolerenaming', get_string('rolerenaming'));
$mform->addHelpButton('rolerenaming', 'rolerenaming');
}
}
-//--------------------------------------------------------------------------------
$this->add_action_buttons();
-//--------------------------------------------------------------------------------
+
$mform->addElement('hidden', 'id', null);
$mform->setType('id', PARAM_INT);
-/// finally set the current form data
-//--------------------------------------------------------------------------------
+ // Finally set the current form data
$this->set_data($course);
}
+ /**
+ * Fill in the current page data for this course.
+ */
function definition_after_data() {
global $DB;
}
}
-/// perform some extra moodle validation
+ /**
+ * Validation.
+ *
+ * @param array $data
+ * @param array $files
+ * @return array the errors that were found
+ */
function validation($data, $files) {
- global $DB, $CFG;
+ global $DB;
$errors = parent::validation($data, $files);
- if ($foundcourses = $DB->get_records('course', array('shortname'=>$data['shortname']))) {
- if (!empty($data['id'])) {
- unset($foundcourses[$data['id']]);
+
+ // Add field validation check for duplicate shortname.
+ if ($course = $DB->get_record('course', array('shortname' => $data['shortname']), '*', IGNORE_MULTIPLE)) {
+ if (empty($data['id']) || $course->id != $data['id']) {
+ $errors['shortname'] = get_string('shortnametaken', '', $course->fullname);
}
- if (!empty($foundcourses)) {
- foreach ($foundcourses as $foundcourse) {
- $foundcoursenames[] = $foundcourse->fullname;
+ }
+
+ // Add field validation check for duplicate idnumber.
+ if (!empty($data['idnumber']) && (empty($data['id']) || $this->course->idnumber != $data['idnumber'])) {
+ if ($course = $DB->get_record('course', array('idnumber' => $data['idnumber']), '*', IGNORE_MULTIPLE)) {
+ if (empty($data['id']) || $course->id != $data['id']) {
+ $errors['idnumber'] = get_string('courseidnumbertaken', 'error', $course->fullname);
}
- $foundcoursenamestring = implode(',', $foundcoursenames);
- $errors['shortname']= get_string('shortnametaken', '', $foundcoursenamestring);
}
}
'maxfiles' => EDITOR_UNLIMITED_FILES,
'maxbytes' => $CFG->maxbytes,
'trusttext' => true,
- 'context' => $editorcontext
+ 'context' => $editorcontext,
+ 'subdirs' => file_area_contains_subdirs($editorcontext, 'coursecat', 'description', $itemid),
);
$category = file_prepare_standard_editor($category, 'description', $editoroptions, $editorcontext, 'coursecat', 'description', $itemid);
require_capability('moodle/course:changefullname', $context);
}
- // Check if the shortname already exist and user have capability.
+ // Check if the user can change shortname.
if (array_key_exists('shortname', $course) && ($oldcourse->shortname != $course['shortname'])) {
require_capability('moodle/course:changeshortname', $context);
- if ($DB->record_exists('course', array('shortname' => $course['shortname']))) {
- throw new moodle_exception('shortnametaken', '', '', $course['shortname']);
- }
}
- // Check if the id number already exist and user have capability.
+ // Check if the user can change the idnumber.
if (array_key_exists('idnumber', $course) && ($oldcourse->idnumber != $course['idnumber'])) {
require_capability('moodle/course:changeidnumber', $context);
- if ($DB->record_exists('course', array('idnumber' => $course['idnumber']))) {
- throw new moodle_exception('courseidnumbertaken', '', '', $course['idnumber']);
- }
}
// Check if user can change summary.
$sectiontitle .= html_writer::tag('span', $sectionnavlinks['previous'], array('class' => 'mdl-left'));
$sectiontitle .= html_writer::tag('span', $sectionnavlinks['next'], array('class' => 'mdl-right'));
// Title attributes
- $titleattr = 'mdl-align title';
+ $classes = 'sectionname';
if (!$thissection->visible) {
- $titleattr .= ' dimmed_text';
+ $classes .= ' dimmed_text';
}
- $sectiontitle .= html_writer::tag('div', get_section_name($course, $displaysection), array('class' => $titleattr));
+ $sectiontitle .= $this->output->heading(get_section_name($course, $displaysection), 3, $classes);
+
$sectiontitle .= html_writer::end_tag('div');
echo $sectiontitle;
return true;
}
+/**
+ * Checks the integrity of the course data.
+ *
+ * In summary - compares course_sections.sequence and course_modules.section.
+ *
+ * More detailed, checks that:
+ * - course_sections.sequence contains each module id not more than once in the course
+ * - for each moduleid from course_sections.sequence the field course_modules.section
+ * refers to the same section id (this means course_sections.sequence is more
+ * important if they are different)
+ * - ($fullcheck only) each module in the course is present in one of
+ * course_sections.sequence
+ * - ($fullcheck only) removes non-existing course modules from section sequences
+ *
+ * If there are any mismatches, the changes are made and records are updated in DB.
+ *
+ * Course cache is NOT rebuilt if there are any errors!
+ *
+ * This function is used each time when course cache is being rebuilt with $fullcheck = false
+ * and in CLI script admin/cli/fix_course_sequence.php with $fullcheck = true
+ *
+ * @param int $courseid id of the course
+ * @param array $rawmods result of funciton {@link get_course_mods()} - containst
+ * the list of enabled course modules in the course. Retrieved from DB if not specified.
+ * Argument ignored in cashe of $fullcheck, the list is retrieved form DB anyway.
+ * @param array $sections records from course_sections table for this course.
+ * Retrieved from DB if not specified
+ * @param bool $fullcheck Will add orphaned modules to their sections and remove non-existing
+ * course modules from sequences. Only to be used in site maintenance mode when we are
+ * sure that another user is not in the middle of the process of moving/removing a module.
+ * @param bool $checkonly Only performs the check without updating DB, outputs all errors as debug messages.
+ * @return array array of messages with found problems. Empty output means everything is ok
+ */
+function course_integrity_check($courseid, $rawmods = null, $sections = null, $fullcheck = false, $checkonly = false) {
+ global $DB;
+ $messages = array();
+ if ($sections === null) {
+ $sections = $DB->get_records('course_sections', array('course' => $courseid), 'section', 'id,section,sequence');
+ }
+ if ($fullcheck) {
+ // Retrieve all records from course_modules regardless of module type visibility.
+ $rawmods = $DB->get_records('course_modules', array('course' => $courseid), 'id', 'id,section');
+ }
+ if ($rawmods === null) {
+ $rawmods = get_course_mods($courseid);
+ }
+ if (!$fullcheck && (empty($sections) || empty($rawmods))) {
+ // If either of the arrays is empty, no modules are displayed anyway.
+ return true;
+ }
+ $debuggingprefix = 'Failed integrity check for course ['.$courseid.']. ';
+
+ // First make sure that each module id appears in section sequences only once.
+ // If it appears in several section sequences the last section wins.
+ // If it appears twice in one section sequence, the first occurence wins.
+ $modsection = array();
+ foreach ($sections as $sectionid => $section) {
+ $sections[$sectionid]->newsequence = $section->sequence;
+ if (!empty($section->sequence)) {
+ $sequence = explode(",", $section->sequence);
+ $sequenceunique = array_unique($sequence);
+ if (count($sequenceunique) != count($sequence)) {
+ // Some course module id appears in this section sequence more than once.
+ ksort($sequenceunique); // Preserve initial order of modules.
+ $sequence = array_values($sequenceunique);
+ $sections[$sectionid]->newsequence = join(',', $sequence);
+ $messages[] = $debuggingprefix.'Sequence for course section ['.
+ $sectionid.'] is "'.$sections[$sectionid]->sequence.'", must be "'.$sections[$sectionid]->newsequence.'"';
+ }
+ foreach ($sequence as $cmid) {
+ if (array_key_exists($cmid, $modsection) && isset($rawmods[$cmid])) {
+ // Some course module id appears to be in more than one section's sequences.
+ $wrongsectionid = $modsection[$cmid];
+ $sections[$wrongsectionid]->newsequence = trim(preg_replace("/,$cmid,/", ',', ','.$sections[$wrongsectionid]->newsequence. ','), ',');
+ $messages[] = $debuggingprefix.'Course module ['.$cmid.'] must be removed from sequence of section ['.
+ $wrongsectionid.'] because it is also present in sequence of section ['.$sectionid.']';
+ }
+ $modsection[$cmid] = $sectionid;
+ }
+ }
+ }
+
+ // Add orphaned modules to their sections if they exist or to section 0 otherwise.
+ if ($fullcheck) {
+ foreach ($rawmods as $cmid => $mod) {
+ if (!isset($modsection[$cmid])) {
+ // This is a module that is not mentioned in course_section.sequence at all.
+ // Add it to the section $mod->section or to the last available section.
+ if ($mod->section && isset($sections[$mod->section])) {
+ $modsection[$cmid] = $mod->section;
+ } else {
+ $firstsection = reset($sections);
+ $modsection[$cmid] = $firstsection->id;
+ }
+ $sections[$modsection[$cmid]]->newsequence = trim($sections[$modsection[$cmid]]->newsequence.','.$cmid, ',');
+ $messages[] = $debuggingprefix.'Course module ['.$cmid.'] is missing from sequence of section ['.
+ $modsection[$cmid].']';
+ }
+ }
+ foreach ($modsection as $cmid => $sectionid) {
+ if (!isset($rawmods[$cmid])) {
+ // Section $sectionid refers to module id that does not exist.
+ $sections[$sectionid]->newsequence = trim(preg_replace("/,$cmid,/", ',', ','.$sections[$sectionid]->newsequence.','), ',');
+ $messages[] = $debuggingprefix.'Course module ['.$cmid.
+ '] does not exist but is present in the sequence of section ['.$sectionid.']';
+ }
+ }
+ }
+
+ // Update changed sections.
+ if (!$checkonly && !empty($messages)) {
+ foreach ($sections as $sectionid => $section) {
+ if ($section->newsequence !== $section->sequence) {
+ $DB->update_record('course_sections', array('id' => $sectionid, 'sequence' => $section->newsequence));
+ }
+ }
+ }
+
+ // Now make sure that all modules point to the correct sections.
+ foreach ($rawmods as $cmid => $mod) {
+ if (isset($modsection[$cmid]) && $modsection[$cmid] != $mod->section) {
+ if (!$checkonly) {
+ $DB->update_record('course_modules', array('id' => $cmid, 'section' => $modsection[$cmid]));
+ }
+ $messages[] = $debuggingprefix.'Course module ['.$cmid.
+ '] points to section ['.$mod->section.'] instead of ['.$modsection[$cmid].']';
+ }
+ }
+
+ return $messages;
+}
+
/**
* For a given course, returns an array of course activity objects
* Each item in the array contains he following properties:
return $mod; // always return array
}
- if ($sections = $DB->get_records("course_sections", array("course"=>$courseid), "section ASC")) {
+ if ($sections = $DB->get_records('course_sections', array('course' => $courseid), 'section ASC', 'id,section,sequence')) {
+ // First check and correct obvious mismatches between course_sections.sequence and course_modules.section.
+ if ($errormessages = course_integrity_check($courseid, $rawmods, $sections)) {
+ debugging(join('<br>', $errormessages));
+ $rawmods = get_course_mods($courseid);
+ $sections = $DB->get_records('course_sections', array('course' => $courseid), 'section ASC', 'id,section,sequence');
+ }
+ // Build array of activities.
foreach ($sections as $section) {
if (!empty($section->sequence)) {
$sequence = explode(",", $section->sequence);
// Groupmode.
if ($hasmanageactivities and plugin_supports('mod', $mod->modname, FEATURE_GROUPS, 0)) {
- if ($mod->coursegroupmodeforce) {
- $modgroupmode = $mod->coursegroupmode;
- } else {
- $modgroupmode = $mod->groupmode;
- }
- if ($modgroupmode == SEPARATEGROUPS) {
+ if ($mod->effectivegroupmode == SEPARATEGROUPS) {
$nextgroupmode = VISIBLEGROUPS;
$grouptitle = $str->groupsseparate;
$forcedgrouptitle = $str->forcedgroupsseparate;
$actionname = 'groupsseparate';
$groupimage = 't/groups';
- } else if ($modgroupmode == VISIBLEGROUPS) {
+ } else if ($mod->effectivegroupmode == VISIBLEGROUPS) {
$nextgroupmode = NOGROUPS;
$grouptitle = $str->groupsvisible;
$forcedgrouptitle = $str->forcedgroupsvisible;
$newparent = context_coursecat::instance($category->id);
$i = 1;
- foreach ($courseids as $courseid) {
- if ($dbcourse = $DB->get_record('course', array('id' => $courseid))) {
- $course = new stdClass();
- $course->id = $courseid;
- $course->category = $category->id;
- $course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - $i++;
- if ($category->visible == 0) {
- // Hide the course when moving into hidden category, do not update the visibleold flag - we want to get
- // to previous state if somebody unhides the category.
- $course->visible = 0;
- }
-
- $DB->update_record('course', $course);
-
- // Store the context.
- $context = context_course::instance($course->id);
-
- // Update the course object we are passing to the event.
- $dbcourse->category = $course->category;
- $dbcourse->sortorder = $course->sortorder;
-
- // Trigger a course updated event.
- $event = \core\event\course_updated::create(array(
- 'objectid' => $course->id,
- 'context' => $context,
- 'other' => array('shortname' => $dbcourse->shortname,
- 'fullname' => $dbcourse->fullname)
- ));
- $event->add_record_snapshot('course', $dbcourse);
- $event->set_legacy_logdata(array($course->id, 'course', 'move', 'edit.php?id=' . $course->id, $course->id));
- $event->trigger();
+ list($where, $params) = $DB->get_in_or_equal($courseids);
+ $dbcourses = $DB->get_records_select('course', 'id ' . $where, $params);
+ foreach ($dbcourses as $dbcourse) {
+ $course = new stdClass();
+ $course->id = $dbcourse->id;
+ $course->category = $category->id;
+ $course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - $i++;
+ if ($category->visible == 0) {
+ // Hide the course when moving into hidden category, do not update the visibleold flag - we want to get
+ // to previous state if somebody unhides the category.
+ $course->visible = 0;
+ }
+
+ $DB->update_record('course', $course);
+
+ // Store the context.
+ $context = context_course::instance($course->id);
+
+ // Update the course object we are passing to the event.
+ $dbcourse->category = $course->category;
+ $dbcourse->sortorder = $course->sortorder;
+ if (isset($course->visible)) {
+ $dbcourse->visible = $course->visible;
+ }
+
+ // Trigger a course updated event.
+ $event = \core\event\course_updated::create(array(
+ 'objectid' => $course->id,
+ 'context' => $context,
+ 'other' => array('shortname' => $dbcourse->shortname,
+ 'fullname' => $dbcourse->fullname)
+ ));
+ $event->add_record_snapshot('course', $dbcourse);
+ $event->set_legacy_logdata(array($course->id, 'course', 'move', 'edit.php?id=' . $course->id, $course->id));
+ $event->trigger();
- $context->update_moved($newparent);
- }
+ $context->update_moved($newparent);
}
fix_course_sortorder();
cache_helper::purge_by_event('changesincourse');
$data = file_postupdate_standard_filemanager($data, 'overviewfiles', $overviewfilesoptions, $context, 'course', 'overviewfiles', 0);
}
+ // Check we don't have a duplicate shortname.
+ if (!empty($data->shortname) && $oldcourse->shortname != $data->shortname) {
+ if ($DB->record_exists('course', array('shortname' => $data->shortname))) {
+ throw new moodle_exception('shortnametaken', '', '', $data->shortname);
+ }
+ }
+
+ // Check we don't have a duplicate idnumber.
+ if (!empty($data->idnumber) && $oldcourse->idnumber != $data->idnumber) {
+ if ($DB->record_exists('course', array('idnumber' => $data->idnumber))) {
+ throw new moodle_exception('courseidnumbertaken', '', '', $data->idnumber);
+ }
+ }
+
if (!isset($data->category) or empty($data->category)) {
// prevent nulls and 0 in category field
unset($data->category);
$context->update_moved($newparent);
}
- fix_course_sortorder();
+ if ($movecat || (isset($data->sortorder) && $oldcourse->sortorder != $data->sortorder)) {
+ fix_course_sortorder();
+ }
+
// purge appropriate caches in case fix_course_sortorder() did not change anything
cache_helper::purge_by_event('changesincourse');
if ($changesincoursecat) {
if (plugin_supports('mod', $data->modulename, FEATURE_MOD_INTRO, true)) {
$draftid_editor = file_get_submitted_draft_itemid('introeditor');
- file_prepare_draft_area($draftid_editor, null, null, null, null);
+ file_prepare_draft_area($draftid_editor, null, null, null, null, array('subdirs'=>true));
$data->introeditor = array('text'=>'', 'format'=>FORMAT_HTML, 'itemid'=>$draftid_editor); // TODO: add better default
}
$label = is_null($customlabel) ? get_string('moduleintro') : $customlabel;
$mform->addElement('editor', 'introeditor', $label, array('rows' => 10), array('maxfiles' => EDITOR_UNLIMITED_FILES,
- 'noclean' => true, 'context' => $this->context));
+ 'noclean' => true, 'context' => $this->context, 'subdirs' => true));
$mform->setType('introeditor', PARAM_RAW); // no XSS prevention here, users must be trusted
if ($required) {
$mform->addRule('introeditor', get_string('required'), 'required', null, 'client');
'numsections' => 5),
array('createsections' => true));
- // Ensure all 6 (0-5) sections were created and modinfo/sectioninfo cache works properly
+ // Ensure all 6 (0-5) sections were created and course content cache works properly
$sectionscreated = array_keys(get_fast_modinfo($course)->get_section_info_all());
$this->assertEquals(range(0, $course->numsections), $sectionscreated);
$this->assertEquals(range(0, $course->numsections + 1), $sectionscreated);
}
+ public function test_update_course() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ $defaultcategory = $DB->get_field_select('course_categories', 'MIN(id)', 'parent = 0');
+
+ $course = new stdClass();
+ $course->fullname = 'Apu loves Unit Təsts';
+ $course->shortname = 'test1';
+ $course->idnumber = '1';
+ $course->summary = 'Awesome!';
+ $course->summaryformat = FORMAT_PLAIN;
+ $course->format = 'topics';
+ $course->newsitems = 0;
+ $course->numsections = 5;
+ $course->category = $defaultcategory;
+
+ $created = create_course($course);
+ // Ensure the checks only work on idnumber/shortname that are not already ours.
+ update_course($created);
+
+ $course->shortname = 'test2';
+ $course->idnumber = '2';
+
+ $created2 = create_course($course);
+
+ // Test duplicate idnumber.
+ $created2->idnumber = '1';
+ try {
+ update_course($created2);
+ $this->fail('Expected exception when trying to update a course with duplicate idnumber');
+ } catch (moodle_exception $e) {
+ $this->assertEquals(get_string('courseidnumbertaken', 'error', $created2->idnumber), $e->getMessage());
+ }
+
+ // Test duplicate shortname.
+ $created2->idnumber = '2';
+ $created2->shortname = 'test1';
+ try {
+ update_course($created2);
+ $this->fail('Expected exception when trying to update a course with a duplicate shortname');
+ } catch (moodle_exception $e) {
+ $this->assertEquals(get_string('shortnametaken', 'error', $created2->shortname), $e->getMessage());
+ }
+ }
+
public function test_course_add_cm_to_section() {
global $DB;
$this->resetAfterTest(true);
$this->assertEquals($cmids[0], $sequence);
// Add a second, this time using courseid variant of parameters.
+ $coursecacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
course_add_cm_to_section($course->id, $cmids[1], 1);
$sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 1));
$this->assertEquals($cmids[0] . ',' . $cmids[1], $sequence);
- // Check modinfo was not rebuilt (important for performance if calling
- // repeatedly).
- $this->assertNull($DB->get_field('course', 'modinfo', array('id' => $course->id)));
+ // Check that modinfo cache was reset but not rebuilt (important for performance if calling repeatedly).
+ $this->assertGreaterThan($coursecacherev, $DB->get_field('course', 'cacherev', array('id' => $course->id)));
+ $this->assertEmpty(cache::make('core', 'coursemodinfo')->get($course->id));
// Add one to section that doesn't exist (this might rebuild modinfo).
course_add_cm_to_section($course, $cmids[2], 2);
// Create a category we are going to move this course to.
$category = $this->getDataGenerator()->create_category();
+ // Create a hidden category we are going to move this course to.
+ $categoryhidden = $this->getDataGenerator()->create_category(array('visible' => 0));
+
// Catch the update events.
$sink = $this->redirectEvents();
// Return the moved course information from the DB.
$movedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
+ // Now move the course to the hidden category, this will also trigger an event.
+ move_courses(array($course->id), $categoryhidden->id);
+
+ // Return the moved course information from the DB.
+ $movedcoursehidden = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
+
// Now we want to set the sortorder back to what it was before fix_course_sortorder() was called. The reason for
// this is because update_course() and move_courses() call fix_course_sortorder() which alters the sort order in
// the DB, but it does not set the value of the sortorder for the course object passed to the event.
$updatedcourse->sortorder = $sortorder;
$movedcourse->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - 1;
+ $movedcoursehidden->sortorder = $categoryhidden->sortorder + MAX_COURSES_IN_CATEGORY - 1;
// Capture the events.
$events = $sink->get_events();
$this->assertEventLegacyData($movedcourse, $event);
$expectedlog = array($movedcourse->id, 'course', 'move', 'edit.php?id=' . $movedcourse->id, $movedcourse->id);
$this->assertEventLegacyLogData($expectedlog, $event);
+
+ $event = $events[2];
+ $this->assertInstanceOf('\core\event\course_updated', $event);
+ $this->assertEquals('course', $event->objecttable);
+ $this->assertEquals($movedcoursehidden->id, $event->objectid);
+ $this->assertEquals(context_course::instance($movedcoursehidden->id)->id, $event->contextid);
+ $this->assertEquals($movedcoursehidden, $event->get_record_snapshot('course', $movedcoursehidden->id));
+ $this->assertEquals('course_updated', $event->get_legacy_eventname());
+ $this->assertEventLegacyData($movedcoursehidden, $event);
+ $expectedlog = array($movedcoursehidden->id, 'course', 'move', 'edit.php?id=' . $movedcoursehidden->id, $movedcoursehidden->id);
+ $this->assertEventLegacyLogData($expectedlog, $event);
}
/**
$expectedlegacydata = array($course->id, "course", "editsection", 'editsection.php?id=' . $id, $sectionnum);
$this->assertEventLegacyLogData($expectedlegacydata, $event);
}
+
+ public function test_course_integrity_check() {
+ global $DB;
+
+ $this->resetAfterTest(true);
+ $course = $this->getDataGenerator()->create_course(array('numsections' => 1),
+ array('createsections'=>true));
+
+ $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
+ array('section' => 0));
+ $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id),
+ array('section' => 0));
+ $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id),
+ array('section' => 0));
+ $correctseq = join(',', array($forum->cmid, $page->cmid, $quiz->cmid));
+
+ $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
+ $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
+ $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
+ $this->assertEquals($correctseq, $section0->sequence);
+ $this->assertEmpty($section1->sequence);
+ $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
+ $this->assertEquals($section0->id, $cms[$page->cmid]->section);
+ $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
+ $this->assertEmpty(course_integrity_check($course->id));
+
+ // Now let's make manual change in DB and let course_integrity_check() fix it:
+
+ // 1. Module appears twice in one section.
+ $DB->update_record('course_sections', array('id' => $section0->id, 'sequence' => $section0->sequence. ','. $page->cmid));
+ $this->assertEquals(
+ array('Failed integrity check for course ['. $course->id.
+ ']. Sequence for course section ['. $section0->id. '] is "'.
+ $section0->sequence. ','. $page->cmid. '", must be "'.
+ $section0->sequence. '"'),
+ course_integrity_check($course->id));
+ $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
+ $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
+ $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
+ $this->assertEquals($correctseq, $section0->sequence);
+ $this->assertEmpty($section1->sequence);
+ $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
+ $this->assertEquals($section0->id, $cms[$page->cmid]->section);
+ $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
+
+ // 2. Module appears in two sections (last section wins).
+ $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''. $page->cmid));
+ // First message about double mentioning in sequence, second message about wrong section field for $page.
+ $this->assertEquals(array(
+ 'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
+ '] must be removed from sequence of section ['. $section0->id.
+ '] because it is also present in sequence of section ['. $section1->id. ']',
+ 'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
+ '] points to section ['. $section0->id. '] instead of ['. $section1->id. ']'),
+ course_integrity_check($course->id));
+ $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
+ $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
+ $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
+ $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
+ $this->assertEquals(''. $page->cmid, $section1->sequence);
+ $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
+ $this->assertEquals($section1->id, $cms[$page->cmid]->section);
+ $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
+
+ // 3. Module id is not present in course_section.sequence (integrity check with $fullcheck = false).
+ $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''));
+ $this->assertEmpty(course_integrity_check($course->id)); // Not an error!
+ $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
+ $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
+ $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
+ $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
+ $this->assertEmpty($section1->sequence);
+ $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
+ $this->assertEquals($section1->id, $cms[$page->cmid]->section); // Not changed.
+ $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
+
+ // 4. Module id is not present in course_section.sequence (integrity check with $fullcheck = true).
+ $this->assertEquals(array('Failed integrity check for course ['. $course->id. ']. Course module ['.
+ $page->cmid. '] is missing from sequence of section ['. $section1->id. ']'),
+ course_integrity_check($course->id, null, null, true)); // Error!
+ $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
+ $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
+ $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
+ $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
+ $this->assertEquals(''. $page->cmid, $section1->sequence); // Yay, module added to section.
+ $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
+ $this->assertEquals($section1->id, $cms[$page->cmid]->section); // Not changed.
+ $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
+
+ // 5. Module id is not present in course_section.sequence and it's section is invalid (integrity check with $fullcheck = true).
+ $DB->update_record('course_modules', array('id' => $page->cmid, 'section' => 8765));
+ $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''));
+ $this->assertEquals(array(
+ 'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
+ '] is missing from sequence of section ['. $section0->id. ']',
+ 'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
+ '] points to section [8765] instead of ['. $section0->id. ']'),
+ course_integrity_check($course->id, null, null, true));
+ $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
+ $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
+ $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
+ $this->assertEquals($forum->cmid. ','. $quiz->cmid. ','. $page->cmid, $section0->sequence); // Module added to section.
+ $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
+ $this->assertEquals($section0->id, $cms[$page->cmid]->section); // Section changed to section0.
+ $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
+
+ // 6. Module is deleted from course_modules but not deleted in sequence (integrity check with $fullcheck = true).
+ $DB->delete_records('course_modules', array('id' => $page->cmid));
+ $this->assertEquals(array('Failed integrity check for course ['. $course->id. ']. Course module ['.
+ $page->cmid. '] does not exist but is present in the sequence of section ['. $section0->id. ']'),
+ course_integrity_check($course->id, null, null, true));
+ $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
+ $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
+ $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
+ $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
+ $this->assertEmpty($section1->sequence);
+ $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
+ $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
+ $this->assertEquals(2, count($cms));
+ }
}
$string['database:unenrol'] = 'Unenrol suspended users';
$string['dbencoding'] = 'Database encoding';
$string['dbhost'] = 'Database host';
-$string['dbhost_desc'] = 'Type database server IP address or host name';
+$string['dbhost_desc'] = 'Type database server IP address or host name. Use a system DSN name if using ODBC.';
$string['dbname'] = 'Database name';
+$string['dbname_desc'] = 'Leave empty if using a DSN name in database host.';
$string['dbpass'] = 'Database password';
$string['dbsetupsql'] = 'Database setup command';
$string['dbsetupsql_desc'] = 'SQL command for special database setup, often used to setup communication encoding - example for MySQL and PostgreSQL: <em>SET NAMES \'utf8\'</em>';
$settings->add(new admin_setting_configpasswordunmask('enrol_database/dbpass', get_string('dbpass', 'enrol_database'), '', ''));
- $settings->add(new admin_setting_configtext('enrol_database/dbname', get_string('dbname', 'enrol_database'), '', ''));
+ $settings->add(new admin_setting_configtext('enrol_database/dbname', get_string('dbname', 'enrol_database'), get_string('dbname_desc', 'enrol_database'), ''));
$settings->add(new admin_setting_configtext('enrol_database/dbencoding', get_string('dbencoding', 'enrol_database'), '', 'utf-8'));
$template->groupmodeforce = $courseconfig->groupmodeforce;
$template->visible = $courseconfig->visible;
$template->lang = $courseconfig->lang;
- $template->groupmodeforce = $courseconfig->groupmodeforce;
+ $template->enablecompletion = $courseconfig->enablecompletion;
}
$course = $template;
} else if (empty($instance->name)) {
$enrol = $this->get_name();
$course = $DB->get_record('course', array('id'=>$instance->customint1));
- $coursename = format_string(get_course_display_name_for_list($course));
+ if ($course) {
+ $coursename = format_string(get_course_display_name_for_list($course));
+ } else {
+ // Use course id, if course is deleted.
+ $coursename = $instance->customint1;
+ }
return get_string('pluginname', 'enrol_' . $enrol) . ' (' . $coursename . ')';
} else {
return format_string($instance->name);
if ($canassign and (is_siteadmin() or isset($assignableroles[$roleid])) and !$role['unchangeable']) {
$strunassign = get_string('unassignarole', 'role', $role['text']);
$icon = html_writer::empty_tag('img', array('alt'=>$strunassign, 'src'=>$iconenrolremove));
- $url = new moodle_url($pageurl, array('action'=>'unassign', 'role'=>$roleid, 'user'=>$userid));
+ $url = new moodle_url($pageurl, array('action'=>'unassign', 'roleid'=>$roleid, 'user'=>$userid));
$rolesoutput .= html_writer::tag('div', $role['text'] . html_writer::link($url, $icon, array('class'=>'unassignrolelink', 'rel'=>$roleid, 'title'=>$strunassign)), array('class'=>'role role_'.$roleid));
} else {
$rolesoutput .= html_writer::tag('div', $role['text'], array('class'=>'role unchangeable', 'rel'=>$roleid));
if ($rusers) {
$contact = reset($rusers);
} else {
- $contact = generate_email_supportuser();
+ $contact = core_user::get_support_user();
}
// Directly emailing welcome message rather than using messaging.
$course = (array)$course;
$this->assertEquals($basefields, array_keys($course), '', 0, 10, true);
- $courses = enrol_get_all_users_courses($user2->id, false, 'modinfo');
+ $courses = enrol_get_all_users_courses($user2->id, false, 'timecreated');
$course = reset($courses);
- $this->assertTrue(property_exists($course, 'modinfo'));
+ $this->assertTrue(property_exists($course, 'timecreated'));
$courses = enrol_get_all_users_courses($user2->id, false, null, 'id DESC');
$this->assertEquals(array($course3->id, $course2->id, $course1->id), array_keys($courses));
*/
case 'unassign':
if (has_capability('moodle/role:assign', $manager->get_context())) {
- $role = required_param('role', PARAM_INT);
+ $role = required_param('roleid', PARAM_INT);
$user = required_param('user', PARAM_INT);
if ($confirm && $manager->unassign_role_from_user($user, $role)) {
redirect($PAGE->url);
$user = $DB->get_record('user', array('id'=>$user), '*', MUST_EXIST);
$allroles = $manager->get_all_roles();
$role = $allroles[$role];
- $yesurl = new moodle_url($PAGE->url, array('action'=>'unassign', 'role'=>$role->id, 'user'=>$user->id, 'confirm'=>1, 'sesskey'=>sesskey()));
+ $yesurl = new moodle_url($PAGE->url, array('action'=>'unassign', 'roleid'=>$role->id, 'user'=>$user->id, 'confirm'=>1, 'sesskey'=>sesskey()));
$message = get_string('unassignconfirm', 'role', array('user'=>fullname($user, true), 'role'=>$role->localname));
$pagetitle = get_string('unassignarole', 'role', $role->localname);
$pagecontent = $OUTPUT->confirm($message, $yesurl, $PAGE->url);
event.detach();
var s = M.str.role, confirmation = {
lightbox : true,
+ visible : true,
+ centered : true,
title : s.confirmunassigntitle,
question : s.confirmunassign,
yesLabel : s.confirmunassignyes,
require('../config.php');
require_once($CFG->libdir.'/eventslib.php');
- if ($form = data_submitted()) { // form submitted, do not check referer (original page unknown)!
-
- /// Only deal with real users
+ // Form submitted, do not check referer (original page unknown).
+ if ($form = data_submitted()) {
+ // Only deal with real users.
if (!isloggedin()) {
redirect($CFG->wwwroot);
}
- /// Work out who to send the message to
- if (!$admin = get_admin() ) {
- print_error('cannotfindadmin', 'debug');
- }
-
- $supportuser = new stdClass();
- $supportuser->email = $CFG->supportemail ? $CFG->supportemail : $admin->email;
- $supportuser->firstname = $CFG->supportname ? $CFG->supportname : $admin->firstname;
- $supportuser->lastname = $CFG->supportname ? '' : $admin->lastname;
- // emailstop could be hard coded "false" to ensure error reports are sent
- // but then admin's would have to alter their messaging preferences to temporarily stop them
- $supportuser->emailstop = $admin->emailstop;
- $supportuser->maildisplay = true;
-
- /// Send the message and redirect
+ // Send the message and redirect.
$eventdata = new stdClass();
- $eventdata->modulename = 'moodle';
+ $eventdata->component = 'moodle';
+ $eventdata->name = 'errors';
$eventdata->userfrom = $USER;
- $eventdata->userto = $supportuser;
+ $eventdata->userto = core_user::get_support_user();
$eventdata->subject = 'Error: '. $form->referer .' -> '. $form->requested;
$eventdata->fullmessage = $form->text;
$eventdata->fullmessageformat = FORMAT_PLAIN;
<button class="{!}fp-file-cancel">'.get_string('cancel').'</button>
</div>
</form>
- <div class="fp-info">
+ <div class="fp-info clearfix">
<div class="fp-hr"></div>
<p class="{!}fp-thumbnail"></p>
<div class="fp-fileinfo">
<button class="{!}fp-select-cancel">'.get_string('cancel').'</button>
</div>
</form>
- <div class="fp-info">
+ <div class="fp-info clearfix">
<div class="fp-hr"></div>
<p class="{!}fp-thumbnail"></p>
<div class="fp-fileinfo">
$modinfo = get_fast_modinfo($courseid);
if (!empty($modinfo->cms)) {
- self::$activitylist = array(); /// We will store all the activities here
+ self::$activitylist = array(); // We will store all the created filters here.
- //Sort modinfo by name length
- $sortedactivities = fullclone($modinfo->cms);
- usort($sortedactivities, 'filter_activitynames_comparemodulenamesbylength');
+ // Create array of visible activities sorted by the name length (we are only interested in properties name and url).
+ $sortedactivities = array();
+ foreach ($modinfo->cms as $cm) {
+ // Exclude labels, hidden activities and activities for group members only.
+ if ($cm->visible and empty($cm->groupmembersonly) and $cm->has_view()) {
+ $sortedactivities[] = (object)array(
+ 'name' => $cm->name,
+ 'url' => $cm->url,
+ 'id' => $cm->id,
+ 'namelen' => strlen($cm->name),
+ );
+ }
+ }
+ core_collator::asort_objects_by_property($sortedactivities, 'namelen', SORT_NUMERIC);
foreach ($sortedactivities as $cm) {
- //Exclude labels, hidden activities and activities for group members only
- if ($cm->visible and empty($cm->groupmembersonly) and $cm->has_view()) {
- $title = s(trim(strip_tags($cm->name)));
- $currentname = trim($cm->name);
- $entitisedname = s($currentname);
- /// Avoid empty or unlinkable activity names
- if (!empty($title)) {
- $href_tag_begin = html_writer::start_tag('a',
- array('class' => 'autolink', 'title' => $title,
- 'href' => $cm->get_url()));
- self::$activitylist[$cm->id] = new filterobject($currentname, $href_tag_begin, '</a>', false, true);
- if ($currentname != $entitisedname) { /// If name has some entity (& " < >) add that filter too. MDL-17545
- self::$activitylist[$cm->id.'-e'] = new filterobject($entitisedname, $href_tag_begin, '</a>', false, true);
- }
+ $title = s(trim(strip_tags($cm->name)));
+ $currentname = trim($cm->name);
+ $entitisedname = s($currentname);
+ // Avoid empty or unlinkable activity names.
+ if (!empty($title)) {
+ $href_tag_begin = html_writer::start_tag('a',
+ array('class' => 'autolink', 'title' => $title,
+ 'href' => $cm->url));
+ self::$activitylist[$cm->id] = new filterobject($currentname, $href_tag_begin, '</a>', false, true);
+ if ($currentname != $entitisedname) {
+ // If name has some entity (& " < >) add that filter too. MDL-17545.
+ self::$activitylist[$cm->id.'-e'] = new filterobject($entitisedname, $href_tag_begin, '</a>', false, true);
}
}
}
}
}
}
-
-
-
-//This function is used to order module names from longer to shorter
-function filter_activitynames_comparemodulenamesbylength($a, $b) {
- if (strlen($a->name) == strlen($b->name)) {
- return 0;
- }
- return (strlen($a->name) < strlen($b->name)) ? 1 : -1;
-}
-
);
if (!empty($outcome_rec->id)) {
+ $editoroptions['subdirs'] = file_area_contains_subdirs($systemcontext, 'grade', 'outcome', $outcome_rec->id);
$outcome_rec = file_prepare_standard_editor($outcome_rec, 'description', $editoroptions, $systemcontext, 'grade', 'outcome', $outcome_rec->id);
} else {
+ $editoroptions['subdirs'] = false;
$outcome_rec = file_prepare_standard_editor($outcome_rec, 'description', $editoroptions, $systemcontext, 'grade', 'outcome', null);
}
);
if (!empty($scale_rec->id)) {
+ $editoroptions['subdirs'] = file_area_contains_subdirs($systemcontext, 'grade', 'scale', $scale_rec->id);
$scale_rec = file_prepare_standard_editor($scale_rec, 'description', $editoroptions, $systemcontext, 'grade', 'scale', $scale_rec->id);
} else {
+ $editoroptions['subdirs'] = false;
$scale_rec = file_prepare_standard_editor($scale_rec, 'description', $editoroptions, $systemcontext, 'grade', 'scale', null);
}
$mform = new edit_scale_form(null, compact('gpr', 'editoroptions'));
$headerlink = $this->gtree->get_element_header($element, true, $this->get_pref('showactivityicons'), false);
$itemcell = new html_table_cell();
- $itemcell->attributes['class'] = $type . ' ' . $catlevel . ' highlightable';
+ $itemcell->attributes['class'] = $type . ' ' . $catlevel . ' highlightable'. ' i'. $element['object']->id;
if ($element['object']->is_hidden()) {
$itemcell->attributes['class'] .= ' dimmed_text';
$eid = $this->gtree->get_grade_eid($grade);
$element = array('eid'=>$eid, 'object'=>$grade, 'type'=>'grade');
- $itemcell->attributes['class'] .= ' grade';
+ $itemcell->attributes['class'] .= ' grade i'.$itemid;
if ($item->is_category_item()) {
$itemcell->attributes['class'] .= ' cat';
}
$eid = $this->gtree->get_item_eid($item);
$element = $this->gtree->locate_element($eid);
$itemcell = new html_table_cell();
- $itemcell->attributes['class'] = 'controls icons';
+ $itemcell->attributes['class'] = 'controls icons i'.$itemid;
$itemcell->text = $this->get_icons($element);
$iconsrow->cells[] = $itemcell;
}
foreach ($this->gtree->items as $itemid=>$unused) {
$item =& $this->gtree->items[$itemid];
$itemcell = new html_table_cell();
- $itemcell->header = true;
- $itemcell->attributes['class'] .= ' header range';
+ $itemcell->attributes['class'] .= ' range i'. $itemid;
$hidden = '';
if ($item->is_hidden()) {
if ($item->needsupdate) {
$avgcell = new html_table_cell();
+ $avgcell->attributes['class'] = 'i'. $itemid;
$avgcell->text = $OUTPUT->container(get_string('error'), 'gradingerror');
$avgrow->cells[] = $avgcell;
continue;
if (!isset($sumarray[$item->id]) || $meancount == 0) {
$avgcell = new html_table_cell();
+ $avgcell->attributes['class'] = 'i'. $itemid;
$avgcell->text = '-';
$avgrow->cells[] = $avgcell;
}
$avgcell = new html_table_cell();
+ $avgcell->attributes['class'] = 'i'. $itemid;
$avgcell->text = $gradehtml.$numberofgrades;
$avgrow->cells[] = $avgcell;
}
tr.all('.cell').toggleClass('hmarked');
};
/**
- * Highlights a cell in the table
+ * Highlights a column in the table
*
* @function
* @param {Event} e
* @param {Y.Node} cell
*/
M.gradereport_grader.classes.report.prototype.table_highlight_column = function(e, cell) {
- var column = 0;
- while (cell = cell.previous('.cell')) {
- column += parseFloat(cell.getAttribute('colspan')) || 1;
+ // Among cell classes find the one that matches pattern / i[\d]+ /
+ var itemclass = (' '+cell.getAttribute('class')+' ').match(/ (i[\d]+) /);
+ if (itemclass) {
+ // Toggle class .vmarked for all cells in the table with the same class
+ this.table.all('.cell.'+itemclass[1]).toggleClass('vmarked');
}
- this.table.all('.c'+column).toggleClass('vmarked');
};
/**
* Builds an object containing information at the relevant cell given either
.path-grade-report-grader .flexible th {
-white-space:normal;
+ white-space: normal;
}
-
.gradestable {
- margin-bottom:0;
+ margin-bottom: 0;
}
-
.gradestable th.user img {
-width:20px;
-height:20px;
+ width: 20px;
+ height: 20px;
}
-
.gradestable th img {
vertical-align: text-bottom;
padding-bottom: 0;
}
-.gradestable th .grade_icons { margin-top: .3em; }
-.gradestable th img.sorticon { margin-left: .3em; }
-.dir-rtl .gradestable th img.sorticon { margin-left: 0; margin-right: .3em; }
-
+.gradestable th .grade_icons {
+ margin-top: .3em;
+}
+.gradestable th img.sorticon {
+ margin-left: .3em;
+}
+.dir-rtl .gradestable th img.sorticon {
+ margin-left: 0;
+ margin-right: .3em;
+}
table#user-grades .catlevel2 {
-background-color:#f9f9f9;
+ background-color: #f9f9f9;
+}
+table#user-grades tr.range td.cell {
+ font-weight: 700;
}
-
table#user-grades tr.avg td.cell {
-background-color:#efefff;
-font-weight:700;
-color:#00008B;
+ background-color: #efefff;
+ font-weight: 700;
+ color: #00008B;
}
-
table#user-grades tr.odd td.cell {
-background-color:#efefef;
-white-space:nowrap;
+ background-color: #efefef;
+ white-space: nowrap;
+}
+table#user-grades tr td.overridden {
+ background-color: #F3E4C0;
+}
+table#user-grades tr.odd td.overridden {
+ background-color: #EFD9A4;
+}
+table#user-grades tr td.ajaxoverridden {
+ background-color: #FFE3A0;
+}
+table#user-grades tr.odd td.ajaxoverridden {
+ background-color: #FFDA83;
}
-
-table#user-grades tr td.overridden {background-color:#F3E4C0;}
-table#user-grades tr.odd td.overridden {background-color:#EFD9A4;}
-
-table#user-grades tr td.ajaxoverridden {background-color:#FFE3A0;}
-table#user-grades tr.odd td.ajaxoverridden {background-color:#FFDA83;}
-
table#user-grades tr.even td.excluded {
-background-color:#EABFFF;
+ background-color: #EABFFF;
}
-
table#user-grades tr.odd td.excluded {
-background-color:#E5AFFF;
+ background-color: #E5AFFF;
}
-
table#user-grades tr.odd th.header {
-background-color:#efefef;
-background-image:none;
+ background-color: #efefef;
+ background-image: none;
+}
+table#user-grades tr.even th.header {
+ background-image: none;
}
-
table#user-grades tr.groupavg td.cell {
-background-color:#efffef;
-font-weight:700;
-color:#006400;
+ background-color: #efffef;
+ font-weight: 700;
+ color: #006400;
}
-
table#user-grades td.cat,
table#user-grades td.course {
-font-weight:700;
+ font-weight: 700;
}
-
table#user-grades {
-font-size:10px;
-width:auto;
-background-color:transparent;
-border-style:solid;
-border-width:1px;
-margin:20px 0 0;
+ font-size: 10px;
+ width: auto;
+ background-color: transparent;
+ border-style: solid;
+ border-width: 1px;
+ margin: 20px 0 0;
}
-
.path-grade-report-grader #overDiv table {
-margin:0;
+ margin: 0;
}
-
.path-grade-report-grader #overDiv table td.feedback {
-border:0;
+ border: 0;
}
-
.path-grade-report-grader #overDiv .feedback {
-font-size:70%;
-background-color:#ABF;
-color:#000;
-font-family:Verdana;
-font-weight:400;
+ font-size: 70%;
+ background-color: #ABF;
+ color: #000;
+ font-family: Verdana;
+ font-weight: 400;
}
-
.path-grade-report-grader #overDiv .caption {
-font-size:70%;
-background-color:#56C;
-color:#CCF;
-font-family:Arial;
-font-weight:700;
+ font-size: 70%;
+ background-color: #56C;
+ color: #CCF;
+ font-family: Arial;
+ font-weight: 700;
}
-
.path-grade-report-grader #overDiv .intersection {
-font-size:70%;
-background-color:#ABF;
-color:#000;
-font-family:Verdana;
-font-weight:400;
+ font-size: 70%;
+ background-color: #ABF;
+ color: #000;
+ font-family: Verdana;
+ font-weight: 400;
}
-
.path-grade-report-grader #overDiv .intersectioncaption {
-background-color:#56C;
-color:#CCF;
-font-family:Arial;
-font-weight:700;
+ background-color: #56C;
+ color: #CCF;
+ font-family: Arial;
+ font-weight: 700;
}
-
.path-grade-report-grader div.submit {
-margin-top:20px;
-text-align:center;
+ margin-top: 20px;
+ text-align: center;
}
-
table#user-grades td {
-text-align:right;
-border-style:solid;
-border-width:0 1px 1px 0;
+ text-align: right;
+ border-style: solid;
+ border-width: 0 1px 1px 0;
}
-
table#user-grades th.category {
-vertical-align:top;
-border-style:solid;
-border-width:1px 1px 0;
+ vertical-align: top;
+ border-style: solid;
+ border-width: 1px 1px 0;
}
-
table#user-grades th.user {
-text-align:left;
-border-style:solid;
-border-width:0 0 1px;
+ text-align: left;
+ border-style: solid;
+ border-width: 1px 0;
}
-
table#user-grades th.userfield {
-border-style:solid;
-border-width:0 0 1px 1px;
+ border-style: solid;
+ border-width: 1px;
}
-
table#user-grades th.categoryitem,
table#user-grades td.topleft {
-vertical-align: bottom;
-border-style:solid;
-border-width:0 1px;
+ vertical-align: bottom;
+ border-style: solid;
+ border-width: 0 1px;
}
-
.path-grade-report-grader td,.path-grade-report-grader th {
-border-color:#CECECE;
+ border-color: #CECECE;
}
-
.path-grade-report-grader table#participants th {
-vertical-align:top;
-width:auto;
+ vertical-align: top;
+ width: auto;
}
-
table#user-grades td.fillerfirst {
-border-style:solid;
-border-width:0 0 0 1px;
+ border-style: solid;
+ border-width: 0 0 0 1px;
}
-
table#user-grades td.fillerlast {
-border-style:solid;
-border-width:0 1px 0 0;
+ border-style: solid;
+ border-width: 0 1px 0 0;
}
-
-table#user-grades th.item ,
+table#user-grades th.item,
table#user-grades th.categoryitem,
table#user-grades th.courseitem {
-border-bottom-color:#000;
-vertical-align:bottom;
-border-style:solid;
-border-width:1px;
+ border-bottom-color: #000;
+ vertical-align: bottom;
+ border-style: solid;
+ border-width: 1px;
}
-
div.gradertoggle {
-display:inline;
-margin-left:20px;
+ display: inline;
+ margin-left: 20px;
}
-
table#user-grades th.range {
-text-align:right;
-border-style:solid;
-border-width:1px;
+ text-align: right;
+ border-style: solid;
+ border-width: 1px;
}
-
table#user-grades .userpic {
-display:inline;
-margin-right:10px;
+ display: inline;
+ margin-right: 10px;
}
-
table#user-grades .quickfeedback {
-border:1px dashed #000;
-margin-left: 10px;
+ border: 1px dashed #000;
+ margin-left: 10px;
+}
+.dir-rtl table#user-grades .quickfeedback {
+ margin-left: 0;
+ margin-right: 10px;
}
-.dir-rtl table#user-grades .quickfeedback { margin-left: 0; margin-right: 10px;}
-
.path-grade-report-grader #siteconfiglink {
-text-align:right;
+ text-align: right;
}
-
table#user-grades .datesubmitted {
-font-size:.7em;
+ font-size: .7em;
}
-
table#user-grades td.cell {
-padding-left:5px;
-padding-right:5px;
-vertical-align:middle;
+ padding-left: 5px;
+ padding-right: 5px;
+ vertical-align: middle;
}
-
.path-grade-report-grader table {
-border-collapse:collapse;
-background-color:#fff;
-border-color:#cecece;
+ border-collapse: collapse;
+ background-color: #fff;
+ border-color: #cecece;
}
-
.path-grade-report-grader th {
-padding:1px 10px;
+ padding: 1px 10px;
}
-
.path-grade-report-grader span.inclusion-links {
-margin:0 5px 0 10px;
+ margin: 0 5px 0 10px;
}
-
table#user-grades .item {
-background-color:#e9e9e9;
+ background-color: #e9e9e9;
}
-
.path-grade-report-grader table tr.odd th.header {
-background-color:#efefef;
-background-image:none;
-border-width:0 0 1px;
+ background-color: #efefef;
+ background-image: none;
+ border-width: 0 0 1px;
}
-
.path-grade-report-grader table tr.heading th.header {
-border-top:1px solid #cecece;
+ border-top: 1px solid #cecece;
}
-
table#user-grades tr.heading th.categoryitem,
table#user-grades tr.heading th.courseitem {
-border-width:0 0 0 1px;
+ border-width: 0 0 0 1px;
}
-
table#user-grades th.category.header.catlevel1 {
-vertical-align:top;
-border-style:solid;
-border-width:1px 1px 0 0;
+ vertical-align: top;
+ border-style: solid;
+ border-width: 1px 1px 0 0;
}
-
.path-grade-report-grader div.left_scroller th.user a {
-vertical-align:middle;
-margin:0;
-padding:0;
+ vertical-align: middle;
+ margin: 0;
+ padding: 0;
}
-
table#user-grades th.categoryitem,
table#user-grades th.courseitem,
.path-grade-report-grader table td.topleft {
-vertical-align:bottom;
-border-color:#cecece #cecece #000;
-border-style:solid;
-border-width:0 1px 1px;
+ vertical-align: bottom;
+ border-color: #cecece #cecece #000;
+ border-style: solid;
+ border-width: 0 1px 1px;
}
-
.path-grade-report-grader table td.topleft {
-border-bottom:0;
+ border-bottom: 0;
}
-
table#user-grades td.topleft {
-background-color:#fff;
+ background-color: #fff;
}
-
.path-grade-report-grader th.user img.userpicture {
-border:3px double #cecece;
-vertical-align:top;
-width:2.7em;
-height:2.7em;
-margin-right:10px;
+ border: 3px double #cecece;
+ vertical-align: top;
+ width: 2.7em;
+ height: 2.7em;
+ margin-right: 10px;
}
-
.path-grade-report-grader a.quickedit {
-line-height:1em;
-display:block;
-float:right;
-clear:none;
-font-size:9px;
-background-color:transparent;
-margin:.1em 0 0;
+ line-height: 1em;
+ display: block;
+ float: right;
+ clear: none;
+ font-size: 9px;
+ background-color: transparent;
+ margin: .1em 0 0;
}
-
.path-grade-report-grader a.quickedit2 {
-display:block;
-float:right;
-clear:none;
-background-color:transparent;
-margin:1.3em 0 0;
+ display: block;
+ float: right;
+ clear: none;
+ background-color: transparent;
+ margin: 1.3em 0 0;
}
-
.path-grade-report-grader table#quick_edit {
-border:1px solid #cecece;
-margin:0 auto;
+ border: 1px solid #cecece;
+ margin: 0 auto;
}
-
.path-grade-report-grader table#quick_edit td {
-vertical-align:middle;
-border:1px solid #cecece;
-text-align:left;
-margin:0;
-padding:5px;
+ vertical-align: middle;
+ border: 1px solid #cecece;
+ text-align: left;
+ margin: 0;
+ padding: 5px;
}
-
.path-grade-report-grader table#quick_edit td img {
-border:3px double #cecece;
-vertical-align:middle;
-padding:0;
+ border: 3px double #cecece;
+ vertical-align: middle;
+ padding: 0;
}
-
.path-grade-report-grader td input.text {
-border:1px solid #666;
+ border: 1px solid #666;
}
-
.path-grade-report-grader td input.submit {
-margin: 10px 10px 0px 10px;
+ margin: 10px 10px 0px 10px;
}
-
.path-grade-report-grader table#quick_edit td.fullname {
-border-left:0;
-padding-left:5px;
+ border-left: 0;
+ padding-left: 5px;
}
-
.path-grade-report-grader table#quick_edit td.picture {
-border-right:0;
+ border-right: 0;
}
-
.path-grade-report-grader table#quick_edit td.finalgrade input {
-width:5em;
+ width: 5em;
}
-
.path-grade-report-grader h1 {
-text-align:center;
-clear:both;
+ text-align: center;
+ clear: both;
}
-
.path-grade-report-grader input.center {
-margin:10px auto 0;
+ margin: 10px auto 0;
}
-
.path-grade-report-grader .lefttbody {
-width:auto;
-vertical-align:middle;
+ width: auto;
+ vertical-align: middle;
}
-
table#user-grades th.fixedcolumn {
-border:1px solid #cecece;
-vertical-align:middle;
+ border: 1px solid #cecece;
+ vertical-align: middle;
}
-
.path-grade-report-grader table#fixed_column th {
-border:1px solid #cecece;
-vertical-align:middle;
-border-right-color:#000;
+ border: 1px solid #cecece;
+ vertical-align: middle;
+ border-right-color: #000;
}
-
.path-grade-report-grader table#fixed_column th.user{
-border-right-color:#cecece;
+ border-right-color: #cecece;
}
-
.path-grade-report-grader table#fixed_column {
-padding-top:20px;
-border-top:1px solid #cecece;
-background-color:#fff;
+ padding-top: 20px;
+ border-top: 1px solid #cecece;
+ background-color: #fff;
}
-
.path-grade-report-grader .left_scroller {
-float:left;
-clear:none;
-padding-top:20px;
+ float: left;
+ clear: none;
+ padding-top: 20px;
+}
+.path-grade-report-grader.dir-rtl .left_scroller {
+ float: right;
}
-.path-grade-report-grader.dir-rtl .left_scroller {float:right;}
-
-
.path-grade-report-grader .right_scroller {
-width:auto;
-clear:none;
-overflow-x:scroll;
+ width: auto;
+ clear: none;
+ overflow-x: scroll;
}
-
.path-grade-report-grader table tr.avg,
.path-grade-report-grader table tr.groupavg td,
.path-grade-report-grader table tr.avg td,
.path-grade-report-grader table tr.range_row,
.path-grade-report-grader table tr.range_row th,
div.right_scroller tr {
-height:2em;
+ height: 2em;
}
-
table#user-grades tr.groupavg td.cell,
tr.groupavg th.header {
-background-color:#efffef;
+ background-color: #efffef;
}
-
.path-grade-report-grader form td.excluded {
-color:red;
+ color: red;
}
-
.path-grade-report-grader .excludedfloater {
-font-weight:700;
-color:red;
-font-size:9px;
-float:left;
+ font-weight: 700;
+ color: red;
+ font-size: 9px;
+ float: left;
}
-
.path-grade-report-grader span.gradepass {
-color:#298721;
+ color: #298721;
}
-
.path-grade-report-grader span.gradefail {
-color:#890d0d;
+ color: #890d0d;
}
-
.path-grade-report-grader .gradeweight {
-color:#461d7c;
-font-weight:700;
+ color: #461d7c;
+ font-weight: 700;
}
-
.path-grade-report-grader td select {
-font-size:100%;
-padding:0;
+ font-size: 100%;
+ padding: 0;
}
-
.path-grade-report-grader .right_scroller td select {
-font-size:86%;
-padding:0;
+ font-size: 86%;
+ padding: 0;
}
-
.path-grade-report-grader tr.avg,
.path-grade-report-grader tr.controls,
.path-grade-report-grader td.controls,
.path-grade-report-grader th.range,
.path-grade-report-grader td.range,
.path-grade-report-grader tr.heading th.range {
-height:2em!important;
-white-space:nowrap;
+ height: 2em!important;
+ white-space: nowrap;
}
-
.path-grade-report-grader .heading_name_row th {
-white-space:nowrap;
-width:2000px;
+ white-space: nowrap;
+ width: 2000px;
}
/*MDL-21088 - IE 7 ignores nowraps on tds or ths so we put a span within it with a nowrap on it*/
.path-grade-report-grader heading_name_row th span {
- white-space:nowrap;
+ white-space: nowrap;
}
-
.path-grade-report-grader .grade_icons img.ajax {
-float:right;
+ float: right;
}
-
.path-grade-report-grader .gradestable th.user,
.path-grade-report-grader .gradestable th.range,
.path-grade-report-grader .flexible th,
.path-grade-report-grader .flexible td a,
.path-grade-report-grader .gradestable th.range,
.path-grade-report-grader td {
-white-space:nowrap;
+ white-space: nowrap;
}
-
table#user-grades .catlevel1,
table#user-grades .r1,
.path-grade-report-grader table tr.even td.cell,
.path-grade-report-grader table tr.even th {
-background-color:#fff;
+ background-color: #fff;
}
-
table#user-grades .catlevel3,
.path-grade-report-grader table tr.odd td.cell {
-background-color:#efefef;
+ background-color: #efefef;
}
-
table#fixed_column tr.odd th ,
table#user-grades tr.odd th {
-background-color:#efefef;
+ background-color: #efefef;
}
-
-table#user-grades td.vmarked,
-table#user-grades tr.odd td.vmarked,
table#user-grades td.vmarked,
table#user-grades tr.odd td.vmarked,
-table#user-grades tr.even td.vmarked {
-background-color:#fc3;
+table#user-grades tr.avg td.vmarked,
+table#user-grades tr.controls td.vmarked,
+table#user-grades .catlevel1.vmarked,
+table#user-grades .catlevel2.vmarked,
+table#user-grades .catlevel3.vmarked,
+table#user-grades tr.range td.vmarked,
+table#user-grades tr.groupavg td.vmarked {
+ background-color: #fc3;
}
-
-table#user-grades td.hmarked,
-table#user-grades tr.odd td.hmarked,
table#user-grades td.hmarked,
table#user-grades tr.odd td.hmarked,
-table#user-grades tr.even td.hmarked {
-background-color:#ff9;
+table#user-grades tr.even td.hmarked,
+table#user-grades tr.odd th.hmarked,
+table#user-grades tr.even th.hmarked {
+ background-color: #ff9;
}
-
table#user-grades td.hmarked.vmarked,
table#user-grades tr.odd td.hmarked.vmarked,
-table#user-grades td.hmarked.vmarked,
-table#user-grades tr.even td.hmarked.vmarked,
-table#user-grades tr.odd td.hmarked.vmarked {
-background-color:#fc9;
+table#user-grades tr.even td.hmarked.vmarked {
+ background-color: #fc9;
}
-
table#user-grades tr.heading,
table#user-grades .heading td {
-border-style:solid;
-border-width:0;
+ border-style: solid;
+ border-width: 0;
}
-
table#user-grades td.userfield,
table#user-grades th,
.path-grade-report-grader div.gradeparent,
.path-grade-report-grader .ie6 form,
table#user-grades td.ajax {
-text-align:left;
+ text-align: left;
}
-
.dir-rtl table#user-grades td.userfield,
.dir-rtl table#user-grades th,
.path-grade-report-grader.dir-rtl div.gradeparent,
.path-grade-report-grader.dir-rtl .ie6 form,
.dir-rtl table#user-grades td.ajax {
-text-align:right;
+ text-align: right;
}
-
.path-grade-report-grader .gradeparent {
- overflow:auto;
+ overflow: auto;
}
-
-.path-grade-report-grader table tr.avg td.cell,
table#user-grades td.controls,
-.path-grade-report-grader table tr.avg,
-.path-grade-report-grader table tr.avg td,
-.path-grade-report-grader table tr.avg th {
-background-color:#f3ead8;
+.path-grade-report-grader table tr.avg .cell,
+.path-grade-report-grader table tr.range .cell {
+ background-color: #f3ead8;
}
-
.path-grade-report-grader div.left_scroller tr,
.path-grade-report-grader div.right_scroller tr,
.path-grade-report-grader div.left_scroller td,
.path-grade-report-grader div.right_scroller td,
.path-grade-report-grader div.left_scroller th,
.path-grade-report-grader div.right_scroller th {
- height:4.5em;
- font-size:10px;
+ height: 4.5em;
+ font-size: 10px;
}
-
.path-grade-report-grader table th.user,
.path-grade-report-grader table td.userfield {
- text-align:left;
- vertical-align:middle;
+ text-align: left;
+ vertical-align: middle;
}
-
-.path-grade-report-grader .usersuspended a:link,
-.path-grade-report-grader .usersuspended a:visited {
+.path-grade-report-grader .usersuspended a: link,
+.path-grade-report-grader .usersuspended a: visited {
color: #666;
}
-
.path-grade-report-grader table th.usersuspended img.usersuspendedicon {
vertical-align: text-bottom;
margin-left: .45em;
}
-
-.path-grade-report-grader .grade_icons { margin-bottom: .3em;}
-
+.path-grade-report-grader .grade_icons {
+ margin-bottom: .3em;
+}
.path-grade-report-grader .yui3-overlay {
background-color: #FFEE69;
border-color: #D4C237 #A6982B #A6982B;
padding: 2px 5px;
font-size: 0.7em;
}
-
.path-grade-report-grader .yui3-overlay .fullname {
color: #5F3E00;
font-weight: bold;
/* table#user-grades td */
/* .grader-report-grader table#user-grades td .yui-panel div.hd { */
.path-grade-report-grader #tooltipPanel {
- text-align: left;
+ text-align: left;
}
-
.path-grade-report-grader .yui3-overlay a.container-close {
- margin-top: -3px;
+ margin-top: -3px;
}
-
-.path-grade-report-grader #hiddentooltiproot, .tooltipDiv {
- display: none;
+.path-grade-report-grader #hiddentooltiproot,
+.tooltipDiv {
+ display: none;
}
-
.path-grade-report-grader.ie .right_scroller {
-overflow-y:hidden;
+ overflow-y: hidden;
}
-
.path-grade-report-grader.ie table#fixed_column th {
-height:4.5em;
+ height: 4.5em;
}
-
.path-grade-report-grader.ie table#fixed_column tr.avg th {
-height:2.1em;
+ height: 2.1em;
}
-
.path-grade-report-grader.ie div.left_scroller td {
-height:4.5em;
+ height: 4.5em;
}
-
.path-grade-report-grader.ie6 div.right_scroller {
-margin-top:4em;
-width:auto;
-position:absolute;
+ margin-top: 4em;
+ width: auto;
+ position: absolute;
}
-
.path-grade-report-grader.ie6 .excludedfloater {
-font-size:7px;
+ font-size: 7px;
+}
+
+/** MDL-40071 **/
+.path-grade-report-grader.dir-rtl table th.user,
+.path-grade-report-grader.dir-rtl table td.userfield {
+ text-align: right;
}
+
+/** MDL-40180 **/
+.dir-rtl table#user-grades th.category,
+.dir-rtl table#user-grades th#studentheader,
+.dir-rtl table#user-grades th.user {
+ text-align: right;
+}
+.path-grade-report-grader.dir-rtl th.user img.userpicture {
+ margin-left: 0.5em;
+}
+
$mform =& $this->_form;
- $mform->addElement('header', 'autogroup', get_string('autocreategroups', 'group'));
+ $mform->addElement('header', 'autogroup', get_string('general'));
+
+ $mform->addElement('text', 'namingscheme', get_string('namingscheme', 'group'));
+ $mform->addHelpButton('namingscheme', 'namingscheme', 'group');
+ $mform->addRule('namingscheme', get_string('required'), 'required', null, 'client');
+ $mform->setType('namingscheme', PARAM_TEXT);
+ // There must not be duplicate group names in course.
+ $template = get_string('grouptemplate', 'group');
+ $gname = groups_parse_name($template, 0);
+ if (!groups_get_group_by_name($COURSE->id, $gname)) {
+ $mform->setDefault('namingscheme', $template);
+ }
+
+ $options = array('groups' => get_string('numgroups', 'group'),
+ 'members' => get_string('nummembers', 'group'));
+ $mform->addElement('select', 'groupby', get_string('groupby', 'group'), $options);
+
+ $mform->addElement('text', 'number', get_string('number', 'group'),'maxlength="4" size="4"');
+ $mform->setType('number', PARAM_INT);
+ $mform->addRule('number', null, 'numeric', null, 'client');
+ $mform->addRule('number', get_string('required'), 'required', null, 'client');
+
+ $mform->addElement('header', 'groupmembershdr', get_string('groupmembers', 'group'));
+ $mform->setExpanded('groupmembershdr', true);
$options = array(0=>get_string('all'));
$options += $this->_customdata['roles'];
$mform->setType('cohortid', PARAM_INT);
$mform->setConstant('cohortid', '0');
}
-
- $options = array('groups' => get_string('numgroups', 'group'),
- 'members' => get_string('nummembers', 'group'));
- $mform->addElement('select', 'groupby', get_string('groupby', 'group'), $options);
-
- $mform->addElement('text', 'number', get_string('number', 'group'),'maxlength="4" size="4"');
- $mform->setType('number', PARAM_INT);
- $mform->addRule('number', null, 'numeric', null, 'client');
- $mform->addRule('number', get_string('required'), 'required', null, 'client');
-
- $mform->addElement('checkbox', 'nosmallgroups', get_string('nosmallgroups', 'group'));
- $mform->disabledIf('nosmallgroups', 'groupby', 'noteq', 'members');
- $mform->setAdvanced('nosmallgroups');
-
$options = array('no' => get_string('noallocation', 'group'),
'random' => get_string('random', 'group'),
'firstname' => get_string('byfirstname', 'group'),
'idnumber' => get_string('byidnumber', 'group'));
$mform->addElement('select', 'allocateby', get_string('allocateby', 'group'), $options);
$mform->setDefault('allocateby', 'random');
- $mform->setAdvanced('allocateby');
- $mform->addElement('text', 'namingscheme', get_string('namingscheme', 'group'));
- $mform->addHelpButton('namingscheme', 'namingscheme', 'group');
- $mform->addRule('namingscheme', get_string('required'), 'required', null, 'client');
- $mform->setType('namingscheme', PARAM_TEXT);
- // there must not be duplicate group names in course
- $template = get_string('grouptemplate', 'group');
- $gname = groups_parse_name($template, 0);
- if (!groups_get_group_by_name($COURSE->id, $gname)) {
- $mform->setDefault('namingscheme', $template);
- }
+ $mform->addElement('checkbox', 'nosmallgroups', get_string('nosmallgroups', 'group'));
+ $mform->disabledIf('nosmallgroups', 'groupby', 'noteq', 'members');
+
+ $mform->addElement('header', 'groupinghdr', get_string('grouping', 'group'));
- $options = array('0' => get_string('no'),
+ $options = array('0' => get_string('nogrouping', 'group'),
'-1'=> get_string('newgrouping', 'group'));
if ($groupings = groups_get_all_groupings($COURSE->id)) {
foreach ($groupings as $grouping) {
// Prepare the description editor: We do support files for group descriptions
$editoroptions = array('maxfiles'=>EDITOR_UNLIMITED_FILES, 'maxbytes'=>$course->maxbytes, 'trust'=>false, 'context'=>$context, 'noclean'=>true);
if (!empty($group->id)) {
+ $editoroptions['subdirs'] = file_area_contains_subdirs($context, 'group', 'description', $group->id);
$group = file_prepare_standard_editor($group, 'description', $editoroptions, $context, 'group', 'description', $group->id);
} else {
+ $editoroptions['subdirs'] = false;
$group = file_prepare_standard_editor($group, 'description', $editoroptions, $context, 'group', 'description', null);
}
$mform->addElement('text','idnumber', get_string('idnumbergroup'), 'maxlength="100" size="10"');
$mform->addHelpButton('idnumber', 'idnumbergroup');
$mform->setType('idnumber', PARAM_RAW);
- $mform->setAdvanced('idnumber');
if (!has_capability('moodle/course:changeidnumber', $coursecontext)) {
$mform->hardFreeze('idnumber');
}
$mform->addElement('text','idnumber', get_string('idnumbergrouping'), 'maxlength="100" size="10"');
$mform->addHelpButton('idnumber', 'idnumbergrouping');
$mform->setType('idnumber', PARAM_RAW);
- $mform->setAdvanced('idnumber');
if (!has_capability('moodle/course:changeidnumber', $coursecontext)) {
$mform->hardFreeze('idnumber');
}
//fill in the data depending on page params
//later using set_data
- $mform->addElement('header', 'general');
+ $mform->addElement('header', 'general', get_string('general'));
$filepickeroptions = array();
$filepickeroptions['filetypes'] = '*';
And I expand all fieldsets
And I fill the moodle form with:
| Group/member count | 2 |
- | Create in grouping | New grouping |
+ | Grouping of auto-created groups | New grouping |
| Grouping name | Grouping name |
And I press "Preview"
Then I should see "Group members"
$string['guestroleid'] = 'Role for guest';
$string['guestroleid_help'] = 'This role is automatically assigned to the guest user. It is also temporarily assigned to not enrolled users that enter the course via guest enrolment plugin.';
$string['helpadminseesall'] = 'Do admins see all calendar events or just those that apply to themselves?';
-$string['helpcalendarsettings'] = 'Configure various calendar and date/time-related aspects of Moodle';
$string['helpcalendarcustomexport'] = 'Enable custom date range export option in calendar exports. Calendar exports must be enabled before this is effective.';
+$string['helpcalendarsettings'] = 'Configure various calendar and date/time-related aspects of Moodle';
+$string['helpcalendartype'] = 'This is the calendar type that will be used throughout your site.';
$string['helpexportlookahead'] = 'How many days in the future does the calendar look for events during export for the custom export option?';
$string['helpexportlookback'] = 'How many days in the past does the calendar look for events during export for the custom export option?';
$string['helpforcetimezone'] = 'You can allow users to individually select their timezone, or force a timezone for everyone.';
$string['entryerrornotyours'] = 'This entry is not yours';
$string['entrysaved'] = 'Your entry has been saved';
$string['entrytitle'] = 'Entry title';
-$string['entryupdated'] = 'Blog entry updated';
$string['evententryadded'] = 'Blog entry added';
$string['evententrydeleted'] = 'Blog entry deleted';
+$string['evententryupdated'] = 'Blog entry updated';
$string['externalblogcrontime'] = 'External blog cron schedule';
$string['externalblogdeleteconfirm'] = 'Unregister this external blog?';
$string['externalblogdeleted'] = 'External blog unregistered';
$string['cachedef_coursecatrecords'] = 'Course categories records';
$string['cachedef_coursecontacts'] = 'List of course contacts';
$string['cachedef_coursecattree'] = 'Course categories tree';
+$string['cachedef_coursemodinfo'] = 'Accumulated information about modules and sections for each course';
$string['cachedef_databasemeta'] = 'Database meta information';
$string['cachedef_eventinvalidation'] = 'Event invalidation';
$string['cachedef_externalbadges'] = 'External badges for particular user';
$string['exportbutton'] = 'Export';
$string['exportcalendar'] = 'Export calendar';
$string['for'] = 'for';
+$string['forcecalendartype'] = 'Force calendar';
$string['fri'] = 'Fri';
$string['friday'] = 'Friday';
$string['generateurlbutton'] = 'Get calendar URL';
$string['pollinterval_help'] = 'How often you would like the calendar to update with new events.';
$string['preferences'] = 'Preferences';
$string['preferences_available'] = 'Your personal preferences';
+$string['preferredcalendar'] = 'Preferred calendar';
$string['pref_lookahead'] = 'Upcoming events look-ahead';
$string['pref_lookahead_help'] = 'This sets the (maximum) number of days in the future that an event has to start in in order to be displayed as an upcoming event. Events that start beyond this will never be displayed as upcoming. Please note that <strong>there is no guarantee</strong> that all events starting in this time frame will be displayed; if there are too many (more than the "Maximum upcoming events" preference) then the most distant events will not be shown.';
$string['pref_maxevents'] = 'Maximum upcoming events';
$string['creategroup'] = 'Create group';
$string['creategrouping'] = 'Create grouping';
$string['creategroupinselectedgrouping'] = 'Create group in grouping';
-$string['createingrouping'] = 'Create in grouping';
+$string['createingrouping'] = 'Grouping of auto-created groups';
$string['createorphangroup'] = 'Create orphan group';
$string['databaseupgradegroups'] = 'Groups version is now {$a}';
$string['defaultgrouping'] = 'Default grouping';
$string['group'] = 'Group';
$string['groupaddedsuccesfully'] = 'Group {$a} added successfully';
$string['groupaddedtogroupingsuccesfully'] = 'Group {$a->groupname} added to grouping {$a->groupingname} successfully';
-$string['groupby'] = 'Specify';
+$string['groupby'] = 'Auto create based on';
$string['groupdescription'] = 'Group description';
$string['groupinfo'] = 'Info about selected group';
$string['groupinfomembers'] = 'Info about selected members';
$string['newpicture'] = 'New picture';
$string['newpicture_help'] = 'Select an image in JPG or PNG format. The image will be cropped to a square and resized to 100x100 pixels.';
$string['noallocation'] = 'No allocation';
+$string['nogrouping'] = 'No grouping';
$string['nogroups'] = 'There are no groups set up in this course yet';
$string['nogroupsassigned'] = 'No groups assigned';
$string['nopermissionforcreation'] = 'Can\'t create group "{$a}" as you don\'t have the required permissions';
$string['removegroupingsmembers'] = 'Remove all groups from groupings';
$string['removegroupsmembers'] = 'Remove all group members';
$string['removeselectedusers'] = 'Remove selected users';
-$string['selectfromrole'] = 'Select members from role';
+$string['selectfromrole'] = 'Select members with role';
$string['showgroupsingrouping'] = 'Show groups in grouping';
$string['showmembersforgroup'] = 'Show members for group';
$string['toomanygroups'] = 'Insufficient users to populate this number of groups - there are only {$a} users in the selected role.';
$string['type_cachelock_plural'] = 'Cache lock handlers';
$string['type_cachestore'] = 'Cache store';
$string['type_cachestore_plural'] = 'Cache stores';
+$string['type_calendartype'] = 'Calendar type';
+$string['type_calendartype_plural'] = 'Calendar types';
$string['type_coursereport'] = 'Course report';
$string['type_coursereport_plural'] = 'Course reports';
$string['type_editor'] = 'Editor';
}
}
- // clear course.modinfo for courses that used this module
- $sql = "UPDATE {course}
- SET modinfo=''
- WHERE id IN (SELECT DISTINCT course
+ // Increment course.cacherev for courses that used this module.
+ // This will force cache rebuilding on the next request.
+ increment_revision_number('course', 'cacherev',
+ "id IN (SELECT DISTINCT course
FROM {course_modules}
- WHERE module=?)";
- $DB->execute($sql, array($module->id));
+ WHERE module=?)",
+ array($module->id));
// delete all the course module records
$DB->delete_records('course_modules', array('module' => $module->id));
}
$site = get_site();
- $supportuser = generate_email_supportuser();
+ $supportuser = core_user::get_support_user();
$data = new stdClass();
$data->firstname = $user->firstname;
$coursecontext = context_course::instance($course->id);
$isfrontpage = (!$coursecontext || $course->id == $SITE->id);
+ $canmanage = has_any_capability(array('moodle/badges:viewawarded',
+ 'moodle/badges:createbadge',
+ 'moodle/badges:awardbadge',
+ 'moodle/badges:configurecriteria',
+ 'moodle/badges:configuremessages',
+ 'moodle/badges:configuredetails',
+ 'moodle/badges:deletebadge'), $coursecontext);
- if (!empty($CFG->enablebadges) && !empty($CFG->badges_allowcoursebadges) && !$isfrontpage) {
- if (has_capability('moodle/badges:configuredetails', $coursecontext)) {
- $coursenode->add(get_string('coursebadges', 'badges'), null,
- navigation_node::TYPE_CONTAINER, null, 'coursebadges',
- new pix_icon('i/badge', get_string('coursebadges', 'badges')));
+ if (!empty($CFG->enablebadges) && !empty($CFG->badges_allowcoursebadges) && !$isfrontpage && $canmanage) {
+ $coursenode->add(get_string('coursebadges', 'badges'), null,
+ navigation_node::TYPE_CONTAINER, null, 'coursebadges',
+ new pix_icon('i/badge', get_string('coursebadges', 'badges')));
- if (has_capability('moodle/badges:viewawarded', $coursecontext)) {
- $url = new moodle_url('/badges/index.php',
- array('type' => BADGE_TYPE_COURSE, 'id' => $course->id));
+ $url = new moodle_url('/badges/index.php', array('type' => BADGE_TYPE_COURSE, 'id' => $course->id));
- $coursenode->get('coursebadges')->add(get_string('managebadges', 'badges'), $url,
- navigation_node::TYPE_SETTING, null, 'coursebadges');
- }
-
- if (has_capability('moodle/badges:createbadge', $coursecontext)) {
- $url = new moodle_url('/badges/newbadge.php',
- array('type' => BADGE_TYPE_COURSE, 'id' => $course->id));
-
- $coursenode->get('coursebadges')->add(get_string('newbadge', 'badges'), $url,
- navigation_node::TYPE_SETTING, null, 'newbadge');
- }
- } else if (has_capability('moodle/badges:awardbadge', $coursecontext)) {
- $coursenode->add(get_string('coursebadges', 'badges'), null,
- navigation_node::TYPE_CONTAINER, null, 'coursebadges',
- new pix_icon('i/badge', get_string('coursebadges', 'badges')));
+ $coursenode->get('coursebadges')->add(get_string('managebadges', 'badges'), $url,
+ navigation_node::TYPE_SETTING, null, 'coursebadges');
- $url = new moodle_url('/badges/index.php',
- array('type' => BADGE_TYPE_COURSE, 'id' => $course->id));
+ if (has_capability('moodle/badges:createbadge', $coursecontext)) {
+ $url = new moodle_url('/badges/newbadge.php', array('type' => BADGE_TYPE_COURSE, 'id' => $course->id));
- $coursenode->get('coursebadges')->add(get_string('managebadges', 'badges'), $url,
- navigation_node::TYPE_SETTING, null, 'coursebadges');
+ $coursenode->get('coursebadges')->add(get_string('newbadge', 'badges'), $url,
+ navigation_node::TYPE_SETTING, null, 'newbadge');
}
}
}
global $CFG, $PAGE, $USER, $SITE;
require_once($CFG->dirroot . '/badges/renderer.php');
- if ($USER->id == $userid || has_capability('moodle/badges:viewotherbadges', context_user::instance($USER->id))) {
+ // Determine context.
+ if (isloggedin()) {
+ $context = context_user::instance($USER->id);
+ } else {
+ $context = context_system::instance();
+ }
+
+ if ($USER->id == $userid || has_capability('moodle/badges:viewotherbadges', $context)) {
$records = badges_get_user_badges($userid, $courseid, null, null, null, true);
$renderer = new core_badges_renderer($PAGE, '');
// Print local badges.
if ($records) {
- $left = get_string('localbadgesp', 'badges', $SITE->fullname);
+ $left = get_string('localbadgesp', 'badges', format_string($SITE->fullname));
$right = $renderer->print_badges_list($records, $userid, true);
echo html_writer::tag('dt', $left);
echo html_writer::tag('dd', $right);
*/
class behat_util extends testing_util {
+ /**
+ * The behat test site fullname and shortname.
+ */
+ const BEHATSITENAME = "Acceptance test site";
+
/**
* @var array Files to skip when resetting dataroot folder
*/
$options = array();
$options['adminuser'] = 'admin';
$options['adminpass'] = 'admin';
- $options['fullname'] = 'Acceptance test site';
- $options['shortname'] = 'Acceptance test site';
+ $options['fullname'] = self::BEHATSITENAME;
+ $options['shortname'] = self::BEHATSITENAME;
install_cli_database($options, false);
protected static $classmap = null;
/** @var null list of some known files that can be included. */
protected static $filemap = null;
+ /** @var int|float core version. */
+ protected static $version = null;
/** @var array list of the files to map. */
protected static $filestomap = array('lib.php', 'settings.php');
include($cachefile);
if (!is_array($cache)) {
// Something is very wrong.
- } else if (!isset($cache['plugintypes']) or !isset($cache['plugins']) or !isset($cache['subsystems']) or !isset($cache['classmap'])) {
+ } else if (!isset($cache['version'])) {
// Something is very wrong.
+ } else if ((float) $cache['version'] !== (float) self::fetch_core_version()) {
+ // Outdated cache. We trigger an error log to track an eventual repetitive failure of float comparison.
+ error_log('Resetting core_component cache after core upgrade to version ' . self::fetch_core_version());
} else if ($cache['plugintypes']['mod'] !== "$CFG->dirroot/mod") {
// $CFG->dirroot was changed.
} else {
'plugins' => self::$plugins,
'classmap' => self::$classmap,
'filemap' => self::$filemap,
+ 'version' => self::$version,
);
return '<?php
self::fill_classmap_cache();
self::fill_filemap_cache();
+ self::fetch_core_version();
+ }
+
+ /**
+ * Get the core version.
+ *
+ * In order for this to work properly, opcache should be reset beforehand.
+ *
+ * @return float core version.
+ */
+ protected static function fetch_core_version() {
+ global $CFG;
+ if (self::$version === null) {
+ require($CFG->dirroot . '/version.php');
+ self::$version = $version;
+ }
+ return self::$version;
}
/**
'qtype' => $CFG->dirroot.'/question/type',
'mod' => $CFG->dirroot.'/mod',
'auth' => $CFG->dirroot.'/auth',
+ 'calendartype' => $CFG->dirroot.'/calendar/type',
'enrol' => $CFG->dirroot.'/enrol',
'message' => $CFG->dirroot.'/message/output',
'block' => $CFG->dirroot.'/blocks',
$versions = array();
// Main version first.
- $version = null;
- include($CFG->dirroot.'/version.php');
- $versions['core'] = $version;
+ $versions['core'] = self::fetch_core_version();
// The problem here is tha the component cache might be stable,
// we want this to work also on frontpage without resetting the component cache.
$module = new stdClass();
$module->version = null;
include($fullplug.'/version.php');
- $versions[$plug] = $module->version;
+ $versions[$type.'_'.$plug] = $module->version;
} else {
$plugin = new stdClass();
$plugin->version = null;
@include($fullplug.'/version.php');
- $versions[$plug] = $plugin->version;
+ $versions[$type.'_'.$plug] = $plugin->version;
}
}
}
namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
/**
* Base event class.
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
/**
* All other event classes must extend this class.
*
namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
/**
* Event for when a new blog entry is added..
*
* @return array of parameters to be passed to legacy add_to_log() function.
*/
protected function get_legacy_logdata() {
- return array (SITEID, 'blog', 'add', 'index.php?userid='.$this->userid.'&entryid='.$this->objectid, $this->customobject->subject);
+ return array (SITEID, 'blog', 'add', 'index.php?userid=' . $this->relateduserid . '&entryid=' . $this->objectid,
+ $this->customobject->subject);
}
}
*/
namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
/**
* class blog_entry_deleted
*
* @return array of parameters to be passed to legacy add_to_log() function.
*/
protected function get_legacy_logdata() {
- return array (SITEID, 'blog', 'delete', 'index.php?userid='.$this->userid, 'deleted blog entry with entry id# '. $this->objectid);
+ return array (SITEID, 'blog', 'delete', 'index.php?userid=' . $this->relateduserid, 'deleted blog entry with entry id# '.
+ $this->objectid);
}
}
--- /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/>.
+
+/**
+ * Event to be triggered when a blog entry is updated.
+ *
+ * @package core
+ * @copyright 2013 Ankit Agarwal
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class blog_entry_updated
+ *
+ * Event to be triggered when a blog entry is updated.
+ *
+ * @package core
+ * @copyright 2013 Ankit Agarwal
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class blog_entry_updated extends base {
+
+ /** @var \blog_entry A reference to the active blog_entry object. */
+ protected $customobject;
+
+ /**
+ * Set basic event properties.
+ */
+ protected function init() {
+ $this->context = \context_system::instance();
+ $this->data['objecttable'] = 'post';
+ $this->data['crud'] = 'u';
+ $this->data['level'] = self::LEVEL_PARTICIPATING;
+ }
+
+ /**
+ * Set custom data of the event.
+ *
+ * @param \blog_entry $data A reference to the active blog_entry object.
+ */
+ public function set_custom_data($data) {
+ $this->customobject = $data;
+ }
+
+ /**
+ * Returns localised general event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('evententryupdated', 'core_blog');
+ }
+
+ /**
+ * Returns non-localised description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return 'User with id {$this->userid} updated blog entry {$this->other["subject"]';
+ }
+
+ /**
+ * Returns relevant URL.
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/blog/index.php', array('entryid' => $this->objectid, 'userid' => $this->userid));
+ }
+
+ /**
+ * Legacy event data if get_legacy_eventname() is not empty.
+ *
+ * @return \blog_entry
+ */
+ protected function get_legacy_eventdata() {
+ return $this->customobject;
+ }
+
+ /**
+ * Legacy event name.
+ *
+ * @return string legacy event name
+ */
+ public static function get_legacy_eventname() {
+ return 'blog_entry_edited';
+ }
+
+ /**
+ * Replace legacy add_to_log() statement.
+ *
+ * @return array of parameters to be passed to legacy add_to_log() function.
+ */
+ protected function get_legacy_logdata() {
+ return array(SITEID, 'blog', 'update', 'index.php?userid=' . $this->relateduserid . '&entryid=' . $this->objectid,
+ $this->other['subject']);
+ }
+}
+
namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
/**
* Class content_viewed.
*
namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
/**
* category deleted event.
*
namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
/**
* Event when course completed.
*
namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
/**
* Course content_deleted event.
*
namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
/**
* Course created event.
*
namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
/**
* Course deleted event.
*
namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
/**
* Event when course module completion is updated.
*
namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
/**
* Course restored event.
*
namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
/**
* Course updated event.
*
namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
/**
* New event manager class.
*
if ($CFG->admin !== 'admin' and strpos($observer['includefile'], '/admin/') === 0) {
$observer['includefile'] = preg_replace('|^/admin/|', '/'.$CFG->admin.'/', $observer['includefile']);
}
+ $observer['includefile'] = $CFG->dirroot . '/' . ltrim($observer['includefile'], '/');
if (!file_exists($observer['includefile'])) {
debugging("Invalid 'includefile' detected in $file observer definition", DEBUG_DEVELOPER);
continue;
namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
/**
* Event when role allow assignments is updated.
*
namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
/**
* Event when role allow override is updated.
*
namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
/**
* Event when role allow switch is updated.
*
namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
/**
* Role assigned event.
*
* @return \moodle_url
*/
public function get_url() {
- return new moodle_url('/admin/roles/assign.php', array('contextid'=>$this->contextid, 'roleid'=>$this->objectid));
+ return new moodle_url('/admin/roles/assign.php', array('contextid' => $this->contextid, 'roleid' => $this->objectid));
}
/**
namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
/**
* Role updated event.
*
if ($this->contextlevel === CONTEXT_SYSTEM) {
return new \moodle_url('admin/roles/define.php', array('action' => 'view', 'roleid' => $this->objectid));
} else {
- return new \moodle_url('/admin/roles/override.php', array('contextid' => $this->contextid, 'roleid' => $this->objectid));
+ return new \moodle_url('/admin/roles/override.php', array('contextid' => $this->contextid,
+ 'roleid' => $this->objectid));
}
}
namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
/**
* Role assigned event.
*
* @return array
*/
protected function get_legacy_logdata() {
- return array(SITEID, 'role', 'delete', 'admin/roles/manage.php?action=delete&roleid='.$this->objectid, $this->other['shortname'], '');
+ return array(SITEID, 'role', 'delete', 'admin/roles/manage.php?action=delete&roleid=' . $this->objectid,
+ $this->other['shortname']