Skip to content
Browse files

Merge branch 'master' of https://github.com/moodle/moodle

  • Loading branch information...
2 parents 4a52712 + d71c486 commit 4873a890df33c8d529e47bcde170ffb281ae0cf4 @pcharsle pcharsle committed Aug 28, 2012
Showing with 3,601 additions and 887 deletions.
  1. +4 −6 admin/roles/lib.php
  2. +5 −0 admin/settings/courses.php
  3. +1 −1 admin/settings/location.php
  4. +1 −1 admin/tool/phpunit/cli/util.php
  5. +3 −1 auth/email/auth.php
  6. +3 −1 auth/ldap/auth.php
  7. +3 −1 auth/manual/auth.php
  8. +18 −9 backup/moodle2/backup_stepslib.php
  9. +69 −19 backup/moodle2/restore_stepslib.php
  10. +1 −1 backup/util/dbops/backup_plan_dbops.class.php
  11. +47 −25 backup/util/helper/backup_cron_helper.class.php
  12. +442 −0 backup/util/helper/tests/cronhelper_test.php
  13. +3 −3 backup/util/structure/restore_path_element.class.php
  14. +3 −0 backup/util/ui/base_moodleform.class.php
  15. +80 −0 backup/util/ui/yui/backupselectall/backupselectall.js
  16. +15 −0 blocks/html/block_html.php
  17. +4 −0 blocks/html/edit_form.php
  18. +2 −0 blocks/html/lang/en/block_html.php
  19. +1 −0 blocks/tags/block_tags.php
  20. +5 −1 completion/completion_completion.php
  21. +3 −1 config-dist.php
  22. +1 −1 course/edit_form.php
  23. +8 −5 course/format/renderer.php
  24. +0 −3 course/lib.php
  25. +0 −2 course/recent_form.php
  26. +1 −1 course/reset_form.php
  27. +1 −1 course/rest.php
  28. +1 −1 course/tests/externallib_test.php
  29. +1 −1 course/view.php
  30. +30 −6 enrol/category/cli/sync.php
  31. +2 −2 enrol/category/db/access.php
  32. +3 −3 enrol/category/db/events.php
  33. +1 −3 enrol/category/db/install.php
  34. +2 −4 enrol/category/lang/en/enrol_category.php
  35. +11 −14 enrol/category/lib.php
  36. +83 −40 enrol/category/locallib.php
  37. +2 −5 enrol/category/settings.php
  38. +365 −0 enrol/category/tests/sync_test.php
  39. +4 −5 enrol/category/version.php
  40. +1 −10 enrol/cohort/lib.php
  41. +5 −9 enrol/locallib.php
  42. +1 −1 enrol/manual/lang/en/enrol_manual.php
  43. +3 −5 enrol/manual/locallib.php
  44. +2 −2 enrol/manual/settings.php
  45. +1 −1 enrol/paypal/lang/en/enrol_paypal.php
  46. +2 −2 enrol/paypal/settings.php
  47. +1 −1 enrol/self/lang/en/enrol_self.php
  48. +2 −2 enrol/self/settings.php
  49. +1 −1 install/lang/es_mx/langconfig.php
  50. +1 −1 install/lang/zh_cn/install.php
  51. +10 −8 iplookup/index.php
  52. +16 −17 iplookup/module.js
  53. +3 −3 lang/en/admin.php
  54. +1 −1 lang/en/completion.php
  55. +1 −0 lang/en/moodle.php
  56. +1 −0 lang/en/role.php
  57. +162 −0 lib/adminlib.php
  58. +6 −0 lib/blocklib.php
  59. +25 −0 lib/completionlib.php
  60. +7 −0 lib/cronlib.php
  61. +11 −0 lib/db/access.php
  62. +7 −0 lib/db/upgrade.php
  63. +37 −0 lib/editor/tinymce/adminlib.php
  64. +0 −1 lib/editor/tinymce/extra/tools/.gitignore
  65. +1 −0 lib/editor/tinymce/lang/en/editor_tinymce.php
  66. +74 −0 lib/editor/tinymce/subplugins.php
  67. +3 −2 lib/form/duration.php
  68. +2 −2 lib/form/tests/dateselector_test.php
  69. +2 −2 lib/form/tests/datetimeselector_test.php
  70. +1 −1 lib/form/tests/duration_test.php
  71. +2 −1 lib/grade/grade_item.php
  72. +1 −1 lib/messagelib.php
  73. +28 −12 lib/moodlelib.php
  74. +3 −0 lib/pagelib.php
  75. +6 −6 lib/phpunit/classes/data_generator.php
  76. +41 −0 lib/phpunit/classes/hint_resultprinter.php
  77. +25 −2 lib/phpunit/classes/util.php
  78. +12 −0 lib/phpunit/tests/generator_test.php
  79. +18 −3 lib/pluginlib.php
  80. +3 −0 lib/setup.php
  81. +5 −0 lib/tablelib.php
  82. +0 −184 lib/tests/backup_test.php
  83. +3 −3 lib/tests/moodlelib_test.php
  84. +1 −0 lib/upgrade.txt
  85. +24 −9 lib/weblib.php
  86. +2 −1 mod/assign/backup/moodle2/backup_assign_stepslib.php
  87. +2 −1 mod/assign/db/install.xml
  88. +15 −0 mod/assign/db/upgrade.php
  89. +4 −2 mod/assign/gradingtable.php
  90. +7 −2 mod/assign/index.php
  91. +2 −0 mod/assign/lang/en/assign.php
  92. +27 −0 mod/assign/lib.php
  93. +21 −1 mod/assign/locallib.php
  94. +26 −1 mod/assign/mod_form.php
  95. +12 −0 mod/assign/module.js
  96. +1 −1 mod/assign/renderer.php
  97. +1 −1 mod/assign/version.php
  98. +12 −0 mod/assignment/assignment.js
  99. +1 −0 mod/assignment/lang/en/assignment.php
  100. +16 −1 mod/assignment/mod_form.php
  101. +3 −0 mod/book/backup/moodle2/restore_book_activity_task.class.php
  102. +1 −1 mod/book/version.php
  103. +47 −32 mod/chat/lib.php
  104. +1 −2 mod/forum/lib.php
  105. +1 −1 mod/lesson/lib.php
  106. +1 −1 mod/quiz/attemptlib.php
  107. +1 −1 mod/quiz/backup/moodle1/lib.php
  108. +1 −1 mod/quiz/db/install.xml
  109. +19 −0 mod/quiz/db/upgrade.php
  110. +9 −12 mod/quiz/lib.php
  111. +7 −5 mod/quiz/renderer.php
  112. +1 −1 mod/quiz/report/responses/responses_table.php
  113. +12 −0 mod/quiz/upgrade.txt
  114. +1 −1 mod/quiz/version.php
  115. +7 −0 mod/upgrade.txt
  116. +2 −1 mod/wiki/editors/wikieditor.php
  117. +2 −1 mod/wiki/lib.php
  118. +0 −2 mod/wiki/mod_form.php
  119. +0 −1 phpunit.xml.dist
  120. +33 −27 question/editlib.php
  121. +11 −0 question/engine/bank.php
  122. +9 −1 question/engine/datalib.php
  123. +54 −0 question/format.php
  124. +388 −265 question/format/blackboard/format.php
  125. +4 −2 question/format/blackboard/lang/en/qformat_blackboard.php
  126. +328 −0 question/format/blackboard/tests/blackboardformat_test.php
  127. +142 −0 question/format/blackboard/tests/fixtures/sample_blackboard.dat
  128. +2 −3 question/format/blackboard/version.php
  129. +64 −39 question/format/examview/format.php
  130. +1 −2 question/format/examview/lang/en/qformat_examview.php
  131. +305 −0 question/format/examview/tests/examviewformat_test.php
  132. +161 −0 question/format/examview/tests/fixtures/examview_sample.xml
  133. +2 −3 question/format/examview/version.php
  134. +27 −0 question/previewlib.php
  135. +1 −1 report/stats/locallib.php
  136. +2 −1 tag/edit.php
  137. +4 −2 tag/lib.php
  138. +2 −1 tag/locallib.php
  139. +1 −1 theme/base/style/core.css
  140. +2 −3 user/selector/lib.php
  141. +2 −2 version.php
View
10 admin/roles/lib.php
@@ -1039,12 +1039,10 @@ public function find_users($search) {
$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;
View
5 admin/settings/courses.php
@@ -47,6 +47,11 @@
$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');
View
2 admin/settings/location.php
@@ -14,7 +14,7 @@
$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+)*)?$/'));
View
2 admin/tool/phpunit/cli/util.php
@@ -150,7 +150,7 @@
} 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);
View
4 auth/email/auth.php
@@ -132,7 +132,9 @@ function user_confirm($username, $confirmsecret) {
} 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 {
View
4 auth/ldap/auth.php
@@ -546,7 +546,9 @@ function user_confirm($username, $confirmsecret) {
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 {
View
4 auth/manual/auth.php
@@ -170,7 +170,9 @@ function user_confirm($username, $confirmsecret = null) {
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 {
View
27 backup/moodle2/backup_stepslib.php
@@ -182,8 +182,17 @@ protected function prepare_activity_structure($activitystructure) {
/**
* 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 @@ protected function add_question_usages($element, $usageidname) {
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 @@ protected function define_execution() {
'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
}
View
88 backup/moodle2/restore_stepslib.php
@@ -3478,31 +3478,74 @@ private function add_result_item($name, $value) {
/**
* 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 @@ protected function process_question_usage($data) {
$this->inform_new_usage_id($newitemid);
- $this->set_mapping('question_usage', $oldid, $newitemid, false);
+ $this->set_mapping($nameprefix . 'question_usage', $oldid, $newitemid, false);
}
/**
@@ -3532,45 +3575,51 @@ protected function process_question_usage($data) {
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;
$oldid = $data->id;
// 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 @@ protected function process_question_attempt_step($data) {
$this->qtypes[$data->questionattemptid],
$this->newquestionids[$data->questionattemptid],
$data->sequencenumber, $response);
+
foreach ($response as $name => $value) {
$row = new stdClass();
$row->attemptstepid = $newitemid;
View
2 backup/util/dbops/backup_plan_dbops.class.php
@@ -112,7 +112,7 @@ public static function get_sections_from_courseid($courseid) {
// 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;
}
View
72 backup/util/helper/backup_cron_helper.class.php
@@ -110,7 +110,7 @@ public static function run_automated_backup($rundirective = self::RUN_ON_SCHEDUL
$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 @@ public static function run_automated_backup($rundirective = self::RUN_ON_SCHEDUL
}
// 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 @@ public static function run_automated_backup($rundirective = self::RUN_ON_SCHEDUL
$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 @@ public static function get_backup_status_array() {
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 @@ public static function get_backup_status_array() {
/**
* 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 @@ public static function get_automated_backup_state($rundirective = self::RUN_ON_S
$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
View
442 backup/util/helper/tests/cronhelper_test.php
@@ -0,0 +1,442 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for backups cron helper.
+ *
+ * @package core_backup
+ * @category phpunit
+ * @copyright 2012 Frédéric Massart <fred@moodle.com>
+ * @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));
+
+ }
+}
View
6 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) {
View
3 backup/util/ui/base_moodleform.class.php
@@ -324,6 +324,9 @@ public function display() {
$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();
}
View
80 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 '<div class="' + classname + '">' +
+ '<div class="fitem fitem_fcheckbox">' +
+ '<div class="fitemtitle">' + str.select + '</div>' +
+ '<div class="felement">' +
+ '<a id="backup-all-' + idtype + '" href="#">' + str.all + '</a> / ' +
+ '<a id="backup-none-' + idtype + '" href="#">' + str.none + '</a>' +
+ '</div>' +
+ '</div>' +
+ '</div>';
+ };
+
+ 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('<div class="grouped_settings section_level">' + html + '</div>');
+ 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']});
View
15 blocks/html/block_html.php
@@ -131,4 +131,19 @@ function content_is_trusted() {
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;
+ }
}
View
4 blocks/html/edit_form.php
@@ -41,6 +41,10 @@ protected function specific_definition($mform) {
$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) {
View
2 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';
View
1 blocks/tags/block_tags.php
@@ -40,6 +40,7 @@ function get_content() {
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');
View
6 completion/completion_completion.php
@@ -158,7 +158,11 @@ public function mark_complete($timecomplete = null) {
$this->timecompleted = $timecomplete;
// Save record
- return $this->_save();
+ if ($result = $this->_save()) {
+ events_trigger('course_completed', $this->get_record_data());
+ }
+
+ return $result;
}
/**
View
4 config-dist.php
@@ -211,10 +211,12 @@
// 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.
View
2 course/edit_form.php
@@ -124,7 +124,7 @@ function definition() {
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";
View
13 course/format/renderer.php
@@ -235,7 +235,7 @@ protected function section_edit_controls($course, $section, $onsectionpage = fal
}
}
- 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 @@ protected function section_summary($section, $course, $mods) {
$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 @@ protected function get_nav_links($course, $sections, $sectionno) {
$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 @@ protected function get_nav_links($course, $sections, $sectionno) {
$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');
View
3 course/lib.php
@@ -47,9 +47,6 @@
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':
View
2 course/recent_form.php
@@ -101,8 +101,6 @@ function definition() {
$mform->setAdvanced('user');
}
- $sectiontitle = get_string('sectionname', 'format_'.$COURSE->format);
-
$options = array(''=>get_string('allactivities'));
$modsused = array();
View
2 course/reset_form.php
@@ -19,7 +19,7 @@ function definition (){
$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');
View
2 course/rest.php
@@ -88,7 +88,7 @@
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';
View
2 course/tests/externallib_test.php
@@ -315,7 +315,7 @@ public function test_create_courses() {
$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;
View
2 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
View
36 enrol/category/cli/sync.php
@@ -1,5 +1,4 @@
<?php
-
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@@ -19,15 +18,14 @@
* CLI sync for full category enrol synchronisation.
*
* Sample execution:
- * $sudo -u www-data /usr/bin/php /var/www/moodle/enrol/category/cli/sync.php
+ * $ sudo -u www-data /usr/bin/php /var/www/moodle/enrol/category/cli/sync.php
*
* Notes:
* - it is required to use the web server account when executing PHP CLI scripts
* - you need to change the "www-data" to match the apache user account
* - use "su" if "sudo" not available
*
- * @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
*/
@@ -36,9 +34,35 @@
require(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php');
require_once("$CFG->dirroot/enrol/category/locallib.php");
+require_once("$CFG->libdir/clilib.php");
+
+// 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));
+}
+
+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')) {
- die('enrol_category plugin is disabled, sync is disabled');
+ cli_error('enrol_category plugin is disabled, synchronisation stopped', 2);
}
-enrol_category_sync_full();
+$verbose = !empty($options['verbose']);
+return enrol_category_sync_full($verbose);
View
4 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,
View
6 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();
View
4 enrol/category/db/install.php
@@ -1,5 +1,4 @@
<?php
-
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@@ -18,8 +17,7 @@
/**
* category enrolment plugin installation.
*
- * @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
*/
View
6 enrol/category/lang/en/enrol_category.php
@@ -1,5 +1,4 @@
<?php
-
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@@ -16,10 +15,9 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
- * 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
*/
View
25 enrol/category/lib.php
@@ -1,5 +1,4 @@
<?php
-
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@@ -18,25 +17,25 @@
/**
* Category enrolment plugin.
*
- * @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();
+
/**
* category enrolment plugin implementation.
- * @author Petr Skoda
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author Petr Skoda
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class enrol_category_plugin extends enrol_plugin {
/**
* Is it possible to delete enrol instance via standard UI?
*
- * @param object $instance
+ * @param stdClass $instance
* @return bool
*/
public function instance_deleteable($instance) {
@@ -45,7 +44,7 @@ public function instance_deleteable($instance) {
if (!enrol_is_enabled('category')) {
return true;
}
- // allow delete only when no synced users here
+ // Allow delete only when no synced users here.
return !$DB->record_exists('user_enrolments', array('enrolid'=>$instance->id));
}
@@ -55,8 +54,8 @@ public function instance_deleteable($instance) {
* @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 @@ public function cron() {
* 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 @@ public function course_updated($inserted, $course, $data) {
return;
}
- // sync category enrols
+ // Sync category enrols.
require_once("$CFG->dirroot/enrol/category/locallib.php");
enrol_category_sync_course($course);
}
}
-
-
View
123 enrol/category/locallib.php
@@ -1,5 +1,4 @@
<?php
-
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@@ -18,42 +17,48 @@
/**
* Local stuff for category enrolment plugin.
*
- * @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();
+
/**
* Event handler for category enrolment plugin.
*