Merge branch 'MDL-34444' of git://github.com/danpoltawski/moodle into MOODLE_23_STABLE
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Wed, 22 Aug 2012 09:14:38 +0000 (11:14 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Wed, 22 Aug 2012 09:14:38 +0000 (11:14 +0200)
215 files changed:
admin/roles/lib.php
admin/roles/usersroles.php
admin/settings/appearance.php
admin/settings/courses.php
admin/settings/location.php
admin/tool/phpunit/cli/util.php
admin/tool/uploaduser/index.php
auth/email/auth.php
auth/ldap/auth.php
auth/manual/auth.php
backup/moodle2/restore_activity_task.class.php
backup/moodle2/restore_section_task.class.php
backup/util/dbops/backup_plan_dbops.class.php
backup/util/helper/backup_cron_helper.class.php
backup/util/helper/tests/cronhelper_test.php [new file with mode: 0644]
backup/util/ui/base_moodleform.class.php
backup/util/ui/yui/backupselectall/backupselectall.js [new file with mode: 0644]
blocks/community/block_community.php
blocks/course_overview/block_course_overview.php
blocks/news_items/block_news_items.php
blocks/tags/block_tags.php
blog/edit_form.php
blog/lib.php
course/edit_form.php
course/format/renderer.php
course/format/topics/format.php
course/format/topics/renderer.php
course/format/weeks/format.php
course/lib.php
course/recent_form.php
course/reset_form.php
course/rest.php
course/tests/externallib_test.php
course/view.php
enrol/authorize/locallib.php
enrol/category/cli/sync.php
enrol/category/db/access.php
enrol/category/db/events.php
enrol/category/db/install.php
enrol/category/lang/en/enrol_category.php
enrol/category/lib.php
enrol/category/locallib.php
enrol/category/settings.php
enrol/category/tests/sync_test.php [new file with mode: 0644]
enrol/category/version.php
enrol/cohort/lib.php
enrol/imsenterprise/lib.php
enrol/locallib.php
enrol/manual/lib.php
enrol/manual/locallib.php
enrol/manual/manage.php
enrol/self/edit_form.php
enrol/self/lang/en/enrol_self.php
enrol/self/lib.php
filter/algebra/algebradebug.php
filter/algebra/lang/en/filter_algebra.php
filter/manage.php
grade/edit/outcome/edit.php
grade/edit/tree/calculation.php
grade/edit/tree/index.php
grade/edit/tree/lib.php
grade/grading/form/guide/lib.php
grade/grading/form/rubric/renderer.php
grade/lib.php
grade/report/grader/lib.php
install/lang/cs/install.php
install/lang/is/admin.php
install/lang/lt/admin.php
install/lang/lt/moodle.php
install/lang/nl/langconfig.php
iplookup/index.php
iplookup/module.js
lang/en/admin.php
lang/en/completion.php
lang/en/message.php
lang/en/moodle.php
lang/en/notes.php
lib/accesslib.php
lib/blocklib.php
lib/completion/completion_completion.php
lib/completionlib.php
lib/cronlib.php
lib/db/upgrade.php
lib/db/upgradelib.php
lib/filelib.php
lib/form/editor.php
lib/form/form.js
lib/form/tests/dateselector_test.php
lib/form/tests/datetimeselector_test.php
lib/formslib.php
lib/grade/grade_item.php
lib/javascript-static.js
lib/medialib.php
lib/messagelib.php
lib/moodlelib.php
lib/navigationlib.php
lib/phpunit/bootstraplib.php
lib/phpunit/classes/data_generator.php
lib/phpunit/classes/hint_resultprinter.php
lib/phpunit/classes/util.php
lib/phpunit/tests/generator_test.php
lib/tests/backup_test.php [deleted file]
lib/tests/medialib_test.php
lib/tests/moodlelib_test.php
lib/webdavlib.php
message/lib.php
message/search.html
mod/assign/feedback/comments/locallib.php
mod/assign/gradingtable.php
mod/assign/index.php
mod/assign/lang/en/assign.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/mod_form.php
mod/assign/module.js
mod/assign/renderer.php
mod/assignment/assignment.js
mod/assignment/lang/en/assignment.php
mod/assignment/lib.php
mod/assignment/mod_form.php
mod/book/backup/moodle2/restore_book_activity_task.class.php
mod/book/version.php
mod/chat/gui_ajax/index.php
mod/chat/gui_header_js/chatinput.php
mod/chat/gui_sockets/chatinput.php
mod/chat/lang/en/chat.php
mod/chat/lib.php
mod/data/field.php
mod/data/field/file/field.class.php
mod/data/field/latlong/field.class.php
mod/data/field/menu/field.class.php
mod/data/field/multimenu/field.class.php
mod/data/field/number/field.class.php
mod/data/field/picture/field.class.php
mod/data/field/radiobutton/field.class.php
mod/data/field/text/field.class.php
mod/data/field/textarea/field.class.php
mod/data/field/url/field.class.php
mod/data/field/url/mod.html
mod/data/lib.php
mod/feedback/analysis_course.php
mod/feedback/edit_form.php
mod/feedback/item/multichoice/lib.php
mod/feedback/item/multichoicerated/lib.php
mod/feedback/item/numeric/lib.php
mod/feedback/lib.php
mod/feedback/mapcourse.php
mod/feedback/show_nonrespondents.php
mod/forum/lib.php
mod/forum/post.php
mod/glossary/editcategories.html
mod/glossary/formats.php
mod/glossary/lib.php
mod/lesson/lib.php
mod/lesson/pagetypes/matching.php
mod/lesson/pagetypes/truefalse.php
mod/lesson/report.php
mod/quiz/attemptlib.php
mod/quiz/backup/moodle1/lib.php
mod/quiz/db/install.xml
mod/quiz/db/upgrade.php
mod/quiz/lib.php
mod/quiz/processattempt.php
mod/quiz/renderer.php
mod/quiz/report/responses/responses_table.php
mod/quiz/version.php
mod/scorm/loadSCO.php
mod/wiki/backup/moodle1/lib.php
mod/wiki/db/upgrade.php
mod/wiki/editors/wikieditor.php
mod/wiki/lang/en/wiki.php
mod/wiki/lib.php
mod/wiki/pagelib.php
mod/wiki/parser/markups/wikimarkup.php
mod/wiki/version.php
phpunit.xml.dist
question/edit.php
question/editlib.php
question/engine/bank.php
question/engine/datalib.php
question/engine/questionusage.php
question/format.php
question/format/blackboard/format.php
question/format/blackboard/lang/en/qformat_blackboard.php
question/format/blackboard/tests/blackboardformat_test.php [new file with mode: 0644]
question/format/blackboard/tests/fixtures/sample_blackboard.dat [new file with mode: 0644]
question/format/blackboard/version.php
question/format/examview/format.php
question/format/examview/lang/en/qformat_examview.php
question/format/examview/tests/examviewformat_test.php [new file with mode: 0644]
question/format/examview/tests/fixtures/examview_sample.xml [new file with mode: 0644]
question/format/examview/version.php
question/preview.php
question/previewlib.php
report/stats/locallib.php
repository/filepicker.js
repository/filepicker.php
repository/filesystem/lib.php
repository/lib.php
repository/repository_ajax.php
repository/webdav/lib.php
tag/edit.php
tag/lib.php
tag/locallib.php
theme/base/style/core.css
theme/formal_white/style/pagelayout.css
theme/fusion/style/pagelayout.css
theme/mymobile/layout/general.php
user/addnote.php
user/groupaddnote.php
user/index.php
user/message.html
user/selector/lib.php
version.php
webservice/lib.php

index d57233f..93703c8 100644 (file)
@@ -1042,12 +1042,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;
index 968f025..3ac05f9 100644 (file)
@@ -166,7 +166,8 @@ function print_report_tree($contextid, $contexts, $systemcontext, $fullname) {
     $context = context::instance_by_id($contextid);
 
     // Print the context name.
-    echo $OUTPUT->heading($context->get_context_name(), 4, 'contextname');
+    echo $OUTPUT->heading(html_writer::link($context->get_url(), $context->get_context_name()),
+            4, 'contextname');
 
     // If there are any role assignments here, print them.
     foreach ($contexts[$contextid]->roleassignments as $ra) {
@@ -189,7 +190,7 @@ function print_report_tree($contextid, $contexts, $systemcontext, $fullname) {
             }
             $a = new stdClass;
             $a->fullname = $fullname;
-            $a->contextlevel = get_contextlevel_name($context->contextlevel);
+            $a->contextlevel = $context->get_level_name();
             if ($context->contextlevel == CONTEXT_SYSTEM) {
                 $strgoto = get_string('gotoassignsystemroles', 'role');
                 $strcheck = get_string('checksystempermissionsfor', 'role', $a);
index b0272b8..9995af3 100644 (file)
@@ -185,6 +185,7 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $setting = new admin_setting_configcheckbox('cachejs', new lang_string('cachejs', 'admin'), new lang_string('cachejs_help', 'admin'), 1);
     $setting->set_updatedcallback('js_reset_all_caches');
     $temp->add($setting);
+    $temp->add(new admin_setting_configcheckbox('modchooserdefault', new lang_string('modchooserdefault', 'admin'), new lang_string('configmodchooserdefault', 'admin'), 1));
     $ADMIN->add('appearance', $temp);
 
     // link to tag management interface
index 92722eb..f07107c 100644 (file)
@@ -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');
index 3d3ccb8..9f74d51 100644 (file)
@@ -14,7 +14,8 @@ 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('googlemapkey', new lang_string('googlemapkey', 'admin'), new lang_string('configgooglemapkey', 'admin', $CFG->wwwroot), '', PARAM_RAW, 60));
+    $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+)*)?$/'));
 
index 2986d64..5afd3ba 100644 (file)
@@ -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);
 
index ec692e2..380db45 100644 (file)
@@ -857,6 +857,8 @@ if ($formdata = $mform2->is_cancelled()) {
                         if ($duration > 0) { // sanity check
                             $timeend = $today + $duration;
                         }
+                    } else if ($manualcache[$courseid]->enrolperiod > 0) {
+                        $timeend = $today + $manualcache[$courseid]->enrolperiod;
                     }
 
                     $manual->enrol_user($manualcache[$courseid], $user->id, $rid, $today, $timeend);
index 0051aaf..e50c09e 100644 (file)
@@ -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 {
index bc90ebc..88bb10d 100644 (file)
@@ -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 {
index e3df78a..29cb59a 100644 (file)
@@ -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  {
index 6917b33..5838a8e 100644 (file)
@@ -293,30 +293,46 @@ abstract class restore_activity_task extends restore_task {
         // Define activity_userinfo. Dependent of:
         // - users root setting
         // - section_userinfo setting (if exists)
-        // - activity_included setting
+        // - activity_included setting.
         $settingname = $settingprefix . 'userinfo';
-        $selectvalues = array(0=>get_string('no')); // Safer options
-        $defaultvalue = false;                      // Safer default
+        $defaultvalue = false;
         if (isset($this->info->settings[$settingname]) && $this->info->settings[$settingname]) { // Only enabled when available
-            $selectvalues = array(1=>get_string('yes'), 0=>get_string('no'));
             $defaultvalue = true;
         }
+
         $activity_userinfo = new restore_activity_userinfo_setting($settingname, base_setting::IS_BOOLEAN, $defaultvalue);
-        $activity_userinfo->set_ui(new backup_setting_ui_select($activity_userinfo, get_string('includeuserinfo','backup'), $selectvalues));
+        if (!$defaultvalue) {
+            // This is a bit hacky, but if there is no user data to restore, then
+            // we replace the standard check-box with a select menu with the
+            // single choice 'No', and the select menu is clever enough that if
+            // there is only one choice, it just displays a static string.
+            //
+            // It would probably be better design to have a special UI class
+            // setting_ui_checkbox_or_no, rather than this hack, but I am not
+            // going to do that today.
+            $activity_userinfo->set_ui(new backup_setting_ui_select($activity_userinfo, '-',
+                    array(0 => get_string('no'))));
+        } else {
+            $activity_userinfo->get_ui()->set_label('-');
+        }
+
         $this->add_setting($activity_userinfo);
+
         // Look for "users" root setting
         $users = $this->plan->get_setting('users');
         $users->add_dependency($activity_userinfo);
+
         // Look for "section_userinfo" section setting (if exists)
         $settingname = 'section_' . $this->info->sectionid . '_userinfo';
         if ($this->plan->setting_exists($settingname)) {
             $section_userinfo = $this->plan->get_setting($settingname);
             $section_userinfo->add_dependency($activity_userinfo);
         }
-        // Look for "activity_included" setting
+
+        // Look for "activity_included" setting.
         $activity_included->add_dependency($activity_userinfo);
 
-        // End of common activity settings, let's add the particular ones
+        // End of common activity settings, let's add the particular ones.
         $this->define_my_settings();
     }
 
index 9d48778..9112ea7 100644 (file)
@@ -169,21 +169,36 @@ class restore_section_task extends restore_task {
 
         // Define section_userinfo. Dependent of:
         // - users root setting
-        // - section_included setting
+        // - section_included setting.
         $settingname = $settingprefix . 'userinfo';
-        $selectvalues = array(0=>get_string('no')); // Safer options
-        $defaultvalue = false;                      // Safer default
+        $defaultvalue = false;
         if (isset($this->info->settings[$settingname]) && $this->info->settings[$settingname]) { // Only enabled when available
-            $selectvalues = array(1=>get_string('yes'), 0=>get_string('no'));
             $defaultvalue = true;
         }
+
         $section_userinfo = new restore_section_userinfo_setting($settingname, base_setting::IS_BOOLEAN, $defaultvalue);
-        $section_userinfo->set_ui(new backup_setting_ui_select($section_userinfo, get_string('includeuserinfo','backup'), $selectvalues));
+        if (!$defaultvalue) {
+            // This is a bit hacky, but if there is no user data to restore, then
+            // we replace the standard check-box with a select menu with the
+            // single choice 'No', and the select menu is clever enough that if
+            // there is only one choice, it just displays a static string.
+            //
+            // It would probably be better design to have a special UI class
+            // setting_ui_checkbox_or_no, rather than this hack, but I am not
+            // going to do that today.
+            $section_userinfo->set_ui(new backup_setting_ui_select($section_userinfo, get_string('includeuserinfo','backup'),
+                    array(0 => get_string('no'))));
+        } else {
+            $section_userinfo->get_ui()->set_label(get_string('includeuserinfo','backup'));
+        }
+
         $this->add_setting($section_userinfo);
-        // Look for "users" root setting
+
+        // Look for "users" root setting.
         $users = $this->plan->get_setting('users');
         $users->add_dependency($section_userinfo);
-        // Look for "section_included" section setting
+
+        // Look for "section_included" section setting.
         $section_included->add_dependency($section_userinfo);
     }
 }
index 2ce9434..30714ed 100644 (file)
@@ -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;
         }
index 00db13d..b6d81ab 100644 (file)
@@ -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
@@ -508,7 +530,18 @@ abstract class backup_cron_automated_helper {
         if (!empty($dir) && ($storage == 1 || $storage == 2)) {
             // Calculate backup filename regex, ignoring the date/time/info parts that can be
             // variable, depending of languages, formats and automated backup settings
-            $filename = $backupword . '-' . backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-' .$course->id . '-';
+
+
+            // MDL-33531: use different filenames depending on backup_shortname option
+            if ( !empty($config->backup_shortname) ) {
+                $context = get_context_instance(CONTEXT_COURSE, $course->id);
+                $courseref = format_string($course->shortname, true, array('context' => $context));
+                $courseref = str_replace(' ', '_', $courseref);
+                $courseref = textlib::strtolower(trim(clean_filename($courseref), '_'));
+            } else {
+                $courseref = $course->id;
+            }
+            $filename = $backupword . '-' . backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-' .$courseref . '-';
             $regex = '#^'.preg_quote($filename, '#').'.*\.mbz$#';
 
             // Store all the matching files into fullpath => timemodified array
diff --git a/backup/util/helper/tests/cronhelper_test.php b/backup/util/helper/tests/cronhelper_test.php
new file mode 100644 (file)
index 0000000..7afd1be
--- /dev/null
@@ -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));
+
+    }
+}
index e0ffc0f..ce95e7a 100644 (file)
@@ -96,7 +96,6 @@ abstract class base_moodleform extends moodleform {
      * @global moodle_page $PAGE
      */
     function definition_after_data() {
-        global $PAGE;
         $buttonarray=array();
         $buttonarray[] = $this->_form->createElement('submit', 'submitbutton', get_string($this->uistage->get_ui()->get_name().'stage'.$this->uistage->get_stage().'action', 'backup'), array('class'=>'proceedbutton'));
         if (!$this->uistage->is_first_stage()) {
@@ -106,13 +105,9 @@ abstract class base_moodleform extends moodleform {
         $this->_form->addGroup($buttonarray, 'buttonar', '', array(' '), false);
         $this->_form->closeHeaderBefore('buttonar');
 
-        $config = new stdClass;
-        $config->title = get_string('confirmcancel', 'backup');
-        $config->question = get_string('confirmcancelquestion', 'backup');
-        $config->yesLabel = get_string('confirmcancelyes', 'backup');
-        $config->noLabel = get_string('confirmcancelno', 'backup');
-        $PAGE->requires->yui_module('moodle-backup-confirmcancel', 'M.core_backup.watch_cancel_buttons', array($config));
+        $this->_definition_finalized = true;
     }
+
     /**
      * Closes any open divs
      */
@@ -318,7 +313,20 @@ abstract class base_moodleform extends moodleform {
      * Displays the form
      */
     public function display() {
+        global $PAGE;
+
         $this->require_definition_after_data();
+
+        $config = new stdClass;
+        $config->title = get_string('confirmcancel', 'backup');
+        $config->question = get_string('confirmcancelquestion', 'backup');
+        $config->yesLabel = get_string('confirmcancelyes', 'backup');
+        $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();
     }
 
@@ -327,7 +335,6 @@ abstract class base_moodleform extends moodleform {
      */
     public function require_definition_after_data() {
         if (!$this->_definition_finalized) {
-            $this->_definition_finalized = true;
             $this->definition_after_data();
         }
     }
diff --git a/backup/util/ui/yui/backupselectall/backupselectall.js b/backup/util/ui/yui/backupselectall/backupselectall.js
new file mode 100644 (file)
index 0000000..9f05b7f
--- /dev/null
@@ -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']});
index c3071c4..35e3f2e 100644 (file)
@@ -71,7 +71,7 @@ class block_community extends block_list {
         $icon = html_writer::empty_tag('img', array('src' => $OUTPUT->pix_url('i/group'),
                     'class' => 'icon', 'alt' => get_string('addcourse', 'block_community')));
         $addcourseurl = new moodle_url('/blocks/community/communitycourse.php',
-                        array('add' => true, 'courseid' => $coursecontext->instanceid));
+                        array('add' => true, 'courseid' => $this->page->course->id));
         $searchlink = html_writer::tag('a', $icon . '&nbsp;' . get_string('addcourse', 'block_community'),
                         array('href' => $addcourseurl->out(false)));
         $this->content->items[] = $searchlink;
@@ -91,7 +91,7 @@ class block_community extends block_list {
                                     'alt' => get_string('removecommunitycourse', 'block_community')));
                 $deleteurl = new moodle_url('/blocks/community/communitycourse.php',
                                 array('remove' => true,
-                                    'courseid' => $coursecontext->instanceid,
+                                    'courseid' => $this->page->course->id,
                                     'communityid' => $course->id, 'sesskey' => sesskey()));
                 $deleteatag = html_writer::tag('a', $deleteicon, array('href' => $deleteurl));
 
index df79e3b..72b60cb 100644 (file)
@@ -64,7 +64,7 @@ class block_course_overview extends block_base {
             $courses_limit = $courses_limit + 1;
         }
 
-        $courses = enrol_get_my_courses('id, shortname, modinfo', 'visible DESC,sortorder ASC', $courses_limit);
+        $courses = enrol_get_my_courses('id, shortname, modinfo, sectioncache', 'visible DESC,sortorder ASC', $courses_limit);
         $site = get_site();
         $course = $site; //just in case we need the old global $course hack
 
index 6fcd152..24e9ca0 100644 (file)
@@ -106,7 +106,7 @@ class block_news_items extends block_base {
                     $tooltiptext = get_string('rsssubscriberssposts','forum');
                 }
                 if (!isloggedin()) {
-                    $userid = 0;
+                    $userid = $CFG->siteguest;
                 } else {
                     $userid = $USER->id;
                 }
index 7ce4a4a..6183a9b 100644 (file)
@@ -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');
index 55ce91d..1def593 100644 (file)
@@ -71,24 +71,28 @@ class blog_edit_form extends moodleform {
         $allmodnames = array();
 
         if (!empty($CFG->useblogassociations)) {
-            if ((!empty($entry->courseassoc) || (!empty($courseid) && empty($modid))) && has_capability('moodle/blog:associatecourse', $sitecontext)) {
+            if ((!empty($entry->courseassoc) || (!empty($courseid) && empty($modid)))) {
                 if (!empty($courseid)) {
                     $course = $DB->get_record('course', array('id' => $courseid));
-                    $mform->addElement('header', 'assochdr', get_string('associations', 'blog'));
-                    $context = get_context_instance(CONTEXT_COURSE, $courseid);
+                    $context = context_course::instance($courseid);
                     $a = new stdClass();
                     $a->coursename = format_string($course->fullname, true, array('context' => $context));
                     $contextid = $context->id;
                 } else {
+                    $context = context::instance_by_id($entry->courseassoc);
                     $sql = 'SELECT fullname FROM {course} cr LEFT JOIN {context} ct ON ct.instanceid = cr.id WHERE ct.id = ?';
                     $a = new stdClass();
                     $a->coursename = $DB->get_field_sql($sql, array($entry->courseassoc));
                     $contextid = $entry->courseassoc;
                 }
 
-                $mform->addElement('advcheckbox', 'courseassoc', get_string('associatewithcourse', 'blog', $a), null, null, array(0, $contextid));
-                $mform->setDefault('courseassoc', $contextid);
-            } else if ((!empty($entry->modassoc) || !empty($modid)) && has_capability('moodle/blog:associatemodule', $sitecontext)) {
+                if (has_capability('moodle/blog:associatecourse', $context)) {
+                    $mform->addElement('header', 'assochdr', get_string('associations', 'blog'));\r
+                    $mform->addElement('advcheckbox', 'courseassoc', get_string('associatewithcourse', 'blog', $a), null, null, array(0, $contextid));\r
+                    $mform->setDefault('courseassoc', $contextid);
+                }
+
+            } else if ((!empty($entry->modassoc) || !empty($modid))) {
                 if (!empty($modid)) {
                     $mod = get_coursemodule_from_id(false, $modid);
                     $a = new stdClass();
@@ -104,9 +108,11 @@ class blog_edit_form extends moodleform {
                     $modid = $context->instanceid;
                 }
 
-                $mform->addElement('header', 'assochdr', get_string('associations', 'blog'));
-                $mform->addElement('advcheckbox', 'modassoc', get_string('associatewithmodule', 'blog', $a), null, null, array(0, $context->id));
-                $mform->setDefault('modassoc', $context->id);
+                if (has_capability('moodle/blog:associatemodule', $context)) {
+                    $mform->addElement('header', 'assochdr', get_string('associations', 'blog'));
+                    $mform->addElement('advcheckbox', 'modassoc', get_string('associatewithmodule', 'blog', $a), null, null, array(0, $context->id));
+                    $mform->setDefault('modassoc', $context->id);
+                }
             }
         }
 
@@ -132,13 +138,13 @@ class blog_edit_form extends moodleform {
         global $CFG, $DB, $USER;
 
         $errors = array();
-        $sitecontext = get_context_instance(CONTEXT_SYSTEM);
 
         // validate course association
-        if (!empty($data['courseassoc']) && has_capability('moodle/blog:associatecourse', $sitecontext)) {
+        if (!empty($data['courseassoc'])) {
             $coursecontext = context::instance_by_id($data['courseassoc'], IGNORE_MISSING);
 
-            if ($coursecontext and $coursecontext->contextlevel == CONTEXT_COURSE)  {
+            $canassociatecourse = has_capability('moodle/blog:associatecourse', $coursecontext);
+            if ($coursecontext->contextlevel == CONTEXT_COURSE && $canassociatecourse) {
                 if (!is_enrolled($coursecontext) and !is_viewing($coursecontext)) {
                     $errors['courseassoc'] = get_string('studentnotallowed', '', fullname($USER, true));
                 }
@@ -152,7 +158,8 @@ class blog_edit_form extends moodleform {
             $modcontextid = $data['modassoc'];
             $modcontext = context::instance_by_id($modcontextid, IGNORE_MISSING);
 
-            if ($modcontext and $modcontext->contextlevel == CONTEXT_MODULE) {
+            $canassociatemodule = has_capability('moodle/blog:associatecourse', $modcontext);
+            if ($modcontext->contextlevel == CONTEXT_MODULE && $canassociatemodule) {
                 // get context of the mod's course
                 $coursecontext = $modcontext->get_course_context(true);
 
index 8a3b687..fc087b9 100644 (file)
@@ -510,8 +510,9 @@ function blog_get_options_for_course(stdClass $course, stdClass $user=null) {
     }
 
     // Check that the user can associate with the course
-    $sitecontext =      get_context_instance(CONTEXT_SYSTEM);
-    if (!has_capability('moodle/blog:associatecourse', $sitecontext)) {
+    $sitecontext = context_system::instance();
+    $coursecontext = context_course::instance($course->id);
+    if (!has_capability('moodle/blog:associatecourse', $coursecontext)) {
         return $options;
     }
     // Generate the cache key
@@ -526,7 +527,6 @@ function blog_get_options_for_course(stdClass $course, stdClass $user=null) {
         return $courseoptions[$key];
     }
 
-    $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
     $canparticipate = (is_enrolled($coursecontext) or is_viewing($coursecontext));
 
     if (has_capability('moodle/blog:view', $coursecontext)) {
@@ -587,8 +587,9 @@ function blog_get_options_for_module($module, $user=null) {
     }
 
     // Check the user can associate with the module
-    $sitecontext =      get_context_instance(CONTEXT_SYSTEM);
-    if (!has_capability('moodle/blog:associatemodule', $sitecontext)) {
+    $modcontext = context_module::instance($module->id);
+    $sitecontext = context_system::instance();
+    if (!has_capability('moodle/blog:associatemodule', $modcontext)) {
         return $options;
     }
 
@@ -604,7 +605,6 @@ function blog_get_options_for_module($module, $user=null) {
         return $moduleoptions[$module->id];
     }
 
-    $modcontext = get_context_instance(CONTEXT_MODULE, $module->id);
     $canparticipate = (is_enrolled($modcontext) or is_viewing($modcontext));
 
     if (has_capability('moodle/blog:view', $modcontext)) {
@@ -743,7 +743,9 @@ function blog_get_headers($courseid=null, $groupid=null, $userid=null, $tagid=nu
 
     $PAGE->set_pagelayout('standard');
 
-    if (!empty($modid) && $CFG->useblogassociations && has_capability('moodle/blog:associatemodule', $sitecontext)) { // modid always overrides courseid, so the $course object may be reset here
+    // modid always overrides courseid, so the $course object may be reset here
+    if (!empty($modid) && $CFG->useblogassociations) {
+
         $headers['filters']['module'] = $modid;
         // A groupid param may conflict with this coursemod's courseid. Ignore groupid in that case
         $courseid = $DB->get_field('course_modules', 'course', array('id'=>$modid));
index dd3d4bd..75380f3 100644 (file)
@@ -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";
index f53c33d..c758cac 100644 (file)
@@ -205,9 +205,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
             return array();
         }
 
-        if (!has_capability('moodle/course:update', context_course::instance($course->id))) {
-            return array();
-        }
+        $coursecontext = context_course::instance($course->id);
 
         if ($onsectionpage) {
             $baseurl = course_get_url($course, $section->section);
@@ -219,23 +217,25 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         $controls = array();
 
         $url = clone($baseurl);
-        if ($section->visible) { // Show the hide/show eye.
-            $strhidefromothers = get_string('hidefromothers', 'format_'.$course->format);
-            $url->param('hide', $section->section);
-            $controls[] = html_writer::link($url,
-                html_writer::empty_tag('img', array('src' => $this->output->pix_url('i/hide'),
-                'class' => 'icon hide', 'alt' => $strhidefromothers)),
-                array('title' => $strhidefromothers, 'class' => 'editing_showhide'));
-        } else {
-            $strshowfromothers = get_string('showfromothers', 'format_'.$course->format);
-            $url->param('show',  $section->section);
-            $controls[] = html_writer::link($url,
-                html_writer::empty_tag('img', array('src' => $this->output->pix_url('i/show'),
-                'class' => 'icon hide', 'alt' => $strshowfromothers)),
-                array('title' => $strshowfromothers, 'class' => 'editing_showhide'));
+        if (has_capability('moodle/course:sectionvisibility', $coursecontext)) {
+            if ($section->visible) { // Show the hide/show eye.
+                $strhidefromothers = get_string('hidefromothers', 'format_'.$course->format);
+                $url->param('hide', $section->section);
+                $controls[] = html_writer::link($url,
+                    html_writer::empty_tag('img', array('src' => $this->output->pix_url('i/hide'),
+                    'class' => 'icon hide', 'alt' => $strhidefromothers)),
+                    array('title' => $strhidefromothers, 'class' => 'editing_showhide'));
+            } else {
+                $strshowfromothers = get_string('showfromothers', 'format_'.$course->format);
+                $url->param('show',  $section->section);
+                $controls[] = html_writer::link($url,
+                    html_writer::empty_tag('img', array('src' => $this->output->pix_url('i/show'),
+                    'class' => 'icon hide', 'alt' => $strshowfromothers)),
+                    array('title' => $strshowfromothers, 'class' => 'editing_showhide'));
+            }
         }
 
-        if (!$onsectionpage) {
+        if (!$onsectionpage && has_capability('moodle/course:update', $coursecontext)) {
             $url = clone($baseurl);
             if ($section->section > 1) { // Add a arrow to move section up.
                 $url->param('section', $section->section);
index 0764ceb..51acd38 100644 (file)
@@ -46,7 +46,7 @@ if (($marker >=0) && has_capability('moodle/course:setcurrentsection', $context)
 
 $renderer = $PAGE->get_renderer('format_topics');
 
-if (!empty($displaysection) && $course->coursedisplay == COURSE_DISPLAY_MULTIPAGE) {
+if (!empty($displaysection)) {
     $renderer->print_single_section_page($course, $sections, $mods, $modnames, $modnamesused, $displaysection);
 } else {
     $renderer->print_multiple_section_page($course, $sections, $mods, $modnames, $modnamesused);
index 5ef126f..e293a9d 100644 (file)
@@ -74,9 +74,7 @@ class format_topics_renderer extends format_section_renderer_base {
             return array();
         }
 
-        if (!has_capability('moodle/course:update', context_course::instance($course->id))) {
-            return array();
-        }
+        $coursecontext = context_course::instance($course->id);
 
         if ($onsectionpage) {
             $url = course_get_url($course, $section->section);
@@ -86,18 +84,20 @@ class format_topics_renderer extends format_section_renderer_base {
         $url->param('sesskey', sesskey());
 
         $controls = array();
-        if ($course->marker == $section->section) {  // Show the "light globe" on/off.
-            $url->param('marker', 0);
-            $controls[] = html_writer::link($url,
-                                html_writer::empty_tag('img', array('src' => $this->output->pix_url('i/marked'),
-                                    'class' => 'icon ', 'alt' => get_string('markedthistopic'))),
-                                array('title' => get_string('markedthistopic'), 'class' => 'editing_highlight'));
-        } else {
-            $url->param('marker', $section->section);
-            $controls[] = html_writer::link($url,
-                            html_writer::empty_tag('img', array('src' => $this->output->pix_url('i/marker'),
-                                'class' => 'icon', 'alt' => get_string('markthistopic'))),
-                            array('title' => get_string('markthistopic'), 'class' => 'editing_highlight'));
+        if (has_capability('moodle/course:setcurrentsection', $coursecontext)) {
+            if ($course->marker == $section->section) {  // Show the "light globe" on/off.
+                $url->param('marker', 0);
+                $controls[] = html_writer::link($url,
+                                    html_writer::empty_tag('img', array('src' => $this->output->pix_url('i/marked'),
+                                        'class' => 'icon ', 'alt' => get_string('markedthistopic'))),
+                                    array('title' => get_string('markedthistopic'), 'class' => 'editing_highlight'));
+            } else {
+                $url->param('marker', $section->section);
+                $controls[] = html_writer::link($url,
+                                html_writer::empty_tag('img', array('src' => $this->output->pix_url('i/marker'),
+                                    'class' => 'icon', 'alt' => get_string('markthistopic'))),
+                                array('title' => get_string('markthistopic'), 'class' => 'editing_highlight'));
+            }
         }
 
         return array_merge($controls, parent::section_edit_controls($course, $section, $onsectionpage));
index 5d70cc5..2f0eb51 100644 (file)
@@ -39,7 +39,7 @@ if ($week = optional_param('week', 0, PARAM_INT)) {
 
 $renderer = $PAGE->get_renderer('format_weeks');
 
-if (!empty($displaysection) && $course->coursedisplay == COURSE_DISPLAY_MULTIPAGE) {
+if (!empty($displaysection)) {
     $renderer->print_single_section_page($course, $sections, $mods, $modnames, $modnamesused, $displaysection);
 } else {
     $renderer->print_multiple_section_page($course, $sections, $mods, $modnames, $modnamesused);
index 09559fd..ac7a957 100644 (file)
@@ -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':
@@ -1869,7 +1866,7 @@ function print_section_add_menus($course, $section, $modnames, $vertical=false,
         $modchooser.= html_writer::end_tag('div');
 
         // Wrap the normal output in a noscript div
-        $usemodchooser = get_user_preferences('usemodchooser', 1);
+        $usemodchooser = get_user_preferences('usemodchooser', $CFG->modchooserdefault);
         if ($usemodchooser) {
             $output = html_writer::tag('div', $output, array('class' => 'hiddenifjs addresourcedropdown'));
             $modchooser = html_writer::tag('div', $modchooser, array('class' => 'visibleifjs addresourcemodchooser'));
@@ -2255,22 +2252,6 @@ function make_categories_options() {
     return $cats;
 }
 
-/**
- * Gets the name of a course to be displayed when showing a list of courses.
- * By default this is just $course->fullname but user can configure it. The
- * result of this function should be passed through print_string.
- * @param object $course Moodle course object
- * @return string Display name of course (either fullname or short + fullname)
- */
-function get_course_display_name_for_list($course) {
-    global $CFG;
-    if (!empty($CFG->courselistshortnames)) {
-        return $course->shortname . ' ' .$course->fullname;
-    } else {
-        return $course->fullname;
-    }
-}
-
 /**
  * Prints the category info in indented fashion
  * This function is only used by print_whole_category_list() above
index b79e443..dc7fa19 100644 (file)
@@ -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();
 
index cb899f0..09fb15b 100644 (file)
@@ -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');
 
index 6efd228..61392ea 100644 (file)
@@ -75,7 +75,6 @@ switch($requestmethod) {
 
         switch ($class) {
             case 'section':
-                require_capability('moodle/course:update', $coursecontext);
 
                 if (!$DB->record_exists('course_sections', array('course'=>$course->id, 'section'=>$id))) {
                     throw new moodle_exception('AJAX commands.php: Bad Section ID '.$id);
@@ -83,11 +82,13 @@ switch($requestmethod) {
 
                 switch ($field) {
                     case 'visible':
+                        require_capability('moodle/course:sectionvisibility', $coursecontext);
                         $resourcestotoggle = set_section_visible($course->id, $id, $value);
                         echo json_encode(array('resourcestotoggle' => $resourcestotoggle));
                         break;
 
                     case 'move':
+                        require_capability('moodle/course:update', $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';
@@ -178,7 +179,7 @@ switch($requestmethod) {
             case 'course':
                 switch($field) {
                     case 'marker':
-                        require_capability('moodle/course:update', $coursecontext);
+                        require_capability('moodle/course:setcurrentsection', $coursecontext);
                         course_set_marker($course->id, $value);
                         break;
                 }
index ac3c4d9..08f8b4a 100644 (file)
@@ -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;
index ad495dd..cb978d8 100644 (file)
@@ -97,7 +97,7 @@
     $logparam = 'id='. $course->id;
     $loglabel = 'view';
     $infoid = $course->id;
-    if(!empty($section)) {
+    if ($section and $section > 0) {
         $loglabel = 'view section';
 
         // Get section details and check it exists.
             set_user_preference('usemodchooser', $modchooser);
         }
 
-        if (has_capability('moodle/course:update', $context)) {
+        if (has_capability('moodle/course:sectionvisibility', $context)) {
             if ($hide && confirm_sesskey()) {
                 set_section_visible($course->id, $hide, '0');
                 redirect($PAGE->url);
                 set_section_visible($course->id, $show, '1');
                 redirect($PAGE->url);
             }
+        }
 
+        if (has_capability('moodle/course:update', $context)) {
             if (!empty($section)) {
                 if (!empty($move) and confirm_sesskey()) {
                     $destsection = $section + $move;
index 058b1b0..9708408 100644 (file)
@@ -58,8 +58,10 @@ function authorize_print_orders($courseid, $userid) {
 
     $searchmenu = array('orderid' => $authstrs->orderid, 'transid' => $authstrs->transid, 'cclastfour' => $authstrs->cclastfour);
     $buttons = "<form method='post' action='index.php' autocomplete='off'><div>";
+    $buttons .= html_writer::label(get_string('orderdetails', 'enrol_authorize'), 'menusearchtype', false, array('class' => 'accesshide'));
     $buttons .= html_writer::select($searchmenu, 'searchtype', $searchtype, false);
-    $buttons .= "<input type='text' size='16' name='searchquery' value='' />";
+    $buttons .= html_writer::label(get_string('search'), 'searchquery', false, array('class' => 'accesshide'));
+    $buttons .= "<input id='searchquery' type='text' size='16' name='searchquery' value='' />";
     $buttons .= "<input type='submit' value='$strs->search' />";
     $buttons .= "</div></form>";
 
index 47b1e37..7db80b8 100644 (file)
@@ -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
  * 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 @@ define('CLI_SCRIPT', true);
 
 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);
index 1a9ead0..7feea4d 100644 (file)
@@ -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,
index 66194df..3a67866 100644 (file)
 /**
  * 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();
index 0de987f..834aa67 100644 (file)
@@ -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
  */
index 5a8bee3..417b28d 100644 (file)
@@ -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
 // 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
  */
index ba60ba0..e6525e3 100644 (file)
@@ -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
 /**
  * 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 @@ class enrol_category_plugin extends enrol_plugin {
         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 @@ 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);
     }
 }
-
-
index 6a7671b..c9687fa 100644 (file)
@@ -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
 /**
  * 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.
  *
@@ -33,6 +32,12 @@ defined('MOODLE_INTERNAL') || die();
  * it may fail sometimes, so we always do a full sync in cron too.
  */
 class enrol_category_handler {
+    /**
+     * Triggered when user is assigned a new role.
+     * @static
+     * @param stdClass $ra
+     * @return bool
+     */
     public static function role_assigned($ra) {
         global $DB;
 
@@ -40,20 +45,20 @@ 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;
         }
 
-        // 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 = get_context_instance(CONTEXT_SYSTEM);
+        // 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,14 +102,14 @@ 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
-        $syscontext = get_context_instance(CONTEXT_SYSTEM);
+        // 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;
         }
@@ -117,9 +128,9 @@ class enrol_category_handler {
         $params['userid'] = $ra->userid;
 
         foreach ($rs as $instance) {
-            $coursecontext = get_context_instance(CONTEXT_COURSE, $instance->courseid);
+            $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) {
@@ -152,11 +163,11 @@ function enrol_category_sync_course($course) {
 
     $plugin = enrol_get_plugin('category');
 
-    $syscontext = get_context_instance(CONTEXT_SYSTEM);
+    $syscontext = context_system::instance();
     $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
-    $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
+    // 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,71 @@ 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 = get_context_instance(CONTEXT_SYSTEM);
+    $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 = array();
+    foreach($roles as $role) {
+        $rolenames[$role->id] = $role->shortname;
+    }
+    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 +324,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 +341,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 +360,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;
 }
index a854267..484c23b 100644 (file)
@@ -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
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * 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 (file)
index 0000000..3649764
--- /dev/null
@@ -0,0 +1,365 @@
+<?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/>.
+
+/**
+ * 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')));
+    }
+}
index 371a7f0..23c33cf 100644 (file)
@@ -25,7 +25,7 @@
 
 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;
index 2009867..cfa352c 100644 (file)
@@ -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.
     }
 
     /**
index 284608a..073e76a 100644 (file)
@@ -321,6 +321,9 @@ function process_group_tag($tagcontents) {
     if (preg_match('{<description>.*?<short>(.*?)</short>.*?</description>}is', $tagcontents, $matches)) {
         $group->shortName = trim($matches[1]);
     }
+    if (preg_match('{<description>.*?<full>(.*?)</full>.*?</description>}is', $tagcontents, $matches)) {
+        $group->fulldescription = trim($matches[1]);
+    }
     if (preg_match('{<org>.*?<orgunit>(.*?)</orgunit>.*?</org>}is', $tagcontents, $matches)) {
         $group->category = trim($matches[1]);
     }
@@ -376,7 +379,10 @@ function process_group_tag($tagcontents) {
                     $courseconfig = get_config('moodlecourse'); // Load Moodle Course shell defaults
                     $course = new stdClass();
                     $course->fullname = $group->description;
-                    $course->shortname = $group->shortName;;
+                    $course->shortname = $group->shortName;
+                    if (!empty($group->fulldescription)) {
+                        $course->summary = format_text($group->fulldescription, FORMAT_HTML);
+                    }
                     $course->idnumber = $coursecode;
                     $course->format = $courseconfig->format;
                     $course->visible = $courseconfig->visible;
index f7d5030..41b7337 100644 (file)
@@ -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;
index 9c3a5d6..e9eac64 100644 (file)
@@ -215,6 +215,7 @@ class enrol_manual_plugin extends enrol_plugin {
         $today = time();
         $today = make_timestamp(date('Y', $today), date('m', $today), date('d', $today), 0, 0, 0);
         $startdateoptions[3] = get_string('today') . ' (' . userdate($today, $timeformat) . ')' ;
+        $defaultduration = $instance->enrolperiod > 0 ? $instance->enrolperiod / 86400 : '';
 
         $modules = array('moodle-enrol_manual-quickenrolment', 'moodle-enrol_manual-quickenrolment-skin');
         $arguments = array(
@@ -224,6 +225,7 @@ class enrol_manual_plugin extends enrol_plugin {
             'url'                 => $manager->get_moodlepage()->url->out(false),
             'optionsStartDate'    => $startdateoptions,
             'defaultRole'         => $instance->roleid,
+            'defaultDuration'     => $defaultduration,
             'disableGradeHistory' => $CFG->disablegradehistory,
             'recoverGradesDefault'=> ''
         );
index 3255882..1d09681 100644 (file)
@@ -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()) {
index 94fdc63..f7ea7e2 100644 (file)
@@ -162,13 +162,13 @@ echo $OUTPUT->heading($instancename);
 
               <div class="enroloptions">
 
-              <p><label for="roleid"><?php print_string('assignrole', 'enrol_manual') ?></label><br />
+              <p><label for="menuroleid"><?php print_string('assignrole', 'enrol_manual') ?></label><br />
               <?php echo html_writer::select($roles, 'roleid', $roleid, false); ?></p>
 
-              <p><label for="extendperiod"><?php print_string('enrolperiod', 'enrol') ?></label><br />
+              <p><label for="menuextendperiod"><?php print_string('enrolperiod', 'enrol') ?></label><br />
               <?php echo html_writer::select($periodmenu, 'extendperiod', $defaultperiod, $unlimitedperiod); ?></p>
 
-              <p><label for="extendbase"><?php print_string('startingfrom') ?></label><br />
+              <p><label for="menuextendbase"><?php print_string('startingfrom') ?></label><br />
               <?php echo html_writer::select($basemenu, 'extendbase', $extendbase, false); ?></p>
 
               </div>
index 5919d69..8b1461c 100644 (file)
@@ -105,6 +105,7 @@ class enrol_self_edit_form extends moodleform {
         $mform->addHelpButton('customint4', 'sendcoursewelcomemessage', 'enrol_self');
 
         $mform->addElement('textarea', 'customtext1', get_string('customwelcomemessage', 'enrol_self'), array('cols'=>'60', 'rows'=>'8'));
+        $mform->addHelpButton('customtext1', 'customwelcomemessage', 'enrol_self');
 
         $mform->addElement('hidden', 'id');
         $mform->setType('id', PARAM_INT);
@@ -176,4 +177,4 @@ class enrol_self_edit_form extends moodleform {
         }
         return $roles;
     }
-}
\ No newline at end of file
+}
index af2c54c..5c96f78 100644 (file)
  */
 
 $string['customwelcomemessage'] = 'Custom welcome message';
+$string['customwelcomemessage_help'] = 'A custom welcome message may be added as plain text or Moodle-auto format, including HTML tags and multi-lang tags.
+
+The following placeholders may be included in the message:
+
+* Course name {$a->coursename}
+* Link to user\'s profile page {$a->profileurl}';
 $string['defaultrole'] = 'Default role assignment';
 $string['defaultrole_desc'] = 'Select role which should be assigned to users during self enrolment';
 $string['editenrolment'] = 'Edit enrolment';
index 32c9d48..771ec76 100644 (file)
@@ -74,7 +74,7 @@ class enrol_self_plugin extends enrol_plugin {
 
         if (empty($instance->name)) {
             if (!empty($instance->roleid) and $role = $DB->get_record('role', array('id'=>$instance->roleid))) {
-                $role = ' (' . role_get_name($role, get_context_instance(CONTEXT_COURSE, $instance->courseid)) . ')';
+                $role = ' (' . role_get_name($role, context_course::instance($instance->courseid, IGNORE_MISSING)) . ')';
             } else {
                 $role = '';
             }
@@ -115,7 +115,7 @@ class enrol_self_plugin extends enrol_plugin {
              throw new coding_exception('Invalid enrol instance type!');
         }
 
-        $context = get_context_instance(CONTEXT_COURSE, $instance->courseid);
+        $context = context_course::instance($instance->courseid);
         if (has_capability('enrol/self:config', $context)) {
             $managelink = new moodle_url('/enrol/self/edit.php', array('courseid'=>$instance->courseid, 'id'=>$instance->id));
             $instancesnode->add($this->get_instance_name($instance), $managelink, navigation_node::TYPE_SETTING);
@@ -133,7 +133,7 @@ class enrol_self_plugin extends enrol_plugin {
         if ($instance->enrol !== 'self') {
             throw new coding_exception('invalid enrol instance!');
         }
-        $context = get_context_instance(CONTEXT_COURSE, $instance->courseid);
+        $context = context_course::instance($instance->courseid);
 
         $icons = array();
 
@@ -151,7 +151,7 @@ class enrol_self_plugin extends enrol_plugin {
      * @return moodle_url page url
      */
     public function get_newinstance_link($courseid) {
-        $context = get_context_instance(CONTEXT_COURSE, $courseid, MUST_EXIST);
+        $context = context_course::instance($courseid, MUST_EXIST);
 
         if (!has_capability('moodle/course:enrolconfig', $context) or !has_capability('enrol/self:config', $context)) {
             return NULL;
@@ -267,22 +267,32 @@ class enrol_self_plugin extends enrol_plugin {
         global $CFG, $DB;
 
         $course = $DB->get_record('course', array('id'=>$instance->courseid), '*', MUST_EXIST);
+        $context = context_course::instance($course->id);
 
         $a = new stdClass();
-        $a->coursename = format_string($course->fullname);
+        $a->coursename = format_string($course->fullname, true, array('context'=>$context));
         $a->profileurl = "$CFG->wwwroot/user/view.php?id=$user->id&course=$course->id";
 
         if (trim($instance->customtext1) !== '') {
             $message = $instance->customtext1;
             $message = str_replace('{$a->coursename}', $a->coursename, $message);
             $message = str_replace('{$a->profileurl}', $a->profileurl, $message);
+            if (strpos($message, '<') === false) {
+                // Plain text only.
+                $messagetext = $message;
+                $messagehtml = text_to_html($messagetext, null, false, true);
+            } else {
+                // This is most probably the tag/newline soup known as FORMAT_MOODLE.
+                $messagehtml = format_text($message, FORMAT_MOODLE, array('context'=>$context, 'para'=>false, 'newlines'=>true, 'filter'=>true));
+                $messagetext = html_to_text($messagehtml);
+            }
         } else {
-            $message = get_string('welcometocoursetext', 'enrol_self', $a);
+            $messagetext = get_string('welcometocoursetext', 'enrol_self', $a);
+            $messagehtml = text_to_html($messagetext, null, false, true);
         }
 
-        $subject = get_string('welcometocourse', 'enrol_self', format_string($course->fullname));
+        $subject = get_string('welcometocourse', 'enrol_self', format_string($course->fullname, true, array('context'=>$context)));
 
-        $context = get_context_instance(CONTEXT_COURSE, $course->id);
         $rusers = array();
         if (!empty($CFG->coursecontact)) {
             $croles = explode(',', $CFG->coursecontact);
@@ -295,7 +305,7 @@ class enrol_self_plugin extends enrol_plugin {
         }
 
         //directly emailing welcome message rather than using messaging
-        email_to_user($user, $contact, $subject, $message);
+        email_to_user($user, $contact, $subject, $messagetext, $messagehtml);
     }
 
     /**
index 67b715c..bdf6ac4 100644 (file)
@@ -280,7 +280,8 @@ function slasharguments($texexp, $md5) {
           <form action="algebradebug.php" method="get"
            target="inlineframe">
             <center>
-             <input type="text" name="algebra" size="50"
+             <label for="algebra" class="accesshide"><?php print_string('algebraicexpression', 'filter_algebra'); ?></label>
+             <input type="text" id="algebra" name="algebra" size="50"
                     value="sin(z)/(x^2+y^2)" />
             </center>
            <ol>
index 5435f7f..6db42a1 100644 (file)
@@ -24,3 +24,4 @@
  */
 
 $string['filtername'] = 'Algebra notation';
+$string['algebraicexpression'] = 'Algebraic expression';
index 943c7fc..04893f1 100644 (file)
@@ -181,7 +181,10 @@ if (empty($availablefilters)) {
         } else {
             $activechoices[TEXTFILTER_INHERIT] = $strdefaultoff;
         }
-        $row[] = html_writer::select($activechoices, str_replace('/', '_', $filter), $filterinfo->localstate, false);
+        $filtername = str_replace('/', '_', $filter);
+        $select = html_writer::label($filterinfo->localstate, 'menu'. $filtername, false, array('class' => 'accesshide'));
+        $select .= html_writer::select($activechoices, $filtername, $filterinfo->localstate, false);
+        $row[] = $select;
 
         // Settings link, if required
         if ($settingscol) {
index d273289..596fd68 100644 (file)
@@ -96,7 +96,7 @@ if ($id) {
 
 if (!$courseid) {
     require_once $CFG->libdir.'/adminlib.php';
-    admin_externalpage_setup('scales');
+    admin_externalpage_setup('outcomes');
 }
 
 // default return url
index 89d5cd2..a18d0c1 100644 (file)
@@ -184,6 +184,7 @@ function get_grade_tree(&$gtree, $element, $current_itemid=null, $errors=null) {
                     $name .= '<div class="error"><span class="error">' . $errors[$grade_item->id].'</span><br />'."\n";
                     $closingdiv = "</div>\n";
                 }
+                $name .= '<label class="accesshide" for="id_idnumber_' . $grade_item->id . '">' . get_string('gradeitems', 'grades')  .'</label>';
                 $name .= '<input class="idnumber" id="id_idnumber_'.$grade_item->id.'" type="text" name="idnumbers['.$grade_item->id.']" />' . "\n";
                 $name .= $closingdiv;
             }
index 801c9ef..8208c45 100644 (file)
@@ -342,8 +342,8 @@ if (!$moving) {
 if (!$moving && count($grade_edit_tree->categories) > 1) {
     echo '<br /><br />';
     echo '<input type="hidden" name="bulkmove" value="0" id="bulkmoveinput" />';
-    echo get_string('moveselectedto', 'grades') . ' ';
     $attributes = array('id'=>'menumoveafter');
+    echo html_writer::label(get_string('moveselectedto', 'grades'), 'menumoveafter');
     echo html_writer::select($grade_edit_tree->categories, 'moveafter', '', array(''=>'choosedots'), $attributes);
     $OUTPUT->add_action_handler(new component_action('change', 'submit_bulk_move'), 'menumoveafter');
     echo '<div id="noscriptgradetreeform" class="hiddenifjs">
index eee8664..2bc2dc7 100644 (file)
@@ -673,7 +673,8 @@ class grade_edit_tree_column_aggregation extends grade_edit_tree_column_category
         } else {
             $attributes = array();
             $attributes['id'] = 'aggregation_'.$category->id;
-            $aggregation = html_writer::select($options, 'aggregation_'.$category->id, $category->aggregation, null, $attributes);
+            $aggregation = html_writer::label(get_string('aggregation', 'grades'), 'aggregation_'.$category->id, false, array('class' => 'accesshide'));
+            $aggregation .= html_writer::select($options, 'aggregation_'.$category->id, $category->aggregation, null, $attributes);
             $action = new component_action('change', 'update_category_aggregation', array('courseid' => $params['id'], 'category' => $category->id, 'sesskey' => sesskey()));
             $OUTPUT->add_action_handler($action, 'aggregation_'.$category->id);
         }
index 5672202..1990064 100644 (file)
@@ -339,7 +339,9 @@ class gradingform_guide_controller extends gradingform_controller {
                 $modulename = substr($modulename, 4);
                 if ($dbman->table_exists($modulename)) {
                     $cm = get_coursemodule_from_id($modulename, $context->instanceid);
-                    $this->moduleinstance = $DB->get_record($modulename, array("id"=>$cm->instance));
+                    if (!empty($cm)) { // This should only occur when the course is being deleted.
+                        $this->moduleinstance = $DB->get_record($modulename, array("id"=>$cm->instance));
+                    }
                 }
             }
         }
index 9469e94..c269c0a 100644 (file)
@@ -164,7 +164,8 @@ class gradingform_rubric_renderer extends plugin_renderer_base {
         $leveltemplate .= html_writer::start_tag('div', array('class' => 'level-wrapper'));
         if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FULL) {
             $definition = html_writer::tag('textarea', htmlspecialchars($level['definition']), array('name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][definition]', 'cols' => '10', 'rows' => '4'));
-            $score = html_writer::empty_tag('input', array('type' => 'text', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][score]', 'size' => '3', 'value' => $level['score']));
+            $score = html_writer::label(get_string('criterionempty', 'gradingform_rubric'), '{NAME}criteria{CRITERION-id}levels{LEVEL-id}', false, array('class' => 'accesshide'));
+            $score .= html_writer::empty_tag('input', array('type' => 'text','id' => '{NAME}criteria{CRITERION-id}levels{LEVEL-id}', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][score]', 'size' => '3', 'value' => $level['score']));
         } else {
             if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FROZEN) {
                 $leveltemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][definition]', 'value' => $level['definition']));
@@ -290,7 +291,7 @@ class gradingform_rubric_renderer extends plugin_renderer_base {
             switch ($option) {
                 case 'sortlevelsasc':
                     // Display option as dropdown
-                    $html .= html_writer::tag('span', get_string($option, 'gradingform_rubric'), array('class' => 'label'));
+                    $html .= html_writer::label(get_string($option, 'gradingform_rubric'), $attrs['id'], false, array('class' => 'label'));
                     $value = (int)(!!$value); // make sure $value is either 0 or 1
                     if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FULL) {
                         $selectoptions = array(0 => get_string($option.'0', 'gradingform_rubric'), 1 => get_string($option.'1', 'gradingform_rubric'));
@@ -448,7 +449,7 @@ class gradingform_rubric_renderer extends plugin_renderer_base {
     public function display_regrade_confirmation($elementname, $changelevel, $value) {
         $html = html_writer::start_tag('div', array('class' => 'gradingform_rubric-regrade'));
         if ($changelevel<=2) {
-            $html .= get_string('regrademessage1', 'gradingform_rubric');
+            $html .= html_writer::label(get_string('regrademessage1', 'gradingform_rubric'), 'menu' . $elementname . 'regrade');
             $selectoptions = array(
                 0 => get_string('regradeoption0', 'gradingform_rubric'),
                 1 => get_string('regradeoption1', 'gradingform_rubric')
index 9d1bda9..b166e54 100644 (file)
@@ -437,7 +437,7 @@ function print_grade_plugin_selector($plugin_info, $active_type, $active_plugin,
     // finally print/return the popup form
     if ($count > 1) {
         $select = new url_select($menu, $active, null, 'choosepluginreport');
-
+        $select->set_label(get_string('gradereport', 'grades'), array('class' => 'accesshide'));
         if ($return) {
             return $OUTPUT->render($select);
         } else {
index d5dafa4..ea8f076 100644 (file)
@@ -1016,7 +1016,8 @@ class grade_report_grader extends grade_report {
                                 $nogradestr = $this->get_lang_string('nooutcome', 'grades');
                             }
                             $attributes = array('tabindex' => $tabindices[$item->id]['grade'], 'id'=>'grade_'.$userid.'_'.$item->id);
-                            $itemcell->text .= html_writer::select($scaleopt, 'grade_'.$userid.'_'.$item->id, $gradeval, array(-1=>$nogradestr), $attributes);;
+                            $itemcell->text .= html_writer::label(get_string('typescale', 'grades'), $attributes['id'], false, array('class' => 'accesshide'));
+                            $itemcell->text .= html_writer::select($scaleopt, 'grade_'.$userid.'_'.$item->id, $gradeval, array(-1=>$nogradestr), $attributes);
                         } elseif(!empty($scale)) {
                             $scales = explode(",", $scale->scale);
 
index bf1040d..8918b2f 100644 (file)
@@ -68,7 +68,10 @@ $string['pathserrcreatedataroot'] = 'Datový adresář ({$a->dataroot}) nemůže
 $string['pathshead'] = 'Potvrdit cesty';
 $string['pathsrodataroot'] = 'Do datového adresáře nelze zapisovat.';
 $string['pathsroparentdataroot'] = 'Do nadřazeného adresáře ({$a->parent}) nelze zapisovat. Datový adresář ({$a->dataroot}) nemůže být tímto průvodcem instalací vytvořen.';
+$string['pathssubadmindir'] = 'Na některých serverech je URL adresa /admin vyhrazena pro speciální účely (např. pro ovládací panel). Na takových serverech může dojít ke kolizi se standardním umístěním stránek pro správu Moodle. Máte-li tento problém, přejmenujte adresář <eM>admin</em> ve vaší instalaci Moodle a sem zadejte jeho nový název - například <em>moodleadmin</em>. Všechny generované odkazy na stránky správy Moodle budou používat tento nový název.';
+$string['pathssubdataroot'] = 'Moodle potřebuje prostor, kam si bude ukládat nahrané soubory a další údaje. K tomuto adresáři musí mít proces webového serveru právo ke čtení i k zápisu (webový server bývá většinou spouštěn pod uživatelem "www-data" nebo "apache"). Tento adresář ale zároveň nesmí být dostupný přímo přes webové rozhraní. Instalační skript se pokusí tento adresář vytvořit, pokud nebude existovat.';
 $string['pathssubdirroot'] = 'Absolutní cesta k adresáři s instalací Moodle';
+$string['pathssubwwwroot'] = 'Zadejte úplnou webovou adresu, na níž bude Moodle dostupný. Moodle potřebuje jedinečnou adresu, není možné jej provozovat na několika URL současně. Používáte-li několik veřejných domén, musíte si sami nastavit permanentní přesměrování na jednu z nich a tu pak použít. Pokud je váš server dostupný z vnější a z vnitřní sítě pod různými IP adresami, použijte jeho veřejnou adresu a nastavte si váš DNS server tak, že ji mohou používat i uživatelé z vnitřní sítě.';
 $string['pathsunsecuredataroot'] = 'Umístění datového adresáře není bezpečné';
 $string['pathswrongadmindir'] = 'Adresář pro správu serveru (admin) neexistuje';
 $string['phpextension'] = '{$a} PHP rozšíření';
index 9bae23d..34600b2 100644 (file)
@@ -36,5 +36,8 @@ $string['cliincorrectvalueerror'] = 'Villa, ótækt gildi "{$a->value}" fyrir "{
 $string['cliincorrectvalueretry'] = 'Rangt gildi, vinsamlegast reyndu aftur';
 $string['clitypevalue'] = 'Sláðu inn gildi';
 $string['clitypevaluedefault'] = 'Sláðu inn gildi, sláðu á Enter hnappinn á lyklaborðinu til að nota sjálfgefið gildi ({$a})';
+$string['cliunknowoption'] = 'Óþekktir valkostir:
+  {$a}
+Vinsamlegast notaðu --help valkostinn.';
 $string['environmentrequireinstall'] = 'verður að vera uppsett og virkjað';
 $string['environmentrequireversion'] = 'krafist er útgáfu {$a->needed} en þú notast við útgáfu {$a->current}';
index f97528e..3c005d8 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
+$string['clianswerno'] = 'n';
+$string['cliansweryes'] = 't';
+$string['cliincorrectvalueerror'] = 'Klaida, klaidinga "{$a->option}" reikšmė "{$a->value}"';
+$string['cliincorrectvalueretry'] = 'Klaidinga reikšmė, bandykite dar kartą';
+$string['clitypevalue'] = 'tipo reikšmė';
+$string['clitypevaluedefault'] = 'tipo reikšmė, paspauskite „Enter“, jei norite naudoti numatytąją reikšmę ({$a})';
+$string['cliunknowoption'] = 'Neatpažintos parinktys: {$a} naudokite --žinyno parinktį.';
+$string['cliyesnoprompt'] = 'įveskite t (taip) arba n (ne)';
 $string['environmentrequireinstall'] = 'turi būti įdiegta ir įgalinta';
-$string['environmentrequireversion'] = 'reikalinga $a-needed, o Jūs turite $a-current';
+$string['environmentrequireversion'] = 'reikalinga {$a->needed} versija, o Jūs turite {$a->current}';
index abfff84..aa66eff 100644 (file)
@@ -31,6 +31,6 @@
 defined('MOODLE_INTERNAL') || die();
 
 $string['language'] = 'Kalba';
-$string['next'] = 'Toliau';
+$string['next'] = 'Pirmyn';
 $string['previous'] = 'Ankstesnis';
 $string['reload'] = 'Įkelti iš naujo';
index 5591fe5..705cb6c 100644 (file)
@@ -30,5 +30,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
+$string['parentlanguage'] = '';
 $string['thisdirection'] = 'ltr';
 $string['thislanguage'] = 'Nederlands';
index afb5383..5eff878 100644 (file)
@@ -80,7 +80,7 @@ $PAGE->set_title(get_string('iplookup', 'admin').': '.$title);
 $PAGE->set_heading($title);
 echo $OUTPUT->header();
 
-if (empty($CFG->googlemapkey)) {
+if (empty($CFG->googlemapkey) and empty($CFG->googlemapkey3)) {
     $imgwidth  = 620;
     $imgheight = 310;
     $dotwidth  = 18;
@@ -95,11 +95,23 @@ if (empty($CFG->googlemapkey)) {
     echo '</div>';
     echo '<div id="note">'.$info['note'].'</div>';
 
-} else {
+} else if (empty($CFG->googlemapkey3)) {
     $PAGE->requires->js(new moodle_url("http://maps.google.com/maps?file=api&v=2&key=$CFG->googlemapkey"));
     $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);
 
+    echo '<div id="map" style="width: 650px; height: 360px"></div>';
+    echo '<div id="note">'.$info['note'].'</div>';
+
+} else {
+    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.init3', array($info['latitude'], $info['longitude'], $ip), true, $module);
+
     echo '<div id="map" style="width: 650px; height: 360px"></div>';
     echo '<div id="note">'.$info['note'].'</div>';
 }
index dba71cc..004b7d2 100644 (file)
@@ -41,3 +41,21 @@ M.core_iplookup.init = function(Y, latitude, longitude) {
         }, document.body);
     }
 };
+
+M.core_iplookup.init3 = function(Y, latitude, longitude, ip) {
+    var ipLatlng = new google.maps.LatLng(latitude, longitude);
+
+    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
+    });
+};
index 30253fd..26fbe3b 100644 (file)
@@ -214,11 +214,11 @@ $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 <a href="http://www.maxmind.com/">MaxMind</a>. You can either buy a commercial version or use the free version.<br />Simply download <a href="http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz" >http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz</a> 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 <a href="http://code.google.com/apis/maps/signup.html" >http://code.google.com/apis/maps/signup.html</a>.<br />Your web site URL is: {$a}';
+$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 <a href="https://developers.google.com/maps/documentation/javascript/v2/introduction#Obtaining_Key">https://developers.google.com/maps/documentation/javascript/v2/introduction#Obtaining_Key</a>.<br />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.';
@@ -253,6 +253,7 @@ $string['configminpasswordlength'] = 'Passwords must be at least these many char
 $string['configminpasswordlower'] = 'Passwords must have at least these many lower case letters.';
 $string['configminpasswordnonalphanum'] = 'Passwords must have at least these many non-alphanumeric characters.';
 $string['configminpasswordupper'] = 'Passwords must have at least these many upper case letters.';
+$string['configmodchooserdefault'] = 'Should the activity chooser be presented to users by default?';
 $string['configmycoursesperpage'] = 'Maximum number of courses to display in any list of a user\'s own courses';
 $string['configmymoodleredirect'] = 'This setting forces redirects to /my on login for non-admins and replaces the top level site navigation with /my';
 $string['configmypagelocked'] = 'This setting prevents the default page from being edited by any non-admins';
@@ -351,8 +352,8 @@ $string['cookiesecure'] = 'Secure cookies only';
 $string['country'] = 'Default country';
 $string['coursecontact'] = 'Course contacts';
 $string['coursecontact_desc'] = 'This setting allows you to control who appears on the course description. Users need to have at least one of these roles in a course to be shown on the course description for that course.';
-$string['courselistshortnames'] = 'Display short names';
-$string['courselistshortnames_desc'] = 'Show short name as well as full name when displaying lists of courses.';
+$string['courselistshortnames'] = 'Display extended course names';
+$string['courselistshortnames_desc'] = 'When showing lists of courses, or when referring to courses on administration screens, show the course short name as well as the full name. In fact, when you turn this setting on, the display uses the \'courseextendednamedisplay\' language string, so you can changewhat is displayed using Language customisation.';
 $string['coursemgmt'] = 'Add/edit courses';
 $string['courseoverview'] = 'Course overview';
 $string['courserequestnotify'] = 'Course request notification';
@@ -544,7 +545,9 @@ $string['globalsquoteswarning'] = '<p><strong>Security Warning</strong>: to oper
 $string['globalswarning'] = '<p><strong>SECURITY WARNING!</strong></p><p> To operate properly, Moodle requires <br />that you make certain changes to your current PHP settings.</p><p>You <em>must</em> set <code>register_globals=off</code>.</p><p>This setting is controlled by editing your <code>php.ini</code>, Apache/IIS <br />configuration or <code>.htaccess</code> file.</p>';
 $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['googlemapkey'] = 'Google Maps API V2 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 <a href="https://developers.google.com/maps/documentation/javascript/tutorial#api_key" target="_blank">https://developers.google.com/maps/documentation/javascript/tutorial#api_key</a>';
 $string['gotofirst'] = 'Go to first missing string';
 $string['gradebook'] = 'Gradebook';
 $string['gradebookroles'] = 'Graded roles';
@@ -679,6 +682,7 @@ $string['mnetrestore_extusers_admin'] = '<strong>Note:</strong> This backup file
 $string['mnetrestore_extusers_mismatch'] = '<strong>Note:</strong> This backup file apparently originates from a different Moodle installation and contains remote Moodle Network user accounts that may fail to restore. This operation is unsupported. If you are certain that it was created on this Moodle installation, or you can ensure that all the needed Moodle Network Hosts are configured, you may want to still try the restore.';
 $string['mnetrestore_extusers_noadmin'] = '<strong>Note:</strong> This backup file seems to come from a different Moodle installation and contains remote Moodle Network user accounts. You are not allowed to execute this type of restore. Contact the administrator of the site or, alternatively, restore this course without any user information (modules, files...)';
 $string['mnetrestore_extusers_switchuserauth'] = 'Remote Moodle Network user {$a->username} (coming from {$a->mnethosturl}) switched to local {$a->auth} authenticated user.';
+$string['modchooserdefault'] = 'Activity chooser default';
 $string['modeditdefaults'] = 'Default values for activity settings';
 $string['modsettings'] = 'Manage activities';
 $string['modulesecurity'] = 'Module security';
index 9a9d10c..f7e0829 100644 (file)
@@ -71,6 +71,7 @@ $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.)';
index 10c83e8..5594c8c 100644 (file)
@@ -80,6 +80,7 @@ $string['maxmessages'] = 'Maximum number of messages to show in the discussion h
 $string['message'] = 'Message';
 $string['messagehistory'] = 'Message history';
 $string['messagehistoryfull'] = 'All messages';
+$string['messagenavigation'] = 'Message navigation:';
 $string['messages'] = 'Messages';
 $string['messaging'] = 'Messaging';
 $string['messagingblockednoncontact'] = '{$a} will not be able to reply as you have blocked non-contacts';
index ec475fa..377b840 100644 (file)
@@ -299,6 +299,7 @@ $string['coursedisplay_help'] = 'This setting determines whether the whole cours
 $string['coursedisplay_single'] = 'Show all sections on one page';
 $string['coursedisplay_multi'] = 'Show one section per page';
 $string['coursedeleted'] = 'Deleted course {$a}';
+$string['courseextendednamedisplay'] = '{$a->shortname} {$a->fullname}';
 $string['coursefiles'] = 'Legacy course files';
 $string['coursefilesedit'] = 'Edit legacy course files';
 $string['coursefileswarning'] = 'Course files are deprecated';
index 49dbf0e..bfe2bf4 100644 (file)
@@ -58,6 +58,7 @@ $string['publishstate_help'] = 'A note\'s context determines who can see the not
 * Personal - The note will be visible only to you
 * Course - The note will be visible to teachers in this course
 * Site - The note will be visible to teachers in all courses';
+$string['selectnotestate'] = "Select note state";
 $string['site'] = 'site';
 $string['sitenotes'] = 'Site notes';
 $string['unknown'] = 'unknown';
index b8aa9f9..c62dae4 100644 (file)
@@ -6319,7 +6319,7 @@ class context_course extends context {
                 if ($short){
                     $name .= format_string($course->shortname, true, array('context' => $this));
                 } else {
-                    $name .= format_string($course->fullname);
+                    $name .= format_string(get_course_display_name_for_list($course));
                }
             }
         }
index 1c7097e..b41b67c 100644 (file)
@@ -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) {
index e6523ea..abfe789 100644 (file)
@@ -165,7 +165,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;
     }
 
     /**
index 4df3bc0..cd07c45 100644 (file)
@@ -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;
+
+        // 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.
      *
index 7155373..1e8b1c8 100644 (file)
@@ -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');
index c948d5f..dcb9f2d 100644 (file)
@@ -937,5 +937,17 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2012062501.06);
     }
 
+    if ($oldversion < 2012062501.08) {
+        // Drop obsolete question upgrade field that should have been added to the install.xml.
+        $table = new xmldb_table('question');
+        $field = new xmldb_field('oldquestiontextformat', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
+
+        if ($dbman->field_exists($table, $field)) {
+            $dbman->drop_field($table, $field);
+        }
+
+        upgrade_main_savepoint(true, 2012062501.08);
+    }
+
     return true;
 }
index e0fb421..5103e0b 100644 (file)
@@ -68,6 +68,23 @@ function upgrade_mysql_fix_unsigned_columns() {
 
             $column = (object)array_change_key_case((array)$column, CASE_LOWER);
             if (stripos($column->type, 'unsigned') !== false) {
+                $maxvalue = 0;
+                if (preg_match('/^int/i', $column->type)) {
+                    $maxvalue = 2147483647;
+                } else if (preg_match('/^medium/i', $column->type)) {
+                    $maxvalue = 8388607;
+                } else if (preg_match('/^smallint/i', $column->type)) {
+                    $maxvalue = 32767;
+                } else if (preg_match('/^tinyint/i', $column->type)) {
+                    $maxvalue = 127;
+                }
+                if ($maxvalue) {
+                    // Make sure nobody is abusing our integer ranges - moodle int sizes are in digits, not bytes!!!
+                    $invalidcount = $DB->get_field_sql("SELECT COUNT('x') FROM `{{$table}}` WHERE `$column->field` > :maxnumber", array('maxnumber'=>$maxvalue));
+                    if ($invalidcount) {
+                        throw new moodle_exception('notlocalisederrormessage', 'error', new moodle_url('/admin/'), "Database table '{$table}'' contains unsigned column '{$column->field}' with $invalidcount values that are out of allowed range, upgrade can not continue.");
+                    }
+                }
                 $type = preg_replace('/unsigned/i', 'signed', $column->type);
                 $notnull = ($column->null === 'NO') ? 'NOT NULL' : 'NULL';
                 $default = (!is_null($column->default) and $column->default !== '') ? "DEFAULT '$column->default'" : '';
index 3b2d453..4f60f3c 100644 (file)
@@ -3471,15 +3471,15 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
             }
         }
 
-        if ('publishstate' === 'public') {
+        if ($entry->publishstate === 'public') {
             if ($CFG->forcelogin) {
                 require_login();
             }
 
-        } else if ('publishstate' === 'site') {
+        } else if ($entry->publishstate === 'site') {
             require_login();
             //ok
-        } else if ('publishstate' === 'draft') {
+        } else if ($entry->publishstate === 'draft') {
             require_login();
             if ($USER->id != $entry->userid) {
                 send_file_not_found();
index 50d32ef..99ee930 100644 (file)
@@ -80,7 +80,12 @@ class MoodleQuickForm_editor extends HTML_QuickForm_element {
             $this->_options['maxbytes'] = get_max_upload_file_size($CFG->maxbytes, $options['maxbytes']);
         }
         if (!$this->_options['context']) {
-            $this->_options['context'] = get_context_instance(CONTEXT_SYSTEM);
+            // trying to set context to the current page context to make legacy files show in filepicker (e.g. forum post)
+            if (!empty($PAGE->context->id)) {
+                $this->_options['context'] = $PAGE->context;
+            } else {
+                $this->_options['context'] = context_system::instance();
+            }
         }
         $this->_options['trusted'] = trusttext_trusted($this->_options['context']);
         parent::HTML_QuickForm_element($elementName, $elementLabel, $attributes);
index b49d76a..1f7856b 100644 (file)
@@ -58,6 +58,11 @@ M.form.initShowAdvanced = function(Y, config) {
     return M.form.showAdvanced;
 };
 
+/**
+ * Stores a list of the dependencyManager for each form on the page.
+ */
+M.form.dependencyManagers = {};
+
 /**
  * Initialises a manager for a forms dependencies.
  * This should happen once per form.
@@ -128,7 +133,7 @@ M.form.initFormDependencies = function(Y, formid, dependencies) {
                 return this.checkDependencies(null);
             },
             /**
-             * Gets all elements in the form by thier name and returns
+             * Gets all elements in the form by their name and returns
              * a YUI NodeList
              * @return Y.NodeList
              */
@@ -352,5 +357,17 @@ M.form.initFormDependencies = function(Y, formid, dependencies) {
         return dependencyManager;
     })();
 
-    return new M.form.dependencyManager();
-};
\ No newline at end of file
+    M.form.dependencyManagers[formid] = new M.form.dependencyManager();
+    return M.form.dependencyManagers[formid];
+};
+
+/**
+ * Update the state of a form. You need to call this after, for example, changing
+ * the state of some of the form input elements in your own code, in order that
+ * things like the disableIf state of elements can be updated.
+ */
+M.form.updateFormState = function(formid) {
+    if (formid in M.form.dependencyManagers) {
+        M.form.dependencyManagers[formid].checkDependencies(null);
+    }
+};
index ab8cb9a..8db9155 100644 (file)
@@ -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
             )
         );
     }
index b0ce16e..abd5cea 100644 (file)
@@ -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
             )
         );
     }
index 77b960e..31a5419 100644 (file)
@@ -81,25 +81,25 @@ function form_init_date_js() {
         $function = 'M.form.dateselector.init_date_selectors';
         $config = array(array(
             'firstdayofweek'    =>  get_string('firstdayofweek', 'langconfig'),
-            'mon'               => strftime('%a', 360000),      // 5th Jan 1970 at 12pm
-            'tue'               => strftime('%a', 446400),
-            'wed'               => strftime('%a', 532800),
-            'thu'               => strftime('%a', 619200),
-            'fri'               => strftime('%a', 705600),
-            'sat'               => strftime('%a', 792000),
-            'sun'               => strftime('%a', 878400),
-            'january'           => strftime('%B', 14400),       // 1st Jan 1970 at 12pm
-            'february'          => strftime('%B', 2692800),
-            'march'             => strftime('%B', 5112000),
-            'april'             => strftime('%B', 7790400),
-            'may'               => strftime('%B', 10382400),
-            'june'              => strftime('%B', 13060800),
-            'july'              => strftime('%B', 15652800),
-            'august'            => strftime('%B', 18331200),
-            'september'         => strftime('%B', 21009600),
-            'october'           => strftime('%B', 23601600),
-            'november'          => strftime('%B', 26280000),
-            'december'          => strftime('%B', 28872000)
+            'mon'               => strftime('%a', strtotime("Monday")),      // 5th Jan 1970 at 12pm
+            'tue'               => strftime('%a', strtotime("Tuesday")),
+            'wed'               => strftime('%a', strtotime("Wednesday")),
+            'thu'               => strftime('%a', strtotime("Thursday")),
+            'fri'               => strftime('%a', strtotime("Friday")),
+            'sat'               => strftime('%a', strtotime("Saturday")),
+            'sun'               => strftime('%a', strtotime("Sunday")),
+            'january'           => strftime('%B', strtotime("January")),       // 1st Jan 1970 at 12pm
+            'february'          => strftime('%B', strtotime("February")),
+            'march'             => strftime('%B', strtotime("March")),
+            'april'             => strftime('%B', strtotime("April")),
+            'may'               => strftime('%B', strtotime("May")),
+            'june'              => strftime('%B', strtotime("June")),
+            'july'              => strftime('%B', strtotime("July")),
+            'august'            => strftime('%B', strtotime("August")),
+            'september'         => strftime('%B', strtotime("September")),
+            'october'           => strftime('%B', strtotime("October")),
+            'november'          => strftime('%B', strtotime("November")),
+            'december'          => strftime('%B', strtotime("December"))
         ));
         $PAGE->requires->yui_module($module, $function, $config);
         $done = true;
index 8b1d312..12afe84 100644 (file)
@@ -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);
             }
 
index 463f824..6e7042d 100644 (file)
@@ -1182,13 +1182,42 @@ function openpopup(event, args) {
     if (!args.url.match(/https?:\/\//)) {
         fullurl = M.cfg.wwwroot + args.url;
     }
+    if (args.fullscreen) {
+        args.options = args.options.
+                replace(/top=\d+/, 'top=0').
+                replace(/left=\d+/, 'left=0').
+                replace(/width=\d+/, 'width=' + screen.availWidth).
+                replace(/height=\d+/, 'height=' + screen.availHeight);
+    }
     var windowobj = window.open(fullurl,args.name,args.options);
     if (!windowobj) {
         return true;
     }
+
     if (args.fullscreen) {
-        windowobj.moveTo(0,0);
-        windowobj.resizeTo(screen.availWidth,screen.availHeight);
+        // In some browser / OS combinations (E.g. Chrome on Windows), the
+        // window initially opens slighly too big. The width and heigh options
+        // seem to control the area inside the browser window, so what with
+        // scroll-bars, etc. the actual window is bigger than the screen.
+        // Therefore, we need to fix things up after the window is open.
+        var hackcount = 100;
+        var get_size_exactly_right = function() {
+            windowobj.moveTo(0, 0);
+            windowobj.resizeTo(screen.availWidth, screen.availHeight);
+
+            // Unfortunately, it seems that in Chrome on Ubuntu, if you call
+            // something like windowobj.resizeTo(1280, 1024) too soon (up to
+            // about 50ms) after the window is open, then it actually behaves
+            // as if you called windowobj.resizeTo(0, 0). Therefore, we need to
+            // check that the resize actually worked, and if not, repeatedly try
+            // again after a short delay until it works (but with a limit of
+            // hackcount repeats.
+            if (hackcount > 0 && (windowobj.innerHeight < 10 || windowobj.innerWidth < 10)) {
+                hackcount -= 1;
+                setTimeout(get_size_exactly_right, 10);
+            }
+        }
+        setTimeout(get_size_exactly_right, 0);
     }
     windowobj.focus();
 
index 57a9e90..2290060 100644 (file)
@@ -493,7 +493,7 @@ class core_media_player_vimeo extends core_media_player_external {
 
         $output = <<<OET
 <span class="mediaplugin mediaplugin_vimeo">
-<iframe title="$info" src="http://player.vimeo.com/video/$videoid"
+<iframe title="$info" src="https://player.vimeo.com/video/$videoid"
   width="$width" height="$height" frameborder="0"></iframe>
 </span>
 OET;
@@ -503,7 +503,7 @@ OET;
 
     protected function get_regex() {
         // Initial part of link.
-        $start = '~^http://vimeo\.com/';
+        $start = '~^https?://vimeo\.com/';
         // Middle bit: either watch?v= or v/.
         $middle = '([0-9]+)';
         return $start . $middle . core_media_player_external::END_LINK_REGEX_PART;
@@ -541,8 +541,9 @@ class core_media_player_youtube extends core_media_player_external {
 
         if (empty($CFG->xmlstrictheaders)) {
             return <<<OET
-<iframe title="$info" width="$width" height="$height"
-  src="$site/embed/$videoid?rel=0&wmode=transparent" frameborder="0" allowfullscreen></iframe>
+<span class="mediaplugin mediaplugin_youtube">
+<iframe title="$info" width="$width" height="$height" src="https://$site/embed/$videoid?rel=0&wmode=transparent" frameborder="0" allowfullscreen="1"></iframe>
+</span>
 OET;
         }
 
@@ -551,7 +552,7 @@ OET;
         $output = <<<OET
 <span class="mediaplugin mediaplugin_youtube">
 <object title="$info" type="application/x-shockwave-flash"
-  data="$site/v/$videoid&amp;fs=1&amp;rel=0" width="$width" height="$height">
+  data="https://$site/v/$videoid&amp;fs=1&amp;rel=0" width="$width" height="$height">
  <param name="movie" value="$site/v/$videoid&amp;fs=1&amp;rel=0" />
  <param name="FlashVars" value="playerMode=embedded" />
  <param name="allowFullScreen" value="true" />
@@ -564,7 +565,7 @@ OET;
 
     protected function get_regex() {
         // Initial part of link.
-        $start = '~^(https?://www\.youtube(-nocookie)?\.com)/';
+        $start = '~^https?://(www\.youtube(-nocookie)?\.com)/';
         // Middle bit: either watch?v= or v/.
         $middle = '(?:watch\?v=|v/)([a-z0-9\-_]+)';
         return $start . $middle . core_media_player_external::END_LINK_REGEX_PART;
@@ -607,26 +608,16 @@ class core_media_player_youtube_playlist extends core_media_player_external {
 
         self::pick_video_size($width, $height);
 
-        // TODO: iframe HTML 5 video not implemented and object does not work
-        // on iOS devices.
-        $fallback = core_media_player::PLACEHOLDER;
-        $output = <<<OET
+        return <<<OET
 <span class="mediaplugin mediaplugin_youtube">
-<object title="$info" type="application/x-shockwave-flash"
-  data="$site/p/$playlist&amp;fs=1&amp;rel=0" width="$width" height="$height">
- <param name="movie" value="$site/v/$playlist&amp;fs=1&amp;rel=0" />
- <param name="FlashVars" value="playerMode=embedded" />
- <param name="allowFullScreen" value="true" />
-$fallback</object>
+<iframe width="$width" height="$height" src="https://$site/embed/videoseries?list=$playlist" frameborder="0" allowfullscreen="1"></iframe>
 </span>
 OET;
-
-        return $output;
     }
 
     protected function get_regex() {
         // Initial part of link.
-        $start = '~^(https?://www\.youtube(-nocookie)?\.com)/';
+        $start = '~^https?://(www\.youtube(-nocookie)?\.com)/';
         // Middle bit: either view_play_list?p= or p/ (doesn't work on youtube) or playlist?list=.
         $middle = '(?:view_play_list\?p=|p/|playlist\?list=)([a-z0-9\-_]+)';
         return $start . $middle . core_media_player_external::END_LINK_REGEX_PART;
index f4674ce..1fee1b0 100644 (file)
@@ -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)) {
index 6591795..55cf4e8 100644 (file)
@@ -480,6 +480,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 ////////////////////////////////////////////////////
 
 /**
@@ -2324,10 +2330,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;
 }
 
@@ -3283,11 +3289,24 @@ function get_user_key($script, $userid, $instance=null, $iprestriction=null, $va
 function update_user_login_times() {
     global $USER, $DB;
 
+    $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->lastaccess = $user->currentlogin = time();
 
-    $user->id = $USER->id;
+    $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;
@@ -4074,10 +4093,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)
 
@@ -4829,12 +4844,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');
@@ -5842,15 +5858,15 @@ function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0)
         }
     }
 
-    if ($sitebytes and $sitebytes < $minimumsize) {
+    if (($sitebytes > 0) and ($sitebytes < $minimumsize)) {
         $minimumsize = $sitebytes;
     }
 
-    if ($coursebytes and $coursebytes < $minimumsize) {
+    if (($coursebytes > 0) and ($coursebytes < $minimumsize)) {
         $minimumsize = $coursebytes;
     }
 
-    if ($modulebytes and $modulebytes < $minimumsize) {
+    if (($modulebytes > 0) and ($modulebytes < $minimumsize)) {
         $minimumsize = $modulebytes;
     }
 
@@ -8369,7 +8385,14 @@ function check_php_version($version='5.2.4') {
           if (empty($version)) {
               return true; // no version specified
           }
-          if (preg_match("/Opera\/([0-9\.]+)/i", $agent, $match)) {
+          // Recent Opera useragents have Version/ with the actual version, e.g.:
+          // Opera/9.80 (Windows NT 6.1; WOW64; U; en) Presto/2.10.289 Version/12.01
+          // That's Opera 12.01, not 9.8.
+          if (preg_match("/Version\/([0-9\.]+)/i", $agent, $match)) {
+              if (version_compare($match[1], $version) >= 0) {
+                  return true;
+              }
+          } else if (preg_match("/Opera\/([0-9\.]+)/i", $agent, $match)) {
               if (version_compare($match[1], $version) >= 0) {
                   return true;
               }
@@ -8384,7 +8407,7 @@ function check_php_version($version='5.2.4') {
           if (empty($version)) {
               return true; // no version specified
           }
-          if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
+          if (preg_match("/AppleWebKit\/([0-9.]+)/i", $agent, $match)) {
               if (version_compare($match[1], $version) >= 0) {
                   return true;
               }
@@ -8420,7 +8443,7 @@ function check_php_version($version='5.2.4') {
           if (empty($version)) {
               return true; // no version specified
           }
-          if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
+          if (preg_match("/AppleWebKit\/([0-9.]+)/i", $agent, $match)) {
               if (version_compare($match[1], $version) >= 0) {
                   return true;
               }
@@ -8669,7 +8692,10 @@ function get_browser_version_classes() {
  */
 function can_use_rotated_text() {
     global $USER;
-    return ajaxenabled(array('Firefox' => 2.0)) && !$USER->screenreader;;
+    return (check_browser_version('MSIE', 9) || check_browser_version('Firefox', 2) ||
+            check_browser_version('Chrome', 21) || check_browser_version('Safari', 536.25) ||
+            check_browser_version('Opera', 12) || check_browser_version('Safari iOS', 533)) &&
+            !$USER->screenreader;
 }
 
 /**
@@ -10780,6 +10806,22 @@ function get_home_page() {
     return HOMEPAGE_SITE;
 }
 
+/**
+ * Gets the name of a course to be displayed when showing a list of courses.
+ * By default this is just $course->fullname but user can configure it. The
+ * result of this function should be passed through print_string.
+ * @param object $course Moodle course object
+ * @return string Display name of course (either fullname or short + fullname)
+ */
+function get_course_display_name_for_list($course) {
+    global $CFG;
+    if (!empty($CFG->courselistshortnames)) {
+        return get_string('courseextendednamedisplay', '', $course);
+    } else {
+        return $course->fullname;
+    }
+}
+
 /**
  * The lang_string class
  *
index 82b3ad9..92f9029 100644 (file)
@@ -3573,7 +3573,7 @@ class settings_navigation extends navigation_node {
             // Add the module chooser toggle
             $modchoosertoggleurl = clone($baseurl);
             if ($this->page->user_is_editing() && course_ajax_enabled($course)) {
-                if ($usemodchooser = get_user_preferences('usemodchooser', 1)) {
+                if ($usemodchooser = get_user_preferences('usemodchooser', $CFG->modchooserdefault)) {
                     $modchoosertogglestring = get_string('modchooserdisable', 'moodle');
                     $modchoosertoggleurl->param('modchooser', 'off');
                 } else {
index bc74dc9..b7c9fde 100644 (file)
@@ -150,8 +150,16 @@ function phpunit_boostrap_fix_file_permissions($file) {
  * @return bool
  */
 function phpunit_bootstrap_is_cygwin() {
-    if (empty($_SERVER['SHELL']) or empty($_SERVER['OS'])) {
+    if (empty($_SERVER['OS']) or $_SERVER['OS'] !== 'Windows_NT') {
+        return false;
+
+    } else if (!empty($_SERVER['SHELL']) and $_SERVER['SHELL'] === '/bin/bash') {
+        return true;
+
+    } else if (!empty($_SERVER['TERM']) and $_SERVER['TERM'] === 'cygwin') {
+        return true;
+
+    } else {
         return false;
     }
-    return ($_SERVER['OS'] === 'Windows_NT' and $_SERVER['SHELL'] === '/bin/bash');
 }
index 48a459f..8225f23 100644 (file)
@@ -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'])) {
index 9e46811..33ec793 100644 (file)
  * @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);
+    }
+}
index 3380549..b580259 100644 (file)
@@ -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";
         }
     }
 
index eb5df6b..316ee70 100644 (file)
@@ -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/tests/backup_test.php b/lib/tests/backup_test.php
deleted file mode 100644 (file)
index 1830f56..0000000
+++ /dev/null
@@ -1,184 +0,0 @@
-<?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.
- *
- * @package   core
- * @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 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));
-
-    }
-}
index dd12e90..8df9c7d 100644 (file)
@@ -338,13 +338,13 @@ class medialib_testcase extends advanced_testcase {
         // Format: youtube playlist.
         $url = new moodle_url('http://www.youtube.com/view_play_list?p=PL6E18E2927047B662');
         $t = $renderer->embed_url($url);
-        $this->assertTrue(self::str_contains($t, '</object>'));
+        $this->assertTrue(self::str_contains($t, '</iframe>'));
         $url = new moodle_url('http://www.youtube.com/playlist?list=PL6E18E2927047B662');
         $t = $renderer->embed_url($url);
-        $this->assertTrue(self::str_contains($t, '</object>'));
+        $this->assertTrue(self::str_contains($t, '</iframe>'));
         $url = new moodle_url('http://www.youtube.com/p/PL6E18E2927047B662');
         $t = $renderer->embed_url($url);
-        $this->assertTrue(self::str_contains($t, '</object>'));
+        $this->assertTrue(self::str_contains($t, '</iframe>'));
 
         // Format: vimeo.
         $url = new moodle_url('http://vimeo.com/1176321');
index 2d03ffc..dd9665a 100644 (file)
@@ -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',
index 1280a3d..d36095e 100644 (file)
@@ -44,6 +44,7 @@ class webdav_client {
     private $_server;
     private $_protocol = 'HTTP/1.1';
     private $_port = 80;
+    private $_socket = '';
     private $_path ='/';
     private $_auth = false;
     private $_user;
@@ -57,6 +58,7 @@ class webdav_client {
     private $_req;
     private $_resp_status;
     private $_parser;
+    private $_parserid;
     private $_xmltree;
     private $_tree;
     private $_ls = array();
@@ -73,13 +75,16 @@ class webdav_client {
     private $_body='';
     private $_connection_closed = false;
     private $_maxheaderlenth = 1000;
+    private $_digestchallenge = null;
+    private $_cnonce = '';
+    private $_nc = 0;
 
     /**#@-*/
 
     /**
      * Constructor - Initialise class variables
      */
-    function __construct($server = '', $user = '', $pass = '', $auth = false) {
+    function __construct($server = '', $user = '', $pass = '', $auth = false, $socket = '') {
         if (!empty($server)) {
             $this->_server = $server;
         }
@@ -88,6 +93,7 @@ class webdav_client {
             $this->pass = $pass;
         }
         $this->_auth = $auth;
+        $this->_socket = $socket;
     }
     public function __set($key, $value) {
         $property = '_' . $key;
@@ -154,7 +160,7 @@ class webdav_client {
     function open() {
         // let's try to open a socket
         $this->_error_log('open a socket connection');
-        $this->sock = fsockopen($this->_server, $this->_port, $this->_errno, $this->_errstr, $this->_socket_timeout);
+        $this->sock = fsockopen($this->_socket . $this->_server, $this->_port, $this->_errno, $this->_errstr, $this->_socket_timeout);
         set_time_limit(30);
         if (is_resource($this->sock)) {
             socket_set_blocking($this->sock, true);
@@ -612,9 +618,10 @@ class webdav_client {
                     if (strcmp($response['header']['Content-Type'], 'text/xml; charset="utf-8"') == 0) {
                         // ok let's get the content of the xml stuff
                         $this->_parser = xml_parser_create_ns();
+                        $this->_parserid = (int) $this->_parser;
                         // forget old data...
-                        unset($this->_lock[$this->_parser]);
-                        unset($this->_xmltree[$this->_parser]);
+                        unset($this->_lock[$this->_parserid]);
+                        unset($this->_xmltree[$this->_parserid]);
                         xml_parser_set_option($this->_parser,XML_OPTION_SKIP_WHITE,0);
                         xml_parser_set_option($this->_parser,XML_OPTION_CASE_FOLDING,0);
                         xml_set_object($this->_parser, $this);
@@ -630,8 +637,8 @@ class webdav_client {
                         // Free resources
                         xml_parser_free($this->_parser);
                         // add status code to array
-                        $this->_lock[$this->_parser]['status'] = 200;
-                        return $this->_lock[$this->_parser];
+                        $this->_lock[$this->_parserid]['status'] = 200;
+                        return $this->_lock[$this->_parserid];
 
                     } else {
                         print 'Missing Content-Type: text/xml header in response.<br>';
@@ -709,9 +716,10 @@ class webdav_client {
                     if (strcmp($response['header']['Content-Type'], 'text/xml; charset="utf-8"') == 0) {
                         // ok let's get the content of the xml stuff
                         $this->_parser = xml_parser_create_ns();
+                        $this->_parserid = (int) $this->_parser;
                         // forget old data...
-                        unset($this->_delete[$this->_parser]);
-                        unset($this->_xmltree[$this->_parser]);
+                        unset($this->_delete[$this->_parserid]);
+                        unset($this->_xmltree[$this->_parserid]);
                         xml_parser_set_option($this->_parser,XML_OPTION_SKIP_WHITE,0);
                         xml_parser_set_option($this->_parser,XML_OPTION_CASE_FOLDING,0);
                         xml_set_object($this->_parser, $this);
@@ -728,8 +736,8 @@ class webdav_client {
 
                         // Free resources
                         xml_parser_free($this->_parser);
-                        $this->_delete[$this->_parser]['status'] = $response['status']['status-code'];
-                        return $this->_delete[$this->_parser];
+                        $this->_delete[$this->_parserid]['status'] = $response['status']['status-code'];
+                        return $this->_delete[$this->_parserid];
 
                     } else {
                         print 'Missing Content-Type: text/xml header in response.<br>';
@@ -800,9 +808,10 @@ EOD;
                     if (preg_match('#(application|text)/xml;\s?charset=[\'\"]?utf-8[\'\"]?#i', $response['header']['Content-Type'])) {
                         // ok let's get the content of the xml stuff
                         $this->_parser = xml_parser_create_ns('UTF-8');
+                        $this->_parserid = (int) $this->_parser;
                         // forget old data...
-                        unset($this->_ls[$this->_parser]);
-                        unset($this->_xmltree[$this->_parser]);
+                        unset($this->_ls[$this->_parserid]);
+                        unset($this->_xmltree[$this->_parserid]);
                         xml_parser_set_option($this->_parser,XML_OPTION_SKIP_WHITE,0);
                         xml_parser_set_option($this->_parser,XML_OPTION_CASE_FOLDING,0);
                         // xml_parser_set_option($this->_parser,XML_OPTION_TARGET_ENCODING,'UTF-8');
@@ -819,7 +828,7 @@ EOD;
 
                         // Free resources
                         xml_parser_free($this->_parser);
-                        $arr = $this->_ls[$this->_parser];
+                        $arr = $this->_ls[$this->_parserid];
                         return $arr;
                     } else {
                         $this->_error_log('Missing Content-Type: text/xml header in response!!');
@@ -1038,7 +1047,8 @@ EOD;
 
     private function _endElement($parser, $name) {
         // end tag was found...
-        $this->_xmltree[$parser] = substr($this->_xmltree[$parser],0, strlen($this->_xmltree[$parser]) - (strlen($name) + 1));
+        $parserid = (int) $parser;
+        $this->_xmltree[$parserid] = substr($this->_xmltree[$parserid],0, strlen($this->_xmltree[$parserid]) - (strlen($name) + 1));
     }
 
     /**
@@ -1053,19 +1063,20 @@ EOD;
      */
     private function _propfind_startElement($parser, $name, $attrs) {
         // lower XML Names... maybe break a RFC, don't know ...
+        $parserid = (int) $parser;
 
         $propname = strtolower($name);
-        if (!empty($this->_xmltree[$parser])) {
-            $this->_xmltree[$parser] .= $propname . '_';
+        if (!empty($this->_xmltree[$parserid])) {
+            $this->_xmltree[$parserid] .= $propname . '_';
         } else {
-            $this->_xmltree[$parser] = $propname . '_';
+            $this->_xmltree[$parserid] = $propname . '_';
         }
 
         // translate xml tree to a flat array ...
-        switch($this->_xmltree[$parser]) {
+        switch($this->_xmltree[$parserid]) {
         case 'dav::multistatus_dav::response_':
             // new element in mu
-            $this->_ls_ref =& $this->_ls[$parser][];
+            $this->_ls_ref =& $this->_ls[$parserid][];
             break;
         case 'dav::multistatus_dav::response_dav::href_':
             $this->_ls_ref_cdata = &$this->_ls_ref['href'];
@@ -1113,7 +1124,7 @@ EOD;
 
         default:
             // handle unknown xml elements...
-            $this->_ls_ref_cdata = &$this->_ls_ref[$this->_xmltree[$parser]];
+            $this->_ls_ref_cdata = &$this->_ls_ref[$this->_xmltree[$parserid]];
         }
     }
 
@@ -1148,14 +1159,15 @@ EOD;
      */
     private function _delete_startElement($parser, $name, $attrs) {
         // lower XML Names... maybe break a RFC, don't know ...
+        $parserid = (int) $parser;
         $propname = strtolower($name);
-        $this->_xmltree[$parser] .= $propname . '_';
+        $this->_xmltree[$parserid] .= $propname . '_';
 
         // translate xml tree to a flat array ...
-        switch($this->_xmltree[$parser]) {
+        switch($this->_xmltree[$parserid]) {
         case 'dav::multistatus_dav::response_':
             // new element in mu
-            $this->_delete_ref =& $this->_delete[$parser][];
+            $this->_delete_ref =& $this->_delete[$parserid][];
             break;
         case 'dav::multistatus_dav::response_dav::href_':
             $this->_delete_ref_cdata = &$this->_ls_ref['href'];
@@ -1163,7 +1175,7 @@ EOD;
 
         default:
             // handle unknown xml elements...
-            $this->_delete_cdata = &$this->_delete_ref[$this->_xmltree[$parser]];
+            $this->_delete_cdata = &$this->_delete_ref[$this->_xmltree[$parserid]];
         }
     }
 
@@ -1199,8 +1211,9 @@ EOD;
      */
     private function _lock_startElement($parser, $name, $attrs) {
         // lower XML Names... maybe break a RFC, don't know ...
+        $parserid = (int) $parser;
         $propname = strtolower($name);
-        $this->_xmltree[$parser] .= $propname . '_';
+        $this->_xmltree[$parserid] .= $propname . '_';
 
         // translate xml tree to a flat array ...
         /*
@@ -1209,10 +1222,10 @@ EOD;
         dav::prop_dav::lockdiscovery_dav::activelock_dav::timeout_=
         dav::prop_dav::lockdiscovery_dav::activelock_dav::locktoken_dav::href_=
          */
-        switch($this->_xmltree[$parser]) {
+        switch($this->_xmltree[$parserid]) {
         case 'dav::prop_dav::lockdiscovery_dav::activelock_':
             // new element
-            $this->_lock_ref =& $this->_lock[$parser][];
+            $this->_lock_ref =& $this->_lock[$parserid][];
             break;
         case 'dav::prop_dav::lockdiscovery_dav::activelock_dav::locktype_dav::write_':
             $this->_lock_ref_cdata = &$this->_lock_ref['locktype'];
@@ -1238,7 +1251,7 @@ EOD;
             break;
         default:
             // handle unknown xml elements...
-            $this->_lock_cdata = &$this->_lock_ref[$this->_xmltree[$parser]];
+            $this->_lock_cdata = &$this->_lock_ref[$this->_xmltree[$parserid]];
 
         }
     }
@@ -1254,8 +1267,9 @@ EOD;
      * @access private
      */
     private function _lock_cData($parser, $cdata) {
+        $parserid = (int) $parser;
         if (trim($cdata) <> '') {
-            // $this->_error_log(($this->_xmltree[$parser]) . '='. htmlentities($cdata));
+            // $this->_error_log(($this->_xmltree[$parserid]) . '='. htmlentities($cdata));
             $this->_lock_ref_cdata .= $cdata;
         } else {
             // do nothing
@@ -1293,7 +1307,6 @@ EOD;
      * @access private
      */
     private function create_basic_request($method) {
-        $request = '';
         $this->header_add(sprintf('%s %s %s', $method, $this->_path, $this->_protocol));
         $this->header_add(sprintf('Host: %s:%s', $this->_server, $this->_port));
         //$request .= sprintf('Connection: Keep-Alive');
@@ -1302,7 +1315,102 @@ EOD;
         $this->header_add('TE: Trailers');
         if ($this->_auth == 'basic') {
             $this->header_add(sprintf('Authorization: Basic %s', base64_encode("$this->_user:$this->_pass")));
+        } else if ($this->_auth == 'digest') {
+            if ($signature = $this->digest_signature($method)){
+                $this->header_add($signature);
+            }
+        }
+    }
+
+    /**
+     * Reads the header, stores the challenge information
+     *
+     * @return void
+     */
+    private function digest_auth() {
+
+        $headers = array();
+        $headers[] = sprintf('%s %s %s', 'HEAD', $this->_path, $this->_protocol);
+        $headers[] = sprintf('Host: %s:%s', $this->_server, $this->_port);
+        $headers[] = sprintf('User-Agent: %s', $this->_user_agent);
+        $headers = implode("\r\n", $headers);
+        $headers .= "\r\n\r\n";
+        fputs($this->sock, $headers);
+
+        // Reads the headers.
+        $i = 0;
+        $header = '';
+        do {
+            $header .= fread($this->sock, 1);
+            $i++;
+        } while (!preg_match('/\\r\\n\\r\\n$/', $header, $matches) && $i < $this->_maxheaderlenth);
+
+        // Analyse the headers.
+        $digest = array();
+        $splitheaders = explode("\r\n", $header);
+        foreach ($splitheaders as $line) {
+            if (!preg_match('/^WWW-Authenticate: Digest/', $line)) {
+                continue;
+            }
+            $line = substr($line, strlen('WWW-Authenticate: Digest '));
+            $params = explode(',', $line);
+            foreach ($params as $param) {
+                list($key, $value) = explode('=', trim($param), 2);
+                $digest[$key] = trim($value, '"');
+            }
+            break;
+        }
+
+        $this->_digestchallenge = $digest;
+    }
+
+    /**
+     * Generates the digest signature
+     *
+     * @return string signature to add to the headers
+     * @access private
+     */
+    private function digest_signature($method) {
+        if (!$this->_digestchallenge) {
+            $this->digest_auth();
         }
+
+        $signature = array();
+        $signature['username'] = '"' . $this->_user . '"';
+        $signature['realm'] = '"' . $this->_digestchallenge['realm'] . '"';
+        $signature['nonce'] = '"' . $this->_digestchallenge['nonce'] . '"';
+        $signature['uri'] = '"' . $this->_path . '"';
+
+        if (isset($this->_digestchallenge['algorithm']) && $this->_digestchallenge['algorithm'] != 'MD5') {
+            $this->_error_log('Algorithm other than MD5 are not supported');
+            return false;
+        }
+
+        $a1 = $this->_user . ':' . $this->_digestchallenge['realm'] . ':' . $this->_pass;
+        $a2 = $method . ':' . $this->_path;
+
+        if (!isset($this->_digestchallenge['qop'])) {
+            $signature['response'] = '"' . md5(md5($a1) . ':' . $this->_digestchallenge['nonce'] . ':' . md5($a2)) . '"';
+        } else {
+            // Assume QOP is auth
+            if (empty($this->_cnonce)) {
+                $this->_cnonce = random_string();
+                $this->_nc = 0;
+            }
+            $this->_nc++;
+            $nc = sprintf('%08d', $this->_nc);
+            $signature['cnonce'] = '"' . $this->_cnonce . '"';
+            $signature['nc'] = '"' . $nc . '"';
+            $signature['qop'] = '"' . $this->_digestchallenge['qop'] . '"';
+            $signature['response'] = '"' . md5(md5($a1) . ':' . $this->_digestchallenge['nonce'] . ':' .
+                    $nc . ':' . $this->_cnonce . ':' . $this->_digestchallenge['qop'] . ':' . md5($a2)) . '"';
+        }
+
+        $response = array();
+        foreach ($signature as $key => $value) {
+            $response[] = "$key=$value";
+        }
+        return 'Authorization: Digest ' . implode(', ', $response);
     }
 
     /**
@@ -1373,7 +1481,10 @@ EOD;
             // Therefore we need to reopen the socket, before are sending the next request...
             $this->_error_log('Connection: close found');
             $this->_connection_closed = true;
+        } else if (preg_match('@^HTTP/1\.(1|0) 401 @', $header)) {
+            $this->_error_log('The server requires an authentication');
         }
+
         // check how to get the data on socket stream
         // chunked or content-length (HTTP/1.1) or
         // one block until feof is received (HTTP/1.0)
@@ -1395,7 +1506,12 @@ EOD;
                 fread($this->sock, 1);                           // also drop off the Line Feed
                 $chunk_size=hexdec($chunk_size);                // convert to a number in decimal system
                 if ($chunk_size > 0) {
-                    $buffer .= fread($this->sock,$chunk_size);
+                    $read = 0;
+                    // Reading the chunk in one bite is not secure, we read it byte by byte.
+                    while ($read < $chunk_size) {
+                        $buffer .= fread($this->sock, 1);
+                        $read++;
+                    }
                 }
                 fread($this->sock, 2);                            // ditch the CRLF that trails the chunk
             } while ($chunk_size);                            // till we reach the 0 length chunk (end marker)
@@ -1472,6 +1588,7 @@ EOD;
         // $this->_buffer = $header . "\r\n\r\n" . $buffer;
         $this->_error_log($this->_header);
         $this->_error_log($this->_body);
+
     }
 
 
index feb364b..1e759d6 100644 (file)
@@ -507,9 +507,10 @@ function message_print_usergroup_selector($viewing, $courses, $coursecontexts, $
     }
 
     echo html_writer::start_tag('form', array('id' => 'usergroupform','method' => 'get','action' => ''));
-        echo html_writer::start_tag('fieldset');
-            echo html_writer::select($options, 'viewing', $viewing, false, array('id' => 'viewing','onchange' => 'this.form.submit()'));
-        echo html_writer::end_tag('fieldset');
+    echo html_writer::start_tag('fieldset');
+    echo html_writer::label(get_string('messagenavigation', 'message'), 'viewing');
+    echo html_writer::select($options, 'viewing', $viewing, false, array('id' => 'viewing','onchange' => 'this.form.submit()'));
+    echo html_writer::end_tag('fieldset');
     echo html_writer::end_tag('form');
 }
 
index 7adedc2..b396135 100644 (file)
@@ -4,6 +4,7 @@
     <tr>
         <td colspan="3" class="message_heading mdl-left">
             <input type="hidden" name="sesskey" value="<?php p(sesskey()); ?>" />
+            <label for="combinedsearch" class="accesshide"><?php print_string('searchcombined', 'message'); ?></label>
             <input type="text" name="combinedsearch" size="40" id="combinedsearch" value="<?php p($combinedsearchstring); ?>" />
             <input type="submit" name="combinedsubmit" value="<?php print_string('searchcombined','message') ?>" />
             <a href="index.php?usergroup=<?php echo MESSAGE_VIEW_SEARCH ?>&advanced=1" id="advancedcontactsearchlink"><?php print_string('advanced') ?></a>
index 237a00d..6510353 100644 (file)
@@ -213,7 +213,8 @@ class assign_feedback_comments extends assign_feedback_plugin {
      */
     public function can_upgrade($type, $version) {
 
-        if (($type == 'upload' || $type == 'uploadsingle') && $version >= 2011112900) {
+        if (($type == 'upload' || $type == 'uploadsingle' ||
+             $type == 'online' || $type == 'offline') && $version >= 2011112900) {
             return true;
         }
         return false;
index be0eb3d..fb5e4ff 100644 (file)
@@ -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));
     }
 
     /**
index cde25a4..b90c8ac 100644 (file)
@@ -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;
 
index 5b2438b..96c1c22 100644 (file)
@@ -64,6 +64,7 @@ $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['conversionexception'] = 'Could not convert assignment. Exception was: {$a}.';
 $string['configshowrecentsubmissions'] = 'Everyone can see notifications of submissions in recent activity reports.';
index a6cdba7..2da6610 100644 (file)
@@ -212,8 +212,9 @@ function assign_print_overview($courses, &$htmlarray) {
     // Do assignment_base::isopen() here without loading the whole thing for speed
     foreach ($assignments as $key => $assignment) {
         $time = time();
-        $isopen = $assignment->allowsubmissionsfromdate <= $time;
+        $isopen = false;
         if ($assignment->duedate) {
+            $isopen = $assignment->allowsubmissionsfromdate <= $time;
             if ($assignment->preventlatesubmissions) {
                 $isopen = ($isopen && $time <= $assignment->duedate);
             }
index 8c538fd..a933ab5 100644 (file)
@@ -2923,13 +2923,13 @@ class assign {
         }
 
         if (has_all_capabilities(array('gradereport/grader:view', 'moodle/grade:viewall'), $this->get_course_context())) {
-            $grade = $this->output->action_link(new moodle_url('/grade/report/grader/index.php',
+            $gradestring = $this->output->action_link(new moodle_url('/grade/report/grader/index.php',
                                                               array('id'=>$this->get_course()->id)),
                                                 $gradinginfo->items[0]->grades[$userid]->str_grade);
         } else {
-            $grade = $gradinginfo->items[0]->grades[$userid]->str_grade;
+            $gradestring = $gradinginfo->items[0]->grades[$userid]->str_grade;
         }
-        $mform->addElement('static', 'finalgrade', get_string('currentgrade', 'assign').':' ,$grade);
+        $mform->addElement('static', 'finalgrade', get_string('currentgrade', 'assign').':', $gradestring);
 
 
         $mform->addElement('static', 'progress', '', get_string('gradingstudentprogress', 'assign', array('index'=>$rownum+1, 'count'=>count($useridlist))));
index 8ad624c..4b9929f 100644 (file)
@@ -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'));
@@ -109,6 +109,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');
+        }
     }
 
     /**
index d01c9b3..a1cbe07 100644 (file)
@@ -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