diff --git a/admin/roles/lib.php b/admin/roles/lib.php index 11341d10cfe..43eb7f530b8 100644 --- a/admin/roles/lib.php +++ b/admin/roles/lib.php @@ -1039,12 +1039,10 @@ class potential_assignees_below_course extends role_assign_user_selector_base { $countfields = 'SELECT COUNT(u.id)'; $sql = " FROM {user} u - WHERE u.id IN ($enrolsql) $wherecondition - AND u.id NOT IN ( - SELECT r.userid - FROM {role_assignments} r - WHERE r.contextid = :contextid - AND r.roleid = :roleid)"; + LEFT JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.roleid = :roleid AND ra.contextid = :contextid) + WHERE u.id IN ($enrolsql) + $wherecondition + AND ra.id IS NULL"; $order = ' ORDER BY lastname ASC, firstname ASC'; $params['contextid'] = $this->context->id; diff --git a/admin/settings/courses.php b/admin/settings/courses.php index 92722eb082f..f07107ca631 100644 --- a/admin/settings/courses.php +++ b/admin/settings/courses.php @@ -47,6 +47,11 @@ if ($hassiteconfig $temp->add(new admin_setting_configselect('moodlecourse/legacyfiles', new lang_string('courselegacyfiles'), new lang_string('courselegacyfiles_help'), key($choices), $choices)); } + $choices = array(); + $choices[COURSE_DISPLAY_SINGLEPAGE] = new lang_string('coursedisplay_single'); + $choices[COURSE_DISPLAY_MULTIPAGE] = new lang_string('coursedisplay_multi'); + $temp->add(new admin_setting_configselect('moodlecourse/coursedisplay', new lang_string('coursedisplay'), new lang_string('coursedisplay_help'), COURSE_DISPLAY_SINGLEPAGE, $choices)); + $temp->add(new admin_setting_heading('groups', new lang_string('groups', 'group'), '')); $choices = array(); $choices[NOGROUPS] = new lang_string('groupsnone', 'group'); diff --git a/admin/settings/location.php b/admin/settings/location.php index 3d3ccb83a6b..607090a529a 100644 --- a/admin/settings/location.php +++ b/admin/settings/location.php @@ -14,7 +14,7 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page $temp->add(new admin_setting_heading('iplookup', new lang_string('iplookup', 'admin'), new lang_string('iplookupinfo', 'admin'))); $temp->add(new admin_setting_configfile('geoipfile', new lang_string('geoipfile', 'admin'), new lang_string('configgeoipfile', 'admin', $CFG->dataroot.'/geoip/'), $CFG->dataroot.'/geoip/GeoLiteCity.dat')); - $temp->add(new admin_setting_configtext('googlemapkey', new lang_string('googlemapkey', 'admin'), new lang_string('configgooglemapkey', 'admin', $CFG->wwwroot), '')); + $temp->add(new admin_setting_configtext('googlemapkey3', new lang_string('googlemapkey3', 'admin'), new lang_string('googlemapkey3_help', 'admin'), '', PARAM_RAW, 60)); $temp->add(new admin_setting_configtext('allcountrycodes', new lang_string('allcountrycodes', 'admin'), new lang_string('configallcountrycodes', 'admin'), '', '/^(?:\w+(?:,\w+)*)?$/')); diff --git a/admin/tool/phpunit/cli/util.php b/admin/tool/phpunit/cli/util.php index 2986d6497ad..5afd3bacbe8 100644 --- a/admin/tool/phpunit/cli/util.php +++ b/admin/tool/phpunit/cli/util.php @@ -150,7 +150,7 @@ if ($diag) { } else if ($drop) { // make sure tests do not run in parallel phpunit_util::acquire_test_lock(); - phpunit_util::drop_site(); + phpunit_util::drop_site(true); // note: we must stop here because $CFG is messed up and we can not reinstall, sorry exit(0); diff --git a/auth/email/auth.php b/auth/email/auth.php index 0051aafdefc..e50c09e44ee 100644 --- a/auth/email/auth.php +++ b/auth/email/auth.php @@ -132,7 +132,9 @@ class auth_plugin_email extends auth_plugin_base { } else if ($user->secret == $confirmsecret) { // They have provided the secret key to get in $DB->set_field("user", "confirmed", 1, array("id"=>$user->id)); - $DB->set_field("user", "firstaccess", time(), array("id"=>$user->id)); + if ($user->firstaccess == 0) { + $DB->set_field("user", "firstaccess", time(), array("id"=>$user->id)); + } return AUTH_CONFIRM_OK; } } else { diff --git a/auth/ldap/auth.php b/auth/ldap/auth.php index 53f867faac9..049fd97667d 100644 --- a/auth/ldap/auth.php +++ b/auth/ldap/auth.php @@ -546,7 +546,9 @@ class auth_plugin_ldap extends auth_plugin_base { return AUTH_CONFIRM_FAIL; } $DB->set_field('user', 'confirmed', 1, array('id'=>$user->id)); - $DB->set_field('user', 'firstaccess', time(), array('id'=>$user->id)); + if ($user->firstaccess == 0) { + $DB->set_field('user', 'firstaccess', time(), array('id'=>$user->id)); + } return AUTH_CONFIRM_OK; } } else { diff --git a/auth/manual/auth.php b/auth/manual/auth.php index e3df78a3f0d..29cb59ae2ef 100644 --- a/auth/manual/auth.php +++ b/auth/manual/auth.php @@ -170,7 +170,9 @@ class auth_plugin_manual extends auth_plugin_base { return AUTH_CONFIRM_ALREADY; } else { $DB->set_field("user", "confirmed", 1, array("id"=>$user->id)); - $DB->set_field("user", "firstaccess", time(), array("id"=>$user->id)); + if ($user->firstaccess == 0) { + $DB->set_field("user", "firstaccess", time(), array("id"=>$user->id)); + } return AUTH_CONFIRM_OK; } } else { diff --git a/backup/moodle2/backup_stepslib.php b/backup/moodle2/backup_stepslib.php index 24d7da9b77c..b4399f24344 100644 --- a/backup/moodle2/backup_stepslib.php +++ b/backup/moodle2/backup_stepslib.php @@ -182,8 +182,17 @@ abstract class backup_questions_activity_structure_step extends backup_activity_ /** * Attach to $element (usually attempts) the needed backup structures * for question_usages and all the associated data. + * + * @param backup_nested_element $element the element that will contain all the question_usages data. + * @param string $usageidname the name of the element that holds the usageid. + * This must be child of $element, and must be a final element. + * @param string $nameprefix this prefix is added to all the element names we create. + * Element names in the XML must be unique, so if you are using usages in + * two different ways, you must give a prefix to at least one of them. If + * you only use one sort of usage, then you can just use the default empty prefix. + * This should include a trailing underscore. For example "myprefix_" */ - protected function add_question_usages($element, $usageidname) { + protected function add_question_usages($element, $usageidname, $nameprefix = '') { global $CFG; require_once($CFG->dirroot . '/question/engine/lib.php'); @@ -195,21 +204,21 @@ abstract class backup_questions_activity_structure_step extends backup_activity_ throw new backup_step_exception('question_states_bad_question_attempt_element', $usageidname); } - $quba = new backup_nested_element('question_usage', array('id'), + $quba = new backup_nested_element($nameprefix . 'question_usage', array('id'), array('component', 'preferredbehaviour')); - $qas = new backup_nested_element('question_attempts'); - $qa = new backup_nested_element('question_attempt', array('id'), array( + $qas = new backup_nested_element($nameprefix . 'question_attempts'); + $qa = new backup_nested_element($nameprefix . 'question_attempt', array('id'), array( 'slot', 'behaviour', 'questionid', 'maxmark', 'minfraction', 'flagged', 'questionsummary', 'rightanswer', 'responsesummary', 'timemodified')); - $steps = new backup_nested_element('steps'); - $step = new backup_nested_element('step', array('id'), array( + $steps = new backup_nested_element($nameprefix . 'steps'); + $step = new backup_nested_element($nameprefix . 'step', array('id'), array( 'sequencenumber', 'state', 'fraction', 'timecreated', 'userid')); - $response = new backup_nested_element('response'); - $variable = new backup_nested_element('variable', null, array('name', 'value')); + $response = new backup_nested_element($nameprefix . 'response'); + $variable = new backup_nested_element($nameprefix . 'variable', null, array('name', 'value')); // Build the tree $element->add_child($quba); @@ -1835,7 +1844,7 @@ class backup_annotate_all_user_files extends backup_execution_step { 'backupid' => $this->get_backupid(), 'itemname' => 'userfinal')); foreach ($rs as $record) { $userid = $record->itemid; - $userctx = context_user::instance($userid); + $userctx = context_user::instance($userid, IGNORE_MISSING); if (!$userctx) { continue; // User has not context, sure it's a deleted user, so cannot have files } diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php index 520274c5e44..3677d7bee02 100644 --- a/backup/moodle2/restore_stepslib.php +++ b/backup/moodle2/restore_stepslib.php @@ -3478,31 +3478,74 @@ abstract class restore_questions_activity_structure_step extends restore_activit /** * Attach below $element (usually attempts) the needed restore_path_elements * to restore question_usages and all they contain. + * + * If you use the $nameprefix parameter, then you will need to implement some + * extra methods in your class, like + * + * protected function process_{nameprefix}question_attempt($data) { + * $this->restore_question_usage_worker($data, '{nameprefix}'); + * } + * protected function process_{nameprefix}question_attempt($data) { + * $this->restore_question_attempt_worker($data, '{nameprefix}'); + * } + * protected function process_{nameprefix}question_attempt_step($data) { + * $this->restore_question_attempt_step_worker($data, '{nameprefix}'); + * } + * + * @param restore_path_element $element the parent element that the usages are stored inside. + * @param array $paths the paths array that is being built. + * @param string $nameprefix should match the prefix passed to the corresponding + * backup_questions_activity_structure_step::add_question_usages call. */ - protected function add_question_usages($element, &$paths) { + protected function add_question_usages($element, &$paths, $nameprefix = '') { // Check $element is restore_path_element if (! $element instanceof restore_path_element) { throw new restore_step_exception('element_must_be_restore_path_element', $element); } + // Check $paths is one array if (!is_array($paths)) { throw new restore_step_exception('paths_must_be_array', $paths); } - $paths[] = new restore_path_element('question_usage', - $element->get_path() . '/question_usage'); - $paths[] = new restore_path_element('question_attempt', - $element->get_path() . '/question_usage/question_attempts/question_attempt'); - $paths[] = new restore_path_element('question_attempt_step', - $element->get_path() . '/question_usage/question_attempts/question_attempt/steps/step', + $paths[] = new restore_path_element($nameprefix . 'question_usage', + $element->get_path() . "/{$nameprefix}question_usage"); + $paths[] = new restore_path_element($nameprefix . 'question_attempt', + $element->get_path() . "/{$nameprefix}question_usage/{$nameprefix}question_attempts/{$nameprefix}question_attempt"); + $paths[] = new restore_path_element($nameprefix . 'question_attempt_step', + $element->get_path() . "/{$nameprefix}question_usage/{$nameprefix}question_attempts/{$nameprefix}question_attempt/{$nameprefix}steps/{$nameprefix}step", true); - $paths[] = new restore_path_element('question_attempt_step_data', - $element->get_path() . '/question_usage/question_attempts/question_attempt/steps/step/response/variable'); + $paths[] = new restore_path_element($nameprefix . 'question_attempt_step_data', + $element->get_path() . "/{$nameprefix}question_usage/{$nameprefix}question_attempts/{$nameprefix}question_attempt/{$nameprefix}steps/{$nameprefix}step/{$nameprefix}response/{$nameprefix}variable"); } /** * Process question_usages */ protected function process_question_usage($data) { + $this->restore_question_usage_worker($data, ''); + } + + /** + * Process question_attempts + */ + protected function process_question_attempt($data) { + $this->restore_question_attempt_worker($data, ''); + } + + /** + * Process question_attempt_steps + */ + protected function process_question_attempt_step($data) { + $this->restore_question_attempt_step_worker($data, ''); + } + + /** + * This method does the acutal work for process_question_usage or + * process_{nameprefix}_question_usage. + * @param array $data the data from the XML file. + * @param string $nameprefix the element name prefix. + */ + protected function restore_question_usage_worker($data, $nameprefix) { global $DB; // Clear our caches. @@ -3520,7 +3563,7 @@ abstract class restore_questions_activity_structure_step extends restore_activit $this->inform_new_usage_id($newitemid); - $this->set_mapping('question_usage', $oldid, $newitemid, false); + $this->set_mapping($nameprefix . 'question_usage', $oldid, $newitemid, false); } /** @@ -3532,30 +3575,36 @@ abstract class restore_questions_activity_structure_step extends restore_activit abstract protected function inform_new_usage_id($newusageid); /** - * Process question_attempts + * This method does the acutal work for process_question_attempt or + * process_{nameprefix}_question_attempt. + * @param array $data the data from the XML file. + * @param string $nameprefix the element name prefix. */ - protected function process_question_attempt($data) { + protected function restore_question_attempt_worker($data, $nameprefix) { global $DB; $data = (object)$data; $oldid = $data->id; $question = $this->get_mapping('question', $data->questionid); - $data->questionusageid = $this->get_new_parentid('question_usage'); + $data->questionusageid = $this->get_new_parentid($nameprefix . 'question_usage'); $data->questionid = $question->newitemid; $data->timemodified = $this->apply_date_offset($data->timemodified); $newitemid = $DB->insert_record('question_attempts', $data); - $this->set_mapping('question_attempt', $oldid, $newitemid); + $this->set_mapping($nameprefix . 'question_attempt', $oldid, $newitemid); $this->qtypes[$newitemid] = $question->info->qtype; $this->newquestionids[$newitemid] = $data->questionid; } /** - * Process question_attempt_steps + * This method does the acutal work for process_question_attempt_step or + * process_{nameprefix}_question_attempt_step. + * @param array $data the data from the XML file. + * @param string $nameprefix the element name prefix. */ - protected function process_question_attempt_step($data) { + protected function restore_question_attempt_step_worker($data, $nameprefix) { global $DB; $data = (object)$data; @@ -3563,14 +3612,14 @@ abstract class restore_questions_activity_structure_step extends restore_activit // Pull out the response data. $response = array(); - if (!empty($data->response['variable'])) { - foreach ($data->response['variable'] as $variable) { + if (!empty($data->{$nameprefix . 'response'}[$nameprefix . 'variable'])) { + foreach ($data->{$nameprefix . 'response'}[$nameprefix . 'variable'] as $variable) { $response[$variable['name']] = $variable['value']; } } unset($data->response); - $data->questionattemptid = $this->get_new_parentid('question_attempt'); + $data->questionattemptid = $this->get_new_parentid($nameprefix . 'question_attempt'); $data->timecreated = $this->apply_date_offset($data->timecreated); $data->userid = $this->get_mappingid('user', $data->userid); @@ -3583,6 +3632,7 @@ abstract class restore_questions_activity_structure_step extends restore_activit $this->qtypes[$data->questionattemptid], $this->newquestionids[$data->questionattemptid], $data->sequencenumber, $response); + foreach ($response as $name => $value) { $row = new stdClass(); $row->attemptstepid = $newitemid; diff --git a/backup/util/dbops/backup_plan_dbops.class.php b/backup/util/dbops/backup_plan_dbops.class.php index 4ff5d8c0d62..e169a8a8ba1 100644 --- a/backup/util/dbops/backup_plan_dbops.class.php +++ b/backup/util/dbops/backup_plan_dbops.class.php @@ -112,7 +112,7 @@ abstract class backup_plan_dbops extends backup_dbops { // Get all sections belonging to requested course $sectionsarr = array(); - $sections = $DB->get_records('course_sections', array('course' => $courseid)); + $sections = $DB->get_records('course_sections', array('course' => $courseid), 'section'); foreach ($sections as $section) { $sectionsarr[] = $section->id; } diff --git a/backup/util/helper/backup_cron_helper.class.php b/backup/util/helper/backup_cron_helper.class.php index 68e92987f49..ea8fda08577 100644 --- a/backup/util/helper/backup_cron_helper.class.php +++ b/backup/util/helper/backup_cron_helper.class.php @@ -110,7 +110,7 @@ abstract class backup_cron_automated_helper { $nextstarttime = backup_cron_automated_helper::calculate_next_automated_backup($admin->timezone, $now); $showtime = "undefined"; if ($nextstarttime > 0) { - $showtime = userdate($nextstarttime,"",$admin->timezone); + $showtime = date('r', $nextstarttime); } $rs = $DB->get_recordset('course'); @@ -124,7 +124,14 @@ abstract class backup_cron_automated_helper { } // Skip courses that do not yet need backup - $skipped = !(($backupcourse->nextstarttime >= 0 && $backupcourse->nextstarttime < $now) || $rundirective == self::RUN_IMMEDIATELY); + $skipped = !(($backupcourse->nextstarttime > 0 && $backupcourse->nextstarttime < $now) || $rundirective == self::RUN_IMMEDIATELY); + if ($skipped && $backupcourse->nextstarttime != $nextstarttime) { + $backupcourse->nextstarttime = $nextstarttime; + $backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_SKIPPED; + $DB->update_record('backup_courses', $backupcourse); + mtrace('Backup of \'' . $course->fullname . '\' is scheduled on ' . $showtime); + } + // Skip backup of unavailable courses that have remained unmodified in a month if (!$skipped && empty($course->visible) && ($now - $course->timemodified) > 31*24*60*60) { //Hidden + settings were unmodified last month //Check log if there were any modifications to the course content @@ -139,9 +146,10 @@ abstract class backup_cron_automated_helper { $skipped = true; } } + //Now we backup every non-skipped course if (!$skipped) { - mtrace('Backing up '.$course->fullname, '...'); + mtrace('Backing up '.$course->fullname.'...'); //We have to send a email because we have included at least one backup $emailpending = true; @@ -255,7 +263,7 @@ abstract class backup_cron_automated_helper { self::BACKUP_STATUS_SKIPPED => 0, ); - $statuses = $DB->get_records_sql('SELECT DISTINCT bc.laststatus, COUNT(bc.courseid) statuscount FROM {backup_courses} bc GROUP BY bc.laststatus'); + $statuses = $DB->get_records_sql('SELECT DISTINCT bc.laststatus, COUNT(bc.courseid) AS statuscount FROM {backup_courses} bc GROUP BY bc.laststatus'); foreach ($statuses as $status) { if (empty($status->statuscount)) { @@ -270,38 +278,47 @@ abstract class backup_cron_automated_helper { /** * Works out the next time the automated backup should be run. * - * @param mixed $timezone - * @param int $now - * @return int + * @param mixed $timezone user timezone + * @param int $now timestamp, should not be in the past, most likely time() + * @return int timestamp of the next execution at server time */ public static function calculate_next_automated_backup($timezone, $now) { - $result = -1; + $result = 0; $config = get_config('backup'); - $midnight = usergetmidnight($now, $timezone); + $autohour = $config->backup_auto_hour; + $automin = $config->backup_auto_minute; + + // Gets the user time relatively to the server time. $date = usergetdate($now, $timezone); + $usertime = mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year']); + $diff = $now - $usertime; - // Get number of days (from today) to execute backups + // Get number of days (from user's today) to execute backups. $automateddays = substr($config->backup_auto_weekdays, $date['wday']) . $config->backup_auto_weekdays; - $daysfromtoday = strpos($automateddays, "1", 1); + $daysfromnow = strpos($automateddays, "1"); - // If we can't find the next day, we set it to tomorrow - if (empty($daysfromtoday)) { - $daysfromtoday = 1; + // Error, there are no days to schedule the backup for. + if ($daysfromnow === false) { + return 0; } - // If some day has been found - if ($daysfromtoday !== false) { - // Calculate distance - $dist = ($daysfromtoday * 86400) + // Days distance - ($config->backup_auto_hour * 3600) + // Hours distance - ($config->backup_auto_minute * 60); // Minutes distance - $result = $midnight + $dist; + // Checks if the date would happen in the future (of the user). + $userresult = mktime($autohour, $automin, 0, $date['mon'], $date['mday'] + $daysfromnow, $date['year']); + if ($userresult <= $usertime) { + // If not, we skip the first scheduled day, that should fix it. + $daysfromnow = strpos($automateddays, "1", 1); + $userresult = mktime($autohour, $automin, 0, $date['mon'], $date['mday'] + $daysfromnow, $date['year']); } - // If that time is past, call the function recursively to obtain the next valid day - if ($result > 0 && $result < time()) { - $result = self::calculate_next_automated_backup($timezone, $result); + // Now we generate the time relative to the server. + $result = $userresult + $diff; + + // If that time is past, call the function recursively to obtain the next valid day. + if ($result <= $now) { + // Checking time() in here works, but makes PHPUnit Tests extremely hard to predict. + // $now should never be earlier than time() anyway... + $result = self::calculate_next_automated_backup($timezone, $now + DAYSECS); } return $result; @@ -411,7 +428,12 @@ abstract class backup_cron_automated_helper { $config = get_config('backup'); $active = (int)$config->backup_auto_active; - if ($active === self::AUTO_BACKUP_DISABLED || ($rundirective == self::RUN_ON_SCHEDULE && $active === self::AUTO_BACKUP_MANUAL)) { + $weekdays = (string)$config->backup_auto_weekdays; + + // In case of automated backup also check that it is scheduled for at least one weekday. + if ($active === self::AUTO_BACKUP_DISABLED || + ($rundirective == self::RUN_ON_SCHEDULE && $active === self::AUTO_BACKUP_MANUAL) || + ($rundirective == self::RUN_ON_SCHEDULE && strpos($weekdays, '1') === false)) { return self::STATE_DISABLED; } else if (!empty($config->backup_auto_running)) { // Detect if the backup_auto_running semaphore is a valid one diff --git a/backup/util/helper/tests/cronhelper_test.php b/backup/util/helper/tests/cronhelper_test.php new file mode 100644 index 00000000000..7afd1be1d08 --- /dev/null +++ b/backup/util/helper/tests/cronhelper_test.php @@ -0,0 +1,442 @@ +. + +/** + * Unit tests for backups cron helper. + * + * @package core_backup + * @category phpunit + * @copyright 2012 Frédéric Massart + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/backup/util/helper/backup_cron_helper.class.php'); + +/** + * Unit tests for backup cron helper + */ +class backup_cron_helper_testcase extends advanced_testcase { + + /** + * Test {@link backup_cron_automated_helper::calculate_next_automated_backup}. + */ + public function test_next_automated_backup() { + $this->resetAfterTest(); + set_config('backup_auto_active', '1', 'backup'); + + // Notes + // - backup_auto_weekdays starts on Sunday + // - Tests cannot be done in the past + // - Only the DST on the server side is handled. + + // Every Tue and Fri at 11pm. + set_config('backup_auto_weekdays', '0010010', 'backup'); + set_config('backup_auto_hour', '23', 'backup'); + set_config('backup_auto_minute', '0', 'backup'); + $timezone = 99; + + $now = strtotime('next Monday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('2-23:00', date('w-H:i', $next)); + + $now = strtotime('next Tuesday 18:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('2-23:00', date('w-H:i', $next)); + + $now = strtotime('next Wednesday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('5-23:00', date('w-H:i', $next)); + + $now = strtotime('next Thursday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('5-23:00', date('w-H:i', $next)); + + $now = strtotime('next Friday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('5-23:00', date('w-H:i', $next)); + + $now = strtotime('next Saturday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('2-23:00', date('w-H:i', $next)); + + $now = strtotime('next Sunday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('2-23:00', date('w-H:i', $next)); + + // Every Sun and Sat at 12pm. + set_config('backup_auto_weekdays', '1000001', 'backup'); + set_config('backup_auto_hour', '0', 'backup'); + set_config('backup_auto_minute', '0', 'backup'); + $timezone = 99; + + $now = strtotime('next Monday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('6-00:00', date('w-H:i', $next)); + + $now = strtotime('next Tuesday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('6-00:00', date('w-H:i', $next)); + + $now = strtotime('next Wednesday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('6-00:00', date('w-H:i', $next)); + + $now = strtotime('next Thursday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('6-00:00', date('w-H:i', $next)); + + $now = strtotime('next Friday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('6-00:00', date('w-H:i', $next)); + + $now = strtotime('next Saturday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('0-00:00', date('w-H:i', $next)); + + $now = strtotime('next Sunday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('6-00:00', date('w-H:i', $next)); + + // Every Sun at 4am. + set_config('backup_auto_weekdays', '1000000', 'backup'); + set_config('backup_auto_hour', '4', 'backup'); + set_config('backup_auto_minute', '0', 'backup'); + $timezone = 99; + + $now = strtotime('next Monday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('0-04:00', date('w-H:i', $next)); + + $now = strtotime('next Tuesday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('0-04:00', date('w-H:i', $next)); + + $now = strtotime('next Wednesday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('0-04:00', date('w-H:i', $next)); + + $now = strtotime('next Thursday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('0-04:00', date('w-H:i', $next)); + + $now = strtotime('next Friday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('0-04:00', date('w-H:i', $next)); + + $now = strtotime('next Saturday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('0-04:00', date('w-H:i', $next)); + + $now = strtotime('next Sunday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('0-04:00', date('w-H:i', $next)); + + // Every day but Wed at 8:30pm. + set_config('backup_auto_weekdays', '1110111', 'backup'); + set_config('backup_auto_hour', '20', 'backup'); + set_config('backup_auto_minute', '30', 'backup'); + $timezone = 99; + + $now = strtotime('next Monday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('1-20:30', date('w-H:i', $next)); + + $now = strtotime('next Tuesday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('2-20:30', date('w-H:i', $next)); + + $now = strtotime('next Wednesday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('4-20:30', date('w-H:i', $next)); + + $now = strtotime('next Thursday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('4-20:30', date('w-H:i', $next)); + + $now = strtotime('next Friday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('5-20:30', date('w-H:i', $next)); + + $now = strtotime('next Saturday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('6-20:30', date('w-H:i', $next)); + + $now = strtotime('next Sunday 17:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('0-20:30', date('w-H:i', $next)); + + // Sun, Tue, Thu, Sat at 12pm. + set_config('backup_auto_weekdays', '1010101', 'backup'); + set_config('backup_auto_hour', '0', 'backup'); + set_config('backup_auto_minute', '0', 'backup'); + $timezone = 99; + + $now = strtotime('next Monday 13:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('2-00:00', date('w-H:i', $next)); + + $now = strtotime('next Tuesday 13:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('4-00:00', date('w-H:i', $next)); + + $now = strtotime('next Wednesday 13:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('4-00:00', date('w-H:i', $next)); + + $now = strtotime('next Thursday 13:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('6-00:00', date('w-H:i', $next)); + + $now = strtotime('next Friday 13:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('6-00:00', date('w-H:i', $next)); + + $now = strtotime('next Saturday 13:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('0-00:00', date('w-H:i', $next)); + + $now = strtotime('next Sunday 13:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('2-00:00', date('w-H:i', $next)); + + // None. + set_config('backup_auto_weekdays', '0000000', 'backup'); + set_config('backup_auto_hour', '15', 'backup'); + set_config('backup_auto_minute', '30', 'backup'); + $timezone = 99; + + $now = strtotime('next Sunday 13:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals('0', $next); + + // Playing with timezones. + set_config('backup_auto_weekdays', '1111111', 'backup'); + set_config('backup_auto_hour', '20', 'backup'); + set_config('backup_auto_minute', '00', 'backup'); + + $timezone = 99; + date_default_timezone_set('Australia/Perth'); + $now = strtotime('18:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals(date('w-20:00'), date('w-H:i', $next)); + + $timezone = 99; + date_default_timezone_set('Europe/Brussels'); + $now = strtotime('18:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals(date('w-20:00'), date('w-H:i', $next)); + + $timezone = 99; + date_default_timezone_set('America/New_York'); + $now = strtotime('18:00:00'); + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals(date('w-20:00'), date('w-H:i', $next)); + + // Viva Australia! (UTC+8). + date_default_timezone_set('Australia/Perth'); + $now = strtotime('18:00:00'); + + $timezone = -10.0; // 12am for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals(date('w-14:00', strtotime('tomorrow')), date('w-H:i', $next)); + + $timezone = -5.0; // 5am for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals(date('w-09:00', strtotime('tomorrow')), date('w-H:i', $next)); + + $timezone = 0.0; // 10am for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals(date('w-04:00', strtotime('tomorrow')), date('w-H:i', $next)); + + $timezone = 3.0; // 1pm for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals(date('w-01:00', strtotime('tomorrow')), date('w-H:i', $next)); + + $timezone = 8.0; // 6pm for the user (same than the server). + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals(date('w-20:00'), date('w-H:i', $next)); + + $timezone = 9.0; // 7pm for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals(date('w-19:00'), date('w-H:i', $next)); + + $timezone = 13.0; // 12am for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $this->assertEquals(date('w-15:00', strtotime('tomorrow')), date('w-H:i', $next)); + + // Let's have a Belgian beer! (UTC+1 / UTC+2 DST). + date_default_timezone_set('Europe/Brussels'); + $now = strtotime('18:00:00'); + $dst = date('I'); + + $timezone = -10.0; // 7am for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? date('w-07:00', strtotime('tomorrow')) : date('w-08:00', strtotime('tomorrow')); + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = -5.0; // 12pm for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? date('w-02:00', strtotime('tomorrow')) : date('w-03:00', strtotime('tomorrow')); + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = 0.0; // 5pm for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? date('w-21:00') : date('w-22:00'); + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = 3.0; // 8pm for the user (note the expected time is today while in DST). + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? date('w-18:00', strtotime('tomorrow')) : date('w-19:00'); + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = 8.0; // 1am for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? date('w-13:00', strtotime('tomorrow')) : date('w-14:00', strtotime('tomorrow')); + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = 9.0; // 2am for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? date('w-12:00', strtotime('tomorrow')) : date('w-13:00', strtotime('tomorrow')); + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = 13.0; // 6am for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? date('w-08:00', strtotime('tomorrow')) : date('w-09:00', strtotime('tomorrow')); + $this->assertEquals($expected, date('w-H:i', $next)); + + // The big apple! (UTC-5 / UTC-4 DST). + date_default_timezone_set('America/New_York'); + $now = strtotime('18:00:00'); + $dst = date('I'); + + $timezone = -10.0; // 1pm for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? date('w-01:00', strtotime('tomorrow')) : date('w-02:00', strtotime('tomorrow')); + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = -5.0; // 6pm for the user (server time). + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? date('w-20:00') : date('w-21:00'); + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = 0.0; // 11pm for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? date('w-15:00', strtotime('tomorrow')) : date('w-16:00', strtotime('tomorrow')); + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = 3.0; // 2am for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? date('w-12:00', strtotime('tomorrow')) : date('w-13:00', strtotime('tomorrow')); + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = 8.0; // 7am for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? date('w-07:00', strtotime('tomorrow')) : date('w-08:00', strtotime('tomorrow')); + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = 9.0; // 8am for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? date('w-06:00', strtotime('tomorrow')) : date('w-07:00', strtotime('tomorrow')); + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = 13.0; // 6am for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? date('w-02:00', strtotime('tomorrow')) : date('w-03:00', strtotime('tomorrow')); + $this->assertEquals($expected, date('w-H:i', $next)); + + // Some more timezone tests + set_config('backup_auto_weekdays', '0100001', 'backup'); + set_config('backup_auto_hour', '20', 'backup'); + set_config('backup_auto_minute', '00', 'backup'); + + date_default_timezone_set('Europe/Brussels'); + $now = strtotime('next Monday 18:00:00'); + $dst = date('I'); + + $timezone = -12.0; // 1pm for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? '2-09:00' : '2-10:00'; + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = -4.0; // 1pm for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? '2-01:00' : '2-02:00'; + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = 0.0; // 5pm for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? '1-21:00' : '1-22:00'; + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = 2.0; // 7pm for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? '1-19:00' : '1-20:00'; + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = 4.0; // 9pm for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? '6-17:00' : '6-18:00'; + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = 12.0; // 6am for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? '6-09:00' : '6-10:00'; + $this->assertEquals($expected, date('w-H:i', $next)); + + // Some more timezone tests + set_config('backup_auto_weekdays', '0100001', 'backup'); + set_config('backup_auto_hour', '02', 'backup'); + set_config('backup_auto_minute', '00', 'backup'); + + date_default_timezone_set('America/New_York'); + $now = strtotime('next Monday 04:00:00'); + $dst = date('I'); + + $timezone = -12.0; // 8pm for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? '1-09:00' : '1-10:00'; + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = -4.0; // 4am for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? '6-01:00' : '6-02:00'; + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = 0.0; // 8am for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? '5-21:00' : '5-22:00'; + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = 2.0; // 10am for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? '5-19:00' : '5-20:00'; + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = 4.0; // 12pm for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? '5-17:00' : '5-18:00'; + $this->assertEquals($expected, date('w-H:i', $next)); + + $timezone = 12.0; // 8pm for the user. + $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); + $expected = !$dst ? '5-09:00' : '5-10:00'; + $this->assertEquals($expected, date('w-H:i', $next)); + + } +} diff --git a/backup/util/structure/restore_path_element.class.php b/backup/util/structure/restore_path_element.class.php index 9914f983bc8..716ac69fe46 100644 --- a/backup/util/structure/restore_path_element.class.php +++ b/backup/util/structure/restore_path_element.class.php @@ -47,9 +47,9 @@ class restore_path_element { /** * Constructor - instantiates one restore_path_element, specifying its basic info. * - * @param string $name name of the element - * @param string $path path of the element - * @param bool $grouped to gather information in grouped mode or no + * @param string $name name of the thing being restored. This determines the name of the process_... method called. + * @param string $path path of the element. + * @param bool $grouped to gather information in grouped mode or no. */ public function __construct($name, $path, $grouped = false) { diff --git a/backup/util/ui/base_moodleform.class.php b/backup/util/ui/base_moodleform.class.php index 3a72edcad20..ce95e7a81d6 100644 --- a/backup/util/ui/base_moodleform.class.php +++ b/backup/util/ui/base_moodleform.class.php @@ -324,6 +324,9 @@ abstract class base_moodleform extends moodleform { $config->noLabel = get_string('confirmcancelno', 'backup'); $PAGE->requires->yui_module('moodle-backup-confirmcancel', 'M.core_backup.watch_cancel_buttons', array($config)); + $PAGE->requires->yui_module('moodle-backup-backupselectall', 'M.core_backup.select_all_init', + array(array('select' => get_string('select'), 'all' => get_string('all'), 'none' => get_string('none')))); + parent::display(); } diff --git a/backup/util/ui/yui/backupselectall/backupselectall.js b/backup/util/ui/yui/backupselectall/backupselectall.js new file mode 100644 index 00000000000..9f05b7f4272 --- /dev/null +++ b/backup/util/ui/yui/backupselectall/backupselectall.js @@ -0,0 +1,80 @@ +YUI.add('moodle-backup-backupselectall', function(Y) { + +// Namespace for the backup +M.core_backup = M.core_backup || {}; + +/** + * Adds select all/none links to the top of the backup/restore/import schema page. + */ +M.core_backup.select_all_init = function(str) { + var formid = null; + + var helper = function(e, check, type) { + e.preventDefault(); + + var len = type.length; + Y.all('input[type="checkbox"]').each(function(checkbox) { + var name = checkbox.get('name'); + if (name.substring(name.length - len) == type) { + checkbox.set('checked', check); + } + }); + + // At this point, we really need to persuade the form we are part of to + // update all of its disabledIf rules. However, as far as I can see, + // given the way that lib/form/form.js is written, that is impossible. + if (formid && M.form) { + M.form.updateFormState(formid); + } + }; + + var html_generator = function(classname, idtype) { + return '
' + + '
' + + '
' + str.select + '
' + + '
' + + '' + str.all + ' / ' + + '' + str.none + '' + + '
' + + '
' + + '
'; + }; + + var firstsection = Y.one('fieldset#coursesettings .fcontainer.clearfix .grouped_settings.section_level'); + if (!firstsection) { + // This is not a relevant page. + return; + } + if (!firstsection.one('.felement.fcheckbox')) { + // No checkboxes. + return; + } + + formid = firstsection.ancestor('form').getAttribute('id'); + + var withuserdata = false; + Y.all('input[type="checkbox"]').each(function(checkbox) { + var name = checkbox.get('name'); + if (name.substring(name.length - 9) == '_userdata') { + withuserdata = '_userdata'; + } else if (name.substring(name.length - 9) == '_userinfo') { + withuserdata = '_userinfo'; + } + }); + + var html = html_generator('include_setting section_level', 'included'); + if (withuserdata) { + html += html_generator('normal_setting', 'userdata'); + } + var links = Y.Node.create('
' + html + '
'); + firstsection.insert(links, 'before'); + + Y.one('#backup-all-included').on('click', function(e) { helper(e, true, '_included'); }); + Y.one('#backup-none-included').on('click', function(e) { helper(e, false, '_included'); }); + if (withuserdata) { + Y.one('#backup-all-userdata').on('click', function(e) { helper(e, true, withuserdata); }); + Y.one('#backup-none-userdata').on('click', function(e) { helper(e, false, withuserdata); }); + } +} + +}, '@VERSION@', {'requires':['base','node','event', 'node-event-simulate']}); diff --git a/blocks/html/block_html.php b/blocks/html/block_html.php index f03040167f3..39040ae47dd 100644 --- a/blocks/html/block_html.php +++ b/blocks/html/block_html.php @@ -131,4 +131,19 @@ class block_html extends block_base { public function instance_can_be_docked() { return (!empty($this->config->title) && parent::instance_can_be_docked()); } + + /* + * Add custom html attributes to aid with theming and styling + * + * @return array + */ + function html_attributes() { + $attributes = parent::html_attributes(); + + if (!empty($this->config->classes)) { + $attributes['class'] .= ' '.$this->config->classes; + } + + return $attributes; + } } diff --git a/blocks/html/edit_form.php b/blocks/html/edit_form.php index b7251905e58..f544f98cee0 100644 --- a/blocks/html/edit_form.php +++ b/blocks/html/edit_form.php @@ -41,6 +41,10 @@ class block_html_edit_form extends block_edit_form { $mform->addElement('editor', 'config_text', get_string('configcontent', 'block_html'), null, $editoroptions); $mform->addRule('config_text', null, 'required', null, 'client'); $mform->setType('config_text', PARAM_RAW); // XSS is prevented when printing the block contents and serving files + + $mform->addElement('text', 'config_classes', get_string('configclasses', 'block_html')); + $mform->setType('config_classes', PARAM_TEXT); + $mform->addHelpButton('config_classes', 'configclasses', 'block_html'); } function set_data($defaults) { diff --git a/blocks/html/lang/en/block_html.php b/blocks/html/lang/en/block_html.php index 91f0811dc05..6e82be80640 100644 --- a/blocks/html/lang/en/block_html.php +++ b/blocks/html/lang/en/block_html.php @@ -23,6 +23,8 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +$string['configclasses'] = 'Additional HTML classes'; +$string['configclasses_help'] = 'The purpose of this configuration is to aid with theming by helping distinguish HTML blocks from each other. Any CSS classes entered here (space delimited) will be appended to the block\'s default classes.'; $string['configcontent'] = 'Content'; $string['configtitle'] = 'Block title'; $string['leaveblanktohide'] = 'leave blank to hide the title'; diff --git a/blocks/tags/block_tags.php b/blocks/tags/block_tags.php index 0e2e341cb89..0e429f39343 100644 --- a/blocks/tags/block_tags.php +++ b/blocks/tags/block_tags.php @@ -40,6 +40,7 @@ class block_tags extends block_base { global $CFG, $COURSE, $SITE, $USER, $SCRIPT, $OUTPUT; if (empty($CFG->usetags)) { + $this->content = new stdClass(); $this->content->text = ''; if ($this->page->user_is_editing()) { $this->content->text = get_string('disabledtags', 'block_tags'); diff --git a/completion/completion_completion.php b/completion/completion_completion.php index e8e80197bdf..dab34d8b453 100644 --- a/completion/completion_completion.php +++ b/completion/completion_completion.php @@ -158,7 +158,11 @@ class completion_completion extends data_object { $this->timecompleted = $timecomplete; // Save record - return $this->_save(); + if ($result = $this->_save()) { + events_trigger('course_completed', $this->get_record_data()); + } + + return $result; } /** diff --git a/config-dist.php b/config-dist.php index eb0ab29c901..feb2902b674 100644 --- a/config-dist.php +++ b/config-dist.php @@ -211,10 +211,12 @@ $CFG->admin = 'admin'; // You can specify a different class to be created for the $PAGE global, and to // compute which blocks appear on each page. However, I cannot think of any good // reason why you would need to change that. It just felt wrong to hard-code the -// the class name. You are stronly advised not to use these to settings unless +// the class name. You are strongly advised not to use these to settings unless // you are absolutely sure you know what you are doing. // $CFG->moodlepageclass = 'moodle_page'; +// $CFG->moodlepageclassfile = "$CFG->dirroot/local/myplugin/mypageclass.php"; // $CFG->blockmanagerclass = 'block_manager'; +// $CFG->blockmanagerclassfile = "$CFG->dirroot/local/myplugin/myblockamanagerclass.php"; // // Seconds for files to remain in caches. Decrease this if you are worried // about students being served outdated versions of uploaded files. diff --git a/course/edit_form.php b/course/edit_form.php index be69a6ff377..355e790efa6 100644 --- a/course/edit_form.php +++ b/course/edit_form.php @@ -124,7 +124,7 @@ class course_edit_form extends moodleform { array(COURSE_DISPLAY_SINGLEPAGE => get_string('coursedisplay_single'), COURSE_DISPLAY_MULTIPAGE => get_string('coursedisplay_multi'))); $mform->addHelpButton('coursedisplay', 'coursedisplay'); - $mform->setDefault('coursedisplay', COURSE_DISPLAY_SINGLEPAGE); + $mform->setDefault('coursedisplay', $courseconfig->coursedisplay); for ($i = 0; $i <= $courseconfig->maxsections; $i++) { $sectionmenu[$i] = "$i"; diff --git a/course/format/renderer.php b/course/format/renderer.php index ea88504ba30..d390202aa3a 100644 --- a/course/format/renderer.php +++ b/course/format/renderer.php @@ -235,7 +235,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base { } } - if (!$onsectionpage && has_capability('moodle/course:update', $coursecontext)) { + if (!$onsectionpage && has_capability('moodle/course:movesections', $coursecontext)) { $url = clone($baseurl); if ($section->section > 1) { // Add a arrow to move section up. $url->param('section', $section->section); @@ -291,8 +291,11 @@ abstract class format_section_renderer_base extends plugin_renderer_base { $o .= html_writer::tag('div', '', array('class' => 'right side')); $o .= html_writer::start_tag('div', array('class' => 'content')); - $title = html_writer::tag('a', get_section_name($course, $section), - array('href' => course_get_url($course, $section->section), 'class' => $linkclasses)); + $title = get_section_name($course, $section); + if ($section->uservisible) { + $title = html_writer::tag('a', $title, + array('href' => course_get_url($course, $section->section), 'class' => $linkclasses)); + } $o .= $this->output->heading($title, 3, 'section-title'); $o.= html_writer::start_tag('div', array('class' => 'summarytext')); @@ -448,7 +451,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base { $links = array('previous' => '', 'next' => ''); $back = $sectionno - 1; while ($back > 0 and empty($links['previous'])) { - if ($canviewhidden || $sections[$back]->visible) { + if ($canviewhidden || $sections[$back]->uservisible) { $params = array(); if (!$sections[$back]->visible) { $params = array('class' => 'dimmed_text'); @@ -462,7 +465,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base { $forward = $sectionno + 1; while ($forward <= $course->numsections and empty($links['next'])) { - if ($canviewhidden || $sections[$forward]->visible) { + if ($canviewhidden || $sections[$forward]->uservisible) { $params = array(); if (!$sections[$forward]->visible) { $params = array('class' => 'dimmed_text'); diff --git a/course/lib.php b/course/lib.php index 06fb241774c..75cc734e453 100644 --- a/course/lib.php +++ b/course/lib.php @@ -47,9 +47,6 @@ define('FIRSTUSEDEXCELROW', 3); define('MOD_CLASS_ACTIVITY', 0); define('MOD_CLASS_RESOURCE', 1); -define('COURSE_DISPLAY_SINGLEPAGE', 0); // display all sections on one page -define('COURSE_DISPLAY_MULTIPAGE', 1); // split pages into a page per section - function make_log_url($module, $url) { switch ($module) { case 'course': diff --git a/course/recent_form.php b/course/recent_form.php index 642a24dfdef..46a31374242 100644 --- a/course/recent_form.php +++ b/course/recent_form.php @@ -101,8 +101,6 @@ class recent_form extends moodleform { $mform->setAdvanced('user'); } - $sectiontitle = get_string('sectionname', 'format_'.$COURSE->format); - $options = array(''=>get_string('allactivities')); $modsused = array(); diff --git a/course/reset_form.php b/course/reset_form.php index f3407253998..751c0c74f8e 100644 --- a/course/reset_form.php +++ b/course/reset_form.php @@ -19,7 +19,7 @@ class course_reset_form extends moodleform { $mform->addElement('checkbox', 'reset_logs', get_string('deletelogs')); $mform->addElement('checkbox', 'reset_notes', get_string('deletenotes', 'notes')); $mform->addElement('checkbox', 'reset_comments', get_string('deleteallcomments', 'moodle')); - $mform->addElement('checkbox', 'reset_course_completion', get_string('deletecoursecompletiondata', 'completion')); + $mform->addElement('checkbox', 'reset_completion', get_string('deletecompletiondata', 'completion')); $mform->addElement('checkbox', 'delete_blog_associations', get_string('deleteblogassociations', 'blog')); $mform->addHelpButton('delete_blog_associations', 'deleteblogassociations', 'blog'); diff --git a/course/rest.php b/course/rest.php index c7cd8af931e..54d473b43e7 100644 --- a/course/rest.php +++ b/course/rest.php @@ -88,7 +88,7 @@ switch($requestmethod) { break; case 'move': - require_capability('moodle/course:update', $coursecontext); + require_capability('moodle/course:movesections', $coursecontext); move_section_to($course, $id, $value); // See if format wants to do something about it $libfile = $CFG->dirroot.'/course/format/'.$course->format.'/lib.php'; diff --git a/course/tests/externallib_test.php b/course/tests/externallib_test.php index ac3c4d92669..08f8b4ac91e 100644 --- a/course/tests/externallib_test.php +++ b/course/tests/externallib_test.php @@ -315,7 +315,7 @@ class core_course_external_testcase extends externallib_advanced_testcase { $course2['format'] = 'weeks'; $course2['showgrades'] = 1; $course2['newsitems'] = 3; - $course2['startdate'] = 32882306400; // 01/01/3012 + $course2['startdate'] = 1420092000; // 01/01/2015 $course2['numsections'] = 4; $course2['maxbytes'] = 100000; $course2['showreports'] = 1; diff --git a/course/view.php b/course/view.php index cb978d86ab4..f0c307983ed 100644 --- a/course/view.php +++ b/course/view.php @@ -176,7 +176,7 @@ if (has_capability('moodle/course:update', $context)) { if (!empty($section)) { - if (!empty($move) and confirm_sesskey()) { + if (!empty($move) and has_capability('moodle/course:movesections', $context) and confirm_sesskey()) { $destsection = $section + $move; if (move_section_to($course, $section, $destsection)) { // Rebuild course cache, after moving section diff --git a/enrol/category/cli/sync.php b/enrol/category/cli/sync.php index 47b1e37477e..7db80b80acc 100644 --- a/enrol/category/cli/sync.php +++ b/enrol/category/cli/sync.php @@ -1,5 +1,4 @@ dirroot/enrol/category/locallib.php"); +require_once("$CFG->libdir/clilib.php"); -if (!enrol_is_enabled('category')) { - die('enrol_category plugin is disabled, sync is disabled'); +// Now get cli options. +list($options, $unrecognized) = cli_get_params(array('verbose'=>false, 'help'=>false), array('v'=>'verbose', 'h'=>'help')); + +if ($unrecognized) { + $unrecognized = implode("\n ", $unrecognized); + cli_error(get_string('cliunknowoption', 'admin', $unrecognized)); } -enrol_category_sync_full(); +if ($options['help']) { + $help = + "Execute course category enrolment sync. + +Options: +-v, --verbose Print verbose progess information +-h, --help Print out this help + +Example: +\$ sudo -u www-data /usr/bin/php enrol/category/cli/sync.php +"; + echo $help; + die; +} + + +if (!enrol_is_enabled('category')) { + cli_error('enrol_category plugin is disabled, synchronisation stopped', 2); +} + +$verbose = !empty($options['verbose']); +return enrol_category_sync_full($verbose); diff --git a/enrol/category/db/access.php b/enrol/category/db/access.php index 1a9ead040d0..7feea4dad1f 100644 --- a/enrol/category/db/access.php +++ b/enrol/category/db/access.php @@ -25,9 +25,9 @@ defined('MOODLE_INTERNAL') || die(); $capabilities = array( - // marks roles that have category role assignments synchronised to course enrolments + // Marks roles that have category role assignments synchronised to course enrolments // overrides below system context are ignored (for performance reasons). - // by default his is not allowed in new installs, admins have to explicitly allow category enrolments + // By default his is not allowed in new installs, admins have to explicitly allow category enrolments. 'enrol/category:synchronised' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, diff --git a/enrol/category/db/events.php b/enrol/category/db/events.php index 66194df447d..3a678664b0a 100644 --- a/enrol/category/db/events.php +++ b/enrol/category/db/events.php @@ -17,10 +17,10 @@ /** * Category enrolment plugin event handler definition. * - * @package enrol_category - * @category event + * @package enrol_category + * @category event * @copyright 2010 Petr Skoda {@link http://skodak.org} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); diff --git a/enrol/category/db/install.php b/enrol/category/db/install.php index 0de987f41e0..834aa6779c7 100644 --- a/enrol/category/db/install.php +++ b/enrol/category/db/install.php @@ -1,5 +1,4 @@ . /** - * Strings for component 'enrol_category', language 'en', branch 'MOODLE_20_STABLE' + * Strings for component 'enrol_category', language 'en'. * - * @package enrol - * @subpackage category + * @package enrol_category * @copyright 2010 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/enrol/category/lib.php b/enrol/category/lib.php index ba60ba049f7..e6525e36b56 100644 --- a/enrol/category/lib.php +++ b/enrol/category/lib.php @@ -1,5 +1,4 @@ record_exists('user_enrolments', array('enrolid'=>$instance->id)); } @@ -55,8 +54,8 @@ class enrol_category_plugin extends enrol_plugin { * @return moodle_url page url */ public function get_newinstance_link($courseid) { - // instances are added automatically as necessary - return NULL; + // Instances are added automatically as necessary. + return null; } /** @@ -78,8 +77,8 @@ class enrol_category_plugin extends enrol_plugin { * Called after updating/inserting course. * * @param bool $inserted true if course just inserted - * @param object $course - * @param object $data form data + * @param stdClass $course + * @param stdClass $data form data * @return void */ public function course_updated($inserted, $course, $data) { @@ -89,10 +88,8 @@ class enrol_category_plugin extends enrol_plugin { return; } - // sync category enrols + // Sync category enrols. require_once("$CFG->dirroot/enrol/category/locallib.php"); enrol_category_sync_course($course); } } - - diff --git a/enrol/category/locallib.php b/enrol/category/locallib.php index b44f9d8323b..2c16f766203 100644 --- a/enrol/category/locallib.php +++ b/enrol/category/locallib.php @@ -1,5 +1,4 @@ contextid); if ($parentcontext->contextlevel != CONTEXT_COURSECAT) { return true; } - // make sure the role is to be actually synchronised - // please note we are ignoring overrides of the synchronised capability (for performance reasons in full sync) + // Make sure the role is to be actually synchronised, + // please note we are ignoring overrides of the synchronised capability (for performance reasons in full sync). $syscontext = context_system::instance(); if (!$DB->record_exists('role_capabilities', array('contextid'=>$syscontext->id, 'roleid'=>$ra->roleid, 'capability'=>'enrol/category:synchronised', 'permission'=>CAP_ALLOW))) { return true; } - // add necessary enrol instances + // Add necessary enrol instances. $plugin = enrol_get_plugin('category'); $sql = "SELECT c.* FROM {course} c @@ -67,7 +72,7 @@ class enrol_category_handler { } $rs->close(); - // now look for missing enrols + // Now look for missing enrolments. $sql = "SELECT e.* FROM {course} c JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :courselevel AND ctx.path LIKE :match) @@ -84,6 +89,12 @@ class enrol_category_handler { return true; } + /** + * Triggered when user role is unassigned. + * @static + * @param stdClass $ra + * @return bool + */ public static function role_unassigned($ra) { global $DB; @@ -91,13 +102,13 @@ class enrol_category_handler { return true; } - // only category level roles are interesting + // Only category level roles are interesting. $parentcontext = get_context_instance_by_id($ra->contextid); if ($parentcontext->contextlevel != CONTEXT_COURSECAT) { return true; } - // now this is going to be a bit slow, take all enrolments in child courses and verify each separately + // Now this is going to be a bit slow, take all enrolments in child courses and verify each separately. $syscontext = context_system::instance(); if (!$roles = get_roles_with_capability('enrol/category:synchronised', CAP_ALLOW, $syscontext)) { return true; @@ -119,7 +130,7 @@ class enrol_category_handler { foreach ($rs as $instance) { $coursecontext = context_course::instance($instance->courseid); $contextids = get_parent_contexts($coursecontext); - array_pop($contextids); // remove system context, we are interested in categories only + array_pop($contextids); // Remove system context, we are interested in categories only. list($contextids, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'c'); $params = array_merge($params, $contextparams); @@ -128,7 +139,7 @@ class enrol_category_handler { FROM {role_assignments} ra WHERE ra.userid = :userid AND ra.contextid $contextids AND ra.roleid $roleids"; if (!$DB->record_exists_sql($sql, $params)) { - // user does not have any interesting role in any parent context, let's unenrol + // User does not have any interesting role in any parent context, let's unenrol. $plugin->unenrol_user($instance, $ra->userid); } } @@ -140,7 +151,7 @@ class enrol_category_handler { /** * Sync all category enrolments in one course - * @param int $courseid course id + * @param stdClass $course * @return void */ function enrol_category_sync_course($course) { @@ -156,7 +167,7 @@ function enrol_category_sync_course($course) { $roles = get_roles_with_capability('enrol/category:synchronised', CAP_ALLOW, $syscontext); if (!$roles) { - //nothing to sync, so remove the instance completely if exists + // Nothing to sync, so remove the instance completely if exists. if ($instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'enrol'=>'category'))) { foreach ($instances as $instance) { $plugin->delete_instance($instance); @@ -165,10 +176,10 @@ function enrol_category_sync_course($course) { return; } - // first find out if any parent category context contains interesting role assignments + // First find out if any parent category context contains interesting role assignments. $coursecontext = context_course::instance($course->id); $contextids = get_parent_contexts($coursecontext); - array_pop($contextids); // remove system context, we are interested in categories only + array_pop($contextids); // Remove system context, we are interested in categories only. list($roleids, $params) = $DB->get_in_or_equal(array_keys($roles), SQL_PARAMS_NAMED, 'r'); list($contextids, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'c'); @@ -180,7 +191,7 @@ function enrol_category_sync_course($course) { WHERE roleid $roleids AND contextid $contextids"; if (!$DB->record_exists_sql($sql, $params)) { if ($instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'enrol'=>'category'))) { - // should be max one instance, but anyway + // Should be max one instance, but anyway. foreach ($instances as $instance) { $plugin->delete_instance($instance); } @@ -188,7 +199,7 @@ function enrol_category_sync_course($course) { return; } - // make sure the enrol instance exists - there should be always only one instance + // Make sure the enrol instance exists - there should be always only one instance. $delinstances = array(); if ($instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'enrol'=>'category'))) { $instance = array_shift($instances); @@ -198,7 +209,7 @@ function enrol_category_sync_course($course) { $instance = $DB->get_record('enrol', array('id'=>$i)); } - // add new enrolments + // Add new enrolments. $sql = "SELECT ra.userid, ra.estart FROM (SELECT xra.userid, MIN(xra.timemodified) AS estart FROM {role_assignments} xra @@ -214,7 +225,7 @@ function enrol_category_sync_course($course) { } $rs->close(); - // remove unwanted enrolments + // Remove unwanted enrolments. $sql = "SELECT DISTINCT ue.userid FROM {user_enrolments} ue LEFT JOIN {role_assignments} ra ON (ra.roleid $roleids AND ra.contextid $contextids AND ra.userid = ue.userid) @@ -226,46 +237,68 @@ function enrol_category_sync_course($course) { $rs->close(); if ($delinstances) { - // we have to do this as the last step in order to prevent temporary unenrolment + // We have to do this as the last step in order to prevent temporary unenrolment. foreach ($delinstances as $delinstance) { $plugin->delete_instance($delinstance); } } } -function enrol_category_sync_full() { +/** + * Synchronise courses in all categories. + * + * It gets out-of-sync if: + * - you move course to different category + * - reorder categories + * - disable enrol_category and enable it again + * + * @param bool $verbose + * @return int exit code - 0 is ok, 1 means error, 2 if plugin disabled + */ +function enrol_category_sync_full($verbose = false) { global $DB; if (!enrol_is_enabled('category')) { - return; + return 2; } - // we may need a lot of time here + // We may need a lot of time here. @set_time_limit(0); $plugin = enrol_get_plugin('category'); $syscontext = context_system::instance(); - // any interesting roles worth synchronising? + // Any interesting roles worth synchronising? if (!$roles = get_roles_with_capability('enrol/category:synchronised', CAP_ALLOW, $syscontext)) { // yay, nothing to do, so let's remove all leftovers + if ($verbose) { + mtrace("No roles with 'enrol/category:synchronised' capability found."); + } if ($instances = $DB->get_records('enrol', array('enrol'=>'category'))) { foreach ($instances as $instance) { + if ($verbose) { + mtrace(" deleting category enrol instance from course {$instance->courseid}"); + } $plugin->delete_instance($instance); } } - return; + return 0; + } + $rolenames = role_fix_names($roles, null, ROLENAME_SHORT, true); + if ($verbose) { + mtrace('Synchronising category enrolments for roles: '.implode(', ', $rolenames).'...'); } list($roleids, $params) = $DB->get_in_or_equal(array_keys($roles), SQL_PARAMS_NAMED, 'r'); $params['courselevel'] = CONTEXT_COURSE; $params['catlevel'] = CONTEXT_COURSECAT; - // first of all add necessary enrol instances to all courses + // First of all add necessary enrol instances to all courses. $parentcat = $DB->sql_concat("cat.path", "'/%'"); - // need whole course records to be used by add_instance(), use inner view (ci) to + $parentcctx = $DB->sql_concat("cctx.path", "'/%'"); + // Need whole course records to be used by add_instance(), use inner view (ci) to // get distinct records only. // TODO: Moodle 2.1. Improve enrol API to accept courseid / courserec $sql = "SELECT c.* @@ -288,17 +321,16 @@ function enrol_category_sync_full() { } $rs->close(); - // now look for courses that do not have any interesting roles in parent contexts, - // but still have the instance and delete them + // Now look for courses that do not have any interesting roles in parent contexts, + // but still have the instance and delete them. $sql = "SELECT e.* FROM {enrol} e JOIN {context} ctx ON (ctx.instanceid = e.courseid AND ctx.contextlevel = :courselevel) - LEFT JOIN (SELECT DISTINCT cctx.path - FROM {course_categories} cc + LEFT JOIN ({course_categories} cc JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel) JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid $roleids) - ) cat ON (ctx.path LIKE $parentcat) - WHERE e.enrol = 'category' AND cat.path IS NULL"; + ) ON (ctx.path LIKE $parentcctx) + WHERE e.enrol = 'category' AND cc.id IS NULL"; $rs = $DB->get_recordset_sql($sql, $params); foreach($rs as $instance) { @@ -306,7 +338,7 @@ function enrol_category_sync_full() { } $rs->close(); - // add missing enrolments + // Add missing enrolments. $sql = "SELECT e.*, cat.userid, cat.estart FROM {enrol} e JOIN {context} ctx ON (ctx.instanceid = e.courseid AND ctx.contextlevel = :courselevel) @@ -325,25 +357,36 @@ function enrol_category_sync_full() { unset($instance->userid); unset($instance->estart); $plugin->enrol_user($instance, $userid, null, $estart); + if ($verbose) { + mtrace(" enrolling: user $userid ==> course $instance->courseid"); + } } $rs->close(); - // remove stale enrolments + // Remove stale enrolments. $sql = "SELECT e.*, ue.userid FROM {enrol} e JOIN {context} ctx ON (ctx.instanceid = e.courseid AND ctx.contextlevel = :courselevel) JOIN {user_enrolments} ue ON (ue.enrolid = e.id) - LEFT JOIN (SELECT DISTINCT cctx.path, ra.userid - FROM {course_categories} cc + LEFT JOIN ({course_categories} cc JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel) JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid $roleids) - ) cat ON (ctx.path LIKE $parentcat AND cat.userid = ue.userid) - WHERE e.enrol = 'category' AND cat.userid IS NULL"; + ) ON (ctx.path LIKE $parentcctx AND ra.userid = ue.userid) + WHERE e.enrol = 'category' AND cc.id IS NULL"; $rs = $DB->get_recordset_sql($sql, $params); foreach($rs as $instance) { $userid = $instance->userid; unset($instance->userid); $plugin->unenrol_user($instance, $userid); + if ($verbose) { + mtrace(" unenrolling: user $userid ==> course $instance->courseid"); + } } $rs->close(); + + if ($verbose) { + mtrace('...user enrolment synchronisation finished.'); + } + + return 0; } diff --git a/enrol/category/settings.php b/enrol/category/settings.php index a8542672406..484c23b1d87 100644 --- a/enrol/category/settings.php +++ b/enrol/category/settings.php @@ -1,5 +1,4 @@ . /** - * category enrolment plugin settings and presets. + * Category enrolment plugin settings and presets. * - * @package enrol - * @subpackage category + * @package enrol_category * @copyright 2010 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -35,4 +33,3 @@ if ($ADMIN->fulltree) { //--- enrol instance defaults ---------------------------------------------------------------------------- } - diff --git a/enrol/category/tests/sync_test.php b/enrol/category/tests/sync_test.php new file mode 100644 index 00000000000..36497647ce3 --- /dev/null +++ b/enrol/category/tests/sync_test.php @@ -0,0 +1,365 @@ +. + +/** + * Category enrolment sync functional test. + * + * @package enrol_category + * @category phpunit + * @copyright 2012 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot.'/enrol/category/locallib.php'); + +class enrol_category_testcase extends advanced_testcase { + + protected function enable_plugin() { + $enabled = enrol_get_plugins(true); + $enabled['category'] = true; + $enabled = array_keys($enabled); + set_config('enrol_plugins_enabled', implode(',', $enabled)); + } + + protected function disable_plugin() { + $enabled = enrol_get_plugins(true); + unset($enabled['category']); + $enabled = array_keys($enabled); + set_config('enrol_plugins_enabled', implode(',', $enabled)); + } + + protected function enable_role_sync($roleid) { + global $DB; + + $syscontext = context_system::instance(); + + if ($rc = $DB->record_exists('role_capabilities', array('capability'=>'enrol/category:synchronised', 'roleid'=>$roleid, 'contextid'=>$syscontext->id))) { + if ($rc->permission != CAP_ALLOW) { + $rc->permission = CAP_ALLOW; + $DB->update_record('role_capabilities', $rc); + } + } else { + $rc = new stdClass(); + $rc->capability = 'enrol/category:synchronised'; + $rc->roleid = $roleid; + $rc->contextid = $syscontext->id; + $rc->permission = CAP_ALLOW; + $rc->timemodified = time(); + $rc->modifierid = 0; + $DB->insert_record('role_capabilities', $rc); + } + } + + protected function disable_role_sync($roleid) { + global $DB; + + $syscontext = context_system::instance(); + + $DB->delete_records('role_capabilities', array('capability'=>'enrol/category:synchronised', 'roleid'=>$roleid, 'contextid'=>$syscontext->id)); + } + + /** + * Test utility methods used in syn test, fail here means something + * in core accesslib was changed, but it is possible that only this test + * is affected, nto the plugin itself... + */ + public function test_utils() { + global $DB; + + $this->resetAfterTest(); + + $syscontext = context_system::instance(); + + $this->assertFalse(enrol_is_enabled('category')); + $this->enable_plugin(); + $this->assertTrue(enrol_is_enabled('category')); + + $roles = get_roles_with_capability('enrol/category:synchronised', CAP_ALLOW, $syscontext); + $this->assertEmpty($roles); + + $studentrole = $DB->get_record('role', array('shortname'=>'student')); + $this->assertNotEmpty($studentrole); + + $this->enable_role_sync($studentrole->id); + $roles = get_roles_with_capability('enrol/category:synchronised', CAP_ALLOW, $syscontext); + $this->assertEquals(1, count($roles)); + $this->assertEquals($studentrole, reset($roles)); + + $this->disable_role_sync($studentrole->id); + $roles = get_roles_with_capability('enrol/category:synchronised', CAP_ALLOW, $syscontext); + $this->assertEmpty($roles); + } + + public function test_handler_sync() { + global $DB; + + $this->resetAfterTest(); + + // Setup a few courses and categories. + + $studentrole = $DB->get_record('role', array('shortname'=>'student')); + $this->assertNotEmpty($studentrole); + $teacherrole = $DB->get_record('role', array('shortname'=>'teacher')); + $this->assertNotEmpty($teacherrole); + $managerrole = $DB->get_record('role', array('shortname'=>'manager')); + $this->assertNotEmpty($managerrole); + + $cat1 = $this->getDataGenerator()->create_category(); + $cat2 = $this->getDataGenerator()->create_category(); + $cat3 = $this->getDataGenerator()->create_category(array('parent'=>$cat2->id)); + + $course1 = $this->getDataGenerator()->create_course(array('category'=>$cat1->id)); + $course2 = $this->getDataGenerator()->create_course(array('category'=>$cat2->id)); + $course3 = $this->getDataGenerator()->create_course(array('category'=>$cat3->id)); + $course4 = $this->getDataGenerator()->create_course(array('category'=>$cat3->id)); + + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + $user3 = $this->getDataGenerator()->create_user(); + $user4 = $this->getDataGenerator()->create_user(); + + $this->enable_role_sync($studentrole->id); + $this->enable_role_sync($teacherrole->id); + $this->enable_plugin(); + + $this->assertEquals(0, $DB->count_records('role_assignments', array())); + $this->assertEquals(0, $DB->count_records('user_enrolments', array())); + + // Test assign event. + + role_assign($managerrole->id, $user1->id, context_coursecat::instance($cat1->id)); + role_assign($managerrole->id, $user3->id, context_course::instance($course1->id)); + role_assign($managerrole->id, $user3->id, context_course::instance($course2->id)); + $this->assertEquals(0, $DB->count_records('user_enrolments', array())); + + role_assign($studentrole->id, $user1->id, context_coursecat::instance($cat2->id)); + $this->assertTrue(is_enrolled(context_course::instance($course2->id), $user1->id)); + $this->assertTrue(is_enrolled(context_course::instance($course3->id), $user1->id)); + $this->assertTrue(is_enrolled(context_course::instance($course4->id), $user1->id)); + $this->assertEquals(3, $DB->count_records('user_enrolments', array())); + + role_assign($managerrole->id, $user2->id, context_coursecat::instance($cat3->id)); + $this->assertEquals(3, $DB->count_records('user_enrolments', array())); + + role_assign($teacherrole->id, $user4->id, context_coursecat::instance($cat1->id)); + $this->assertTrue(is_enrolled(context_course::instance($course1->id), $user4->id)); + $this->assertEquals(4, $DB->count_records('user_enrolments', array())); + + // Test role unassigned event. + + role_unassign($teacherrole->id, $user4->id, context_coursecat::instance($cat1->id)->id); + $this->assertFalse(is_enrolled(context_course::instance($course1->id), $user4->id)); + $this->assertEquals(3, $DB->count_records('user_enrolments', array())); + + // Make sure handlers are disabled when plugin disabled. + + $this->disable_plugin(); + role_unassign($studentrole->id, $user1->id, context_coursecat::instance($cat2->id)->id); + $this->assertEquals(3, $DB->count_records('user_enrolments', array())); + + role_assign($studentrole->id, $user3->id, context_coursecat::instance($cat1->id)); + $this->assertEquals(3, $DB->count_records('user_enrolments', array())); + + } + + public function test_sync_course() { + global $DB; + + $this->resetAfterTest(); + + // Setup a few courses and categories. + + $studentrole = $DB->get_record('role', array('shortname'=>'student')); + $this->assertNotEmpty($studentrole); + $teacherrole = $DB->get_record('role', array('shortname'=>'teacher')); + $this->assertNotEmpty($teacherrole); + $managerrole = $DB->get_record('role', array('shortname'=>'manager')); + $this->assertNotEmpty($managerrole); + + $cat1 = $this->getDataGenerator()->create_category(); + $cat2 = $this->getDataGenerator()->create_category(); + $cat3 = $this->getDataGenerator()->create_category(array('parent'=>$cat2->id)); + + $course1 = $this->getDataGenerator()->create_course(array('category'=>$cat1->id)); + $course2 = $this->getDataGenerator()->create_course(array('category'=>$cat2->id)); + $course3 = $this->getDataGenerator()->create_course(array('category'=>$cat3->id)); + $course4 = $this->getDataGenerator()->create_course(array('category'=>$cat3->id)); + + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + $user3 = $this->getDataGenerator()->create_user(); + $user4 = $this->getDataGenerator()->create_user(); + + $this->enable_role_sync($studentrole->id); + $this->enable_role_sync($teacherrole->id); + $this->enable_plugin(); + + $this->assertEquals(0, $DB->count_records('role_assignments', array())); + role_assign($managerrole->id, $user1->id, context_coursecat::instance($cat1->id)); + role_assign($managerrole->id, $user3->id, context_course::instance($course1->id)); + role_assign($managerrole->id, $user3->id, context_course::instance($course2->id)); + $this->assertEquals(0, $DB->count_records('user_enrolments', array())); + + + $this->disable_plugin(); // Stops the event handlers. + role_assign($studentrole->id, $user1->id, context_coursecat::instance($cat2->id)); + $this->assertEquals(0, $DB->count_records('user_enrolments', array())); + $this->enable_plugin(); + enrol_category_sync_course($course2); + $this->assertTrue(is_enrolled(context_course::instance($course2->id), $user1->id)); + $this->assertFalse(is_enrolled(context_course::instance($course3->id), $user1->id)); + $this->assertFalse(is_enrolled(context_course::instance($course4->id), $user1->id)); + $this->assertEquals(1, $DB->count_records('user_enrolments', array())); + + enrol_category_sync_course($course2); + enrol_category_sync_course($course3); + enrol_category_sync_course($course4); + $this->assertFalse(is_enrolled(context_course::instance($course1->id), $user1->id)); + $this->assertTrue(is_enrolled(context_course::instance($course2->id), $user1->id)); + $this->assertTrue(is_enrolled(context_course::instance($course3->id), $user1->id)); + $this->assertTrue(is_enrolled(context_course::instance($course4->id), $user1->id)); + $this->assertEquals(3, $DB->count_records('user_enrolments', array())); + + $this->disable_plugin(); // Stops the event handlers. + role_assign($studentrole->id, $user2->id, context_coursecat::instance($cat1->id)); + role_assign($teacherrole->id, $user4->id, context_coursecat::instance($cat1->id)); + role_unassign($studentrole->id, $user1->id, context_coursecat::instance($cat2->id)->id); + $this->assertEquals(3, $DB->count_records('user_enrolments', array())); + $this->enable_plugin(); + enrol_category_sync_course($course2); + $this->assertFalse(is_enrolled(context_course::instance($course2->id), $user1->id)); + $this->assertFalse(is_enrolled(context_course::instance($course2->id), $user2->id)); + $this->assertFalse(is_enrolled(context_course::instance($course2->id), $user4->id)); + enrol_category_sync_course($course1); + enrol_category_sync_course($course3); + enrol_category_sync_course($course4); + $this->assertEquals(2, $DB->count_records('user_enrolments', array())); + $this->assertTrue(is_enrolled(context_course::instance($course1->id), $user2->id)); + $this->assertTrue(is_enrolled(context_course::instance($course1->id), $user4->id)); + + $this->disable_role_sync($studentrole->id); + enrol_category_sync_course($course1); + enrol_category_sync_course($course2); + enrol_category_sync_course($course3); + enrol_category_sync_course($course4); + $this->assertEquals(1, $DB->count_records('user_enrolments', array())); + $this->assertTrue(is_enrolled(context_course::instance($course1->id), $user4->id)); + + $this->assertEquals(1, $DB->count_records('enrol', array('enrol'=>'category'))); + $this->disable_role_sync($teacherrole->id); + enrol_category_sync_course($course1); + enrol_category_sync_course($course2); + enrol_category_sync_course($course3); + enrol_category_sync_course($course4); + $this->assertEquals(0, $DB->count_records('user_enrolments', array())); + $this->assertEquals(0, $DB->count_records('enrol', array('enrol'=>'category'))); + } + + public function test_sync_full() { + global $DB; + + $this->resetAfterTest(); + + // Setup a few courses and categories. + + $studentrole = $DB->get_record('role', array('shortname'=>'student')); + $this->assertNotEmpty($studentrole); + $teacherrole = $DB->get_record('role', array('shortname'=>'teacher')); + $this->assertNotEmpty($teacherrole); + $managerrole = $DB->get_record('role', array('shortname'=>'manager')); + $this->assertNotEmpty($managerrole); + + $cat1 = $this->getDataGenerator()->create_category(); + $cat2 = $this->getDataGenerator()->create_category(); + $cat3 = $this->getDataGenerator()->create_category(array('parent'=>$cat2->id)); + + $course1 = $this->getDataGenerator()->create_course(array('category'=>$cat1->id)); + $course2 = $this->getDataGenerator()->create_course(array('category'=>$cat2->id)); + $course3 = $this->getDataGenerator()->create_course(array('category'=>$cat3->id)); + $course4 = $this->getDataGenerator()->create_course(array('category'=>$cat3->id)); + + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + $user3 = $this->getDataGenerator()->create_user(); + $user4 = $this->getDataGenerator()->create_user(); + + $this->enable_role_sync($studentrole->id); + $this->enable_role_sync($teacherrole->id); + $this->enable_plugin(); + + $this->assertEquals(0, $DB->count_records('role_assignments', array())); + role_assign($managerrole->id, $user1->id, context_coursecat::instance($cat1->id)); + role_assign($managerrole->id, $user3->id, context_course::instance($course1->id)); + role_assign($managerrole->id, $user3->id, context_course::instance($course2->id)); + $this->assertEquals(0, $DB->count_records('user_enrolments', array())); + + $result = enrol_category_sync_full(); + $this->assertSame(0, $result); + + $this->disable_plugin(); + role_assign($studentrole->id, $user1->id, context_coursecat::instance($cat2->id)); + $this->enable_plugin(); + $result = enrol_category_sync_full(); + $this->assertSame(0, $result); + $this->assertEquals(3, $DB->count_records('user_enrolments', array())); + $this->assertTrue(is_enrolled(context_course::instance($course2->id), $user1->id)); + $this->assertTrue(is_enrolled(context_course::instance($course3->id), $user1->id)); + $this->assertTrue(is_enrolled(context_course::instance($course4->id), $user1->id)); + + $this->disable_plugin(); + role_unassign($studentrole->id, $user1->id, context_coursecat::instance($cat2->id)->id); + role_assign($studentrole->id, $user2->id, context_coursecat::instance($cat1->id)); + role_assign($teacherrole->id, $user4->id, context_coursecat::instance($cat1->id)); + role_assign($teacherrole->id, $user3->id, context_coursecat::instance($cat2->id)); + role_assign($managerrole->id, $user3->id, context_course::instance($course3->id)); + $this->enable_plugin(); + $result = enrol_category_sync_full(); + $this->assertSame(0, $result); + $this->assertEquals(5, $DB->count_records('user_enrolments', array())); + $this->assertTrue(is_enrolled(context_course::instance($course1->id), $user2->id)); + $this->assertTrue(is_enrolled(context_course::instance($course1->id), $user4->id)); + $this->assertTrue(is_enrolled(context_course::instance($course2->id), $user3->id)); + $this->assertTrue(is_enrolled(context_course::instance($course3->id), $user3->id)); + $this->assertTrue(is_enrolled(context_course::instance($course4->id), $user3->id)); + + // Cleanup everything. + + $this->assertNotEmpty($DB->count_records('role_assignments', array())); + $this->assertNotEmpty($DB->count_records('user_enrolments', array())); + + $this->disable_plugin(); + role_unassign_all(array('roleid'=>$studentrole->id)); + role_unassign_all(array('roleid'=>$managerrole->id)); + role_unassign_all(array('roleid'=>$teacherrole->id)); + + $result = enrol_category_sync_full(); + $this->assertSame(2, $result); + $this->assertEquals(0, $DB->count_records('role_assignments', array())); + $this->assertNotEmpty($DB->count_records('user_enrolments', array())); + $this->disable_role_sync($studentrole->id); + $this->disable_role_sync($teacherrole->id); + + $this->enable_plugin(); + $result = enrol_category_sync_full(); + $this->assertSame(0, $result); + $this->assertEquals(0, $DB->count_records('role_assignments', array())); + $this->assertEquals(0, $DB->count_records('user_enrolments', array())); + $this->assertEquals(0, $DB->count_records('enrol', array('enrol'=>'category'))); + } +} diff --git a/enrol/category/version.php b/enrol/category/version.php index 371a7f06dcb..c44f22de54d 100644 --- a/enrol/category/version.php +++ b/enrol/category/version.php @@ -17,15 +17,14 @@ /** * Category enrolment plugin version specification. * - * @package enrol - * @subpackage category + * @package enrol_category * @copyright 2010 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2012061700; // The current plugin version (Date: YYYYMMDDXX) -$plugin->requires = 2012061700; // Requires this Moodle version +$plugin->version = 2012081800; // The current plugin version (Date: YYYYMMDDXX) +$plugin->requires = 2012062501; // Requires this Moodle version $plugin->component = 'enrol_category'; // Full name of the plugin (used for diagnostics) -$plugin->cron = 60; \ No newline at end of file +$plugin->cron = 60; diff --git a/enrol/cohort/lib.php b/enrol/cohort/lib.php index 9f986f9e238..b70b89f2fb1 100644 --- a/enrol/cohort/lib.php +++ b/enrol/cohort/lib.php @@ -121,16 +121,7 @@ class enrol_cohort_plugin extends enrol_plugin { * @return void */ public function course_updated($inserted, $course, $data) { - global $CFG; - - if (!$inserted) { - // sync cohort enrols - require_once("$CFG->dirroot/enrol/cohort/locallib.php"); - enrol_cohort_sync($course->id); - } else { - // cohorts are never inserted automatically - } - + // It turns out there is no need for cohorts to deal with this hook, see MDL-34870. } /** diff --git a/enrol/locallib.php b/enrol/locallib.php index 30c9db8d1d7..b19a716b2ba 100644 --- a/enrol/locallib.php +++ b/enrol/locallib.php @@ -278,7 +278,7 @@ class course_enrolment_manager { global $DB, $CFG; // Add some additional sensible conditions - $tests = array("id <> :guestid", 'u.deleted = 0', 'u.confirmed = 1'); + $tests = array("u.id <> :guestid", 'u.deleted = 0', 'u.confirmed = 1'); $params = array('guestid' => $CFG->siteguest); if (!empty($search)) { $conditions = get_extra_user_fields($this->get_context()); @@ -306,10 +306,9 @@ class course_enrolment_manager { $fields = 'SELECT '.$ufields; $countfields = 'SELECT COUNT(1)'; $sql = " FROM {user} u + LEFT JOIN {user_enrolments} ue ON (ue.userid = u.id AND ue.enrolid = :enrolid) WHERE $wherecondition - AND u.id NOT IN (SELECT ue.userid - FROM {user_enrolments} ue - JOIN {enrol} e ON (e.id = ue.enrolid AND e.id = :enrolid))"; + AND ue.id IS NULL"; $order = ' ORDER BY u.lastname ASC, u.firstname ASC'; $params['enrolid'] = $enrolid; $totalusers = $DB->count_records_sql($countfields . $sql, $params); @@ -353,12 +352,9 @@ class course_enrolment_manager { $fields = 'SELECT '.user_picture::fields('u', array('username','lastaccess')); $countfields = 'SELECT COUNT(u.id)'; $sql = " FROM {user} u + LEFT JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.contextid = :contextid) WHERE $wherecondition - AND u.id NOT IN ( - SELECT u.id - FROM {role_assignments} r, {user} u - WHERE r.contextid = :contextid AND - u.id = r.userid)"; + AND ra.id IS NULL"; $order = ' ORDER BY lastname ASC, firstname ASC'; $params['contextid'] = $this->context->id; diff --git a/enrol/manual/lang/en/enrol_manual.php b/enrol/manual/lang/en/enrol_manual.php index 4d11100afec..9e51fd1f314 100644 --- a/enrol/manual/lang/en/enrol_manual.php +++ b/enrol/manual/lang/en/enrol_manual.php @@ -30,7 +30,7 @@ $string['altertimestart'] = 'Alter start time'; $string['assignrole'] = 'Assign role'; $string['confirmbulkdeleteenrolment'] = 'Are you sure you want to delete these users enrolments?'; $string['defaultperiod'] = 'Default enrolment duration'; -$string['defaultperiod_desc'] = 'Default length of time that the enrolment is valid (in seconds). If set to zero, the enrolment duration will be unlimited by default.'; +$string['defaultperiod_desc'] = 'Default length of time that the enrolment is valid. If set to zero, the enrolment duration will be unlimited by default.'; $string['defaultperiod_help'] = 'Default length of time that the enrolment is valid, starting with the moment the user is enrolled. If disabled, the enrolment duration will be unlimited by default.'; $string['deleteselectedusers'] = 'Delete selected user enrolments'; $string['editenrolment'] = 'Edit enrolment'; diff --git a/enrol/manual/locallib.php b/enrol/manual/locallib.php index 32558827159..1d09681ad12 100644 --- a/enrol/manual/locallib.php +++ b/enrol/manual/locallib.php @@ -55,11 +55,9 @@ class enrol_manual_potential_participant extends user_selector_base { $countfields = 'SELECT COUNT(1)'; $sql = " FROM {user} u - WHERE $wherecondition AND - u.id NOT IN ( - SELECT ue.userid - FROM {user_enrolments} ue - JOIN {enrol} e ON (e.id = ue.enrolid AND e.id = :enrolid))"; + LEFT JOIN {user_enrolments} ue ON (ue.userid = u.id AND ue.enrolid = :enrolid) + WHERE $wherecondition + AND ue.id IS NULL"; $order = ' ORDER BY u.lastname ASC, u.firstname ASC'; if (!$this->is_validating()) { diff --git a/enrol/manual/settings.php b/enrol/manual/settings.php index 5ef1b519122..13d72ed9259 100644 --- a/enrol/manual/settings.php +++ b/enrol/manual/settings.php @@ -44,8 +44,8 @@ if ($ADMIN->fulltree) { $settings->add(new admin_setting_configselect('enrol_manual/status', get_string('status', 'enrol_manual'), get_string('status_desc', 'enrol_manual'), ENROL_INSTANCE_ENABLED, $options)); - $settings->add(new admin_setting_configtext('enrol_manual/enrolperiod', - get_string('defaultperiod', 'enrol_manual'), get_string('defaultperiod_desc', 'enrol_manual'), 0, PARAM_INT)); + $settings->add(new admin_setting_configduration('enrol_manual/enrolperiod', + get_string('defaultperiod', 'enrol_manual'), get_string('defaultperiod_desc', 'enrol_manual'), 0)); if (!during_initial_install()) { $options = get_default_enrol_roles(context_system::instance()); diff --git a/enrol/paypal/lang/en/enrol_paypal.php b/enrol/paypal/lang/en/enrol_paypal.php index d15a2bb3e61..803d5604fa1 100644 --- a/enrol/paypal/lang/en/enrol_paypal.php +++ b/enrol/paypal/lang/en/enrol_paypal.php @@ -37,7 +37,7 @@ $string['enrolenddate'] = 'End date'; $string['enrolenddate_help'] = 'If enabled, users can be enrolled until this date only.'; $string['enrolenddaterror'] = 'Enrolment end date cannot be earlier than start date'; $string['enrolperiod'] = 'Enrolment duration'; -$string['enrolperiod_desc'] = 'Default length of time that the enrolment is valid (in seconds). If set to zero, the enrolment duration will be unlimited by default.'; +$string['enrolperiod_desc'] = 'Default length of time that the enrolment is valid. If set to zero, the enrolment duration will be unlimited by default.'; $string['enrolperiod_help'] = 'Length of time that the enrolment is valid, starting with the moment the user is enrolled. If disabled, the enrolment duration will be unlimited.'; $string['enrolstartdate'] = 'Start date'; $string['enrolstartdate_help'] = 'If enabled, users can be enrolled from this date onward only.'; diff --git a/enrol/paypal/settings.php b/enrol/paypal/settings.php index 80ac4c81bfa..6e3acfbf933 100644 --- a/enrol/paypal/settings.php +++ b/enrol/paypal/settings.php @@ -68,6 +68,6 @@ if ($ADMIN->fulltree) { get_string('defaultrole', 'enrol_paypal'), get_string('defaultrole_desc', 'enrol_paypal'), $student->id, $options)); } - $settings->add(new admin_setting_configtext('enrol_paypal/enrolperiod', - get_string('enrolperiod', 'enrol_paypal'), get_string('enrolperiod_desc', 'enrol_paypal'), 0, PARAM_INT)); + $settings->add(new admin_setting_configduration('enrol_paypal/enrolperiod', + get_string('enrolperiod', 'enrol_paypal'), get_string('enrolperiod_desc', 'enrol_paypal'), 0)); } diff --git a/enrol/self/lang/en/enrol_self.php b/enrol/self/lang/en/enrol_self.php index 5c96f78fbf5..95595fc7f95 100644 --- a/enrol/self/lang/en/enrol_self.php +++ b/enrol/self/lang/en/enrol_self.php @@ -39,7 +39,7 @@ $string['enrolenddate_help'] = 'If enabled, users can enrol themselves until thi $string['enrolenddaterror'] = 'Enrolment end date cannot be earlier than start date'; $string['enrolme'] = 'Enrol me'; $string['enrolperiod'] = 'Enrolment duration'; -$string['enrolperiod_desc'] = 'Default length of time that the enrolment is valid (in seconds). If set to zero, the enrolment duration will be unlimited by default.'; +$string['enrolperiod_desc'] = 'Default length of time that the enrolment is valid. If set to zero, the enrolment duration will be unlimited by default.'; $string['enrolperiod_help'] = 'Length of time that the enrolment is valid, starting with the moment the user enrols themselves. If disabled, the enrolment duration will be unlimited.'; $string['enrolstartdate'] = 'Start date'; $string['enrolstartdate_help'] = 'If enabled, users can enrol themselves from this date onward only.'; diff --git a/enrol/self/settings.php b/enrol/self/settings.php index 0b11ccd487c..bab0909616a 100644 --- a/enrol/self/settings.php +++ b/enrol/self/settings.php @@ -65,8 +65,8 @@ if ($ADMIN->fulltree) { get_string('defaultrole', 'enrol_self'), get_string('defaultrole_desc', 'enrol_self'), $student->id, $options)); } - $settings->add(new admin_setting_configtext('enrol_self/enrolperiod', - get_string('enrolperiod', 'enrol_self'), get_string('enrolperiod_desc', 'enrol_self'), 0, PARAM_INT)); + $settings->add(new admin_setting_configduration('enrol_self/enrolperiod', + get_string('enrolperiod', 'enrol_self'), get_string('enrolperiod_desc', 'enrol_self'), 0)); $options = array(0 => get_string('never'), 1800 * 3600 * 24 => get_string('numdays', '', 1800), diff --git a/install/lang/es_mx/langconfig.php b/install/lang/es_mx/langconfig.php index 1dfd27c999f..f7c639bbc00 100644 --- a/install/lang/es_mx/langconfig.php +++ b/install/lang/es_mx/langconfig.php @@ -30,6 +30,6 @@ defined('MOODLE_INTERNAL') || die(); -$string['parentlanguage'] = 'es'; +$string['parentlanguage'] = ''; $string['thisdirection'] = 'ltr'; $string['thislanguage'] = 'Español - Mexico'; diff --git a/install/lang/zh_cn/install.php b/install/lang/zh_cn/install.php index c338c8b45b0..93647deac02 100644 --- a/install/lang/zh_cn/install.php +++ b/install/lang/zh_cn/install.php @@ -81,7 +81,7 @@ $string['phpversionhelp'] = '

Moodle需要PHP 4.3.0或5.1.0(5.0.x有若干 (如果正使用5.0.x,您也可以降级到4.4.x版)

'; $string['welcomep10'] = '{$a->installername} ({$a->installerversion})'; -$string['welcomep20'] = '您看到这个页面表明您已经成功地在您的计算机上安装了{$a->packname} {$a->packversion}。恭喜您!'; +$string['welcomep20'] = '您看到这个页面表明您已经成功地在您的计算机上安装并启用了{$a->packname} {$a->packversion}软件包。恭喜您!'; $string['welcomep30'] = '{$a->installername}的此发行版包含了可以创建Moodle运行环境的应用程序:'; $string['welcomep40'] = '这个软件包还包含了Moodle {$a->moodlerelease} ({$a->moodleversion})。'; $string['welcomep50'] = '使用本软件包中包含的应用程序时应遵循它们各自的授权协议。整个{$a->installername}软件包都是开源的,并且遵循GPL授权协议发布。'; diff --git a/iplookup/index.php b/iplookup/index.php index e876e8f2641..92dd51ea462 100644 --- a/iplookup/index.php +++ b/iplookup/index.php @@ -1,5 +1,4 @@ iplookup)) { - //clean up of old settings + // Clean up of old settings. set_config('iplookup', NULL); } @@ -61,7 +59,7 @@ if ($match[1] == '127' or $match[1] == '10' or ($match[1] == '172' and $match[2] $info = iplookup_find_location($ip); if ($info['error']) { - // can not display + // Can not display. notice($info['error']); } @@ -80,7 +78,7 @@ $PAGE->set_title(get_string('iplookup', 'admin').': '.$title); $PAGE->set_heading($title); echo $OUTPUT->header(); -if (empty($CFG->googlemapkey)) { +if (empty($CFG->googlemapkey3)) { $imgwidth = 620; $imgheight = 310; $dotwidth = 18; @@ -96,9 +94,13 @@ if (empty($CFG->googlemapkey)) { echo '
'.$info['note'].'
'; } else { - $PAGE->requires->js(new moodle_url("http://maps.google.com/maps?file=api&v=2&key=$CFG->googlemapkey")); + if (strpos($CFG->wwwroot, 'https:') === 0) { + $PAGE->requires->js(new moodle_url('https://maps.googleapis.com/maps/api/js', array('key'=>$CFG->googlemapkey3, 'sensor'=>'false'))); + } else { + $PAGE->requires->js(new moodle_url('http://maps.googleapis.com/maps/api/js', array('key'=>$CFG->googlemapkey3, 'sensor'=>'false'))); + } $module = array('name'=>'core_iplookup', 'fullpath'=>'/iplookup/module.js'); - $PAGE->requires->js_init_call('M.core_iplookup.init', array($info['latitude'], $info['longitude']), true, $module); + $PAGE->requires->js_init_call('M.core_iplookup.init3', array($info['latitude'], $info['longitude'], $ip), true, $module); echo '
'; echo '
'.$info['note'].'
'; diff --git a/iplookup/module.js b/iplookup/module.js index dba71cc4158..d706caf270d 100644 --- a/iplookup/module.js +++ b/iplookup/module.js @@ -16,28 +16,27 @@ /** * Iplookup utility functions * - * @package core - * @subpackage iplookup + * @package core_iplookup * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ M.core_iplookup = {}; -M.core_iplookup.init = function(Y, latitude, longitude) { - if (GBrowserIsCompatible()) { - var map = new GMap2(document.getElementById("map")); - map.addControl(new GSmallMapControl()); - map.addControl(new GMapTypeControl()); - var point = new GLatLng(latitude, longitude); - map.setCenter(point, 4); - map.addOverlay(new GMarker(point)); - map.setMapType(G_HYBRID_MAP); +M.core_iplookup.init3 = function(Y, latitude, longitude, ip) { + var ipLatlng = new google.maps.LatLng(latitude, longitude); - Y.on('unload', function() { - if (GBrowserIsCompatible()) { - GUnload(); - } - }, document.body); - } + var mapOptions = { + center: ipLatlng, + zoom: 6, + mapTypeId: google.maps.MapTypeId.ROADMAP + }; + + var map = new google.maps.Map(document.getElementById("map"), mapOptions); + + var marker = new google.maps.Marker({ + position: ipLatlng, + map: map, + title: ip + }); }; diff --git a/lang/en/admin.php b/lang/en/admin.php index 14fec0983c5..7f8972a8564 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -215,11 +215,10 @@ $string['configforcelogin'] = 'Normally, the front page of the site and the cour $string['configforceloginforprofiles'] = 'This setting forces people to login as a real (non-guest) account before viewing any user\'s profile. If you disabled this setting, you may find that some users post advertising (spam) or other inappropriate content in their profiles, which is then visible to the whole world.'; $string['configfrontpage'] = 'The items selected above will be displayed on the site\'s front page.'; $string['configfrontpageloggedin'] = 'The items selected above will be displayed on the site\'s front page when a user is logged in.'; -$string['configfullnamedisplay'] = 'This defines how names are shown when they are displayed in full. For most mono-lingual sites the most efficient setting is the default "First name + Surname", but you may choose to hide surnames altogether, or to leave it up to the current language pack to decide (some languages have different conventions).'; +$string['configfullnamedisplay'] = 'This defines how names are shown when they are displayed in full. For most mono-lingual sites the most efficient setting is "First name + Surname", but you may choose to hide surnames altogether, or to leave it up to the current language pack to decide (some languages have different conventions).'; $string['configgdversion'] = 'Indicate the version of GD that is installed. The version shown by default is the one that has been auto-detected. Don\'t change this unless you really know what you\'re doing.'; $string['configgeoipfile'] = 'Location of GeoIP City binary data file. This file is not part of Moodle distribution and must be obtained separately from MaxMind. You can either buy a commercial version or use the free version.
Simply download http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz and extract it into "{$a}" directory on your server.'; $string['configgetremoteaddrconf'] = 'If your server is behind a reverse proxy, you can use this setting to specify which HTTP headers can be trusted to contain the remote IP address. The headers are read in order, using the first one that is available.'; -$string['configgooglemapkey'] = 'You need to enter a special key to use Google Maps for IP address lookup visualization. You can obtain the key free of charge at http://code.google.com/apis/maps/signup.html.
Your web site URL is: {$a}'; $string['configgradebookroles'] = 'This setting allows you to control who appears on the gradebook. Users need to have at least one of these roles in a course to be shown in the gradebook for that course.'; $string['configgradeexport'] = 'Choose which gradebook export formats are your primary methods for exporting grades. Chosen plugins will then set and use a "last exported" field for every grade. For example, this might result in exported records being identified as being "new" or "updated". If you are not sure about this then leave everything unchecked.'; $string['confighiddenuserfields'] = 'Select which user information fields you wish to hide from other users other than course teachers/admins. This will increase student privacy. Hold CTRL key to select multiple fields.'; @@ -546,7 +545,8 @@ $string['globalsquoteswarning'] = '

Security Warning: to oper $string['globalswarning'] = '

SECURITY WARNING!

To operate properly, Moodle requires
that you make certain changes to your current PHP settings.

You must set register_globals=off.

This setting is controlled by editing your php.ini, Apache/IIS
configuration or .htaccess file.

'; $string['groupenrolmentkeypolicy'] = 'Group enrolment key policy'; $string['groupenrolmentkeypolicy_desc'] = 'Turning this on will make Moodle check group enrolment keys against a valid password policy.'; -$string['googlemapkey'] = 'Google Maps API key'; +$string['googlemapkey3'] = 'Google Maps API V3 key'; +$string['googlemapkey3_help'] = 'You need to enter a special key to use Google Maps for IP address lookup visualization. You can obtain the key free of charge at https://developers.google.com/maps/documentation/javascript/tutorial#api_key'; $string['gotofirst'] = 'Go to first missing string'; $string['gradebook'] = 'Gradebook'; $string['gradebookroles'] = 'Graded roles'; diff --git a/lang/en/completion.php b/lang/en/completion.php index 8b73c59733c..a488b9dabd6 100644 --- a/lang/en/completion.php +++ b/lang/en/completion.php @@ -70,7 +70,7 @@ $string['completionview'] = 'Require view'; $string['completionview_desc'] = 'Student must view this activity to complete it'; $string['configenablecompletion'] = 'When enabled, this lets you turn on completion tracking (progress) features at course level.'; $string['csvdownload'] = 'Download in spreadsheet format (UTF-8 .csv)'; -$string['deletecoursecompletiondata'] = 'Delete course completion data'; +$string['deletecompletiondata'] = 'Delete completion data'; $string['enablecompletion'] = 'Enable completion tracking'; $string['err_noactivities'] = 'Completion information is not enabled for any activity, so none can be displayed. You can enable completion information by editing the settings for an activity.'; $string['err_nousers'] = 'There are no students on this course or group for whom completion information is displayed. (By default, completion information is displayed only for students, so if there are no students, you will see this error. Administrators can alter this option via the admin screens.)'; diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 2b8eadbecb9..2fc4fac2df5 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -1805,6 +1805,7 @@ $string['visibletostudents'] = 'Visible to {$a}'; $string['warningdeleteresource'] = 'Warning: {$a} is referred in a resource. Would you like to update the resource?'; $string['webpage'] = 'Web page'; $string['week'] = 'Week'; +$string['weeks'] = 'weeks'; $string['weekhide'] = 'Hide this week from {$a}'; $string['weeklyoutline'] = 'Weekly outline'; $string['weekshow'] = 'Show this week to {$a}'; diff --git a/lang/en/role.php b/lang/en/role.php index 29fe2ed14b5..2e05346e335 100644 --- a/lang/en/role.php +++ b/lang/en/role.php @@ -127,6 +127,7 @@ $string['course:managegrades'] = 'Manage grades'; $string['course:managegroups'] = 'Manage groups'; $string['course:managescales'] = 'Manage scales'; $string['course:markcomplete'] = 'Mark users as complete in course completion'; +$string['course:movesections'] = 'Move sections'; $string['course:publish'] = 'Publish a course into hub'; $string['course:request'] = 'Request new courses'; $string['course:reset'] = 'Reset course'; diff --git a/lib/adminlib.php b/lib/adminlib.php index 52f78ae82d1..8ee900ac7ba 100644 --- a/lib/adminlib.php +++ b/lib/adminlib.php @@ -2768,6 +2768,168 @@ class admin_setting_configtime extends admin_setting { } +/** + * Seconds duration setting. + * + * @copyright 2012 Petr Skoda (http://skodak.org) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class admin_setting_configduration extends admin_setting { + + /** @var int default duration unit */ + protected $defaultunit; + + /** + * Constructor + * @param string $name unique ascii name, either 'mysetting' for settings that in config, + * or 'myplugin/mysetting' for ones in config_plugins. + * @param string $visiblename localised name + * @param string $description localised long description + * @param mixed $defaultsetting string or array depending on implementation + * @param int $defaultunit - day, week, etc. (in seconds) + */ + public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) { + if (is_number($defaultsetting)) { + $defaultsetting = self::parse_seconds($defaultsetting); + } + $units = self::get_units(); + if (isset($units[$defaultunit])) { + $this->defaultunit = $defaultunit; + } else { + $this->defaultunit = 86400; + } + parent::__construct($name, $visiblename, $description, $defaultsetting); + } + + /** + * Returns selectable units. + * @static + * @return array + */ + protected static function get_units() { + return array( + 604800 => get_string('weeks'), + 86400 => get_string('days'), + 3600 => get_string('hours'), + 60 => get_string('minutes'), + 1 => get_string('seconds'), + ); + } + + /** + * Converts seconds to some more user friendly string. + * @static + * @param int $seconds + * @return string + */ + protected static function get_duration_text($seconds) { + if (empty($seconds)) { + return get_string('none'); + } + $data = self::parse_seconds($seconds); + switch ($data['u']) { + case (60*60*24*7): + return get_string('numweeks', '', $data['v']); + case (60*60*24): + return get_string('numdays', '', $data['v']); + case (60*60): + return get_string('numhours', '', $data['v']); + case (60): + return get_string('numminutes', '', $data['v']); + default: + return get_string('numseconds', '', $data['v']*$data['u']); + } + } + + /** + * Finds suitable units for given duration. + * @static + * @param int $seconds + * @return array + */ + protected static function parse_seconds($seconds) { + foreach (self::get_units() as $unit => $unused) { + if ($seconds % $unit === 0) { + return array('v'=>(int)($seconds/$unit), 'u'=>$unit); + } + } + return array('v'=>(int)$seconds, 'u'=>1); + } + + /** + * Get the selected duration as array. + * + * @return mixed An array containing 'v'=>xx, 'u'=>xx, or null if not set + */ + public function get_setting() { + $seconds = $this->config_read($this->name); + if (is_null($seconds)) { + return null; + } + + return self::parse_seconds($seconds); + } + + /** + * Store the duration as seconds. + * + * @param array $data Must be form 'h'=>xx, 'm'=>xx + * @return bool true if success, false if not + */ + public function write_setting($data) { + if (!is_array($data)) { + return ''; + } + + $seconds = (int)($data['v']*$data['u']); + if ($seconds < 0) { + return get_string('errorsetting', 'admin'); + } + + $result = $this->config_write($this->name, $seconds); + return ($result ? '' : get_string('errorsetting', 'admin')); + } + + /** + * Returns duration text+select fields. + * + * @param array $data Must be form 'v'=>xx, 'u'=>xx + * @param string $query + * @return string duration text+select fields and wrapping div(s) + */ + public function output_html($data, $query='') { + $default = $this->get_defaultsetting(); + + if (is_number($default)) { + $defaultinfo = self::get_duration_text($default); + } else if (is_array($default)) { + $defaultinfo = self::get_duration_text($default['v']*$default['u']); + } else { + $defaultinfo = null; + } + + $units = self::get_units(); + + $return = '
'; + $return .= ''; + $return .= '
'; + return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query); + } +} + + /** * Used to validate a textarea used for ip addresses * diff --git a/lib/blocklib.php b/lib/blocklib.php index b74272f1276..79ec70d1856 100644 --- a/lib/blocklib.php +++ b/lib/blocklib.php @@ -457,6 +457,12 @@ class block_manager { if (!$this->page->theme->enable_dock) { return false; } + + // Do not dock the region when the user attemps to move a block. + if ($this->movingblock) { + return false; + } + $this->check_is_loaded(); $this->ensure_content_created($region, $output); foreach($this->visibleblockcontent[$region] as $instance) { diff --git a/lib/completionlib.php b/lib/completionlib.php index f30b4f0399e..c96203790d2 100644 --- a/lib/completionlib.php +++ b/lib/completionlib.php @@ -711,6 +711,31 @@ class completion_info { $DB->delete_records('course_completion_crit_compl', array('course' => $this->course_id)); } + /** + * Deletes all activity and course completion data for an entire course + * (the below delete_all_state function does this for a single activity). + * + * Used by course reset page. + */ + public function delete_all_completion_data() { + global $DB, $SESSION; + + // Delete from database. + $DB->delete_records_select('course_modules_completion', + 'coursemoduleid IN (SELECT id FROM {course_modules} WHERE course=?)', + array($this->course_id)); + + // Reset cache for current user. + if (isset($SESSION->completioncache) && + array_key_exists($this->course_id, $SESSION->completioncache)) { + + unset($SESSION->completioncache[$this->course_id]); + } + + // Wipe course completion data too. + $this->delete_course_completion_data(); + } + /** * Deletes completion state related to an activity for all users. * diff --git a/lib/cronlib.php b/lib/cronlib.php index 7ec99e5c0f0..457336af0c9 100644 --- a/lib/cronlib.php +++ b/lib/cronlib.php @@ -373,6 +373,13 @@ function cron_run() { } + // Run question bank clean-up. + mtrace("Starting the question bank cron...", ''); + require_once($CFG->libdir . '/questionlib.php'); + question_bank::cron(); + mtrace('done.'); + + //Run registration updated cron mtrace(get_string('siteupdatesstart', 'hub')); require_once($CFG->dirroot . '/' . $CFG->admin . '/registration/lib.php'); diff --git a/lib/db/access.php b/lib/db/access.php index 121750c02bd..ebf6021347e 100644 --- a/lib/db/access.php +++ b/lib/db/access.php @@ -1427,6 +1427,17 @@ $capabilities = array( ) ), + 'moodle/course:movesections' => array( + + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => array( + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ), + 'clonepermissionsfrom' => 'moodle/course:update' + ), + 'moodle/site:mnetlogintoremote' => array( 'captype' => 'read', diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index 6315ca1b870..d5b98563d2c 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -1113,5 +1113,12 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2012081400.01); } + if ($oldversion < 2012081600.01) { + // Delete removed setting - Google Maps API V2 will not work in 2013. + unset_config('googlemapkey'); + upgrade_main_savepoint(true, 2012081600.01); + } + + return true; } diff --git a/lib/editor/tinymce/adminlib.php b/lib/editor/tinymce/adminlib.php new file mode 100644 index 00000000000..8e599d63b91 --- /dev/null +++ b/lib/editor/tinymce/adminlib.php @@ -0,0 +1,37 @@ +. + +/** + * TinyMCE admin setting stuff. + * + * @package editor_tinymce + * @copyright 2012 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +require_once("$CFG->libdir/pluginlib.php"); + +/** + * Editor subplugin info class. + */ +class plugininfo_tinymce extends plugininfo_base { + + public function get_uninstall_url() { + return new moodle_url('/lib/editor/tinymce/subplugins.php', array('delete' => $this->name, 'sesskey' => sesskey())); + } +} diff --git a/lib/editor/tinymce/extra/tools/.gitignore b/lib/editor/tinymce/extra/tools/.gitignore deleted file mode 100644 index 9c595a6fb76..00000000000 --- a/lib/editor/tinymce/extra/tools/.gitignore +++ /dev/null @@ -1 +0,0 @@ -temp diff --git a/lib/editor/tinymce/lang/en/editor_tinymce.php b/lib/editor/tinymce/lang/en/editor_tinymce.php index dc48ba37e53..c52236fa153 100644 --- a/lib/editor/tinymce/lang/en/editor_tinymce.php +++ b/lib/editor/tinymce/lang/en/editor_tinymce.php @@ -29,6 +29,7 @@ $string['common:browsemedia'] = 'Find or upload a sound, video or applet...'; $string['fontselectlist'] = 'Available fonts list'; $string['media_dlg:filename'] = 'Filename'; $string['pluginname'] = 'TinyMCE HTML editor'; +$string['subplugindeleteconfirm'] = 'You are about to completely delete TinyMCE subplugin \'{$a}\'. This will completely delete everything in the database associated with this subplugin. Are you SURE you want to continue?'; // == TinyMCE upstream lang strings from all standard upstream plugins == diff --git a/lib/editor/tinymce/subplugins.php b/lib/editor/tinymce/subplugins.php new file mode 100644 index 00000000000..beba8f432ef --- /dev/null +++ b/lib/editor/tinymce/subplugins.php @@ -0,0 +1,74 @@ +. + +/** + * TinyMCE subplugin management. + * + * @package editor_tinymce + * @copyright 2012 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require(__DIR__ . '/../../../config.php'); +require_once($CFG->libdir.'/adminlib.php'); + +$delete = optional_param('delete', '', PARAM_PLUGIN); +$confirm = optional_param('confirm', '', PARAM_BOOL); +$return = optional_param('return', 'overview', PARAM_ALPHA); + +$PAGE->set_context(context_system::instance()); +$PAGE->set_url('/lib/editor/tinymce/subplugins.php', array('delete'=>$delete)); + +require_login(); +require_capability('moodle/site:config', context_system::instance()); +require_sesskey(); + +if ($return === 'settings') { + $returnurl = new moodle_url('/admin/settings.php', array('section'=>'editorsettingstinymce')); +} else { + $returnurl = new moodle_url('/admin/plugins.php'); +} + +if ($delete) { + echo $OUTPUT->header(); + echo $OUTPUT->heading(get_string('pluginname', 'editor_tinymce')); + + if (!$confirm) { + if (get_string_manager()->string_exists('pluginname', 'tinymce_' . $delete)) { + $strpluginname = get_string('pluginname', 'tinymce_' . $delete); + } else { + $strpluginname = $delete; + } + echo $OUTPUT->confirm(get_string('subplugindeleteconfirm', 'editor_tinymce', $strpluginname), + new moodle_url($PAGE->url, array('delete' => $delete, 'confirm' => 1, 'return'=>$return)), + $returnurl); + echo $OUTPUT->footer(); + die(); + + } else { + uninstall_plugin('tinymce', $delete); + $a = new stdclass(); + $a->name = $delete; + $pluginlocation = get_plugin_types(); + $a->directory = $pluginlocation['tinymce'] . '/' . $delete; + echo $OUTPUT->notification(get_string('plugindeletefiles', '', $a), 'notifysuccess'); + echo $OUTPUT->continue_button($returnurl); + echo $OUTPUT->footer(); + die(); + } +} + +redirect($returnurl); diff --git a/lib/form/duration.php b/lib/form/duration.php index 9f1b9669dfa..4298f002150 100644 --- a/lib/form/duration.php +++ b/lib/form/duration.php @@ -58,8 +58,8 @@ class MoodleQuickForm_duration extends MoodleQuickForm_group { * @param string $elementName Element's name * @param mixed $elementLabel Label(s) for an element * @param array $options Options to control the element's display. Recognised values are - 'optional' => true/false - whether to display an 'enabled' checkbox next to the element. - 'defaultunit' => 1|60|3600|86400 - the default unit to display when the time is blank. + * 'optional' => true/false - whether to display an 'enabled' checkbox next to the element. + * 'defaultunit' => 1|60|3600|86400|604800 - the default unit to display when the time is blank. * If not specified, minutes is used. * @param mixed $attributes Either a typical HTML attribute string or an associative array */ @@ -91,6 +91,7 @@ class MoodleQuickForm_duration extends MoodleQuickForm_group { public function get_units() { if (is_null($this->_units)) { $this->_units = array( + 604800 => get_string('weeks'), 86400 => get_string('days'), 3600 => get_string('hours'), 60 => get_string('minutes'), diff --git a/lib/form/tests/dateselector_test.php b/lib/form/tests/dateselector_test.php index ab8cb9a0fef..8db9155f834 100644 --- a/lib/form/tests/dateselector_test.php +++ b/lib/form/tests/dateselector_test.php @@ -128,7 +128,7 @@ class dateselector_form_element_testcase extends basic_testcase { 'year' => 2011, 'usertimezone' => 0.0, 'timezone' => 0.0, - 'timestamp' => 1309449600 + 'timestamp' => 1309478400 // 6am at UTC+0 ), array ( 'day' => 1, @@ -136,7 +136,7 @@ class dateselector_form_element_testcase extends basic_testcase { 'year' => 2011, 'usertimezone' => 0.0, 'timezone' => 99, - 'timestamp' => 1309449600 + 'timestamp' => 1309478400 // 6am at UTC+0 ) ); } diff --git a/lib/form/tests/datetimeselector_test.php b/lib/form/tests/datetimeselector_test.php index b0ce16e0b12..abd5cea3426 100644 --- a/lib/form/tests/datetimeselector_test.php +++ b/lib/form/tests/datetimeselector_test.php @@ -138,7 +138,7 @@ class datetimeselector_form_element_testcase extends basic_testcase { 'year' => 2011, 'usertimezone' => 0.0, 'timezone' => 0.0, - 'timestamp' => 1309449600 + 'timestamp' => 1309478400 // 6am at UTC+0 ), array ( 'minute' => 0, @@ -148,7 +148,7 @@ class datetimeselector_form_element_testcase extends basic_testcase { 'year' => 2011, 'usertimezone' => 0.0, 'timezone' => 99, - 'timestamp' => 1309449600 + 'timestamp' => 1309478400 // 6am at UTC+0 ) ); } diff --git a/lib/form/tests/duration_test.php b/lib/form/tests/duration_test.php index 4a345fc2a19..796b3f6030a 100644 --- a/lib/form/tests/duration_test.php +++ b/lib/form/tests/duration_test.php @@ -77,7 +77,7 @@ class duration_form_element_testcase extends basic_testcase { $units = $this->element->get_units(); ksort($units); $this->assertEquals($units, array(1 => get_string('seconds'), 60 => get_string('minutes'), - 3600 => get_string('hours'), 86400 => get_string('days'))); + 3600 => get_string('hours'), 86400 => get_string('days'), 604800 => get_string('weeks'))); } /** diff --git a/lib/grade/grade_item.php b/lib/grade/grade_item.php index 8b1d3122f08..12afe84e393 100644 --- a/lib/grade/grade_item.php +++ b/lib/grade/grade_item.php @@ -742,7 +742,8 @@ class grade_item extends grade_object { // Standardise score to the new grade range // NOTE: this is not compatible with current assignment grading - if ($this->itemmodule != 'assignment' and ($rawmin != $this->grademin or $rawmax != $this->grademax)) { + $isassignmentmodule = ($this->itemmodule == 'assignment') || ($this->itemmodule == 'assign'); + if (!$isassignmentmodule && ($rawmin != $this->grademin or $rawmax != $this->grademax)) { $rawgrade = grade_grade::standardise_score($rawgrade, $rawmin, $rawmax, $this->grademin, $this->grademax); } diff --git a/lib/messagelib.php b/lib/messagelib.php index b184f0dafa0..140ae0e4f12 100644 --- a/lib/messagelib.php +++ b/lib/messagelib.php @@ -60,7 +60,7 @@ function message_send($eventdata) { //TODO: we need to solve problems with database transactions here somehow, for now we just prevent transactions - sorry $DB->transactions_forbidden(); - if (is_int($eventdata->userto)) { + if (is_number($eventdata->userto)) { $eventdata->userto = $DB->get_record('user', array('id' => $eventdata->userto)); } if (is_int($eventdata->userfrom)) { diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 24f452f62e6..55cfd1ffa16 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -487,6 +487,12 @@ define('MOODLE_OFFICIAL_MOBILE_SERVICE', 'moodle_mobile_app'); */ define('USER_CAN_IGNORE_FILE_SIZE_LIMITS', -1); +/** + * Course display settings + */ +define('COURSE_DISPLAY_SINGLEPAGE', 0); // display all sections on one page +define('COURSE_DISPLAY_MULTIPAGE', 1); // split pages into a page per section + /// PARAMETER HANDLING //////////////////////////////////////////////////// /** @@ -2330,10 +2336,10 @@ function get_user_timezone($tz = 99) { $tz = 99; - while(($tz == '' || $tz == 99 || $tz == NULL) && $next = each($timezones)) { + // Loop while $tz is, empty but not zero, or 99, and there is another timezone is the array + while(((empty($tz) && !is_numeric($tz)) || $tz == 99) && $next = each($timezones)) { $tz = $next['value']; } - return is_numeric($tz) ? (float) $tz : $tz; } @@ -3289,12 +3295,25 @@ function get_user_key($script, $userid, $instance=null, $iprestriction=null, $va function update_user_login_times() { global $USER, $DB; - $user = new stdClass(); - $USER->lastlogin = $user->lastlogin = $USER->currentlogin; - $USER->currentlogin = $user->lastaccess = $user->currentlogin = time(); + $now = time(); + $user = new stdClass(); $user->id = $USER->id; + // Make sure all users that logged in have some firstaccess. + if ($USER->firstaccess == 0) { + $USER->firstaccess = $user->firstaccess = $now; + } + + // Store the previous current as lastlogin. + $USER->lastlogin = $user->lastlogin = $USER->currentlogin; + + $USER->currentlogin = $user->currentlogin = $now; + + // Function user_accesstime_log() may not update immediately, better do it here. + $USER->lastaccess = $user->lastaccess = $now; + $USER->lastip = $user->lastip = getremoteaddr(); + $DB->update_record('user', $user); return true; } @@ -4093,10 +4112,6 @@ function authenticate_user_login($username, $password) { $DB->set_field('user', 'auth', $auth, array('username'=>$username)); $user->auth = $auth; } - if (empty($user->firstaccess)) { //prevent firstaccess from remaining 0 for manual account that never required confirmation - $DB->set_field('user','firstaccess', $user->timemodified, array('id' => $user->id)); - $user->firstaccess = $user->timemodified; - } update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day) @@ -4850,12 +4865,13 @@ function reset_course_userdata($data) { $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteblogassociations', 'blog'), 'error'=>false); } - if (!empty($data->reset_course_completion)) { - // Delete course completion information + if (!empty($data->reset_completion)) { + // Delete course and activity completion information. $course = $DB->get_record('course', array('id'=>$data->courseid)); $cc = new completion_info($course); - $cc->delete_course_completion_data(); - $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecoursecompletiondata', 'completion'), 'error'=>false); + $cc->delete_all_completion_data(); + $status[] = array('component' => $componentstr, + 'item' => get_string('deletecompletiondata', 'completion'), 'error' => false); } $componentstr = get_string('roles'); diff --git a/lib/pagelib.php b/lib/pagelib.php index 107911bb2eb..f62f8da1332 100644 --- a/lib/pagelib.php +++ b/lib/pagelib.php @@ -578,6 +578,9 @@ class moodle_page { global $CFG; if (is_null($this->_blocks)) { if (!empty($CFG->blockmanagerclass)) { + if (!empty($CFG->blockmanagerclassfile)) { + require_once($CFG->blockmanagerclassfile); + } $classname = $CFG->blockmanagerclass; } else { $classname = 'block_manager'; diff --git a/lib/phpunit/classes/data_generator.php b/lib/phpunit/classes/data_generator.php index 48a459f1d9f..8225f236761 100644 --- a/lib/phpunit/classes/data_generator.php +++ b/lib/phpunit/classes/data_generator.php @@ -240,11 +240,11 @@ EOD; } if (!isset($record['descriptionformat'])) { - $record['description'] = FORMAT_MOODLE; + $record['descriptionformat'] = FORMAT_MOODLE; } if (!isset($record['parent'])) { - $record['descriptionformat'] = 0; + $record['parent'] = 0; } if (empty($record['parent'])) { @@ -310,12 +310,12 @@ EOD; $record['numsections'] = 5; } - if (!isset($record['description'])) { - $record['description'] = "Test course $i\n$this->loremipsum"; + if (!isset($record['summary'])) { + $record['summary'] = "Test course $i\n$this->loremipsum"; } - if (!isset($record['descriptionformat'])) { - $record['description'] = FORMAT_MOODLE; + if (!isset($record['summaryformat'])) { + $record['summaryformat'] = FORMAT_MOODLE; } if (!isset($record['category'])) { diff --git a/lib/phpunit/classes/hint_resultprinter.php b/lib/phpunit/classes/hint_resultprinter.php index 9e468110bc0..33ec7938aa2 100644 --- a/lib/phpunit/classes/hint_resultprinter.php +++ b/lib/phpunit/classes/hint_resultprinter.php @@ -34,6 +34,20 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class Hint_ResultPrinter extends PHPUnit_TextUI_ResultPrinter { + public function __construct() { + // ARRGH - PHPUnit does not give us commandline arguments or xml config, so let's hack hard! + if (defined('DEBUG_BACKTRACE_PROVIDE_OBJECT')) { + $backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT); + if (isset($backtrace[2]['object']) and ($backtrace[2]['object'] instanceof PHPUnit_TextUI_Command)) { + list($verbose, $colors, $debug) = Hacky_TextUI_Command_reader::get_settings_hackery($backtrace[2]['object']); + parent::__construct(null, $verbose, $colors, $debug); + return; + } + } + // Fallback if something goes wrong. + parent::__construct(null, false, false, false); + } + protected function printDefectTrace(PHPUnit_Framework_TestFailure $defect) { global $CFG; @@ -83,3 +97,30 @@ class Hint_ResultPrinter extends PHPUnit_TextUI_ResultPrinter { $this->write("\nTo re-run:\n $executable $testName $file\n"); } } + + +/** + * Class used in bloody hack that works around result printer constructor troubles. + * + * @package core + * @category phpunit + * @copyright 2012 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class Hacky_TextUI_Command_reader extends PHPUnit_TextUI_Command { + public static function get_settings_hackery(PHPUnit_TextUI_Command $toread) { + $arguments = $toread->arguments; + $config = PHPUnit_Util_Configuration::getInstance($arguments['configuration'])->getPHPUnitConfiguration(); + + $verbose = isset($config['verbose']) ? $config['verbose'] : false; + $verbose = isset($arguments['verbose']) ? $arguments['verbose'] : $verbose; + + $colors = isset($config['colors']) ? $config['colors'] : false; + $colors = isset($arguments['colors']) ? $arguments['colors'] : $colors; + + $debug = isset($config['debug']) ? $config['debug'] : false; + $debug = isset($arguments['debug']) ? $arguments['debug'] : $debug; + + return array($verbose, $colors, $debug); + } +} diff --git a/lib/phpunit/classes/util.php b/lib/phpunit/classes/util.php index 33805494dfd..b580259da3e 100644 --- a/lib/phpunit/classes/util.php +++ b/lib/phpunit/classes/util.php @@ -762,16 +762,20 @@ class phpunit_util { * Note: To be used from CLI scripts only. * * @static + * @param bool $displayprogress if true, this method will echo progress information. * @return void may terminate execution with exit code */ - public static function drop_site() { + public static function drop_site($displayprogress = false) { global $DB, $CFG; if (!self::is_test_site()) { phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, 'Can not drop non-test site!!'); } - // purge dataroot + // Purge dataroot + if ($displayprogress) { + echo "Purging dataroot:\n"; + } self::reset_dataroot(); phpunit_bootstrap_initdataroot($CFG->dataroot); $keep = array('.', '..', 'lock', 'webrunner.xml'); @@ -795,9 +799,28 @@ class phpunit_util { unset($tables['config']); $tables['config'] = 'config'; } + + if ($displayprogress) { + echo "Dropping tables:\n"; + } + $dotsonline = 0; foreach ($tables as $tablename) { $table = new xmldb_table($tablename); $DB->get_manager()->drop_table($table); + + if ($dotsonline == 60) { + if ($displayprogress) { + echo "\n"; + } + $dotsonline = 0; + } + if ($displayprogress) { + echo '.'; + } + $dotsonline += 1; + } + if ($displayprogress) { + echo "\n"; } } diff --git a/lib/phpunit/tests/generator_test.php b/lib/phpunit/tests/generator_test.php index eb5df6ba0e4..316ee706561 100644 --- a/lib/phpunit/tests/generator_test.php +++ b/lib/phpunit/tests/generator_test.php @@ -48,10 +48,22 @@ class core_phpunit_generator_testcase extends advanced_testcase { $count = $DB->count_records('course_categories'); $category = $generator->create_category(); $this->assertEquals($count+1, $DB->count_records('course_categories')); + $this->assertRegExp('/^Course category \d/', $category->name); + $this->assertSame('', $category->idnumber); + $this->assertRegExp('/^Test course category \d/', $category->description); + $this->assertSame(FORMAT_MOODLE, $category->descriptionformat); $count = $DB->count_records('course'); $course = $generator->create_course(); $this->assertEquals($count+1, $DB->count_records('course')); + $this->assertRegExp('/^Test course \d/', $course->fullname); + $this->assertRegExp('/^tc_\d/', $course->shortname); + $this->assertSame('', $course->idnumber); + $this->assertSame('topics', $course->format); + $this->assertEquals(0, $course->newsitems); + $this->assertEquals(5, $course->numsections); + $this->assertRegExp('/^Test course \d/', $course->summary); + $this->assertSame(FORMAT_MOODLE, $course->summaryformat); $section = $generator->create_course_section(array('course'=>$course->id, 'section'=>3)); $this->assertEquals($course->id, $section->course); diff --git a/lib/pluginlib.php b/lib/pluginlib.php index fcca051e05e..61d2a6ea609 100644 --- a/lib/pluginlib.php +++ b/lib/pluginlib.php @@ -30,8 +30,6 @@ defined('MOODLE_INTERNAL') || die(); -require_once($CFG->libdir.'/filelib.php'); // curl class needed here - /** * Singleton class providing general plugins management functionality */ @@ -100,6 +98,16 @@ class plugin_manager { global $CFG; if ($disablecache or is_null($this->pluginsinfo)) { + // Hack: include mod and editor subplugin management classes first, + // the adminlib.php is supposed to contain extra admin settings too. + require_once($CFG->libdir.'/adminlib.php'); + foreach(array('mod', 'editor') as $type) { + foreach (get_plugin_list($type) as $dir) { + if (file_exists("$dir/adminlib.php")) { + include_once("$dir/adminlib.php"); + } + } + } $this->pluginsinfo = array(); $plugintypes = get_plugin_types(); $plugintypes = $this->reorder_plugin_types($plugintypes); @@ -148,10 +156,11 @@ class plugin_manager { if ($disablecache or is_null($this->subpluginsinfo)) { $this->subpluginsinfo = array(); foreach (array('mod', 'editor') as $type) { - $owners = get_plugin_list('type'); + $owners = get_plugin_list($type); foreach ($owners as $component => $ownerdir) { $componentsubplugins = array(); if (file_exists($ownerdir . '/db/subplugins.php')) { + $subplugins = array(); include($ownerdir . '/db/subplugins.php'); foreach ($subplugins as $subplugintype => $subplugintyperootdir) { $subplugin = new stdClass(); @@ -785,6 +794,9 @@ class available_update_checker { * @throws available_update_checker_exception */ protected function get_response() { + global $CFG; + require_once($CFG->libdir.'/filelib.php'); + $curl = new curl(array('proxy' => true)); $response = $curl->post($this->prepare_request_url(), $this->prepare_request_params()); $curlinfo = $curl->get_info(); @@ -961,6 +973,9 @@ class available_update_checker { return; } + $version = null; + $release = null; + require($CFG->dirroot.'/version.php'); $this->currentversion = $version; $this->currentrelease = $release; diff --git a/lib/setup.php b/lib/setup.php index 6f73e0f756d..c26b81b277f 100644 --- a/lib/setup.php +++ b/lib/setup.php @@ -791,6 +791,9 @@ moodle_setlocale(); // Create the $PAGE global - this marks the PAGE and OUTPUT fully initialised, this MUST be done at the end of setup! if (!empty($CFG->moodlepageclass)) { + if (!empty($CFG->moodlepageclassfile)) { + require_once($CFG->moodlepageclassfile); + } $classname = $CFG->moodlepageclass; } else { $classname = 'moodle_page'; diff --git a/lib/tablelib.php b/lib/tablelib.php index 21f44366aa8..75711ec29db 100644 --- a/lib/tablelib.php +++ b/lib/tablelib.php @@ -941,6 +941,7 @@ class flexible_table { } else { $this->start_html(); $this->print_headers(); + echo html_writer::start_tag('tbody'); } } @@ -1004,6 +1005,7 @@ class flexible_table { $this->print_nothing_to_display(); } else { + echo html_writer::end_tag('tbody'); echo html_writer::end_tag('table'); echo html_writer::end_tag('div'); $this->wrap_html_finish(); @@ -1051,6 +1053,7 @@ class flexible_table { function print_headers() { global $CFG, $OUTPUT; + echo html_writer::start_tag('thead'); echo html_writer::start_tag('tr'); foreach ($this->columns as $column => $index) { @@ -1121,6 +1124,7 @@ class flexible_table { } echo html_writer::end_tag('tr'); + echo html_writer::end_tag('thead'); } /** @@ -1717,6 +1721,7 @@ EOF; function output_headers($headers) { $this->table->print_headers(); + echo html_writer::start_tag('tbody'); } function add_data($row) { diff --git a/lib/tests/backup_test.php b/lib/tests/backup_test.php deleted file mode 100644 index 1830f56dce0..00000000000 --- a/lib/tests/backup_test.php +++ /dev/null @@ -1,184 +0,0 @@ -. - -/** - * Unit tests for backups. - * - * @package core - * @category phpunit - * @copyright 2012 Frédéric Massart - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -global $CFG; -require_once($CFG->dirroot . '/backup/util/helper/backup_cron_helper.class.php'); - -/** - * Unit tests for backup system - */ -class backup_testcase extends advanced_testcase { - - public function test_next_automated_backup() { - - $this->resetAfterTest(); - $admin = get_admin(); - $timezone = $admin->timezone; - - // Notes - // - The next automated backup will never be on the same date than $now - // - backup_auto_weekdays starts on Sunday - // - Tests cannot be done in the past. - - // Every Wed and Sat at 11pm. - set_config('backup_auto_active', '1', 'backup'); - set_config('backup_auto_weekdays', '0010010', 'backup'); - set_config('backup_auto_hour', '23', 'backup'); - set_config('backup_auto_minute', '0', 'backup'); - - $now = strtotime('next Monday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('2-23:00', date('w-H:i', $next)); - - $now = strtotime('next Tuesday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('5-23:00', date('w-H:i', $next)); - - $now = strtotime('next Wednesday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('5-23:00', date('w-H:i', $next)); - - $now = strtotime('next Thursday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('5-23:00', date('w-H:i', $next)); - - $now = strtotime('next Friday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('2-23:00', date('w-H:i', $next)); - - $now = strtotime('next Saturday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('2-23:00', date('w-H:i', $next)); - - $now = strtotime('next Sunday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('2-23:00', date('w-H:i', $next)); - - // Every Sun and Sat at 12pm. - set_config('backup_auto_active', '1', 'backup'); - set_config('backup_auto_weekdays', '1000001', 'backup'); - set_config('backup_auto_hour', '0', 'backup'); - set_config('backup_auto_minute', '0', 'backup'); - - $now = strtotime('next Monday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('6-00:00', date('w-H:i', $next)); - - $now = strtotime('next Tuesday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('6-00:00', date('w-H:i', $next)); - - $now = strtotime('next Wednesday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('6-00:00', date('w-H:i', $next)); - - $now = strtotime('next Thursday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('6-00:00', date('w-H:i', $next)); - - $now = strtotime('next Friday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('6-00:00', date('w-H:i', $next)); - - $now = strtotime('next Saturday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('0-00:00', date('w-H:i', $next)); - - $now = strtotime('next Sunday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('6-00:00', date('w-H:i', $next)); - - // Every Sun at 4am. - set_config('backup_auto_active', '1', 'backup'); - set_config('backup_auto_weekdays', '1000000', 'backup'); - set_config('backup_auto_hour', '4', 'backup'); - set_config('backup_auto_minute', '0', 'backup'); - - $now = strtotime('next Monday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('0-04:00', date('w-H:i', $next)); - - $now = strtotime('next Tuesday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('0-04:00', date('w-H:i', $next)); - - $now = strtotime('next Wednesday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('0-04:00', date('w-H:i', $next)); - - $now = strtotime('next Thursday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('0-04:00', date('w-H:i', $next)); - - $now = strtotime('next Friday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('0-04:00', date('w-H:i', $next)); - - $now = strtotime('next Saturday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('0-04:00', date('w-H:i', $next)); - - $now = strtotime('next Sunday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('0-04:00', date('w-H:i', $next)); - - // Every day but Wed at 8:30pm. - set_config('backup_auto_active', '1', 'backup'); - set_config('backup_auto_weekdays', '1110111', 'backup'); - set_config('backup_auto_hour', '20', 'backup'); - set_config('backup_auto_minute', '30', 'backup'); - - $now = strtotime('next Monday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('2-20:30', date('w-H:i', $next)); - - $now = strtotime('next Tuesday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('4-20:30', date('w-H:i', $next)); - - $now = strtotime('next Wednesday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('4-20:30', date('w-H:i', $next)); - - $now = strtotime('next Thursday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('5-20:30', date('w-H:i', $next)); - - $now = strtotime('next Friday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('6-20:30', date('w-H:i', $next)); - - $now = strtotime('next Saturday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('0-20:30', date('w-H:i', $next)); - - $now = strtotime('next Sunday'); - $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now); - $this->assertEquals('1-20:30', date('w-H:i', $next)); - - } -} diff --git a/lib/tests/moodlelib_test.php b/lib/tests/moodlelib_test.php index 2d03ffcad25..dd9665a5d59 100644 --- a/lib/tests/moodlelib_test.php +++ b/lib/tests/moodlelib_test.php @@ -1549,9 +1549,9 @@ class moodlelib_testcase extends advanced_testcase { 'hour' => '10', 'minutes' => '00', 'seconds' => '00', - 'timezone' => '0.0', //no dst offset - 'applydst' => false, - 'expectedoutput' => '1309528800' + 'timezone' => '0.0', + 'applydst' => false, //no dst offset + 'expectedoutput' => '1309514400' // 6pm at UTC+0 ), array( 'usertimezone' => 'America/Moncton', diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 36e54e4ab0d..34db5a016a0 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -5,6 +5,7 @@ information provided here is intended especially for developers. * Pagelib: Numerous deprecated functions were removed as classes page_base, page_course and page_generic_activity. +* use $CFG->googlemapkey3 instead of removed $CFG->googlemapkey and migrate to Google Maps API V3 YUI changes: * moodle-enrol-notification has been renamed to moodle-core-notification diff --git a/lib/weblib.php b/lib/weblib.php index 8384f373871..1295b00182d 100644 --- a/lib/weblib.php +++ b/lib/weblib.php @@ -2551,23 +2551,38 @@ function obfuscate_text($plaintext) { * @param string $email The email address to display * @param string $label The text to displayed as hyperlink to $email * @param boolean $dimmed If true then use css class 'dimmed' for hyperlink + * @param string $subject The subject of the email in the mailto link + * @param string $body The content of the email in the mailto link * @return string The obfuscated mailto link */ -function obfuscate_mailto($email, $label='', $dimmed=false) { +function obfuscate_mailto($email, $label='', $dimmed=false, $subject = '', $body = '') { if (empty($label)) { $label = $email; } - if ($dimmed) { - $title = get_string('emaildisable'); - $dimmed = ' class="dimmed"'; - } else { - $title = ''; - $dimmed = ''; + + $label = obfuscate_text($label); + $email = obfuscate_email($email); + $mailto = obfuscate_text('mailto'); + $url = new moodle_url("mailto:$email"); + $attrs = array(); + + if (!empty($subject)) { + $url->param('subject', format_string($subject)); } - return sprintf("%s", - obfuscate_text('mailto'), obfuscate_email($email), - obfuscate_text($label)); + if (!empty($body)) { + $url->param('body', format_string($body)); + } + + // Use the obfuscated mailto + $url = preg_replace('/^mailto/', $mailto, $url->out()); + + if ($dimmed) { + $attrs['title'] = get_string('emaildisable'); + $attrs['class'] = 'dimmed'; + } + + return html_writer::link($url, $label, $attrs); } /** diff --git a/mod/assign/backup/moodle2/backup_assign_stepslib.php b/mod/assign/backup/moodle2/backup_assign_stepslib.php index fa972085109..06a8fdc4d9b 100644 --- a/mod/assign/backup/moodle2/backup_assign_stepslib.php +++ b/mod/assign/backup/moodle2/backup_assign_stepslib.php @@ -55,7 +55,8 @@ class backup_assign_activity_structure_step extends backup_activity_structure_st 'duedate', 'allowsubmissionsfromdate', 'grade', - 'timemodified')); + 'timemodified', + 'completionsubmit')); $submissions = new backup_nested_element('submissions'); diff --git a/mod/assign/db/install.xml b/mod/assign/db/install.xml index c075dbd9bb5..213a62b6655 100644 --- a/mod/assign/db/install.xml +++ b/mod/assign/db/install.xml @@ -21,7 +21,8 @@ - + + diff --git a/mod/assign/db/upgrade.php b/mod/assign/db/upgrade.php index e12e781d81f..b80a60eb74a 100644 --- a/mod/assign/db/upgrade.php +++ b/mod/assign/db/upgrade.php @@ -65,6 +65,21 @@ function xmldb_assign_upgrade($oldversion) { upgrade_mod_savepoint(true, 2012071800, 'assign'); } + if ($oldversion < 2012081600) { + + // Define field sendlatenotifications to be added to assign. + $table = new xmldb_table('assign'); + $field = new xmldb_field('completionsubmit', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'timemodified'); + + // Conditionally launch add field sendlatenotifications. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Assign savepoint reached. + upgrade_mod_savepoint(true, 2012081600, 'assign'); + } + return true; } diff --git a/mod/assign/gradingtable.php b/mod/assign/gradingtable.php index be0eb3d792f..fb5e4ffb9de 100644 --- a/mod/assign/gradingtable.php +++ b/mod/assign/gradingtable.php @@ -287,13 +287,15 @@ class assign_grading_table extends table_sql implements renderable { } /** - * Format a user record for display (don't link to profile) + * Format a user record for display (link to profile) * * @param stdClass $row * @return string */ function col_fullname($row) { - return fullname($row); + $courseid = $this->assignment->get_course()->id; + $link= new moodle_url('/user/view.php', array('id' =>$row->id, 'course'=>$courseid)); + return $this->output->action_link($link, fullname($row)); } /** diff --git a/mod/assign/index.php b/mod/assign/index.php index cde25a421db..b90c8ac3018 100644 --- a/mod/assign/index.php +++ b/mod/assign/index.php @@ -56,8 +56,13 @@ foreach ($assignments as $assignment) { $cm = get_coursemodule_from_instance('assign', $assignment->id, 0, false, MUST_EXIST); $link = html_writer::link(new moodle_url('/mod/assign/view.php', array('id' => $cm->id)), $assignment->name); - $date = userdate($assignment->duedate); - $submissions = $DB->count_records('assign_submission', array('assignment'=>$cm->instance)); + $date = '-'; + if (!empty($assignment->duedate)) { + $date = userdate($assignment->duedate); + } + + $params = array('assignment'=>$cm->instance, 'status'=>ASSIGN_SUBMISSION_STATUS_SUBMITTED); + $submissions = $DB->count_records('assign_submission', $params); $row = array($link, $date, $submissions); $table->data[] = $row; diff --git a/mod/assign/lang/en/assign.php b/mod/assign/lang/en/assign.php index 9ab4132b561..29fbc833c51 100644 --- a/mod/assign/lang/en/assign.php +++ b/mod/assign/lang/en/assign.php @@ -65,7 +65,9 @@ $string['batchoperationconfirmreverttodraft'] = 'Revert selected submissions to $string['batchoperationlock'] = 'lock submissions'; $string['batchoperationunlock'] = 'unlock submissions'; $string['batchoperationreverttodraft'] = 'revert submissions to draft'; +$string['changegradewarning'] = 'This assignment has graded submissions and changing the grade will not automatically re-calculate existing submission grades. You must re-grade all existing submissions, if you wish to change the grade.'; $string['comment'] = 'Comment'; +$string['completionsubmit'] = 'Student must submit to this activity to complete it'; $string['conversionexception'] = 'Could not convert assignment. Exception was: {$a}.'; $string['configshowrecentsubmissions'] = 'Everyone can see notifications of submissions in recent activity reports.'; $string['confirmsubmission'] = 'Are you sure you want to submit your work for grading? You will not be able to make any more changes'; diff --git a/mod/assign/lib.php b/mod/assign/lib.php index 1c98958cb95..18a1b11659c 100644 --- a/mod/assign/lib.php +++ b/mod/assign/lib.php @@ -83,6 +83,7 @@ function assign_supports($feature) { case FEATURE_GROUPMEMBERSONLY: return true; case FEATURE_MOD_INTRO: return true; case FEATURE_COMPLETION_TRACKS_VIEWS: return true; + case FEATURE_COMPLETION_HAS_RULES: return true; case FEATURE_GRADE_HAS_GRADE: return true; case FEATURE_GRADE_OUTCOMES: return true; case FEATURE_BACKUP_MOODLE2: return true; @@ -937,3 +938,29 @@ function assign_user_outline($course, $user, $coursemodule, $assignment) { return $result; } + +/** + * Obtains the automatic completion state for this module based on any conditions + * in assign settings. + * + * @param object $course Course + * @param object $cm Course-module + * @param int $userid User ID + * @param bool $type Type of comparison (or/and; can be used as return value if no conditions) + * @return bool True if completed, false if not, $type if conditions not set. + */ +function assign_get_completion_state($course, $cm, $userid, $type) { + global $CFG,$DB; + require_once($CFG->dirroot . '/mod/assign/locallib.php'); + + $assign = new assign(null, $cm, $course); + + // If completion option is enabled, evaluate it and return true/false. + if ($assign->get_instance()->completionsubmit) { + $submission = $DB->get_record('assign_submission', array('assignment'=>$assign->get_instance()->id, 'userid'=>$userid), '*', IGNORE_MISSING); + return $submission && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED; + } else { + // Completion option is not enabled so just return $type. + return $type; + } +} diff --git a/mod/assign/locallib.php b/mod/assign/locallib.php index 94d18719593..60a3f9251c1 100644 --- a/mod/assign/locallib.php +++ b/mod/assign/locallib.php @@ -417,6 +417,7 @@ class assign { $update->duedate = $formdata->duedate; $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate; $update->grade = $formdata->grade; + $update->completionsubmit = $formdata->completionsubmit; $returnid = $DB->insert_record('assign', $update); $this->instance = $DB->get_record('assign', array('id'=>$returnid), '*', MUST_EXIST); // cache the course record @@ -636,6 +637,7 @@ class assign { $update->duedate = $formdata->duedate; $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate; $update->grade = $formdata->grade; + $update->completionsubmit = $formdata->completionsubmit; $result = $DB->update_record('assign', $update); $this->instance = $DB->get_record('assign', array('id'=>$update->id), '*', MUST_EXIST); @@ -2561,6 +2563,11 @@ class assign { $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; $this->update_submission($submission); + $completion = new completion_info($this->get_course()); + if ($completion->is_enabled($this->get_course_module()) && $this->get_instance()->completionsubmit) { + $completion->update_state($this->get_course_module(), COMPLETION_COMPLETE, $USER->id); + } + if (isset($data->submissionstatement)) { $this->add_to_log('submission statement accepted', get_string('submissionstatementacceptedlog', 'mod_assign', fullname($USER))); } @@ -2838,6 +2845,15 @@ class assign { } $this->add_to_log('submit', $this->format_submission_for_log($submission)); + $complete = COMPLETION_INCOMPLETE; + if ($submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) { + $complete = COMPLETION_COMPLETE; + } + $completion = new completion_info($this->get_course()); + if ($completion->is_enabled($this->get_course_module()) && $this->get_instance()->completionsubmit) { + $completion->update_state($this->get_course_module(), $complete, $USER->id); + } + if (!$this->get_instance()->submissiondrafts) { $this->notify_student_submission_receipt($submission); $this->notify_graders($submission); @@ -3140,7 +3156,7 @@ class assign { * @return void */ private function process_revert_to_draft($userid = 0) { - global $USER, $DB; + global $DB; // Need grade permission require_capability('mod/assign:grade', $this->context); @@ -3163,6 +3179,10 @@ class assign { $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST); + $completion = new completion_info($this->get_course()); + if ($completion->is_enabled($this->get_course_module()) && $this->get_instance()->completionsubmit) { + $completion->update_state($this->get_course_module(), COMPLETION_INCOMPLETE, $userid); + } $this->add_to_log('revert submission to draft', get_string('reverttodraftforstudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user)))); } diff --git a/mod/assign/mod_form.php b/mod/assign/mod_form.php index 89d0159631c..c2cde375520 100644 --- a/mod/assign/mod_form.php +++ b/mod/assign/mod_form.php @@ -45,7 +45,7 @@ class mod_assign_mod_form extends moodleform_mod { * @return void */ function definition() { - global $CFG, $DB; + global $CFG, $DB, $PAGE; $mform = $this->_form; $mform->addElement('header', 'general', get_string('general', 'form')); @@ -120,6 +120,21 @@ class mod_assign_mod_form extends moodleform_mod { $this->standard_coursemodule_elements(); $this->add_action_buttons(); + + // Add warning popup/noscript tag, if grades are changed by user. + if ($mform->elementExists('grade') && !empty($this->_instance) && $DB->record_exists_select('assign_grades', 'assignment = ? AND grade <> -1', array($this->_instance))) { + $module = array( + 'name' => 'mod_assign', + 'fullpath' => '/mod/assign/module.js', + 'requires' => array('node', 'event'), + 'strings' => array(array('changegradewarning', 'mod_assign')) + ); + $PAGE->requires->js_init_call('M.mod_assign.init_grade_change', null, false, $module); + + // Add noscript tag in case + $noscriptwarning = $mform->createElement('static', 'warning', null, html_writer::tag('noscript', get_string('changegradewarning', 'mod_assign'))); + $mform->insertElementBefore($noscriptwarning, 'grade'); + } } /** @@ -161,5 +176,15 @@ class mod_assign_mod_form extends moodleform_mod { $assignment->plugin_data_preprocessing($defaultvalues); } + function add_completion_rules() { + $mform =& $this->_form; + + $mform->addElement('checkbox', 'completionsubmit', '', get_string('completionsubmit', 'assign')); + return array('completionsubmit'); + } + + function completion_rule_enabled($data) { + return !empty($data['completionsubmit']); + } } diff --git a/mod/assign/module.js b/mod/assign/module.js index 0e8c0d9620e..a89f4176b2a 100644 --- a/mod/assign/module.js +++ b/mod/assign/module.js @@ -127,4 +127,16 @@ M.mod_assign.init_grading_options = function(Y) { }); } }); +}; + +M.mod_assign.init_grade_change = function(Y) { + var gradenode = Y.one('#id_grade'); + if (gradenode) { + var originalvalue = gradenode.get('value'); + gradenode.on('change', function() { + if (gradenode.get('value') != originalvalue) { + alert(M.str.mod_assign.changegradewarning); + } + }); + } }; \ No newline at end of file diff --git a/mod/assign/renderer.php b/mod/assign/renderer.php index e90556fd562..c44f451e036 100644 --- a/mod/assign/renderer.php +++ b/mod/assign/renderer.php @@ -417,7 +417,7 @@ class mod_assign_renderer extends plugin_renderer_base { $row = new html_table_row(); $cell1 = new html_table_cell(get_string('timeremaining', 'assign')); if ($duedate - $time <= 0) { - if (!$status->submission || $status->submission != ASSIGN_SUBMISSION_STATUS_SUBMITTED) { + if (!$status->submission || $status->submission->status != ASSIGN_SUBMISSION_STATUS_SUBMITTED) { if ($status->submissionsenabled) { $cell2 = new html_table_cell(get_string('overdue', 'assign', format_time($time - $duedate))); $cell2->attributes = array('class'=>'overdue'); diff --git a/mod/assign/version.php b/mod/assign/version.php index 99ef6478807..b9a29a3d16c 100644 --- a/mod/assign/version.php +++ b/mod/assign/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); $module->component = 'mod_assign'; // Full name of the plugin (used for diagnostics) -$module->version = 2012071800; // The current module version (Date: YYYYMMDDXX) +$module->version = 2012081600; // The current module version (Date: YYYYMMDDXX) $module->requires = 2012061700; // Requires this Moodle version $module->cron = 60; diff --git a/mod/assignment/assignment.js b/mod/assignment/assignment.js index 6f63c2cc6fc..8e77ad923c5 100644 --- a/mod/assignment/assignment.js +++ b/mod/assignment/assignment.js @@ -35,3 +35,15 @@ M.mod_assignment.init_tree = function(Y, expand_all, htmlid) { tree.render(); }); }; + +M.mod_assignment.init_grade_change = function(Y) { + var gradenode = Y.one('#id_grade'); + if (gradenode) { + var originalvalue = gradenode.get('value'); + gradenode.on('change', function() { + if (gradenode.get('value') != originalvalue) { + alert(M.str.mod_assignment.changegradewarning); + } + }); + } +}; diff --git a/mod/assignment/lang/en/assignment.php b/mod/assignment/lang/en/assignment.php index 98ec8f4ec09..7aef1ae676d 100644 --- a/mod/assignment/lang/en/assignment.php +++ b/mod/assignment/lang/en/assignment.php @@ -57,6 +57,7 @@ $string['assignment:view'] = 'View assignment'; $string['availabledate'] = 'Available from'; $string['cannotdeletefiles'] = 'An error occurred and files could not be deleted'; $string['cannotviewassignment'] = 'You can not view this assignment'; +$string['changegradewarning'] = 'This assignment has graded submissions and changing the grade will not automatically re-calculate existing submission grades. You must re-grade all existing submissions, if you wish to change the grade.'; $string['comment'] = 'Comment'; $string['commentinline'] = 'Comment inline'; $string['commentinline_help'] = 'If enabled, the submission text will be copied into the feedback comment field during grading, making it easier to comment inline (using a different colour, perhaps) or to edit the original text.'; diff --git a/mod/assignment/mod_form.php b/mod/assignment/mod_form.php index 85c84238a1f..56b407d6ea6 100644 --- a/mod/assignment/mod_form.php +++ b/mod/assignment/mod_form.php @@ -9,7 +9,7 @@ class mod_assignment_mod_form extends moodleform_mod { protected $_assignmentinstance = null; function definition() { - global $CFG, $DB; + global $CFG, $DB, $PAGE; $mform =& $this->_form; // this hack is needed for different settings of each subtype @@ -76,6 +76,21 @@ class mod_assignment_mod_form extends moodleform_mod { $this->standard_coursemodule_elements(); $this->add_action_buttons(); + + // Add warning popup/noscript tag, if grades are changed by user. + if ($mform->elementExists('grade') && !empty($this->_instance) && $DB->record_exists_select('assignment_submissions', 'assignment = ? AND grade <> -1', array($this->_instance))) { + $module = array( + 'name' => 'mod_assignment', + 'fullpath' => '/mod/assignment/assignment.js', + 'requires' => array('node', 'event'), + 'strings' => array(array('changegradewarning', 'mod_assignment')) + ); + $PAGE->requires->js_init_call('M.mod_assignment.init_grade_change', null, false, $module); + + // Add noscript tag in case + $noscriptwarning = $mform->createElement('static', 'warning', null, html_writer::tag('noscript', get_string('changegradewarning', 'mod_assignment'))); + $mform->insertElementBefore($noscriptwarning, 'grade'); + } } // Needed by plugin assignment types if they include a filemanager element in the settings form diff --git a/mod/book/backup/moodle2/restore_book_activity_task.class.php b/mod/book/backup/moodle2/restore_book_activity_task.class.php index da1b75f94d4..d9ee50e1448 100644 --- a/mod/book/backup/moodle2/restore_book_activity_task.class.php +++ b/mod/book/backup/moodle2/restore_book_activity_task.class.php @@ -82,6 +82,9 @@ class restore_book_activity_task extends restore_activity_task { $rules[] = new restore_decode_rule('BOOKVIEWBYB', '/mod/book/view.php?b=$1', 'book'); $rules[] = new restore_decode_rule('BOOKVIEWBYBCH', '/mod/book/view.php?b=$1&chapterid=$2', array('book', 'book_chapter')); + // Convert old book links MDL-33362 + $rules[] = new restore_decode_rule('BOOKSTART', '/mod/book/view.php?id=$1', 'course_module'); + return $rules; } diff --git a/mod/book/version.php b/mod/book/version.php index cc4ad8f4acc..edbbd9dae23 100644 --- a/mod/book/version.php +++ b/mod/book/version.php @@ -25,6 +25,6 @@ defined('MOODLE_INTERNAL') || die; $module->component = 'mod_book'; // Full name of the plugin (used for diagnostics) -$module->version = 2012061700; // The current module version (Date: YYYYMMDDXX) +$module->version = 2012081600; // The current module version (Date: YYYYMMDDXX) $module->requires = 2012061700; // Requires this Moodle version $module->cron = 0; // Period for cron to check this module (secs) diff --git a/mod/chat/lib.php b/mod/chat/lib.php index f5e52aed968..9b5698155c5 100644 --- a/mod/chat/lib.php +++ b/mod/chat/lib.php @@ -767,16 +767,15 @@ function chat_format_message_manually($message, $courseid, $sender, $currentuser } // It's not a system event - - $text = $message->message; + $text = trim($message->message); /// Parse the text to clean and filter it - $options = new stdClass(); $options->para = false; $text = format_text($text, FORMAT_MOODLE, $options, $courseid); // And now check for special cases + $patternTo = '#^\s*To\s([^:]+):(.*)#'; $special = false; if (substr($text, 0, 5) == 'beep ') { @@ -799,23 +798,32 @@ function chat_format_message_manually($message, $courseid, $sender, $currentuser return false; } } else if (substr($text, 0, 1) == '/') { /// It's a user command - // support some IRC commands - $pattern = '#(^\/)(\w+).*#'; - preg_match($pattern, trim($text), $matches); - $command = $matches[2]; - switch ($command){ - case 'me': - $special = true; - $outinfo = $message->strtime; - $outmain = '*** '.$sender->firstname.' '.substr($text, 4).''; - break; - } - } elseif (substr($text, 0, 2) == 'To') { - $pattern = '#To[[:space:]](.*):(.*)#'; - preg_match($pattern, trim($text), $matches); $special = true; - $outinfo = $message->strtime; - $outmain = $sender->firstname.' '.get_string('saidto', 'chat').' '.$matches[1].': '.$matches[2]; + $pattern = '#(^\/)(\w+).*#'; + preg_match($pattern, $text, $matches); + $command = isset($matches[2]) ? $matches[2] : false; + // Support some IRC commands. + switch ($command){ + case 'me': + $outinfo = $message->strtime; + $outmain = '*** '.$sender->firstname.' '.substr($text, 4).''; + break; + default: + // Error, we set special back to false to use the classic message output. + $special = false; + break; + } + } else if (preg_match($patternTo, $text)) { + $special = true; + $matches = array(); + preg_match($patternTo, $text, $matches); + if (isset($matches[1]) && isset($matches[2])) { + $outinfo = $message->strtime; + $outmain = $sender->firstname.' '.get_string('saidto', 'chat').' '.$matches[1].': '.$matches[2]; + } else { + // Error, we set special back to false to use the classic message output. + $special = false; + } } if(!$special) { @@ -924,7 +932,7 @@ function chat_format_message_theme ($message, $chatuser, $currentuser, $grouping } // It's not a system event - $text = $message->message; + $text = trim($message->message); /// Parse the text to clean and filter it $options = new stdClass(); @@ -935,8 +943,9 @@ function chat_format_message_theme ($message, $chatuser, $currentuser, $grouping $special = false; $outtime = $message->strtime; - //Initilise output variable. + // Initialise variables. $outmain = ''; + $patternTo = '#^\s*To\s([^:]+):(.*)#'; if (substr($text, 0, 5) == 'beep ') { $special = true; @@ -964,26 +973,33 @@ function chat_format_message_theme ($message, $chatuser, $currentuser, $grouping } else if (substr($text, 0, 1) == '/') { /// It's a user command $special = true; $result->type = 'command'; - // support some IRC commands $pattern = '#(^\/)(\w+).*#'; - preg_match($pattern, trim($text), $matches); - $command = $matches[2]; - $special = true; + preg_match($pattern, $text, $matches); + $command = isset($matches[2]) ? $matches[2] : false; + // Support some IRC commands. switch ($command){ - case 'me': - $outmain = '*** '.$sender->firstname.' '.substr($text, 4).''; - break; + case 'me': + $outmain = '*** '.$sender->firstname.' '.substr($text, 4).''; + break; + default: + // Error, we set special back to false to use the classic message output. + $special = false; + break; } - } elseif (substr($text, 0, 2) == 'To') { + } else if (preg_match($patternTo, $text)) { $special = true; $result->type = 'dialogue'; - $pattern = '#To[[:space:]](.*):(.*)#'; - preg_match($pattern, trim($text), $matches); - $special = true; - $outmain = $sender->firstname.' '.get_string('saidto', 'chat').' '.$matches[1].': '.$matches[2]; + $matches = array(); + preg_match($patternTo, $text, $matches); + if (isset($matches[1]) && isset($matches[2])) { + $outmain = $sender->firstname.' '.get_string('saidto', 'chat').' '.$matches[1].': '.$matches[2]; + } else { + // Error, we set special back to false to use the classic message output. + $special = false; + } } - if(!$special) { + if (!$special) { $outmain = $text; } @@ -1008,7 +1024,6 @@ function chat_format_message_theme ($message, $chatuser, $currentuser, $grouping } } - /** * @global object $DB * @global object $CFG diff --git a/mod/forum/lib.php b/mod/forum/lib.php index 7236bd09210..405bc95df6e 100644 --- a/mod/forum/lib.php +++ b/mod/forum/lib.php @@ -2092,8 +2092,7 @@ function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=5 u.lastname, u.email, u.picture, - u.imagealt, - u.email + u.imagealt FROM $fromsql WHERE $selectsql ORDER BY p.modified DESC"; diff --git a/mod/lesson/lib.php b/mod/lesson/lib.php index 31daa38da68..89f4eda36d8 100644 --- a/mod/lesson/lib.php +++ b/mod/lesson/lib.php @@ -345,7 +345,7 @@ function lesson_get_user_grades($lesson, $userid=0) { $params = array("lessonid" => $lesson->id,"lessonid2" => $lesson->id); - if (isset($userid)) { + if (!empty($userid)) { $params["userid"] = $userid; $params["userid2"] = $userid; $user = "AND u.id = :userid"; diff --git a/mod/quiz/attemptlib.php b/mod/quiz/attemptlib.php index 017c8632322..1c649946f04 100644 --- a/mod/quiz/attemptlib.php +++ b/mod/quiz/attemptlib.php @@ -1687,7 +1687,7 @@ class quiz_review_nav_panel extends quiz_nav_panel_base { get_string('showall', 'quiz')); } } - $html .= $output->finish_review_link($this->attemptobj->view_url()); + $html .= $output->finish_review_link($this->attemptobj); $html .= $this->render_restart_preview_link($output); return $html; } diff --git a/mod/quiz/backup/moodle1/lib.php b/mod/quiz/backup/moodle1/lib.php index 2e25b40a77c..09efca0df64 100644 --- a/mod/quiz/backup/moodle1/lib.php +++ b/mod/quiz/backup/moodle1/lib.php @@ -56,7 +56,7 @@ class moodle1_mod_quiz_handler extends moodle1_mod_handler { array( 'newfields' => array( 'showuserpicture' => 0, - 'questiondecimalpoints' => -2, + 'questiondecimalpoints' => -1, 'introformat' => 0, 'showblocks' => 0, ) diff --git a/mod/quiz/db/install.xml b/mod/quiz/db/install.xml index fd12420079c..dede549feda 100644 --- a/mod/quiz/db/install.xml +++ b/mod/quiz/db/install.xml @@ -21,7 +21,7 @@ - + diff --git a/mod/quiz/db/upgrade.php b/mod/quiz/db/upgrade.php index 3bbbb6d4d83..cb8e76df0a9 100644 --- a/mod/quiz/db/upgrade.php +++ b/mod/quiz/db/upgrade.php @@ -341,6 +341,25 @@ function xmldb_quiz_upgrade($oldversion) { upgrade_mod_savepoint(true, 2012061702, 'quiz'); } + if ($oldversion < 2012061703) { + + // MDL-34702 the questiondecimalpoints column was created with default -2 + // when it should have been -1, and no-one has noticed in the last 2+ years! + + // Changing the default of field questiondecimalpoints on table quiz to -1. + $table = new xmldb_table('quiz'); + $field = new xmldb_field('questiondecimalpoints', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '-1', 'decimalpoints'); + + // Launch change of default for field questiondecimalpoints. + $dbman->change_field_default($table, $field); + + // Correct any wrong values. + $DB->set_field('quiz', 'questiondecimalpoints', -1, array('questiondecimalpoints' => -2)); + + // Quiz savepoint reached. + upgrade_mod_savepoint(true, 2012061703, 'quiz'); + } + return true; } diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php index 5b8ff20c530..06a2e513b92 100644 --- a/mod/quiz/lib.php +++ b/mod/quiz/lib.php @@ -373,7 +373,7 @@ function quiz_has_grades($quiz) { */ function quiz_user_outline($course, $user, $mod, $quiz) { global $DB, $CFG; - require_once("$CFG->libdir/gradelib.php"); + require_once($CFG->libdir . '/gradelib.php'); $grades = grade_get_grades($course->id, 'mod', 'quiz', $quiz->id, $user->id); if (empty($grades->items[0]->grades)) { @@ -411,7 +411,7 @@ function quiz_user_outline($course, $user, $mod, $quiz) { function quiz_user_complete($course, $user, $mod, $quiz) { global $DB, $CFG, $OUTPUT; require_once($CFG->libdir . '/gradelib.php'); - require_once($CFG->libdir . '/mod/quiz/locallib.php'); + require_once($CFG->dirroot . '/mod/quiz/locallib.php'); $grades = grade_get_grades($course->id, 'mod', 'quiz', $quiz->id, $user->id); if (!empty($grades->items[0]->grades)) { @@ -603,7 +603,7 @@ function quiz_format_question_grade($quiz, $grade) { */ function quiz_update_grades($quiz, $userid = 0, $nullifnone = true) { global $CFG, $DB; - require_once($CFG->libdir.'/gradelib.php'); + require_once($CFG->libdir . '/gradelib.php'); if ($quiz->grade == 0) { quiz_grade_item_update($quiz); @@ -661,7 +661,7 @@ function quiz_upgrade_grades() { function quiz_grade_item_update($quiz, $grades = null) { global $CFG, $OUTPUT; require_once($CFG->dirroot . '/mod/quiz/locallib.php'); - require_once($CFG->libdir.'/gradelib.php'); + require_once($CFG->libdir . '/gradelib.php'); if (array_key_exists('cmidnumber', $quiz)) { // May not be always present. $params = array('itemname' => $quiz->name, 'idnumber' => $quiz->cmidnumber); @@ -792,7 +792,7 @@ function quiz_refresh_events($courseid = 0) { function quiz_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid = 0, $groupid = 0) { global $CFG, $COURSE, $USER, $DB; - require_once('locallib.php'); + require_once($CFG->dirroot . '/mod/quiz/locallib.php'); if ($COURSE->id == $courseid) { $course = $COURSE; @@ -1308,15 +1308,13 @@ function quiz_reset_gradebook($courseid, $type='') { */ function quiz_reset_userdata($data) { global $CFG, $DB; - require_once($CFG->libdir.'/questionlib.php'); + require_once($CFG->libdir . '/questionlib.php'); $componentstr = get_string('modulenameplural', 'quiz'); $status = array(); // Delete attempts. if (!empty($data->reset_quiz_attempts)) { - require_once($CFG->libdir . '/questionlib.php'); - question_engine::delete_questions_usage_by_activities(new qubaid_join( '{quiz_attempts} quiza JOIN {quiz} quiz ON quiza.quiz = quiz.id', 'quiza.uniqueid', 'quiz.course = :quizcourseid', @@ -1364,8 +1362,7 @@ function quiz_reset_userdata($data) { */ function quiz_check_file_access($attemptuniqueid, $questionid, $context = null) { global $USER, $DB, $CFG; - require_once(dirname(__FILE__).'/attemptlib.php'); - require_once(dirname(__FILE__).'/locallib.php'); + require_once($CFG->dirroot . '/mod/quiz/locallib.php'); $attempt = $DB->get_record('quiz_attempts', array('uniqueid' => $attemptuniqueid)); $attemptobj = quiz_attempt::create($attempt->id); @@ -1570,7 +1567,7 @@ function quiz_supports($feature) { */ function quiz_get_extra_capabilities() { global $CFG; - require_once($CFG->libdir.'/questionlib.php'); + require_once($CFG->libdir . '/questionlib.php'); $caps = question_get_all_capabilities(); $caps[] = 'moodle/site:accessallgroups'; return $caps; @@ -1599,7 +1596,7 @@ function quiz_extend_navigation($quiznode, $course, $module, $cm) { } if (has_any_capability(array('mod/quiz:viewreports', 'mod/quiz:grade'), $context)) { - require_once($CFG->dirroot.'/mod/quiz/report/reportlib.php'); + require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php'); $reportlist = quiz_report_list($context); $url = new moodle_url('/mod/quiz/report.php', diff --git a/mod/quiz/renderer.php b/mod/quiz/renderer.php index 820e711f455..d8fcae117c3 100644 --- a/mod/quiz/renderer.php +++ b/mod/quiz/renderer.php @@ -226,16 +226,18 @@ class mod_quiz_renderer extends plugin_renderer_base { /** * Returns either a liink or button * - * @param $url contains a url for the review link + * @param quiz_attempt $attemptobj instance of quiz_attempt */ - public function finish_review_link($url) { - if ($this->page->pagelayout == 'popup') { - // In a 'secure' popup window. + public function finish_review_link(quiz_attempt $attemptobj) { + $url = $attemptobj->view_url(); + + if ($attemptobj->get_access_manager(time())->attempt_must_be_in_popup()) { $this->page->requires->js_init_call('M.mod_quiz.secure_window.init_close_button', array($url), quiz_get_js_module()); return html_writer::empty_tag('input', array('type' => 'button', 'value' => get_string('finishreview', 'quiz'), 'id' => 'secureclosebutton')); + } else { return html_writer::link($url, get_string('finishreview', 'quiz')); } @@ -250,7 +252,7 @@ class mod_quiz_renderer extends plugin_renderer_base { */ public function review_next_navigation(quiz_attempt $attemptobj, $page, $lastpage) { if ($lastpage) { - $nav = $this->finish_review_link($attemptobj->view_url()); + $nav = $this->finish_review_link($attemptobj); } else { $nav = link_arrow_right(get_string('next'), $attemptobj->review_url(null, $page + 1)); } diff --git a/mod/quiz/report/responses/responses_table.php b/mod/quiz/report/responses/responses_table.php index ed6ada43198..a5a0c094d83 100644 --- a/mod/quiz/report/responses/responses_table.php +++ b/mod/quiz/report/responses/responses_table.php @@ -63,7 +63,7 @@ class quiz_responses_table extends quiz_attempts_report_table { } public function col_sumgrades($attempt) { - if ($attempt->state == quiz_attempt::FINISHED) { + if ($attempt->state != quiz_attempt::FINISHED) { return '-'; } diff --git a/mod/quiz/upgrade.txt b/mod/quiz/upgrade.txt new file mode 100644 index 00000000000..cd73830b338 --- /dev/null +++ b/mod/quiz/upgrade.txt @@ -0,0 +1,12 @@ +This files describes API changes in the quiz code. + + +=== 2.4 === + +* mod_quiz_renderer::finish_review_link now requires $attemptobj to be passed in + instead of a moodle_url. + + +=== Earlier changes === + +* Were not documented in this way. Sorry. diff --git a/mod/quiz/version.php b/mod/quiz/version.php index 67ca371213d..59c7b3c332d 100644 --- a/mod/quiz/version.php +++ b/mod/quiz/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); -$module->version = 2012061702; // The current module version (Date: YYYYMMDDXX). +$module->version = 2012061703; // The current module version (Date: YYYYMMDDXX). $module->requires = 2012061700; // Requires this Moodle version. $module->component = 'mod_quiz'; // Full name of the plugin (used for diagnostics). $module->cron = 60; diff --git a/mod/upgrade.txt b/mod/upgrade.txt index 72411c8168d..c5b23355384 100644 --- a/mod/upgrade.txt +++ b/mod/upgrade.txt @@ -2,6 +2,13 @@ This files describes API changes in /mod/* - activity modules, information provided here is intended especially for developers. +=== 2.4 === + +new features: + +* mod/xxx/adminlib.php may now include 'plugininfo_yoursubplugintype' class definition + used by plugin_manager; it is recommended to store extra admin settings classes in this file + === 2.3 === required changes in code: diff --git a/mod/wiki/editors/wikieditor.php b/mod/wiki/editors/wikieditor.php index e8804936079..4865a7cb52d 100644 --- a/mod/wiki/editors/wikieditor.php +++ b/mod/wiki/editors/wikieditor.php @@ -120,7 +120,8 @@ class MoodleQuickForm_wikieditor extends MoodleQuickForm_textarea { $html .= html_writer::empty_tag('img', array('alt' => $button[1], 'src' => $CFG->wwwroot . '/mod/wiki/editors/wiki/images/' . $button[0])); $html .= ""; } - $html .= ""; $html .= "'; foreach ($this->files as $filename) { $html .= "