Merge branch 'MDL-35817' of git://github.com/rwijaya/moodle
authorSam Hemelryk <sam@moodle.com>
Sun, 20 Jan 2013 20:47:00 +0000 (09:47 +1300)
committerSam Hemelryk <sam@moodle.com>
Sun, 20 Jan 2013 20:47:00 +0000 (09:47 +1300)
244 files changed:
admin/blocks.php
admin/filters.php
admin/localplugins.php
admin/plugins.php
admin/qtypes.php
admin/renderer.php
admin/repository.php
admin/roles/assign.php
admin/roles/manage.php
admin/settings/appearance.php
admin/settings/plugins.php
admin/tool/uploaduser/index.php
admin/tool/xmldb/actions/main_view/main_view.class.php
admin/user.php
backup/moodle2/backup_gradingform_plugin.class.php
backup/moodle2/restore_gradingform_plugin.class.php
backup/moodle2/restore_stepslib.php
backup/util/dbops/restore_dbops.class.php
backup/util/helper/backup_anonymizer_helper.class.php
backup/util/includes/restore_includes.php
blocks/completionstatus/details.php
blocks/recent_activity/block_recent_activity.php
blocks/recent_activity/renderer.php [new file with mode: 0644]
blocks/recent_activity/version.php
blocks/section_links/block_section_links.php
blocks/site_main_menu/block_site_main_menu.php
blocks/social_activities/block_social_activities.php
cache/README.md
calendar/export_execute.php
calendar/externallib.php [new file with mode: 0644]
calendar/renderer.php
calendar/tests/externallib_tests.php [new file with mode: 0644]
cohort/index.php
comment/comment.js
comment/locallib.php
course/dndupload.js
course/dndupload.php
course/dnduploadlib.php
course/edit_form.php
course/editsection_form.php
course/externallib.php
course/format/renderer.php
course/index.php
course/jumpto.php
course/lib.php
course/modduplicate.php
course/moodleform_mod.php
course/renderer.php
course/switchrole.php
course/tests/externallib_test.php
course/togglecompletion.php
course/view.php
course/yui/modchooser/modchooser.js
course/yui/toolboxes/toolboxes.js
enrol/cohort/ajax.php
enrol/cohort/yui/quickenrolment/quickenrolment.js
enrol/editenrolment.php [moved from enrol/self/editenrolment.php with 61% similarity]
enrol/editenrolment_form.php [moved from enrol/self/editenrolment_form.php with 94% similarity]
enrol/flatfile/cli/sync.php [new file with mode: 0644]
enrol/flatfile/db/access.php [new file with mode: 0644]
enrol/flatfile/db/upgrade.php
enrol/flatfile/lang/en/enrol_flatfile.php
enrol/flatfile/lib.php
enrol/flatfile/settings.php
enrol/flatfile/tests/flatfile_test.php [new file with mode: 0644]
enrol/flatfile/version.php
enrol/imsenterprise/lang/en/enrol_imsenterprise.php
enrol/imsenterprise/lib.php
enrol/imsenterprise/locallib.php
enrol/imsenterprise/settings.php
enrol/imsenterprise/tests/imsenterprise_test.php [new file with mode: 0644]
enrol/ldap/cli/sync.php
enrol/ldap/lib.php
enrol/ldap/tests/ldap_test.php [new file with mode: 0644]
enrol/manual/editenrolment.php [deleted file]
enrol/manual/editenrolment_form.php [deleted file]
enrol/manual/lang/en/enrol_manual.php
enrol/manual/lib.php
enrol/meta/settings.php
enrol/paypal/cli/sync.php [new file with mode: 0644]
enrol/paypal/lang/en/enrol_paypal.php
enrol/paypal/lib.php
enrol/paypal/settings.php
enrol/paypal/tests/paypal_test.php [new file with mode: 0644]
enrol/paypal/version.php
enrol/self/db/upgrade.php
enrol/self/edit.php
enrol/self/edit_form.php
enrol/self/lang/en/enrol_self.php
enrol/self/lib.php
enrol/self/settings.php
enrol/self/tests/self_test.php
enrol/self/version.php
enrol/tests/externallib_test.php
enrol/upgrade.txt
files/renderer.php
filter/manage.php
filter/tex/README.mimetex
filter/tex/mimetex.darwin
filter/tex/mimetex.exe
filter/tex/mimetex.freebsd
filter/tex/mimetex.linux
filter/tex/readme_moodle.txt
grade/grading/form/lib.php
grade/grading/form/rubric/backup/moodle2/backup_gradingform_rubric_plugin.class.php
grade/grading/form/rubric/backup/moodle2/restore_gradingform_rubric_plugin.class.php
grade/grading/form/rubric/db/upgrade.php
grade/grading/form/rubric/edit.php
grade/grading/form/rubric/edit_form.php
grade/grading/form/rubric/lang/en/gradingform_rubric.php
grade/grading/form/rubric/lib.php
grade/grading/form/rubric/renderer.php
grade/grading/form/rubric/rubriceditor.php
grade/grading/form/rubric/version.php
grade/grading/lib.php
grade/grading/manage.php
grade/grading/pick.php
grade/grading/pick_form.php
grade/grading/renderer.php
grade/grading/tests/lib_test.php
grade/report/user/lib.php
grade/tests/reportuser_test.php [new file with mode: 0644]
group/assign.php
group/autogroup.php
group/import.php
group/lib.php
group/tests/externallib_test.php
index.php
install/lang/zh_tw/install.php
lang/en/admin.php
lang/en/block.php
lang/en/cache.php
lang/en/calendar.php
lang/en/condition.php
lang/en/enrol.php
lang/en/error.php
lang/en/group.php
lang/en/question.php
lib/accesslib.php
lib/adminlib.php
lib/blocklib.php
lib/completionlib.php
lib/conditionlib.php
lib/db/caches.php
lib/db/services.php
lib/deprecatedlib.php
lib/external/externallib.php
lib/external/tests/externallib_test.php
lib/externallib.php
lib/filelib.php
lib/filestorage/file_archive.php
lib/filestorage/file_exceptions.php
lib/filestorage/file_packer.php
lib/filestorage/tests/fixtures/empty.zip [new file with mode: 0644]
lib/filestorage/tests/fixtures/zip_info.php
lib/filestorage/tests/zip_packer_test.php
lib/filestorage/zip_archive.php
lib/filestorage/zip_packer.php
lib/form/dndupload.js
lib/form/filemanager.js
lib/form/filemanager.php
lib/form/filepicker.js
lib/form/selectyesno.php
lib/grouplib.php
lib/messagelib.php
lib/modinfolib.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputcomponents.php
lib/outputrenderers.php
lib/phpunit/bootstrap.php
lib/phpunit/classes/util.php
lib/plagiarismlib.php
lib/setup.php
lib/setuplib.php
lib/tests/accesslib_test.php
lib/tests/externallib_test.php
lib/tests/grouplib_test.php
lib/tests/moodlelib_test.php
lib/tests/weblib_test.php
lib/upgrade.txt
lib/weblib.php
lib/yui/chooserdialogue/chooserdialogue.js
lib/yui/formautosubmit/formautosubmit.js
message/externallib.php
message/lib.php
message/tests/externallib_test.php
mod/assign/assignmentplugin.php
mod/assign/db/services.php
mod/assign/externallib.php
mod/assign/feedback/file/importziplib.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/submission/file/locallib.php
mod/assign/submission/onlinetext/locallib.php
mod/assign/tests/externallib_test.php
mod/assign/version.php
mod/book/tool/importhtml/locallib.php
mod/chat/lib.php
mod/forum/lib.php
mod/quiz/backup/moodle2/restore_quiz_stepslib.php
mod/quiz/lang/en/quiz.php
mod/scorm/module.js
mod/wiki/lang/en/wiki.php
mod/wiki/lib.php
notes/tests/externallib_test.php
phpunit.xml.dist
question/format/xml/format.php
question/format/xml/tests/fixtures/multichoice.xml [new file with mode: 0644]
question/format/xml/tests/fixtures/truefalse.xml [new file with mode: 0644]
question/format/xml/tests/xmlformat_test.php
question/previewlib.php
question/type/calculated/lang/en/qtype_calculated.php
question/type/calculated/questiontype.php
question/type/calculatedmulti/edit_calculatedmulti_form.php
question/type/calculatedsimple/lang/en/qtype_calculatedsimple.php
question/type/edit_question_form.php
question/type/multichoice/lang/en/qtype_multichoice.php
question/type/numerical/edit_numerical_form.php
question/type/numerical/lang/en/qtype_numerical.php
question/type/questiontypebase.php
question/type/shortanswer/edit_shortanswer_form.php
report/completion/index.php
report/configlog/index.php
report/security/index.php
repository/equella/lib.php
repository/flickr_public/lib.php
tag/manage.php
theme/base/pix/progress.gif [new file with mode: 0644]
theme/base/style/admin.css
theme/base/style/core.css
theme/base/style/course.css
theme/base/style/filemanager.css
theme/formal_white/style/course.css
theme/formal_white/style/formal_white.css
theme/standard/style/admin.css
theme/standard/style/course.css
user/editadvanced_form.php
user/index.php
user/lib.php
user/tests/externallib_test.php
user/view.php
version.php
webservice/tests/externallib_test.php

index e703ee8..5b5c3a1 100644 (file)
     $table->define_columns(array('name', 'instances', 'version', 'hideshow', 'undeletable', 'delete', 'settings'));
     $table->define_headers(array($strname, $strcourses, $strversion, $strhide.'/'.$strshow, $strprotecthdr, $strdelete, $strsettings));
     $table->define_baseurl($CFG->wwwroot.'/'.$CFG->admin.'/blocks.php');
-    $table->set_attribute('class', 'compatibleblockstable blockstable generaltable');
+    $table->set_attribute('class', 'admintable blockstable generaltable');
+    $table->set_attribute('id', 'compatibleblockstable');
     $table->setup();
     $tablerows = array();
 
index 64589cf..0dbb415 100644 (file)
     $table = new html_table();
     $table->head  = array(get_string('filter'), get_string('isactive', 'filters'),
             get_string('order'), get_string('applyto', 'filters'), get_string('settings'), get_string('delete'));
-    $table->align = array('left', 'left', 'center', 'left', 'left');
-    $table->width = '100%';
+    $table->colclasses = array ('leftalign', 'leftalign', 'centeralign', 'leftalign', 'leftalign', 'leftalign');
+    $table->attributes['class'] = 'admintable generaltable';
+    $table->id = 'filterssetting';
     $table->data  = array();
 
     $lastactive = null;
index 9146c82..cd2f26e 100644 (file)
@@ -77,7 +77,7 @@ $table->define_columns(array('name', 'version', 'delete'));
 $table->define_headers(array(get_string('plugin'), get_string('version'), get_string('delete')));
 $table->define_baseurl($PAGE->url);
 $table->set_attribute('id', 'localplugins');
-$table->set_attribute('class', 'generaltable generalbox boxaligncenter boxwidthwide');
+$table->set_attribute('class', 'admintable generaltable');
 $table->setup();
 
 $plugins = array();
index 4a99a29..c6738df 100644 (file)
@@ -28,8 +28,8 @@ require_once(dirname(dirname(__FILE__)) . '/config.php');
 require_once($CFG->libdir . '/adminlib.php');
 require_once($CFG->libdir . '/pluginlib.php');
 
-require_capability('moodle/site:config', context_system::instance());
 admin_externalpage_setup('pluginsoverview');
+require_capability('moodle/site:config', context_system::instance());
 
 $fetchremote = optional_param('fetchremote', false, PARAM_BOOL);
 $updatesonly = optional_param('updatesonly', false, PARAM_BOOL);
index 1b35c43..e12d3cc 100644 (file)
@@ -195,7 +195,7 @@ $table->define_headers(array(get_string('questiontype', 'question'), get_string(
         get_string('version'), get_string('requires', 'admin'), get_string('availableq', 'question'),
         get_string('delete'), get_string('settings')));
 $table->set_attribute('id', 'qtypes');
-$table->set_attribute('class', 'generaltable generalbox boxaligncenter boxwidthwide');
+$table->set_attribute('class', 'admintable generaltable');
 $table->setup();
 
 // Add a row for each question type.
index 8d49636..b39976d 100644 (file)
@@ -1245,10 +1245,9 @@ class core_admin_renderer extends plugin_renderer_base {
             get_string('report'),
             get_string('status'),
         );
-        $servertable->align = array('center', 'center', 'left', 'center');
-        $servertable->wrap  = array('nowrap', '', '', 'nowrap');
-        $servertable->size  = array('10', 10, '100%', '10');
-        $servertable->attributes['class'] = 'environmenttable generaltable';
+        $servertable->colclasses = array('centeralign name', 'centeralign status', 'leftalign report', 'centeralign info');
+        $servertable->attributes['class'] = 'admintable environmenttable generaltable';
+        $servertable->id = 'serverstatus';
 
         $serverdata = array('ok'=>array(), 'warn'=>array(), 'error'=>array());
 
@@ -1258,10 +1257,9 @@ class core_admin_renderer extends plugin_renderer_base {
             get_string('report'),
             get_string('status'),
         );
-        $othertable->align = array('center', 'left', 'center');
-        $othertable->wrap  = array('', '', 'nowrap');
-        $othertable->size  = array(10, '100%', '10');
-        $othertable->attributes['class'] = 'environmenttable generaltable';
+        $othertable->colclasses = array('aligncenter info', 'alignleft report', 'aligncenter status');
+        $othertable->attributes['class'] = 'admintable environmenttable generaltable';
+        $othertable->id = 'otherserverstatus';
 
         $otherdata = array('ok'=>array(), 'warn'=>array(), 'error'=>array());
 
index 3eb48b0..a4cdd22 100644 (file)
@@ -281,8 +281,11 @@ if (($action == 'edit') || ($action == 'new')) {
     // Table to list plug-ins
     $table = new html_table();
     $table->head = array(get_string('name'), get_string('isactive', 'repository'), get_string('order'), $settingsstr);
-    $table->align = array('left', 'center', 'center', 'center', 'center');
+
+    $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
+    $table->id = 'repositoriessetting';
     $table->data = array();
+    $table->attributes['class'] = 'admintable generaltable';
 
     // Get list of used plug-ins
     $instances = repository::get_types();
index 45ea5f8..4b7cef8 100644 (file)
@@ -189,7 +189,7 @@ $assignurl = new moodle_url($PAGE->url, array('roleid'=>$roleid));
 <form id="assignform" method="post" action="<?php echo $assignurl ?>"><div>
   <input type="hidden" name="sesskey" value="<?php echo sesskey() ?>" />
 
-  <table summary="" class="roleassigntable generaltable generalbox boxaligncenter" cellspacing="0">
+  <table id="assigningrole" summary="" class="admintable roleassigntable generaltable" cellspacing="0">
     <tr>
       <td id="existingcell">
           <p><label for="removeselect"><?php print_string('extusers', 'role'); ?></label></p>
@@ -278,15 +278,13 @@ $assignurl = new moodle_url($PAGE->url, array('roleid'=>$roleid));
 
     // Print overview table
     $table = new html_table();
-    $table->tablealign = 'center';
-    $table->width = '60%';
+    $table->id = 'assignrole';
     $table->head = array(get_string('role'), get_string('description'), get_string('userswiththisrole', 'role'));
-    $table->wrap = array('nowrap', '', 'nowrap');
-    $table->align = array('left', 'left', 'center');
+    $table->colclasses = array('leftalign role', 'leftalign', 'centeralign userrole');
+    $table->attributes['class'] = 'admintable generaltable';
     if ($showroleholders) {
         $table->headspan = array(1, 1, 2);
-        $table->wrap[] = 'nowrap';
-        $table->align[] = 'left';
+        $table->colclasses[] = 'leftalign roleholder';
     }
 
     foreach ($assignableroles as $roleid => $rolename) {
index 745681e..c7a4f69 100644 (file)
 
 /// Initialise table.
     $table = new html_table();
-    $table->tablealign = 'center';
-    $table->align = array('left', 'left', 'left', 'left');
-    $table->wrap = array('nowrap', '', 'nowrap','nowrap');
-    $table->width = '90%';
+    $table->colclasses = array('leftalign', 'leftalign', 'leftalign', 'leftalign');
+    $table->id = 'roles';
+    $table->attributes['class'] = 'admintable generaltable';
     $table->head = array(
         get_string('role') . ' ' . $OUTPUT->help_icon('roles', 'role'),
         get_string('description'),
index 99a5eea..6b527ec 100644 (file)
@@ -67,6 +67,19 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     }
     $temp->add(new admin_setting_configselect('calendar_maxevents',new lang_string('configmaxevents','admin'),new lang_string('helpupcomingmaxevents', 'admin'),10,$options));
     $temp->add(new admin_setting_configcheckbox('enablecalendarexport', new lang_string('enablecalendarexport', 'admin'), new lang_string('configenablecalendarexport','admin'), 1));
+
+    // Calendar custom export settings.
+    $days = array(365 => new lang_string('numdays', '', 365),
+            180 => new lang_string('numdays', '', 180),
+            150 => new lang_string('numdays', '', 150),
+            120 => new lang_string('numdays', '', 120),
+            90  => new lang_string('numdays', '', 90),
+            60  => new lang_string('numdays', '', 60),
+            30  => new lang_string('numdays', '', 30),
+            5  => new lang_string('numdays', '', 5));
+    $temp->add(new admin_setting_configcheckbox('calendar_customexport', new lang_string('configcalendarcustomexport', 'admin'), new lang_string('helpcalendarcustomexport','admin'), 1));
+    $temp->add(new admin_setting_configselect('calendar_exportlookahead', new lang_string('configexportlookahead','admin'), new lang_string('helpexportlookahead', 'admin'), 365, $days));
+    $temp->add(new admin_setting_configselect('calendar_exportlookback', new lang_string('configexportlookback','admin'), new lang_string('helpexportlookback', 'admin'), 5, $days));
     $temp->add(new admin_setting_configtext('calendar_exportsalt', new lang_string('calendarexportsalt','admin'), new lang_string('configcalendarexportsalt', 'admin'), random_string(60)));
     $ADMIN->add('appearance', $temp);
 
index b7fa57e..f2d6f58 100644 (file)
@@ -316,6 +316,7 @@ if ($hassiteconfig || has_capability('moodle/question:config', $systemcontext))
         require_once("$CFG->libdir/pluginlib.php");
         $allplugins = plugin_manager::instance()->get_plugins();
     }
+
     // Question behaviour settings.
     $ADMIN->add('modules', new admin_category('qbehavioursettings', new lang_string('questionbehaviours', 'admin')));
     $ADMIN->add('qbehavioursettings', new admin_page_manageqbehaviours());
@@ -323,6 +324,54 @@ if ($hassiteconfig || has_capability('moodle/question:config', $systemcontext))
     // Question type settings.
     $ADMIN->add('modules', new admin_category('qtypesettings', new lang_string('questiontypes', 'admin')));
     $ADMIN->add('qtypesettings', new admin_page_manageqtypes());
+
+    // Question preview defaults.
+    $settings = new admin_settingpage('qdefaultsetting',
+            get_string('questionpreviewdefaults', 'question'),
+            'moodle/question:config');
+    $ADMIN->add('qtypesettings', $settings);
+
+    $settings->add(new admin_setting_heading('qdefaultsetting_preview_options',
+            '', get_string('questionpreviewdefaults_desc', 'question')));
+
+    // These keys are question_display_options::HIDDEN and VISIBLE.
+    $hiddenofvisible = array(
+        0 => get_string('notshown', 'question'),
+        1 => get_string('shown', 'question'),
+    );
+
+    $settings->add(new admin_setting_question_behaviour('question_preview/behaviour',
+            get_string('howquestionsbehave', 'question'), '',
+                    'deferredfeedback'));
+
+    $settings->add(new admin_setting_configselect('question_preview/correctness',
+            get_string('whethercorrect', 'question'), '', 1, $hiddenofvisible));
+
+    // These keys are question_display_options::HIDDEN, MARK_ONLY and MARK_AND_MAX.
+    $marksoptions = array(
+        0 => get_string('notshown', 'question'),
+        1 => get_string('showmaxmarkonly', 'question'),
+        2 => get_string('showmarkandmax', 'question'),
+    );
+    $settings->add(new admin_setting_configselect('question_preview/marks',
+            get_string('marks', 'question'), '', 1, $marksoptions));
+
+    $settings->add(new admin_setting_configselect('question_preview/markdp',
+            get_string('decimalplacesingrades', 'question'), '', 2, array(0, 1, 2, 3, 4, 5, 6, 7)));
+
+    $settings->add(new admin_setting_configselect('question_preview/feedback',
+            get_string('specificfeedback', 'question'), '', 1, $hiddenofvisible));
+
+    $settings->add(new admin_setting_configselect('question_preview/generalfeedback',
+            get_string('generalfeedback', 'question'), '', 1, $hiddenofvisible));
+
+    $settings->add(new admin_setting_configselect('question_preview/rightanswer',
+            get_string('rightanswer', 'question'), '', 1, $hiddenofvisible));
+
+    $settings->add(new admin_setting_configselect('question_preview/history',
+            get_string('responsehistory', 'question'), '', 0, $hiddenofvisible));
+
+    // Settings for particular question types.
     foreach ($allplugins['qtype'] as $qtype) {
         $qtype->load_settings($ADMIN, 'qtypesettings', $hassiteconfig);
     }
index 9a6a104..384a900 100644 (file)
@@ -527,6 +527,16 @@ if ($formdata = $mform2->is_cancelled()) {
                             }
                         }
 
+                        if ($column === 'lang') {
+                            if (empty($user->lang)) {
+                                // Do not change to not-set value.
+                                continue;
+                            } else if (clean_param($user->lang, PARAM_LANG) === '') {
+                                $upt->track('status', get_string('cannotfindlang', 'error', $user->lang), 'warning');
+                                continue;
+                            }
+                        }
+
                         if (in_array($column, $upt->columns)) {
                             $upt->track($column, s($existinguser->$column).'-->'.s($user->$column), 'info', false);
                         }
@@ -682,6 +692,13 @@ if ($formdata = $mform2->is_cancelled()) {
                 $upt->track('email', get_string('invalidemail'), 'warning');
             }
 
+            if (empty($user->lang)) {
+                $user->lang = '';
+            } else if (clean_param($user->lang, PARAM_LANG) === '') {
+                $upt->track('status', get_string('cannotfindlang', 'error', $user->lang), 'warning');
+                $user->lang = '';
+            }
+
             $forcechangepassword = false;
 
             if ($isinternalauth) {
index 9d4e002..5ac58fc 100644 (file)
@@ -120,7 +120,7 @@ class main_view extends XMLDBAction {
         $result = $this->launch('get_db_directories');
         // Display list of DB directories if everything is ok
         if ($result && !empty($XMLDB->dbdirs)) {
-            $o .= '<table id="listdirectories" border="0" cellpadding="5" cellspacing="1" class="boxaligncenter flexible">';
+            $o .= '<table id="listdirectories" border="0" cellpadding="5" cellspacing="1" class="admintable generaltable">';
             $row = 0;
             foreach ($XMLDB->dbdirs as $key => $dbdir) {
                 // Detect if this is the lastused dir
index aac7112..6f6bc92 100644 (file)
 
         $table = new html_table();
         $table->head = array ();
-        $table->align = array();
+        $table->colclasses = array();
         $table->head[] = $fullnamedisplay;
-        $table->align[] = 'left';
+        $table->attributes['class'] = 'admintable generaltable';
+        $table->colclasses[] = 'leftalign';
         foreach ($extracolumns as $field) {
             $table->head[] = ${$field};
-            $table->align[] = 'left';
+            $table->colclasses[] = 'leftalign';
         }
         $table->head[] = $city;
-        $table->align[] = 'left';
+        $table->colclasses[] = 'leftalign';
         $table->head[] = $country;
-        $table->align[] = 'left';
+        $table->colclasses[] = 'leftalign';
         $table->head[] = $lastaccess;
-        $table->align[] = 'left';
+        $table->colclasses[] = 'leftalign';
         $table->head[] = get_string('edit');
-        $table->align[] = 'center';
+        $table->colclasses[] = 'centeralign';
         $table->head[] = "";
-        $table->align[] = 'center';
+        $table->colclasses[] = 'centeralign';
 
-        $table->width = "95%";
+        $table->id = "users";
         foreach ($users as $user) {
             if (isguestuser($user)) {
                 continue; // do not display guest here
index 7fd4029..aba56d4 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/>.
 
 /**
- * Defines backup_gradingform_plugin class
+ * Contains class backup_gradingform_plugin responsible for advanced grading form plugin backup
  *
  * @package     core_backup
  * @subpackage  moodle2
- * @category    backup
  * @copyright   2011 David Mudrak <david@moodle.com>
  * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 defined('MOODLE_INTERNAL') || die();
 
 /**
- * Base class for all advanced grading form plugins
+ * Base class for backup all advanced grading form plugins
+ *
+ * As an example of implementation see {@link backup_gradingform_rubric_plugin}
+ *
+ * @copyright  2011 David Mudrak <david@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @category   backup
  */
 abstract class backup_gradingform_plugin extends backup_plugin {
 }
index 6caa7dc..320967f 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/>.
 
 /**
- * Defines restore_gradingform_plugin class
- * @package     core_backup
+ * Contains class restore_gradingform_plugin responsible for advanced grading form plugin backup
+ *
+ * @package    core_backup
  * @subpackage  moodle2
- * @category    backup
- * @copyright   2011 David Mudrak <david@moodle.com>
- * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @copyright  2011 David Mudrak <david@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
 defined('MOODLE_INTERNAL') || die();
 
 /**
- * Base class for all advanced grading form plugins
+ * Base class for restoring all advanced grading form plugins
+ *
+ * As an example of implementation see {@link restore_gradingform_rubric_plugin}
+ *
+ * @copyright  2011 David Mudrak <david@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @category   backup
  */
 abstract class restore_gradingform_plugin extends restore_plugin {
 
index b7da2d3..f254475 100644 (file)
@@ -778,6 +778,8 @@ class restore_groups_structure_step extends restore_structure_step {
         }
         // Save the id mapping
         $this->set_mapping('group', $oldid, $newitemid, $restorefiles);
+        // Invalidate the course group data cache just in case.
+        cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($data->courseid));
     }
 
     public function process_grouping($data) {
@@ -821,23 +823,17 @@ class restore_groups_structure_step extends restore_structure_step {
         }
         // Save the id mapping
         $this->set_mapping('grouping', $oldid, $newitemid, $restorefiles);
+        // Invalidate the course group data cache just in case.
+        cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($data->courseid));
     }
 
     public function process_grouping_group($data) {
-        global $DB;
-
-        $data = (object)$data;
-
-        $data->groupingid = $this->get_new_parentid('grouping'); // Use new parentid
-        $data->groupid    = $this->get_mappingid('group', $data->groupid); // Get from mappings
+        global $CFG;
 
-        $params = array();
-        $params['groupingid'] = $data->groupingid;
-        $params['groupid']    = $data->groupid;
+        require_once($CFG->dirroot.'/group/lib.php');
 
-        if (!$DB->record_exists('groupings_groups', $params)) {
-            $DB->insert_record('groupings_groups', $data);  // No need to set this mapping (no child info nor files)
-        }
+        $data = (object)$data;
+        groups_assign_grouping($this->get_new_parentid('grouping'), $this->get_mappingid('group', $data->groupid), $data->timeadded);
     }
 
     protected function after_execute() {
@@ -846,6 +842,8 @@ class restore_groups_structure_step extends restore_structure_step {
         $this->add_related_files('group', 'description', 'group');
         // Add grouping related files, matching with "grouping" mappings
         $this->add_related_files('grouping', 'description', 'grouping');
+        // Invalidate the course group data.
+        cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($this->get_courseid()));
     }
 
 }
index a906801..82c4897 100644 (file)
@@ -1147,14 +1147,16 @@ abstract class restore_dbops {
     *
     *  If restoring users from same site backup:
     *      1A - Normal check: If match by id and username and mnethost  => ok, return target user
-    *      1B - Handle users deleted in DB and "alive" in backup file:
+    *      1B - If restoring an 'anonymous' user (created via the 'Anonymize user information' option) try to find a
+    *           match by username only => ok, return target user MDL-31484
+    *      1C - Handle users deleted in DB and "alive" in backup file:
     *           If match by id and mnethost and user is deleted in DB and
     *           (match by username LIKE 'backup_email.%' or by non empty email = md5(username)) => ok, return target user
-    *      1C - Handle users deleted in backup file and "alive" in DB:
+    *      1D - Handle users deleted in backup file and "alive" in DB:
     *           If match by id and mnethost and user is deleted in backup file
     *           and match by email = email_without_time(backup_email) => ok, return target user
-    *      1D - Conflict: If match by username and mnethost and doesn't match by id => conflict, return false
-    *      1E - None of the above, return true => User needs to be created
+    *      1E - Conflict: If match by username and mnethost and doesn't match by id => conflict, return false
+    *      1F - None of the above, return true => User needs to be created
     *
     *  if restoring from another site backup (cannot match by id here, replace it by email/firstaccess combination):
     *      2A - Normal check: If match by username and mnethost and (email or non-zero firstaccess) => ok, return target user
@@ -1186,7 +1188,16 @@ abstract class restore_dbops {
                 return $rec; // Matching user found, return it
             }
 
-            // 1B - Handle users deleted in DB and "alive" in backup file
+            // 1B - If restoring an 'anonymous' user (created via the 'Anonymize user information' option) try to find a
+            // match by username only => ok, return target user MDL-31484
+            // This avoids username / id mis-match problems when restoring subsequent anonymized backups.
+            if (backup_anonymizer_helper::is_anonymous_user($user)) {
+                if ($rec = $DB->get_record('user', array('username' => $user->username))) {
+                    return $rec; // Matching anonymous user found - return it
+                }
+            }
+
+            // 1C - Handle users deleted in DB and "alive" in backup file
             // Note: for DB deleted users email is stored in username field, hence we
             //       are looking there for emails. See delete_user()
             // Note: for DB deleted users md5(username) is stored *sometimes* in the email field,
@@ -1209,7 +1220,7 @@ abstract class restore_dbops {
                 return $rec; // Matching user, deleted in DB found, return it
             }
 
-            // 1C - Handle users deleted in backup file and "alive" in DB
+            // 1D - Handle users deleted in backup file and "alive" in DB
             // If match by id and mnethost and user is deleted in backup file
             // and match by email = email_without_time(backup_email) => ok, return target user
             if ($user->deleted) {
@@ -1227,7 +1238,7 @@ abstract class restore_dbops {
                 }
             }
 
-            // 1D - If match by username and mnethost and doesn't match by id => conflict, return false
+            // 1E - If match by username and mnethost and doesn't match by id => conflict, return false
             if ($rec = $DB->get_record('user', array('username'=>$user->username, 'mnethostid'=>$user->mnethostid))) {
                 if ($user->id != $rec->id) {
                     return false; // Conflict, username already exists and belongs to another id
index be56d2c..04a9d2d 100644 (file)
  */
 class backup_anonymizer_helper {
 
+    /**
+     * Determine if the given user is an 'anonymous' user, based on their username, firstname, lastname
+     * and email address.
+     * @param stdClass $user the user record to test
+     * @return bool true if this is an 'anonymous' user
+     */
+    public static function is_anonymous_user($user) {
+        if (preg_match('/^anon\d*$/', $user->username)) {
+            $match = preg_match('/^anonfirstname\d*$/', $user->firstname);
+            $match = $match && preg_match('/^anonlastname\d*$/', $user->lastname);
+            $match = $match && preg_match('/^anon\d*@doesntexist\.com$/', $user->email);
+            if ($match) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public static function process_user_auth($value) {
         return 'manual'; // Set them to manual always
     }
index 8cbe681..ae01911 100644 (file)
@@ -34,6 +34,7 @@ require_once($CFG->dirroot . '/backup/util/interfaces/executable.class.php');
 require_once($CFG->dirroot . '/backup/util/interfaces/processable.class.php');
 require_once($CFG->dirroot . '/backup/backup.class.php');
 require_once($CFG->dirroot . '/backup/util/structure/restore_path_element.class.php');
+require_once($CFG->dirroot . '/backup/util/helper/backup_anonymizer_helper.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/backup_file_manager.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/restore_prechecks_helper.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/restore_moodlexml_parser_processor.class.php');
index bb1b051..3878964 100644 (file)
@@ -46,25 +46,9 @@ if ($userid) {
 
 
 // Check permissions
-require_login($course);
-
-$coursecontext   = context_course::instance($course->id);
-$personalcontext = context_user::instance($user->id);
-
-$can_view = false;
-
-// Can view own report
-if ($USER->id == $user->id) {
-    $can_view = true;
-} else if (has_capability('moodle/user:viewuseractivitiesreport', $personalcontext)) {
-    $can_view = true;
-} else if (has_capability('report/completion:view', $coursecontext)) {
-    $can_view = true;
-} else if (has_capability('report/completion:view', $personalcontext)) {
-    $can_view = true;
-}
+require_login();
 
-if (!$can_view) {
+if (!completion_can_view_data($user->id, $course)) {
     print_error('cannotviewreport');
 }
 
index 83b146a..846ab98 100644 (file)
@@ -1,12 +1,57 @@
 <?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/>.
+
+/**
+ * class block_recent_activity
+ *
+ * @package    block_recent_activity
+ * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
 
 require_once($CFG->dirroot.'/course/lib.php');
 
+/**
+ * class block_recent_activity
+ *
+ * @package    block_recent_activity
+ * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
 class block_recent_activity extends block_base {
+
+    /**
+     * Use {@link block_recent_activity::get_timestart()} to access
+     *
+     * @var int stores the time since when we want to show recent activity
+     */
+    protected $timestart = null;
+
+    /**
+     * Initialises the block
+     */
     function init() {
         $this->title = get_string('pluginname', 'block_recent_activity');
     }
 
+    /**
+     * Returns the content object
+     *
+     * @return stdObject
+     */
     function get_content() {
         if ($this->content !== NULL) {
             return $this->content;
@@ -21,15 +66,150 @@ class block_recent_activity extends block_base {
         $this->content->text = '';
         $this->content->footer = '';
 
-        // Slightly hacky way to do it but...
-        ob_start();
-        print_recent_activity($this->page->course);
-        $this->content->text = ob_get_contents();
-        ob_end_clean();
+        $renderer = $this->page->get_renderer('block_recent_activity');
+        $this->content->text = $renderer->recent_activity($this->page->course,
+                $this->get_timestart(),
+                $this->get_recent_enrolments(),
+                $this->get_structural_changes(),
+                $this->get_modules_recent_activity());
 
         return $this->content;
     }
 
+    /**
+     * Returns the time since when we want to show recent activity
+     *
+     * For guest users it is 2 days, for registered users it is the time of last access to the course
+     *
+     * @return int
+     */
+    protected function get_timestart() {
+        global $USER;
+        if ($this->timestart === null) {
+            $this->timestart = round(time() - COURSE_MAX_RECENT_PERIOD, -2); // better db caching for guests - 100 seconds
+
+            if (!isguestuser()) {
+                if (!empty($USER->lastcourseaccess[$this->page->course->id])) {
+                    if ($USER->lastcourseaccess[$this->page->course->id] > $this->timestart) {
+                        $this->timestart = $USER->lastcourseaccess[$this->page->course->id];
+                    }
+                }
+            }
+        }
+        return $this->timestart;
+    }
+
+    /**
+     * Returns all recent enrollments
+     *
+     * @todo MDL-36993 this function always return empty array
+     * @return array array of entries from {user} table
+     */
+    protected function get_recent_enrolments() {
+        return get_recent_enrolments($this->page->course->id, $this->get_timestart());
+    }
+
+    /**
+     * Returns list of recent changes in course structure
+     *
+     * It includes adding, editing or deleting of the resources or activities
+     * Excludes changes on labels, and also if activity was both added and deleted
+     *
+     * @return array array of changes. Each element is an array containing attributes:
+     *    'action' - one of: 'add mod', 'update mod', 'delete mod'
+     *    'module' - instance of cm_info (for 'delete mod' it is an object with attributes modname and modfullname)
+     */
+    protected function get_structural_changes() {
+        global $DB, $CFG;
+        $course = $this->page->course;
+        $timestart = $this->get_timestart();
+        $changelist = array();
+        $logs = $DB->get_records_select('log',
+                "time > ? AND course = ? AND
+                    module = 'course' AND
+                    (action = 'add mod' OR action = 'update mod' OR action = 'delete mod')",
+                array($timestart, $course->id), "id ASC");
+        if ($logs) {
+            $modinfo = get_fast_modinfo($course);
+            $newgones = array(); // added and later deleted items
+            foreach ($logs as $key => $log) {
+                $info = explode(' ', $log->info);
+
+                // note: in most cases I replaced hardcoding of label with use of
+                // $cm->has_view() but it was not possible to do this here because
+                // we don't necessarily have the $cm for it
+                if ($info[0] == 'label') {     // Labels are ignored in recent activity
+                    continue;
+                }
+
+                if (count($info) != 2) {
+                    debugging("Incorrect log entry info: id = ".$log->id, DEBUG_DEVELOPER);
+                    continue;
+                }
+
+                $modname    = $info[0];
+                $instanceid = $info[1];
+
+                if ($log->action == 'delete mod') {
+                    // unfortunately we do not know if the mod was visible
+                    if (!array_key_exists($log->info, $newgones)) {
+                        $changelist[$log->info] = array('action' => $log->action,
+                            'module' => (object)array(
+                                'modname' => $modname,
+                                'modfullname' => get_string('modulename', $modname)
+                             ));
+                    }
+                } else {
+                    if (!isset($modinfo->instances[$modname][$instanceid])) {
+                        if ($log->action == 'add mod') {
+                            // do not display added and later deleted activities
+                            $newgones[$log->info] = true;
+                        }
+                        continue;
+                    }
+                    $cm = $modinfo->instances[$modname][$instanceid];
+                    if ($cm->uservisible && empty($changelist[$log->info])) {
+                        $changelist[$log->info] = array('action' => $log->action, 'module' => $cm);
+                    }
+                }
+            }
+        }
+        return $changelist;
+    }
+
+    /**
+     * Returns list of recent activity within modules
+     *
+     * For each used module type executes callback MODULE_print_recent_activity()
+     *
+     * @return array array of pairs moduletype => content
+     */
+    protected function get_modules_recent_activity() {
+        $context = context_course::instance($this->page->course->id);
+        $viewfullnames = has_capability('moodle/site:viewfullnames', $context);
+        $hascontent = false;
+
+        $modinfo = get_fast_modinfo($this->page->course);
+        $usedmodules = $modinfo->get_used_module_names();
+        $recentactivity = array();
+        foreach ($usedmodules as $modname => $modfullname) {
+            // Each module gets it's own logs and prints them
+            ob_start();
+            $hascontent = component_callback('mod_'. $modname, 'print_recent_activity',
+                    array($this->page->course, $viewfullnames, $this->get_timestart()), false);
+            if ($hascontent) {
+                $recentactivity[$modname] = ob_get_contents();
+            }
+            ob_end_clean();
+        }
+        return $recentactivity;
+    }
+
+    /**
+     * Which page types this block may appear on.
+     *
+     * @return array page-type prefix => true/false.
+     */
     function applicable_formats() {
         return array('all' => true, 'my' => false, 'tag' => false);
     }
diff --git a/blocks/recent_activity/renderer.php b/blocks/recent_activity/renderer.php
new file mode 100644 (file)
index 0000000..b77f584
--- /dev/null
@@ -0,0 +1,128 @@
+<?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/>.
+
+/**
+ * Renderer for block recent_activity
+ *
+ * @package    block_recent_activity
+ * @copyright  2012 Marina Glancy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * recent_activity block rendrer
+ *
+ * @package    block_recent_activity
+ * @copyright  2012 Marina Glancy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class block_recent_activity_renderer extends plugin_renderer_base {
+
+    /**
+     * Renders HTML to display recent_activity block
+     *
+     * @param stdClass $course
+     * @param int $timestart
+     * @param array $recentenrolments array of changes in enrolments
+     * @param array $structuralchanges array of changes in course structure
+     * @param array $modulesrecentactivity array of changes in modules (provided by modules)
+     * @return string
+     */
+    public function recent_activity($course, $timestart, $recentenrolments, $structuralchanges,
+            $modulesrecentactivity) {
+
+        $output = html_writer::tag('div',
+                get_string('activitysince', '', userdate($timestart)),
+                array('class' => 'activityhead'));
+
+        $output .= html_writer::tag('div',
+                html_writer::link(new moodle_url('/course/recent.php', array('id' => $course->id)),
+                    get_string('recentactivityreport')),
+                array('class' => 'activityhead'));
+
+        $content = false;
+
+        // Firstly, have there been any new enrolments?
+        if ($recentenrolments) {
+            $content = true;
+            $context = context_course::instance($course->id);
+            $viewfullnames = has_capability('moodle/site:viewfullnames', $context);
+            $output .= html_writer::start_tag('div', array('class' => 'newusers'));
+            $output .= $this->heading(get_string("newusers").':', 3);
+            //Accessibility: new users now appear in an <OL> list.
+            $output .= html_writer::start_tag('ol', array('class' => 'list'));
+            foreach ($recentenrolments as $user) {
+                $output .= html_writer::tag('li',
+                        html_writer::link(new moodle_url('/user/view.php', array('id' => $user->id, 'course' => $course->id)),
+                                fullname($user, $viewfullnames)),
+                        array('class' => 'name'));
+            }
+            $output .= html_writer::end_tag('ol');
+            $output .= html_writer::end_tag('div');
+        }
+
+        // Next, have there been any modifications to the course structure?
+        if (!empty($structuralchanges)) {
+            $content = true;
+            $output .= $this->heading(get_string("courseupdates").':', 3);
+            foreach ($structuralchanges as $changeinfo => $change) {
+                $output .= $this->structural_change($change);
+            }
+        }
+
+        // Now display new things from each module
+        foreach ($modulesrecentactivity as $modname => $moduleactivity) {
+            $content = true;
+            $output .= $moduleactivity;
+        }
+
+        if (! $content) {
+            $output .= html_writer::tag('p', get_string('nothingnew'), array('class' => 'message'));
+        }
+        return $output;
+    }
+
+    /**
+     * Renders HTML for one change in course structure
+     *
+     * @see block_recent_activity::get_structural_changes()
+     * @param array $change array containing attributes
+     *    'action' - one of: 'add mod', 'update mod', 'delete mod'
+     *    'module' - instance of cm_info (for 'delete mod' it is an object with attributes modname and modfullname)
+     * @return string
+     */
+    protected function structural_change($change) {
+        $cm = $change['module'];
+        switch ($change['action']) {
+            case 'delete mod':
+                $text = get_string('deletedactivity', 'moodle', $cm->modfullname);
+                break;
+            case 'add mod':
+                $text = get_string('added', 'moodle', $cm->modfullname). '<br />'.
+                    html_writer::link($cm->get_url(), format_string($cm->name, true));
+                break;
+            case 'update mod':
+                $text = get_string('updated', 'moodle', $cm->modfullname). '<br />'.
+                    html_writer::link($cm->get_url(), format_string($cm->name, true));
+                break;
+            default:
+                return '';
+        }
+        return html_writer::tag('p', $text, array('class' => 'activity'));
+    }
+}
index 1197d67..4b54991 100644 (file)
@@ -17,8 +17,7 @@
 /**
  * Version details
  *
- * @package    block
- * @subpackage recent_activity
+ * @package    block_recent_activity
  * @copyright  1999 onwards Martin Dougiamas (http://dougiamas.com)
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
index 2942b47..7721931 100644 (file)
@@ -150,6 +150,8 @@ class block_section_links extends block_base {
         global $DB;
         // TODO: Move these config settings to proper ones using component name
         $DB->delete_records('config_plugins', array('plugin' => 'blocks/section_links'));
+        // Have to manually purge the cache as well
+        cache_helper::invalidate_by_definition('core', 'config', array(), 'blocks/section_links');
     }
 
     function has_config() {
index d4374bc..bc4420c 100644 (file)
@@ -41,8 +41,8 @@ class block_site_main_menu extends block_list {
                         continue;
                     }
 
-                    list($content, $instancename) =
-                            get_print_section_cm_text($cm, $course);
+                    $content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
+                    $instancename = $cm->get_formatted_name();
 
                     if (!($url = $cm->get_url())) {
                         $this->content->items[] = $content;
@@ -60,14 +60,12 @@ class block_site_main_menu extends block_list {
         }
 
 /// slow & hacky editing mode
+        $courserenderer = $this->page->get_renderer('core', 'course');
         $ismoving = ismoving($course->id);
         course_create_sections_if_missing($course, 0);
         $modinfo = get_fast_modinfo($course);
         $section = $modinfo->get_section_info(0);
 
-        $groupbuttons = $course->groupmode;
-        $groupbuttonslink = (!$course->groupmodeforce);
-
         if ($ismoving) {
             $strmovehere = get_string('movehere');
             $strmovefull = strip_tags(get_string('movefull', '', "'$USER->activitycopyname'"));
@@ -90,15 +88,10 @@ class block_site_main_menu extends block_list {
                     continue;
                 }
                 if (!$ismoving) {
-                    if ($groupbuttons) {
-                        if (! $mod->groupmodelink = $groupbuttonslink) {
-                            $mod->groupmode = $course->groupmode;
-                        }
-
-                    } else {
-                        $mod->groupmode = false;
-                    }
-                    $editbuttons = '<div class="buttons">'.make_editing_buttons($mod, true, true).'</div>';
+                    $actions = course_get_cm_edit_actions($mod, -1);
+                    $editbuttons = html_writer::tag('div',
+                            $courserenderer->course_section_cm_edit_actions($actions),
+                            array('class' => 'buttons'));
                 } else {
                     $editbuttons = '';
                 }
@@ -111,8 +104,8 @@ class block_site_main_menu extends block_list {
                             '<img style="height:16px; width:80px; border:0px" src="'.$OUTPUT->pix_url('movehere') . '" alt="'.$strmovehere.'" /></a>';
                         $this->content->icons[] = '';
                     }
-                    list($content, $instancename) =
-                            get_print_section_cm_text($modinfo->cms[$modnumber], $course);
+                    $content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
+                    $instancename = $mod->get_formatted_name();
                     $linkcss = $mod->visible ? '' : ' class="dimmed" ';
 
                     if (!($url = $mod->get_url())) {
@@ -134,7 +127,8 @@ class block_site_main_menu extends block_list {
             $this->content->icons[] = '';
         }
 
-        $this->content->footer = print_section_add_menus($course, 0, null, true, true);
+        $this->content->footer = $courserenderer->course_section_add_cm_control($course,
+                0, null, array('inblock' => true));
 
         return $this->content;
     }
index b98f44f..5e394e0 100644 (file)
@@ -43,8 +43,8 @@ class block_social_activities extends block_list {
                         continue;
                     }
 
-                    list($content, $instancename) =
-                            get_print_section_cm_text($cm, $course);
+                    $content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
+                    $instancename = $cm->get_formatted_name();
 
                     if (!($url = $cm->get_url())) {
                         $this->content->items[] = $content;
@@ -63,13 +63,11 @@ class block_social_activities extends block_list {
 
 
 /// slow & hacky editing mode
+        $courserenderer = $this->page->get_renderer('core', 'course');
         $ismoving = ismoving($course->id);
         $modinfo = get_fast_modinfo($course);
         $section = $modinfo->get_section_info(0);
 
-        $groupbuttons = $course->groupmode;
-        $groupbuttonslink = (!$course->groupmodeforce);
-
         if ($ismoving) {
             $strmovehere = get_string('movehere');
             $strmovefull = strip_tags(get_string('movefull', '', "'$USER->activitycopyname'"));
@@ -92,15 +90,9 @@ class block_social_activities extends block_list {
                     continue;
                 }
                 if (!$ismoving) {
-                    if ($groupbuttons) {
-                        if (! $mod->groupmodelink = $groupbuttonslink) {
-                            $mod->groupmode = $course->groupmode;
-                        }
-
-                    } else {
-                        $mod->groupmode = false;
-                    }
-                    $editbuttons = '<br />'.make_editing_buttons($mod, true, true);
+                    $actions = course_get_cm_edit_actions($mod, -1);
+                    $editbuttons = '<br />'.
+                            $courserenderer->course_section_cm_edit_actions($actions);
                 } else {
                     $editbuttons = '';
                 }
@@ -113,8 +105,8 @@ class block_social_activities extends block_list {
                             '<img style="height:16px; width:80px; border:0px" src="'.$OUTPUT->pix_url('movehere') . '" alt="'.$strmovehere.'" /></a>';
                         $this->content->icons[] = '';
                     }
-                    list($content, $instancename) =
-                                get_print_section_cm_text($modinfo->cms[$modnumber], $course);
+                    $content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
+                    $instancename = $mod->get_formatted_name();
 
                     $linkcss = $mod->visible ? '' : ' class="dimmed" ';
 
@@ -137,7 +129,8 @@ class block_social_activities extends block_list {
             $this->content->icons[] = '';
         }
 
-        $this->content->footer = print_section_add_menus($course, 0, null, true, true);
+        $this->content->footer = $courserenderer->course_section_add_cm_control($course,
+                0, null, array('inblock' => true));
 
         return $this->content;
     }
index b6f4e4f..c04c7e7 100644 (file)
@@ -86,7 +86,7 @@ There are several parts that make up the Cache API.
 The loader is central to the whole thing.
 It is used by the end developer to get an object that handles caching.
 90% of end developers will not need to know or use anything else in the cache API.
-In order to get a loader you must use one of two static methods, make or make_with_params.
+In order to get a loader you must use one of two static methods, make or make_from_params.
 The loader has been kept as simple as possible, interaction is summarised by the cache_loader interface.
 Internally there is lots of magic going on. The important parts to know about are:
 * There are two ways to get a loader, the first with a definition (discussed below) the second with params. When params are used they are turned into an adhoc definition with default params.
@@ -225,4 +225,4 @@ Cache information can be invalidated in two ways.
 The first method is designed to be used when you have a single known definition you want to invalidate entries within.
 The second method is a lot more intensive for the system. There are defined invalidation events that definitions can "subscribe" to (through the definitions invalidationevents option).
 When you invalidate by event the cache API finds all of the definitions that subscribe to the event, it then loads the stores for each of those definitions and purges the keys from each store.
-This is obviously a recursive, and therefore, intense process.
\ No newline at end of file
+This is obviously a recursive, and therefore, intense process.
index f4cb51c..7cd585b 100644 (file)
@@ -37,7 +37,7 @@ $time = optional_param('preset_time', 'weeknow', PARAM_ALPHA);
 $now = usergetdate(time());
 // Let's see if we have sufficient and correct data
 $allowed_what = array('all', 'courses');
-$allowed_time = array('weeknow', 'weeknext', 'monthnow', 'monthnext', 'recentupcoming');
+$allowed_time = array('weeknow', 'weeknext', 'monthnow', 'monthnext', 'recentupcoming', 'custom');
 
 if (!empty($generateurl)) {
     $authtoken = sha1($user->id . $user->password . $CFG->calendar_exportsalt);
@@ -127,6 +127,11 @@ if(!empty($what) && !empty($time)) {
                 $timestart = time() - 432000;
                 $timeend = time() + 5184000;
             break;
+            case 'custom':
+                // Events based on custom date range.
+                $timestart = time() - $CFG->calendar_exportlookback * DAYSECS;
+                $timeend = time() + $CFG->calendar_exportlookahead * DAYSECS;
+            break;
         }
     }
     else {
diff --git a/calendar/externallib.php b/calendar/externallib.php
new file mode 100644 (file)
index 0000000..320d81c
--- /dev/null
@@ -0,0 +1,106 @@
+<?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/>.
+
+
+/**
+ * External calendar API
+ *
+ * @package    core_calendar
+ * @category   external
+ * @copyright  2012 Ankit Agarwal
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.5
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once("$CFG->libdir/externallib.php");
+
+/**
+ * Calendar external functions
+ *
+ * @package    core_calendar
+ * @category   external
+ * @copyright  2012 Ankit Agarwal
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.5
+ */
+class core_calendar_external extends external_api {
+
+
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 2.5
+     */
+    public static function delete_calendar_events_parameters() {
+        return new external_function_parameters(
+                array('events' => new external_multiple_structure(
+                        new external_single_structure(
+                                array(
+                                        'eventid' => new external_value(PARAM_INT, 'Event ID', VALUE_REQUIRED, '', NULL_NOT_ALLOWED),
+                                        'repeat'  => new external_value(PARAM_BOOL, 'Delete comeplete series if repeated event')
+                                ), 'List of events to delete'
+                        )
+                    )
+                )
+        );
+    }
+
+    /**
+     * Delete Calendar events
+     *
+     * @param array $eventids A list of event ids with repeat flag to delete
+     * @return null
+     * @since Moodle 2.5
+     */
+    public static function delete_calendar_events($events) {
+        global $CFG, $DB;
+        require_once($CFG->dirroot."/calendar/lib.php");
+
+        // Parameter validation.
+        $params = self::validate_parameters(self:: delete_calendar_events_parameters(), array('events' => $events));
+
+        $transaction = $DB->start_delegated_transaction();
+
+        foreach ($params['events'] as $event) {
+            $eventobj = calendar_event::load($event['eventid']);
+
+            // Let's check if the user is allowed to delete an event.
+            if (!calendar_edit_event_allowed($eventobj)) {
+                throw new moodle_exception("nopermissions");
+            }
+            // Time to do the magic.
+            $eventobj->delete($event['repeat']);
+        }
+
+        // Everything done smoothly, let's commit.
+        $transaction->allow_commit();
+
+        return null;
+    }
+
+    /**
+     * Returns description of method result value
+     *
+     * @return external_description
+     * @since Moodle 2.5
+     */
+    public static function  delete_calendar_events_returns() {
+        return null;
+    }
+}
index b7cbd79..f5e6885 100644 (file)
@@ -43,6 +43,7 @@ class core_calendar_renderer extends plugin_renderer_base {
      * @return string
      */
     public function basic_export_form($allowthisweek, $allownextweek, $allownextmonth, $userid, $authtoken) {
+        global $CFG;
 
         $output  = html_writer::tag('div', get_string('export', 'calendar'), array('class'=>'header'));
         $output .= html_writer::start_tag('fieldset');
@@ -84,8 +85,20 @@ class core_calendar_renderer extends plugin_renderer_base {
         $output .= html_writer::empty_tag('input', array('type'=>'radio', 'name'=>'preset_time', 'id'=>'pt_recupc', 'value'=>'recentupcoming'));
         $output .= html_writer::tag('label', get_string('recentupcoming', 'calendar'), array('for'=>'pt_recupc'));
         $output .= html_writer::empty_tag('br');
-        $output .= html_writer::end_tag('div');
 
+        if ($CFG->calendar_customexport) {
+            $a = new stdClass();
+            $now = time();
+            $time = $now - $CFG->calendar_exportlookback * DAYSECS;
+            $a->timestart = userdate($time, get_string('strftimedatefullshort', 'langconfig'));
+            $time = $now + $CFG->calendar_exportlookahead * DAYSECS;
+            $a->timeend = userdate($time, get_string('strftimedatefullshort', 'langconfig'));
+            $output .= html_writer::empty_tag('input', array('type' => 'radio', 'name' => 'preset_time', 'id' => 'pt_custom', 'value' => 'custom'));
+            $output .= html_writer::tag('label', get_string('customexport', 'calendar', $a), array('for' => 'pt_custom'));
+            $output .= html_writer::empty_tag('br');
+        }
+
+        $output .= html_writer::end_tag('div');
         $output .= html_writer::start_tag('div', array('class'=>'rightalign'));
         $output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'cal_d', 'value'=>''));
         $output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'cal_m', 'value'=>''));
diff --git a/calendar/tests/externallib_tests.php b/calendar/tests/externallib_tests.php
new file mode 100644 (file)
index 0000000..96071bc
--- /dev/null
@@ -0,0 +1,257 @@
+<?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/>.
+
+/**
+ * External calendar functions unit tests
+ *
+ * @package    core_calendar
+ * @category   external
+ * @copyright  2012 Ankit Agarwal
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+
+/**
+ * External course functions unit tests
+ *
+ * @package    core_calendar
+ * @category   external
+ * @copyright  2012 Ankit Agarwal
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.5
+ */
+class core_calendar_external_testcase extends externallib_advanced_testcase {
+
+    /**
+     * Tests set up
+     */
+    protected function setUp() {
+        global $CFG;
+        require_once($CFG->dirroot . '/calendar/externallib.php');
+    }
+
+    /** Create calendar events or update them
+     * Set $prop->id, if you want to do an update instead of creating an new event
+     *
+     * @param string $name        Event title
+     * @param int    $userid      User id
+     * @param string $type        Event type
+     * @param int    $repeats     Number of repeated events to create
+     * @param int    $timestart   Time stamp of the event start
+     * @param mixed  $prop        List of event properties as array or object
+     * @return mixed              Event object or false;
+     * @since Moodle 2.5
+     */
+
+    public static function create_calendar_event($name, $userid = 0, $type = 'user', $repeats = 0, $timestart  = null, $prop = null) {
+        global $CFG, $DB, $USER, $SITE;
+
+        require_once("$CFG->dirroot/calendar/lib.php");
+        if (!empty($prop)) {
+           if (is_array($prop)) {
+               $prop = (object)$prop;
+           }
+        } else {
+            $prop = new stdClass();
+        }
+        $prop->name = $name;
+        if (empty($prop->eventtype)) {
+            $prop->eventtype = $type;
+        }
+        if (empty($prop->repeats)) {
+            $prop->repeats = $repeats;
+        }
+        if (empty($prop->timestart)) {
+            $prop->timestart = time();
+        }
+        if (empty($prop->timeduration)) {
+            $prop->timeduration = 0;
+        }
+        if (empty($prop->repeats)) {
+            $prop->repeat = 0;
+        } else {
+            $prop->repeat = 1;
+        }
+        if (empty($prop->userid)) {
+           if (!empty($userid)) {
+                $prop->userid = $userid;
+           } else {
+               return false;
+           }
+        }
+        if (empty($prop->courseid)) {
+            $prop->courseid = $SITE->id;
+        }
+        $event = new calendar_event($prop);
+        return $event->create($prop);
+    }
+
+    public function test_create_calendar_events () {
+        global $DB, $USER;
+
+        $this->setAdminUser();
+        $this->resetAfterTest();
+        $prevcount = count($DB->get_records("event"));
+
+        // Create a few events and do asserts.
+        $this->create_calendar_event('test', $USER->id);
+        $count = count($DB->get_records("event", array('name' => 'test')));
+        $this->assertEquals(1, $count);
+        $aftercount = count($DB->get_records("event"));
+        $this->assertEquals($prevcount + 1, $aftercount);
+
+        $this->create_calendar_event('user', $USER->id, 'user', 3);
+        $count = count($DB->get_records("event", array('name' => 'user')));
+        $this->assertEquals(3, $count);
+        $aftercount = count($DB->get_records("event"));
+        $this->assertEquals($prevcount + 4, $aftercount);
+
+    }
+
+    /**
+     * Test delete_courses
+     */
+    public function test_delete_calendar_events() {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+        $this->setAdminUser();
+
+        // Create a few stuff to test with.
+        $user = $this->getDataGenerator()->create_user();
+        $course = $this->getDataGenerator()->create_course();
+        $record = new stdClass();
+        $record->courseid = $course->id;
+        $group = $this->getDataGenerator()->create_group($record);
+
+        $notdeletedcount = $DB->count_records('event');
+
+        // Let's create a few events.
+        $siteevent = $this->create_calendar_event('site', $USER->id, 'site');
+        $record = new stdClass();
+        $record->courseid = $course->id;
+        $courseevent = $this->create_calendar_event('course', $USER->id, 'course', 2, time(), $record);
+        $userevent = $this->create_calendar_event('user', $USER->id);
+        $record = new stdClass();
+        $record->courseid = $course->id;
+        $record->groupid = $group->id;
+        $groupevent = $this->create_calendar_event('group', $USER->id, 'group', 0, time(), $record);
+
+        // Now lets try to delete stuff with proper rights.
+        $events = array(
+                array('eventid' => $siteevent->id, 'repeat' => 0),
+                array('eventid' => $courseevent->id, 'repeat' => 1),
+                array('eventid' => $userevent->id, 'repeat' => 0),
+                array('eventid' => $groupevent->id, 'repeat' => 0)
+                );
+        core_calendar_external::delete_calendar_events($events);
+
+        // Check to see if things were deleted properly.
+        $deletedcount = $DB->count_records('event');
+        $this->assertEquals($notdeletedcount, $deletedcount);
+
+        // Let's create a few events.
+        $siteevent = $this->create_calendar_event('site', $USER->id, 'site');
+        $record = new stdClass();
+        $record->courseid = $course->id;
+        $courseevent = $this->create_calendar_event('course', $USER->id, 'course', 3, time(), $record);
+        $userevent = $this->create_calendar_event('user', $USER->id);
+        $record = new stdClass();
+        $record->courseid = $course->id;
+        $record->groupid = $group->id;
+        $groupevent = $this->create_calendar_event('group', $USER->id, 'group', 0, time(), $record);
+
+        $this->setuser($user);
+        $sitecontext = context_system::instance();
+        $coursecontext = context_course::instance($course->id);
+        $usercontext = context_user::instance($user->id);
+        $role = $DB->get_record('role', array('shortname' => 'student'));
+        $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
+
+        // Remove all caps.
+        $this->unassignUserCapability('moodle/calendar:manageentries', $sitecontext->id, $role->id);
+        $this->unassignUserCapability('moodle/calendar:manageentries', $coursecontext->id, $role->id);
+        $this->unassignUserCapability('moodle/calendar:managegroupentries', $coursecontext->id, $role->id);
+        $this->unassignUserCapability('moodle/calendar:manageownentries', $usercontext->id, $role->id);
+
+        // Assign proper caps and attempt delete.
+         $this->assignUserCapability('moodle/calendar:manageentries', $sitecontext->id, $role->id);
+         $events = array(
+                array('eventid' => $siteevent->id, 'repeat' => 0),
+                );
+        core_calendar_external::delete_calendar_events($events);
+        $deletedcount = $DB->count_records('event');
+        $count = $notdeletedcount+5;
+        $this->assertEquals($count, $deletedcount);
+
+         $this->assignUserCapability('moodle/calendar:manageentries', $sitecontext->id, $role->id);
+         $events = array(
+                array('eventid' => $courseevent->id, 'repeat' => 0),
+                );
+        core_calendar_external::delete_calendar_events($events);
+        $deletedcount = $DB->count_records('event');
+        $count = $notdeletedcount+4;
+        $this->assertEquals($count, $deletedcount);
+
+         $this->assignUserCapability('moodle/calendar:manageownentries', $usercontext->id, $role->id);
+         $events = array(
+                array('eventid' => $userevent->id, 'repeat' => 0),
+                );
+        core_calendar_external::delete_calendar_events($events);
+        $deletedcount = $DB->count_records('event');
+        $count = $notdeletedcount+3;
+        $this->assertEquals($count, $deletedcount);
+
+         $this->assignUserCapability('moodle/calendar:managegroupentries', $coursecontext->id, $role->id);
+         $events = array(
+                array('eventid' => $groupevent->id, 'repeat' => 0),
+                );
+        core_calendar_external::delete_calendar_events($events);
+        $deletedcount = $DB->count_records('event');
+        $count = $notdeletedcount+2;
+        $this->assertEquals($count, $deletedcount);
+
+        $notdeletedcount = $deletedcount;
+
+        // Let us try deleting without caps.
+
+        $siteevent = $this->create_calendar_event('site', $USER->id, 'site');
+        $record = new stdClass();
+        $record->courseid = $course->id;
+        $courseevent = $this->create_calendar_event('course', $USER->id, 'course', 3, time(), $record);
+        $userevent = $this->create_calendar_event('user', $USER->id);
+        $record = new stdClass();
+        $record->courseid = $course->id;
+        $record->groupid = $group->id;
+        $groupevent = $this->create_calendar_event('group', $USER->id, 'group', 0, time(), $record);
+
+        $this->setGuestUser();
+        $this->setExpectedException('moodle_exception');
+        $events = array(
+            array('eventid' => $siteevent->id, 'repeat' => 0),
+            array('eventid' => $courseevent->id, 'repeat' => 0),
+            array('eventid' => $userevent->id, 'repeat' => 0),
+            array('eventid' => $groupevent->id, 'repeat' => 0)
+        );
+        core_calendar_external::delete_calendar_events($events);
+    }
+
+}
\ No newline at end of file
index 35d794f..38ecfe6 100644 (file)
@@ -134,9 +134,9 @@ foreach($cohorts['cohorts'] as $cohort) {
 $table = new html_table();
 $table->head  = array(get_string('name', 'cohort'), get_string('idnumber', 'cohort'), get_string('description', 'cohort'),
                       get_string('memberscount', 'cohort'), get_string('component', 'cohort'), get_string('edit'));
-$table->size  = array('20%', '10%', '40%', '10%', '10%', '10%');
-$table->align = array('left', 'left', 'left', 'left','center', 'center');
-$table->width = '80%';
+$table->colclasses = array('leftalign name', 'leftalign id', 'leftalign description', 'leftalign size','centeralign source', 'centeralign action');
+$table->id = 'cohorts';
+$table->attributes['class'] = 'admintable generaltable';
 $table->data  = $data;
 echo html_writer::table($table);
 echo $OUTPUT->paging_bar($cohorts['totalcohorts'], $page, 25, $baseurl);
index f0f4ddf..544d82f 100644 (file)
@@ -157,8 +157,7 @@ bodyContent: '<div class="comment-delete-confirm"><a href="#" id="confirmdelete-
                         scope: scope
                     },
                     headers: {
-                        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
-                        'User-Agent': 'MoodleComment/3.0'
+                        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
                     },
                     data: build_querystring(params)
                 };
@@ -472,8 +471,7 @@ bodyContent: '<div class="comment-delete-confirm"><a href="#" id="confirmdelete-
                             scope: this
                         },
                         headers: {
-                            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
-                            'User-Agent': 'MoodleComment/3.0'
+                            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
                         },
                         data: build_querystring(data)
                     };
index e7e3690..d56d954 100644 (file)
@@ -160,8 +160,9 @@ class comment_manager {
             get_string('content'),
             get_string('action')
         );
-        $table->align = array ('left', 'left', 'left', 'left');
-        $table->attributes = array('class'=>'generaltable commentstable');
+        $table->colclasses = array ('leftalign', 'leftalign', 'leftalign', 'leftalign');
+        $table->attributes = array('class'=>'admintable generaltable');
+        $table->id = 'commentstable';
         $table->data = array();
 
         $link = new moodle_url('/comment/index.php', array('action' => 'delete', 'sesskey' => sesskey()));
index d41bfe9..1ee8395 100644 (file)
@@ -729,6 +729,9 @@ M.course_dndupload = {
                             resel.icon.src = result.icon;
                             resel.a.href = result.link;
                             resel.namespan.innerHTML = result.name;
+                            if (!parseInt(result.visible, 10)) {
+                                resel.a.className = 'dimmed';
+                            }
 
                             if (result.groupingname) {
                                 resel.groupingspan.innerHTML = '(' + result.groupingname + ')';
@@ -916,6 +919,9 @@ M.course_dndupload = {
                             resel.icon.src = result.icon;
                             resel.a.href = result.link;
                             resel.namespan.innerHTML = result.name;
+                            if (!parseInt(result.visible, 10)) {
+                                resel.a.className = 'dimmed';
+                            }
 
                             if (result.groupingname) {
                                 resel.groupingspan.innerHTML = '(' + result.groupingname + ')';
index c4df5a8..797e688 100644 (file)
@@ -35,5 +35,7 @@ $modulename = required_param('module', PARAM_PLUGIN);
 $displayname = optional_param('displayname', null, PARAM_TEXT);
 $contents = optional_param('contents', null, PARAM_RAW); // It will be up to each plugin to clean this data, before saving it.
 
+$PAGE->set_url('/course/dndupload.php');
+
 $dndproc = new dndupload_ajax_processor($courseid, $section, $type, $modulename);
 $dndproc->process($displayname, $contents);
index 21dc7a7..ecb519b 100644 (file)
@@ -620,6 +620,9 @@ class dndupload_ajax_processor {
             throw new moodle_exception('errorcreatingactivity', 'moodle', '', $this->module->name);
         }
 
+        // Note the section visibility
+        $visible = get_fast_modinfo($this->course)->get_section_info($this->section)->visible;
+
         $DB->set_field('course_modules', 'instance', $instanceid, array('id' => $this->cm->id));
         // Rebuild the course cache after update action
         rebuild_course_cache($this->course->id, true);
@@ -627,7 +630,10 @@ class dndupload_ajax_processor {
 
         $sectionid = course_add_cm_to_section($this->course, $this->cm->id, $this->section);
 
-        set_coursemodule_visible($this->cm->id, true);
+        set_coursemodule_visible($this->cm->id, $visible);
+        if (!$visible) {
+            $DB->set_field('course_modules', 'visibleold', 1, array('id' => $this->cm->id));
+        }
 
         // retrieve the final info about this module.
         $info = get_fast_modinfo($this->course);
@@ -636,7 +642,7 @@ class dndupload_ajax_processor {
             delete_course_module($this->cm->id);
             throw new moodle_exception('errorcreatingactivity', 'moodle', '', $this->module->name);
         }
-        $mod = $info->cms[$this->cm->id];
+        $mod = $info->get_cm($this->cm->id);
         $mod->groupmodelink = $this->cm->groupmodelink;
         $mod->groupmode = $this->cm->groupmode;
 
@@ -665,7 +671,8 @@ class dndupload_ajax_processor {
      * @param cm_info $mod details of the mod just created
      */
     protected function send_response($mod) {
-        global $OUTPUT;
+        global $OUTPUT, $PAGE;
+        $courserenderer = $PAGE->get_renderer('core', 'course');
 
         $resp = new stdClass();
         $resp->error = self::ERROR_OK;
@@ -673,8 +680,10 @@ class dndupload_ajax_processor {
         $resp->name = $mod->name;
         $resp->link = $mod->get_url()->out();
         $resp->elementid = 'module-'.$mod->id;
-        $resp->commands = make_editing_buttons($mod, true, true, 0, $mod->sectionnum);
+        $actions = course_get_cm_edit_actions($mod, 0, $mod->sectionnum);
+        $resp->commands = ' '. $courserenderer->course_section_cm_edit_actions($actions);
         $resp->onclick = $mod->get_on_click();
+        $resp->visible = $mod->visible;
 
         // if using groupings, then display grouping name
         if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', $this->context)) {
index 34dbe12..8f7e875 100644 (file)
@@ -252,7 +252,7 @@ class course_edit_form extends moodleform {
                 array(0=>get_string('completiondisabled','completion'), 1=>get_string('completionenabled','completion')));
             $mform->setDefault('enablecompletion', $courseconfig->enablecompletion);
 
-            $mform->addElement('checkbox', 'completionstartonenrol', get_string('completionstartonenrol', 'completion'));
+            $mform->addElement('advcheckbox', 'completionstartonenrol', get_string('completionstartonenrol', 'completion'));
             $mform->setDefault('completionstartonenrol', $courseconfig->completionstartonenrol);
             $mform->disabledIf('completionstartonenrol', 'enablecompletion', 'eq', 0);
         } else {
index 4dffac5..7fdbdf6 100644 (file)
@@ -71,11 +71,19 @@ class editsection_form extends moodleform {
                 $mform->addHelpButton('groupingid', 'groupingsection', 'group');
             }
 
-            // Date and time conditions
+            // Available from/to defaults to midnight because then the display
+            // will be nicer where it tells users when they can access it (it
+            // shows only the date and not time).
+            $date = usergetdate(time());
+            $midnight = make_timestamp($date['year'], $date['mon'], $date['mday']);
+
+            // Date and time conditions.
             $mform->addElement('date_time_selector', 'availablefrom',
-                    get_string('availablefrom', 'condition'), array('optional' => true));
+                    get_string('availablefrom', 'condition'),
+                    array('optional' => true, 'defaulttime' => $midnight));
             $mform->addElement('date_time_selector', 'availableuntil',
-                    get_string('availableuntil', 'condition'), array('optional' => true));
+                    get_string('availableuntil', 'condition'),
+                    array('optional' => true, 'defaulttime' => $midnight));
 
             // Conditions based on grades
             $gradeoptions = array();
@@ -220,10 +228,41 @@ class editsection_form extends moodleform {
         // Conditions: Don't let them set dates which make no sense
         if (array_key_exists('availablefrom', $data) &&
                 $data['availablefrom'] && $data['availableuntil'] &&
-                $data['availablefrom'] > $data['availableuntil']) {
+                $data['availablefrom'] >= $data['availableuntil']) {
             $errors['availablefrom'] = get_string('badavailabledates', 'condition');
         }
 
+        // Conditions: Verify that the grade conditions are numbers, and make sense.
+        if (array_key_exists('conditiongradegroup', $data)) {
+            foreach ($data['conditiongradegroup'] as $i => $gradedata) {
+                if ($gradedata['conditiongrademin'] !== '' &&
+                        !is_numeric(unformat_float($gradedata['conditiongrademin']))) {
+                    $errors["conditiongradegroup[{$i}]"] = get_string('gradesmustbenumeric', 'condition');
+                    continue;
+                }
+                if ($gradedata['conditiongrademax'] !== '' &&
+                        !is_numeric(unformat_float($gradedata['conditiongrademax']))) {
+                    $errors["conditiongradegroup[{$i}]"] = get_string('gradesmustbenumeric', 'condition');
+                    continue;
+                }
+                if ($gradedata['conditiongrademin'] !== '' && $gradedata['conditiongrademax'] !== '' &&
+                        unformat_float($gradedata['conditiongrademax']) <= unformat_float($gradedata['conditiongrademin'])) {
+                    $errors["conditiongradegroup[{$i}]"] = get_string('badgradelimits', 'condition');
+                    continue;
+                }
+                if ($gradedata['conditiongrademin'] === '' && $gradedata['conditiongrademax'] === '' &&
+                        $gradedata['conditiongradeitemid']) {
+                    $errors["conditiongradegroup[{$i}]"] = get_string('gradeitembutnolimits', 'condition');
+                    continue;
+                }
+                if (($gradedata['conditiongrademin'] !== '' || $gradedata['conditiongrademax'] !== '') &&
+                        !$gradedata['conditiongradeitemid']) {
+                    $errors["conditiongradegroup[{$i}]"] = get_string('gradelimitsbutnoitem', 'condition');
+                    continue;
+                }
+            }
+        }
+
         // Conditions: Verify that the user profile field has not been declared more than once
         if (array_key_exists('conditionfieldgroup', $data)) {
             // Array to store the existing fields
index b5c82f4..3476dcc 100644 (file)
@@ -125,7 +125,7 @@ class core_course_external extends external_api {
                 $sectioncontents = array();
 
                 //for each module of the section
-                foreach ($modinfo->sections[$section->section] as $cmid) { //matching /course/lib.php:print_section() logic
+                foreach ($modinfo->sections[$section->section] as $cmid) {
                     $cm = $modinfo->cms[$cmid];
 
                     // stop here if the module is not visible to the user
index befde23..d9a7edc 100644 (file)
@@ -38,6 +38,20 @@ defined('MOODLE_INTERNAL') || die();
  */
 abstract class format_section_renderer_base extends plugin_renderer_base {
 
+    /** @var contains instance of core course renderer */
+    protected $courserenderer;
+
+    /**
+     * Constructor method, calls the parent constructor
+     *
+     * @param moodle_page $page
+     * @param string $target one of rendering target constants
+     */
+    public function __construct(moodle_page $page, $target) {
+        parent::__construct($page, $target);
+        $this->courserenderer = $this->page->get_renderer('core', 'course');
+    }
+
     /**
      * Generate the starting container html for a list of sections
      * @return string HTML to output.
@@ -175,7 +189,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         }
         $o.= html_writer::end_tag('div');
 
-        $o .= $this->section_availability_message($section);
+        $o .= $this->section_availability_message($section,
+                has_capability('moodle/course:viewhiddensections', $context));
 
         return $o;
     }
@@ -305,7 +320,9 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         $o.= html_writer::end_tag('div');
         $o.= $this->section_activity_summary($section, $course, null);
 
-        $o.= $this->section_availability_message($section);
+        $context = context_course::instance($course->id);
+        $o .= $this->section_availability_message($section,
+                has_capability('moodle/course:viewhiddensections', $context));
 
         $o .= html_writer::end_tag('div');
         $o .= html_writer::end_tag('li');
@@ -389,22 +406,38 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
     }
 
     /**
-     * If section is not visible to current user, display the message about that
-     * ('Not available until...', that sort of thing). Otherwise, returns blank.
+     * If section is not visible, display the message about that ('Not available
+     * until...', that sort of thing). Otherwise, returns blank.
+     *
+     * For users with the ability to view hidden sections, it shows the
+     * information even though you can view the section and also may include
+     * slightly fuller information (so that teachers can tell when sections
+     * are going to be unavailable etc). This logic is the same as for
+     * activities.
      *
      * @param stdClass $section The course_section entry from DB
+     * @param bool $canviewhidden True if user can view hidden sections
      * @return string HTML to output
      */
-    protected function section_availability_message($section) {
+    protected function section_availability_message($section, $canviewhidden) {
+        global $CFG;
         $o = '';
-        if (!$section->uservisible || $section->availableinfo) {
+        if (!$section->uservisible) {
             $o .= html_writer::start_tag('div', array('class' => 'availabilityinfo'));
-            if (!empty($section->availableinfo)) {
-                $o .= $section->availableinfo;
-            } else {
-                $o .= get_string('notavailable');
-            }
+            // Note: We only get to this function if availableinfo is non-empty,
+            // so there is definitely something to print.
+            $o .= $section->availableinfo;
             $o .= html_writer::end_tag('div');
+        } else if ($canviewhidden && !empty($CFG->enableavailability) && $section->visible) {
+            $ci = new condition_info_section($section);
+            $fullinfo = $ci->get_full_information();
+            if ($fullinfo) {
+                $o .= html_writer::start_tag('div', array('class' => 'availabilityinfo'));
+                $o .= get_string(
+                        ($section->showavailability ? 'userrestriction_visible' : 'userrestriction_hidden'),
+                        'condition', $fullinfo);
+                $o .= html_writer::end_tag('div');
+            }
         }
         return $o;
     }
@@ -568,10 +601,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         if ($thissection->summary or !empty($modinfo->sections[0]) or $PAGE->user_is_editing()) {
             echo $this->start_section_list();
             echo $this->section_header($thissection, $course, true, $displaysection);
-            print_section($course, $thissection, null, null, true, "100%", false, $displaysection);
-            if ($PAGE->user_is_editing()) {
-                print_section_add_menus($course, 0, null, false, false, $displaysection);
-            }
+            echo $this->courserenderer->course_section_cm_list($course, $thissection, $displaysection);
+            echo $this->courserenderer->course_section_add_cm_control($course, 0, $displaysection);
             echo $this->section_footer();
             echo $this->end_section_list();
         }
@@ -605,10 +636,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         $completioninfo = new completion_info($course);
         echo $completioninfo->display_help_icon();
 
-        print_section($course, $thissection, null, null, true, '100%', false, $displaysection);
-        if ($PAGE->user_is_editing()) {
-            print_section_add_menus($course, $displaysection, null, false, false, $displaysection);
-        }
+        echo $this->courserenderer->course_section_cm_list($course, $thissection, $displaysection);
+        echo $this->courserenderer->course_section_add_cm_control($course, $displaysection, $displaysection);
         echo $this->section_footer();
         echo $this->end_section_list();
 
@@ -658,10 +687,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
                 // 0-section is displayed a little different then the others
                 if ($thissection->summary or !empty($modinfo->sections[0]) or $PAGE->user_is_editing()) {
                     echo $this->section_header($thissection, $course, false, 0);
-                    print_section($course, $thissection, null, null, true, "100%", false, 0);
-                    if ($PAGE->user_is_editing()) {
-                        print_section_add_menus($course, 0, null, false, false, 0);
-                    }
+                    echo $this->courserenderer->course_section_cm_list($course, $thissection);
+                    echo $this->courserenderer->course_section_add_cm_control($course, 0);
                     echo $this->section_footer();
                 }
                 continue;
@@ -671,9 +698,10 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
                 continue;
             }
             // Show the section if the user is permitted to access it, OR if it's not available
-            // but showavailability is turned on
+            // but showavailability is turned on (and there is some available info text).
             $showsection = $thissection->uservisible ||
-                    ($thissection->visible && !$thissection->available && $thissection->showavailability);
+                    ($thissection->visible && !$thissection->available && $thissection->showavailability
+                    && !empty($thissection->availableinfo));
             if (!$showsection) {
                 // Hidden section message is overridden by 'unavailable' control
                 // (showavailability option).
@@ -690,10 +718,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
             } else {
                 echo $this->section_header($thissection, $course, false, 0);
                 if ($thissection->uservisible) {
-                    print_section($course, $thissection, null, null, true, "100%", false, 0);
-                    if ($PAGE->user_is_editing()) {
-                        print_section_add_menus($course, $section, null, false, false, 0);
-                    }
+                    echo $this->courserenderer->course_section_cm_list($course, $thissection);
+                    echo $this->courserenderer->course_section_add_cm_control($course, $section);
                 }
                 echo $this->section_footer();
             }
@@ -707,7 +733,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
                     continue;
                 }
                 echo $this->stealth_section_header($section);
-                print_section($course, $thissection, null, null, true, "100%", false, 0);
+                echo $this->courserenderer->course_section_cm_list($course, $thissection);
                 echo $this->stealth_section_footer();
             }
 
index a1a5e56..f67ac5b 100644 (file)
@@ -67,12 +67,11 @@ $straction = get_string('action');
 $strfulllistofcourses = get_string('fulllistofcourses');
 
 
-/// Unless it's an editing admin, just print the regular listing of courses/categories
+// Unless it's an editing admin, just print the regular listing of courses/categories.
 if (!$adminediting) {
-
-/// Print form for creating new categories
+    $showaddcoursebutton = true;
+    // Print form for creating new categories.
     $countcategories = $DB->count_records('course_categories');
-
     if ($countcategories > 1 || ($countcategories == 1 && $DB->count_records('course') > 200)) {
         $strcourses = get_string('courses');
         $strcategories = get_string('categories');
@@ -96,14 +95,13 @@ if (!$adminediting) {
         echo $OUTPUT->header();
         echo $OUTPUT->skip_link_target();
         echo $OUTPUT->box_start('courseboxes');
-        print_courses(0);
+        $showaddcoursebutton = print_courses(0);
         echo $OUTPUT->box_end();
     }
 
     echo $OUTPUT->container_start('buttons');
-    if (has_capability('moodle/course:create', $systemcontext)) {
-    /// Print link to create a new course
-    /// Get the 1st available category
+    if (has_capability('moodle/course:create', $systemcontext) && $showaddcoursebutton) {
+        // Print link to create a new course, for the 1st available category.
         $options = array('category' => $CFG->defaultrequestcategory);
         echo $OUTPUT->single_button(new moodle_url('edit.php', $options), get_string('addnewcourse'), 'get');
     }
@@ -254,7 +252,7 @@ $parentlist = array();
 $displaylist[0] = get_string('top');
 make_categories_list($displaylist, $parentlist);
 
-echo '<table class="generaltable editcourse boxaligncenter"><tr class="header">';
+echo '<table id="coursecategories" class="admintable generaltable editcourse"><tr class="header">';
 echo '<th class="header" scope="col">'.$strcategories.'</th>';
 echo '<th class="header" scope="col">'.$strcourses.'</th>';
 echo '<th class="header" scope="col">'.$stredit.'</th>';
@@ -309,7 +307,7 @@ function print_category_edit($category, $displaylist, $parentslist, $depth=-1, $
             $category->context = context_coursecat::instance($category->id);
         }
 
-        echo '<tr><td align="left" class="name">';
+        echo '<tr><td class="leftalign name">';
         for ($i=0; $i<$depth;$i++) {
             echo '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
         }
@@ -319,9 +317,9 @@ function print_category_edit($category, $displaylist, $parentslist, $depth=-1, $
              format_string($category->name, true, array('context' => $category->context)).'</a>';
         echo '</td>';
 
-        echo '<td class="count">'.$category->coursecount.'</td>';
+        echo '<td class="centeralign count">'.$category->coursecount.'</td>';
 
-        echo '<td class="icons">';    /// Print little icons
+        echo '<td class="centeralign icons">';  /// Print little icons
 
         if (has_capability('moodle/category:manage', $category->context)) {
             echo '<a title="'.$str->edit.'" href="editcategory.php?id='.$category->id.'"><img'.
@@ -358,7 +356,7 @@ function print_category_edit($category, $displaylist, $parentslist, $depth=-1, $
         }
         echo '</td>';
 
-        echo '<td align="left">';
+        echo '<td class="leftalign">';
         if (has_capability('moodle/category:manage', $category->context)) {
             $tempdisplaylist = $displaylist;
             unset($tempdisplaylist[$category->id]);
index c4d11ef..98c4e49 100644 (file)
@@ -34,7 +34,7 @@ if (!confirm_sesskey()) {
     print_error('confirmsesskeybad');
 }
 
-if (strpos($jump, '/') === 0) {
+if (strpos($jump, '/') === 0 || strpos($jump, $CFG->wwwroot) === 0) {
     redirect(new moodle_url($jump));
 } else {
     print_error('error');
index 5230240..7b55e59 100644 (file)
@@ -847,209 +847,6 @@ function print_log_ods($course, $user, $date, $order='l.time DESC', $modname,
     return true;
 }
 
-
-function print_overview($courses, array $remote_courses=array()) {
-    global $CFG, $USER, $DB, $OUTPUT;
-
-    $htmlarray = array();
-    if ($modules = $DB->get_records('modules')) {
-        foreach ($modules as $mod) {
-            if (file_exists(dirname(dirname(__FILE__)).'/mod/'.$mod->name.'/lib.php')) {
-                include_once(dirname(dirname(__FILE__)).'/mod/'.$mod->name.'/lib.php');
-                $fname = $mod->name.'_print_overview';
-                if (function_exists($fname)) {
-                    $fname($courses,$htmlarray);
-                }
-            }
-        }
-    }
-    foreach ($courses as $course) {
-        $fullname = format_string($course->fullname, true, array('context' => context_course::instance($course->id)));
-        echo $OUTPUT->box_start('coursebox');
-        $attributes = array('title' => s($fullname));
-        if (empty($course->visible)) {
-            $attributes['class'] = 'dimmed';
-        }
-        echo $OUTPUT->heading(html_writer::link(
-            new moodle_url('/course/view.php', array('id' => $course->id)), $fullname, $attributes), 3);
-        if (array_key_exists($course->id,$htmlarray)) {
-            foreach ($htmlarray[$course->id] as $modname => $html) {
-                echo $html;
-            }
-        }
-        echo $OUTPUT->box_end();
-    }
-
-    if (!empty($remote_courses)) {
-        echo $OUTPUT->heading(get_string('remotecourses', 'mnet'));
-    }
-    foreach ($remote_courses as $course) {
-        echo $OUTPUT->box_start('coursebox');
-        $attributes = array('title' => s($course->fullname));
-        echo $OUTPUT->heading(html_writer::link(
-            new moodle_url('/auth/mnet/jump.php', array('hostid' => $course->hostid, 'wantsurl' => '/course/view.php?id='.$course->remoteid)),
-            format_string($course->shortname),
-            $attributes) . ' (' . format_string($course->hostname) . ')', 3);
-        echo $OUTPUT->box_end();
-    }
-}
-
-
-/**
- * This function trawls through the logs looking for
- * anything new since the user's last login
- */
-function print_recent_activity($course) {
-    // $course is an object
-    global $CFG, $USER, $SESSION, $DB, $OUTPUT;
-
-    $context = context_course::instance($course->id);
-
-    $viewfullnames = has_capability('moodle/site:viewfullnames', $context);
-
-    $timestart = round(time() - COURSE_MAX_RECENT_PERIOD, -2); // better db caching for guests - 100 seconds
-
-    if (!isguestuser()) {
-        if (!empty($USER->lastcourseaccess[$course->id])) {
-            if ($USER->lastcourseaccess[$course->id] > $timestart) {
-                $timestart = $USER->lastcourseaccess[$course->id];
-            }
-        }
-    }
-
-    echo '<div class="activitydate">';
-    echo get_string('activitysince', '', userdate($timestart));
-    echo '</div>';
-    echo '<div class="activityhead">';
-
-    echo '<a href="'.$CFG->wwwroot.'/course/recent.php?id='.$course->id.'">'.get_string('recentactivityreport').'</a>';
-
-    echo "</div>\n";
-
-    $content = false;
-
-/// Firstly, have there been any new enrolments?
-
-    $users = get_recent_enrolments($course->id, $timestart);
-
-    //Accessibility: new users now appear in an <OL> list.
-    if ($users) {
-        echo '<div class="newusers">';
-        echo $OUTPUT->heading(get_string("newusers").':', 3);
-        $content = true;
-        echo "<ol class=\"list\">\n";
-        foreach ($users as $user) {
-            $fullname = fullname($user, $viewfullnames);
-            echo '<li class="name"><a href="'."$CFG->wwwroot/user/view.php?id=$user->id&amp;course=$course->id\">$fullname</a></li>\n";
-        }
-        echo "</ol>\n</div>\n";
-    }
-
-/// Next, have there been any modifications to the course structure?
-
-    $modinfo = get_fast_modinfo($course);
-
-    $changelist = array();
-
-    $logs = $DB->get_records_select('log', "time > ? AND course = ? AND
-                                            module = 'course' AND
-                                            (action = 'add mod' OR action = 'update mod' OR action = 'delete mod')",
-                                    array($timestart, $course->id), "id ASC");
-
-    if ($logs) {
-        $actions  = array('add mod', 'update mod', 'delete mod');
-        $newgones = array(); // added and later deleted items
-        foreach ($logs as $key => $log) {
-            if (!in_array($log->action, $actions)) {
-                continue;
-            }
-            $info = explode(' ', $log->info);
-
-            // note: in most cases I replaced hardcoding of label with use of
-            // $cm->has_view() but it was not possible to do this here because
-            // we don't necessarily have the $cm for it
-            if ($info[0] == 'label') {     // Labels are ignored in recent activity
-                continue;
-            }
-
-            if (count($info) != 2) {
-                debugging("Incorrect log entry info: id = ".$log->id, DEBUG_DEVELOPER);
-                continue;
-            }
-
-            $modname    = $info[0];
-            $instanceid = $info[1];
-
-            if ($log->action == 'delete mod') {
-                // unfortunately we do not know if the mod was visible
-                if (!array_key_exists($log->info, $newgones)) {
-                    $strdeleted = get_string('deletedactivity', 'moodle', get_string('modulename', $modname));
-                    $changelist[$log->info] = array ('operation' => 'delete', 'text' => $strdeleted);
-                }
-            } else {
-                if (!isset($modinfo->instances[$modname][$instanceid])) {
-                    if ($log->action == 'add mod') {
-                        // do not display added and later deleted activities
-                        $newgones[$log->info] = true;
-                    }
-                    continue;
-                }
-                $cm = $modinfo->instances[$modname][$instanceid];
-                if (!$cm->uservisible) {
-                    continue;
-                }
-
-                if ($log->action == 'add mod') {
-                    $stradded = get_string('added', 'moodle', get_string('modulename', $modname));
-                    $changelist[$log->info] = array('operation' => 'add', 'text' => "$stradded:<br /><a href=\"$CFG->wwwroot/mod/$cm->modname/view.php?id={$cm->id}\">".format_string($cm->name, true)."</a>");
-
-                } else if ($log->action == 'update mod' and empty($changelist[$log->info])) {
-                    $strupdated = get_string('updated', 'moodle', get_string('modulename', $modname));
-                    $changelist[$log->info] = array('operation' => 'update', 'text' => "$strupdated:<br /><a href=\"$CFG->wwwroot/mod/$cm->modname/view.php?id={$cm->id}\">".format_string($cm->name, true)."</a>");
-                }
-            }
-        }
-    }
-
-    if (!empty($changelist)) {
-        echo $OUTPUT->heading(get_string("courseupdates").':', 3);
-        $content = true;
-        foreach ($changelist as $changeinfo => $change) {
-            echo '<p class="activity">'.$change['text'].'</p>';
-        }
-    }
-
-/// Now display new things from each module
-
-    $usedmodules = array();
-    foreach($modinfo->cms as $cm) {
-        if (isset($usedmodules[$cm->modname])) {
-            continue;
-        }
-        if (!$cm->uservisible) {
-            continue;
-        }
-        $usedmodules[$cm->modname] = $cm->modname;
-    }
-
-    foreach ($usedmodules as $modname) {      // Each module gets it's own logs and prints them
-        if (file_exists($CFG->dirroot.'/mod/'.$modname.'/lib.php')) {
-            include_once($CFG->dirroot.'/mod/'.$modname.'/lib.php');
-            $print_recent_activity = $modname.'_print_recent_activity';
-            if (function_exists($print_recent_activity)) {
-                // NOTE: original $isteacher (second parameter below) was replaced with $viewfullnames!
-                $content = $print_recent_activity($course, $viewfullnames, $timestart) || $content;
-            }
-        } else {
-            debugging("Missing lib.php in lib/{$modname} - please reinstall files or uninstall the module");
-        }
-    }
-
-    if (! $content) {
-        echo '<p class="message">'.get_string('nothingnew').'</p>';
-    }
-}
-
 /**
  * For a given course, returns an array of course activity objects
  * Each item in the array contains he following properties:
@@ -1301,576 +1098,6 @@ function set_section_visible($courseid, $sectionnumber, $visibility) {
     return $resourcestotoggle;
 }
 
-/**
- * Obtains shared data that is used in print_section when displaying a
- * course-module entry.
- *
- * Calls format_text or format_string as appropriate, and obtains the correct icon.
- *
- * This data is also used in other areas of the code.
- * @param cm_info $cm Course-module data (must come from get_fast_modinfo)
- * @param object $course Moodle course object
- * @return array An array with the following values in this order:
- *   $content (optional extra content for after link),
- *   $instancename (text of link)
- */
-function get_print_section_cm_text(cm_info $cm, $course) {
-    global $OUTPUT;
-
-    // Get content from modinfo if specified. Content displays either
-    // in addition to the standard link (below), or replaces it if
-    // the link is turned off by setting ->url to null.
-    if (($content = $cm->get_content()) !== '') {
-        // Improve filter performance by preloading filter setttings for all
-        // activities on the course (this does nothing if called multiple
-        // times)
-        filter_preload_activities($cm->get_modinfo());
-
-        // Get module context
-        $modulecontext = context_module::instance($cm->id);
-        $labelformatoptions = new stdClass();
-        $labelformatoptions->noclean = true;
-        $labelformatoptions->overflowdiv = true;
-        $labelformatoptions->context = $modulecontext;
-        $content = format_text($content, FORMAT_HTML, $labelformatoptions);
-    } else {
-        $content = '';
-    }
-
-    // Get course context
-    $coursecontext = context_course::instance($course->id);
-    $stringoptions = new stdClass;
-    $stringoptions->context = $coursecontext;
-    $instancename = format_string($cm->name, true,  $stringoptions);
-    return array($content, $instancename);
-}
-
-/**
- * Prints a section full of activity modules
- *
- * @param stdClass $course The course
- * @param stdClass|section_info $section The section object containing properties id and section
- * @param array $mods (argument not used)
- * @param array $modnamesused (argument not used)
- * @param bool $absolute All links are absolute
- * @param string $width Width of the container
- * @param bool $hidecompletion Hide completion status
- * @param int $sectionreturn The section to return to
- * @return void
- */
-function print_section($course, $section, $mods, $modnamesused, $absolute=false, $width="100%", $hidecompletion=false, $sectionreturn=null) {
-    global $CFG, $USER, $DB, $PAGE, $OUTPUT;
-
-    static $initialised;
-
-    static $groupbuttons;
-    static $groupbuttonslink;
-    static $isediting;
-    static $ismoving;
-    static $strmovehere;
-    static $strmovefull;
-    static $strunreadpostsone;
-
-    if (!isset($initialised)) {
-        $groupbuttons     = ($course->groupmode or (!$course->groupmodeforce));
-        $groupbuttonslink = (!$course->groupmodeforce);
-        $isediting        = $PAGE->user_is_editing();
-        $ismoving         = $isediting && ismoving($course->id);
-        if ($ismoving) {
-            $strmovehere  = get_string("movehere");
-            $strmovefull  = strip_tags(get_string("movefull", "", "'$USER->activitycopyname'"));
-        }
-        $initialised = true;
-    }
-
-    $modinfo = get_fast_modinfo($course);
-    $completioninfo = new completion_info($course);
-
-    //Accessibility: replace table with list <ul>, but don't output empty list.
-    if (!empty($modinfo->sections[$section->section])) {
-
-        // Fix bug #5027, don't want style=\"width:$width\".
-        echo "<ul class=\"section img-text\">\n";
-
-        foreach ($modinfo->sections[$section->section] as $modnumber) {
-            $mod = $modinfo->cms[$modnumber];
-
-            if ($ismoving and $mod->id == $USER->activitycopy) {
-                // do not display moving mod
-                continue;
-            }
-
-            // We can continue (because it will not be displayed at all)
-            // if:
-            // 1) The activity is not visible to users
-            // and
-            // 2a) The 'showavailability' option is not set (if that is set,
-            //     we need to display the activity so we can show
-            //     availability info)
-            // or
-            // 2b) The 'availableinfo' is empty, i.e. the activity was
-            //     hidden in a way that leaves no info, such as using the
-            //     eye icon.
-            if (!$mod->uservisible &&
-                (empty($mod->showavailability) ||
-                  empty($mod->availableinfo))) {
-                // visibility shortcut
-                continue;
-            }
-
-            // In some cases the activity is visible to user, but it is
-            // dimmed. This is done if viewhiddenactivities is true and if:
-            // 1. the activity is not visible, or
-            // 2. the activity has dates set which do not include current, or
-            // 3. the activity has any other conditions set (regardless of whether
-            //    current user meets them)
-            $modcontext = context_module::instance($mod->id);
-            $canviewhidden = has_capability('moodle/course:viewhiddenactivities', $modcontext);
-            $accessiblebutdim = false;
-            $conditionalhidden = false;
-            if ($canviewhidden) {
-                $accessiblebutdim = !$mod->visible;
-                if (!empty($CFG->enableavailability)) {
-                    $conditionalhidden = $mod->availablefrom > time() ||
-                        ($mod->availableuntil && $mod->availableuntil < time()) ||
-                        count($mod->conditionsgrade) > 0 ||
-                        count($mod->conditionscompletion) > 0;
-                }
-                $accessiblebutdim = $conditionalhidden || $accessiblebutdim;
-            }
-
-            $liclasses = array();
-            $liclasses[] = 'activity';
-            $liclasses[] = $mod->modname;
-            $liclasses[] = 'modtype_'.$mod->modname;
-            $extraclasses = $mod->get_extra_classes();
-            if ($extraclasses) {
-                $liclasses = array_merge($liclasses, explode(' ', $extraclasses));
-            }
-            echo html_writer::start_tag('li', array('class'=>join(' ', $liclasses), 'id'=>'module-'.$modnumber));
-            if ($ismoving) {
-                echo '<a title="'.$strmovefull.'"'.
-                     ' href="'.$CFG->wwwroot.'/course/mod.php?moveto='.$mod->id.'&amp;sesskey='.sesskey().'">'.
-                     '<img class="movetarget" src="'.$OUTPUT->pix_url('movehere') . '" '.
-                     ' alt="'.$strmovehere.'" /></a><br />
-                     ';
-            }
-
-            $classes = array('mod-indent');
-            if (!empty($mod->indent)) {
-                $classes[] = 'mod-indent-'.$mod->indent;
-                if ($mod->indent > 15) {
-                    $classes[] = 'mod-indent-huge';
-                }
-            }
-            echo html_writer::start_tag('div', array('class'=>join(' ', $classes)));
-
-            // Get data about this course-module
-            list($content, $instancename) =
-                    get_print_section_cm_text($modinfo->cms[$modnumber], $course);
-
-            //Accessibility: for files get description via icon, this is very ugly hack!
-            $altname = '';
-            $altname = $mod->modfullname;
-            // Avoid unnecessary duplication: if e.g. a forum name already
-            // includes the word forum (or Forum, etc) then it is unhelpful
-            // to include that in the accessible description that is added.
-            if (false !== strpos(textlib::strtolower($instancename),
-                    textlib::strtolower($altname))) {
-                $altname = '';
-            }
-            // File type after name, for alphabetic lists (screen reader).
-            if ($altname) {
-                $altname = get_accesshide(' '.$altname);
-            }
-
-            // Start the div for the activity title, excluding the edit icons.
-            echo html_writer::start_tag('div', array('class' => 'activityinstance'));
-
-            // We may be displaying this just in order to show information
-            // about visibility, without the actual link
-            $contentpart = '';
-            if ($mod->uservisible) {
-                // Nope - in this case the link is fully working for user
-                $linkclasses = '';
-                $textclasses = '';
-                if ($accessiblebutdim) {
-                    $linkclasses .= ' dimmed';
-                    $textclasses .= ' dimmed_text';
-                    if ($conditionalhidden) {
-                        $linkclasses .= ' conditionalhidden';
-                        $textclasses .= ' conditionalhidden';
-                    }
-                    $accesstext = get_accesshide(get_string('hiddenfromstudents').': ');
-                } else {
-                    $accesstext = '';
-                }
-                if ($linkclasses) {
-                    $linkcss = trim($linkclasses) . ' ';
-                } else {
-                    $linkcss = '';
-                }
-                if ($textclasses) {
-                    $textcss = trim($textclasses) . ' ';
-                } else {
-                    $textcss = '';
-                }
-
-                // Get on-click attribute value if specified and decode the onclick - it
-                // has already been encoded for display (puke).
-                $onclick = htmlspecialchars_decode($mod->get_on_click(), ENT_QUOTES);
-
-                $groupinglabel = '';
-                if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', context_course::instance($course->id))) {
-                    $groupings = groups_get_all_groupings($course->id);
-                    $groupinglabel = html_writer::tag('span', '('.format_string($groupings[$mod->groupingid]->name).')',
-                            array('class' => 'groupinglabel'));
-                }
-
-                if ($url = $mod->get_url()) {
-                    // Display link itself.
-                    $activitylink = html_writer::empty_tag('img', array('src' => $mod->get_icon_url(),
-                            'class' => 'iconlarge activityicon', 'alt' => $mod->modfullname)) . $accesstext .
-                            html_writer::tag('span', $instancename . $altname, array('class' => 'instancename'));
-                    echo html_writer::link($url, $activitylink, array('class' => $linkcss, 'onclick' => $onclick)) .
-                            $groupinglabel;
-
-                    // If specified, display extra content after link.
-                    if ($content) {
-                        $contentpart = html_writer::tag('div', $content, array('class' =>
-                                trim('contentafterlink ' . $textclasses)));
-                    }
-                } else {
-                    // No link, so display only content.
-                    $contentpart = html_writer::tag('div', $accesstext . $content, array('class' => $textcss));
-                }
-
-            } else {
-                $textclasses = $extraclasses;
-                $textclasses .= ' dimmed_text';
-                if ($textclasses) {
-                    $textcss = 'class="' . trim($textclasses) . '" ';
-                } else {
-                    $textcss = '';
-                }
-                $accesstext = '<span class="accesshide">' .
-                        get_string('notavailableyet', 'condition') .
-                        ': </span>';
-
-                if ($url = $mod->get_url()) {
-                    // Display greyed-out text of link
-                    echo '<div ' . $textcss . $mod->extra .
-                            ' >' . '<img src="' . $mod->get_icon_url() .
-                            '" class="activityicon" alt="" /> <span>'. $instancename . $altname .
-                            '</span></div>';
-
-                    // Do not display content after link when it is greyed out like this.
-                } else {
-                    // No link, so display only content (also greyed)
-                    $contentpart = '<div ' . $textcss . $mod->extra . '>' .
-                            $accesstext . $content . '</div>';
-                }
-            }
-
-            // Module can put text after the link (e.g. forum unread)
-            echo $mod->get_after_link();
-
-            // Closing the tag which contains everything but edit icons. $contentpart should not be part of this.
-            echo html_writer::end_tag('div');
-
-            // If there is content but NO link (eg label), then display the
-            // content here (BEFORE any icons). In this case cons must be
-            // displayed after the content so that it makes more sense visually
-            // and for accessibility reasons, e.g. if you have a one-line label
-            // it should work similarly (at least in terms of ordering) to an
-            // activity.
-            if (empty($url)) {
-                echo $contentpart;
-            }
-
-            if ($isediting) {
-                if ($groupbuttons and plugin_supports('mod', $mod->modname, FEATURE_GROUPS, 0)) {
-                    if (! $mod->groupmodelink = $groupbuttonslink) {
-                        $mod->groupmode = $course->groupmode;
-                    }
-
-                } else {
-                    $mod->groupmode = false;
-                }
-                echo make_editing_buttons($mod, $absolute, true, $mod->indent, $sectionreturn);
-                echo $mod->get_after_edit_icons();
-            }
-
-            // Completion
-            $completion = $hidecompletion
-                ? COMPLETION_TRACKING_NONE
-                : $completioninfo->is_enabled($mod);
-            if ($completion!=COMPLETION_TRACKING_NONE && isloggedin() &&
-                !isguestuser() && $mod->uservisible) {
-                $completiondata = $completioninfo->get_data($mod,true);
-                $completionicon = '';
-                if ($isediting) {
-                    switch ($completion) {
-                        case COMPLETION_TRACKING_MANUAL :
-                            $completionicon = 'manual-enabled'; break;
-                        case COMPLETION_TRACKING_AUTOMATIC :
-                            $completionicon = 'auto-enabled'; break;
-                        default: // wtf
-                    }
-                } else if ($completion==COMPLETION_TRACKING_MANUAL) {
-                    switch($completiondata->completionstate) {
-                        case COMPLETION_INCOMPLETE:
-                            $completionicon = 'manual-n'; break;
-                        case COMPLETION_COMPLETE:
-                            $completionicon = 'manual-y'; break;
-                    }
-                } else { // Automatic
-                    switch($completiondata->completionstate) {
-                        case COMPLETION_INCOMPLETE:
-                            $completionicon = 'auto-n'; break;
-                        case COMPLETION_COMPLETE:
-                            $completionicon = 'auto-y'; break;
-                        case COMPLETION_COMPLETE_PASS:
-                            $completionicon = 'auto-pass'; break;
-                        case COMPLETION_COMPLETE_FAIL:
-                            $completionicon = 'auto-fail'; break;
-                    }
-                }
-                if ($completionicon) {
-                    $imgsrc = $OUTPUT->pix_url('i/completion-'.$completionicon);
-                    $formattedname = format_string($mod->name, true, array('context' => $modcontext));
-                    $imgalt = get_string('completion-alt-' . $completionicon, 'completion', $formattedname);
-                    if ($completion == COMPLETION_TRACKING_MANUAL && !$isediting) {
-                        $imgtitle = get_string('completion-title-' . $completionicon, 'completion', $formattedname);
-                        $newstate =
-                            $completiondata->completionstate==COMPLETION_COMPLETE
-                            ? COMPLETION_INCOMPLETE
-                            : COMPLETION_COMPLETE;
-                        // In manual mode the icon is a toggle form...
-
-                        // If this completion state is used by the
-                        // conditional activities system, we need to turn
-                        // off the JS.
-                        if (!empty($CFG->enableavailability) &&
-                            condition_info::completion_value_used_as_condition($course, $mod)) {
-                            $extraclass = ' preventjs';
-                        } else {
-                            $extraclass = '';
-                        }
-                        echo html_writer::start_tag('form', array(
-                                'class' => 'togglecompletion' . $extraclass,
-                                'method' => 'post',
-                                'action' => $CFG->wwwroot . '/course/togglecompletion.php'));
-                        echo html_writer::start_tag('div');
-                        echo html_writer::empty_tag('input', array(
-                                'type' => 'hidden', 'name' => 'id', 'value' => $mod->id));
-                        echo html_writer::empty_tag('input', array(
-                                'type' => 'hidden', 'name' => 'modulename',
-                                'value' => $mod->name));
-                        echo html_writer::empty_tag('input', array(
-                                'type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
-                        echo html_writer::empty_tag('input', array(
-                                'type' => 'hidden', 'name' => 'completionstate',
-                                'value' => $newstate));
-                        echo html_writer::empty_tag('input', array(
-                                'type' => 'image', 'src' => $imgsrc, 'alt' => $imgalt, 'title' => $imgtitle));
-                        echo html_writer::end_tag('div');
-                        echo html_writer::end_tag('form');
-                    } else {
-                        // In auto mode, or when editing, the icon is just an image
-                        echo "<span class='autocompletion'>";
-                        echo "<img src='$imgsrc' alt='$imgalt' title='$imgalt' /></span>";
-                    }
-                }
-            }
-
-            // If there is content AND a link, then display the content here
-            // (AFTER any icons). Otherwise it was displayed before
-            if (!empty($url)) {
-                echo $contentpart;
-            }
-
-            // Show availability information (for someone who isn't allowed to
-            // see the activity itself, or for staff)
-            if (!$mod->uservisible) {
-                echo '<div class="availabilityinfo">'.$mod->availableinfo.'</div>';
-            } else if ($canviewhidden && !empty($CFG->enableavailability)) {
-                // Don't add availability information if user is not editing and activity is hidden.
-                if ($mod->visible || $PAGE->user_is_editing()) {
-                    $hidinfoclass = '';
-                    if (!$mod->visible) {
-                        $hidinfoclass = 'hide';
-                    }
-                    $ci = new condition_info($mod);
-                    $fullinfo = $ci->get_full_information();
-                    if($fullinfo) {
-                        echo '<div class="availabilityinfo '.$hidinfoclass.'">'.get_string($mod->showavailability
-                            ? 'userrestriction_visible'
-                            : 'userrestriction_hidden','condition',
-                            $fullinfo).'</div>';
-                    }
-                }
-            }
-
-            echo html_writer::end_tag('div');
-            echo html_writer::end_tag('li')."\n";
-        }
-
-    } elseif ($ismoving) {
-        echo "<ul class=\"section\">\n";
-    }
-
-    if ($ismoving) {
-        echo '<li><a title="'.$strmovefull.'"'.
-             ' href="'.$CFG->wwwroot.'/course/mod.php?movetosection='.$section->id.'&amp;sesskey='.sesskey().'">'.
-             '<img class="movetarget" src="'.$OUTPUT->pix_url('movehere') . '" '.
-             ' alt="'.$strmovehere.'" /></a></li>
-             ';
-    }
-    if (!empty($modinfo->sections[$section->section]) || $ismoving) {
-        echo "</ul><!--class='section'-->\n\n";
-    }
-}
-
-/**
- * Prints the menus to add activities and resources.
- *
- * @param stdClass $course The course
- * @param int $section relative section number (field course_sections.section)
- * @param null|array $modnames An array containing the list of modules and their names
- *     if omitted will be taken from get_module_types_names()
- * @param bool $vertical Vertical orientation
- * @param bool $return Return the menus or send them to output
- * @param int $sectionreturn The section to link back to
- * @return void|string depending on $return
- */
-function print_section_add_menus($course, $section, $modnames = null, $vertical=false, $return=false, $sectionreturn=null) {
-    global $CFG, $OUTPUT;
-
-    if ($modnames === null) {
-        $modnames = get_module_types_names();
-    }
-
-    // check to see if user can add menus and there are modules to add
-    if (!has_capability('moodle/course:manageactivities', context_course::instance($course->id))
-            || empty($modnames)) {
-        if ($return) {
-            return '';
-        } else {
-            return false;
-        }
-    }
-
-    // Retrieve all modules with associated metadata
-    $modules = get_module_metadata($course, $modnames, $sectionreturn);
-
-    // We'll sort resources and activities into two lists
-    $resources = array();
-    $activities = array();
-
-    // We need to add the section section to the link for each module
-    $sectionlink = '&section=' . $section . '&sr=' . $sectionreturn;
-
-    foreach ($modules as $module) {
-        if (isset($module->types)) {
-            // This module has a subtype
-            // NOTE: this is legacy stuff, module subtypes are very strongly discouraged!!
-            $subtypes = array();
-            foreach ($module->types as $subtype) {
-                $subtypes[$subtype->link . $sectionlink] = $subtype->title;
-            }
-
-            // Sort module subtypes into the list
-            if (!empty($module->title)) {
-                // This grouping has a name
-                if ($module->archetype == MOD_CLASS_RESOURCE) {
-                    $resources[] = array($module->title=>$subtypes);
-                } else {
-                    $activities[] = array($module->title=>$subtypes);
-                }
-            } else {
-                // This grouping does not have a name
-                if ($module->archetype == MOD_CLASS_RESOURCE) {
-                    $resources = array_merge($resources, $subtypes);
-                } else {
-                    $activities = array_merge($activities, $subtypes);
-                }
-            }
-        } else {
-            // This module has no subtypes
-            if ($module->archetype == MOD_ARCHETYPE_RESOURCE) {
-                $resources[$module->link . $sectionlink] = $module->title;
-            } else if ($module->archetype === MOD_ARCHETYPE_SYSTEM) {
-                // System modules cannot be added by user, do not add to dropdown
-            } else {
-                $activities[$module->link . $sectionlink] = $module->title;
-            }
-        }
-    }
-
-    $straddactivity = get_string('addactivity');
-    $straddresource = get_string('addresource');
-    $sectionname = get_section_name($course, $section);
-    $strresourcelabel = get_string('addresourcetosection', null, $sectionname);
-    $stractivitylabel = get_string('addactivitytosection', null, $sectionname);
-
-    $output = html_writer::start_tag('div', array('class' => 'section_add_menus', 'id' => 'add_menus-section-' . $section));
-
-    if (!$vertical) {
-        $output .= html_writer::start_tag('div', array('class' => 'horizontal'));
-    }
-
-    if (!empty($resources)) {
-        $select = new url_select($resources, '', array(''=>$straddresource), "ressection$section");
-        $select->set_help_icon('resources');
-        $select->set_label($strresourcelabel, array('class' => 'accesshide'));
-        $output .= $OUTPUT->render($select);
-    }
-
-    if (!empty($activities)) {
-        $select = new url_select($activities, '', array(''=>$straddactivity), "section$section");
-        $select->set_help_icon('activities');
-        $select->set_label($stractivitylabel, array('class' => 'accesshide'));
-        $output .= $OUTPUT->render($select);
-    }
-
-    if (!$vertical) {
-        $output .= html_writer::end_tag('div');
-    }
-
-    $output .= html_writer::end_tag('div');
-
-    if (course_ajax_enabled($course)) {
-        $straddeither = get_string('addresourceoractivity');
-        // The module chooser link
-        $modchooser = html_writer::start_tag('div', array('class' => 'mdl-right'));
-        $modchooser.= html_writer::start_tag('div', array('class' => 'section-modchooser'));
-        $icon = $OUTPUT->pix_icon('t/add', '');
-        $span = html_writer::tag('span', $straddeither, array('class' => 'section-modchooser-text'));
-        $modchooser .= html_writer::tag('span', $icon . $span, array('class' => 'section-modchooser-link'));
-        $modchooser.= html_writer::end_tag('div');
-        $modchooser.= html_writer::end_tag('div');
-
-        // Wrap the normal output in a noscript div
-        $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'));
-        } else {
-            // If the module chooser is disabled, we need to ensure that the dropdowns are shown even if javascript is disabled
-            $output = html_writer::tag('div', $output, array('class' => 'show addresourcedropdown'));
-            $modchooser = html_writer::tag('div', $modchooser, array('class' => 'hide addresourcemodchooser'));
-        }
-        $output = $modchooser . $output;
-    }
-
-    if ($return) {
-        return $output;
-    } else {
-        echo $output;
-    }
-}
-
 /**
  * Retrieve all metadata for the requested modules
  *
@@ -1892,7 +1119,10 @@ function get_module_metadata($course, $modnames, $sectionreturn = null) {
     }
 
     $return = array();
-    $urlbase = "/course/mod.php?id=$course->id&sesskey=".sesskey().'&sr='.$sectionreturn.'&add=';
+    $urlbase = new moodle_url('/course/mod.php', array('id' => $course->id, 'sesskey' => sesskey()));
+    if ($sectionreturn !== null) {
+        $urlbase->param('sr', $sectionreturn);
+    }
     foreach($modnames as $modname => $modnamestr) {
         if (!course_allowed_module($course, $modname)) {
             continue;
@@ -1939,16 +1169,16 @@ function get_module_metadata($course, $modnames, $sectionreturn = null) {
                     if (get_string_manager()->string_exists('help' . $subtype->name, $modname)) {
                         $subtype->help = get_string('help' . $subtype->name, $modname);
                     }
-                    $subtype->link = $urlbase . $subtype->type;
+                    $subtype->link = new moodle_url($urlbase, array('add' => $modname, 'type' => $subtype->name));
                     $group->types[] = $subtype;
                 }
                 $modlist[$course->id][$modname] = $group;
             }
         } else {
             $module = new stdClass();
-            $module->title = get_string('modulename', $modname);
+            $module->title = $modnamestr;
             $module->name = $modname;
-            $module->link = $urlbase . $modname;
+            $module->link = new moodle_url($urlbase, array('add' => $modname));
             $module->icon = $OUTPUT->pix_icon('icon', '', $module->name, array('class' => 'icon'));
             $sm = get_string_manager();
             if ($sm->string_exists('modulename_help', $modname)) {
@@ -2179,7 +1409,7 @@ function get_course_category_tree($id = 0, $depth = 0) {
  * Recursive function to print out all the categories in a nice format
  * with or without courses included
  */
-function print_whole_category_list($category=NULL, $displaylist=NULL, $parentslist=NULL, $depth=-1, $showcourses = true) {
+function print_whole_category_list($category=NULL, $displaylist=NULL, $parentslist=NULL, $depth=-1, $showcourses = true, $categorycourses=NULL) {
     global $CFG;
 
     // maxcategorydepth == 0 meant no limit
@@ -2191,9 +1421,17 @@ function print_whole_category_list($category=NULL, $displaylist=NULL, $parentsli
         make_categories_list($displaylist, $parentslist);
     }
 
+    if (!$categorycourses) {
+        if ($category) {
+            $categorycourses = get_category_courses_array($category->id);
+        } else {
+            $categorycourses = get_category_courses_array();
+        }
+    }
+
     if ($category) {
         if ($category->visible or has_capability('moodle/category:viewhiddencategories', context_system::instance())) {
-            print_category_info($category, $depth, $showcourses);
+            print_category_info($category, $depth, $showcourses, $categorycourses[$category->id]);
         } else {
             return;  // Don't bother printing children of invisible categories
         }
@@ -2217,11 +1455,41 @@ function print_whole_category_list($category=NULL, $displaylist=NULL, $parentsli
             $down = $last ? false : true;
             $first = false;
 
-            print_whole_category_list($cat, $displaylist, $parentslist, $depth + 1, $showcourses);
+            print_whole_category_list($cat, $displaylist, $parentslist, $depth + 1, $showcourses, $categorycourses);
         }
     }
 }
 
+/**
+ * Gets an array whose keys are category ids and whose values are arrays of courses in the corresponding category.
+ *
+ * @param int $categoryid
+ * @return array
+ */
+function get_category_courses_array($categoryid = 0) {
+    $tree = get_course_category_tree($categoryid);
+    $flattened = array();
+    foreach ($tree as $category) {
+        get_category_courses_array_recursively($flattened, $category);
+    }
+    return $flattened;
+}
+
+/**
+ * Recursive function to help flatten the course category tree.
+ *
+ * Do not call this function directly, instead calll its parent function {@link get_category_courses_array}
+ *
+ * @param array &$flattened An array passed by reference in which to store courses for each category.
+ * @param stdClass $category The category to get courses for.
+ */
+function get_category_courses_array_recursively(array &$flattened, $category) {
+    $flattened[$category->id] = $category->courses;
+    foreach ($category->categories as $childcategory) {
+        get_category_courses_array_recursively($flattened, $childcategory);
+    }
+}
+
 /**
  * This function will return $options array for html_writer::select(), with whitespace to denote nesting.
  */
@@ -2240,10 +1508,16 @@ function make_categories_options() {
 }
 
 /**
- * Prints the category info in indented fashion
+ * Prints the category information.
+ *
  * This function is only used by print_whole_category_list() above
+ *
+ * @param stdClass $category
+ * @param int $depth The depth of the category.
+ * @param bool $showcourses If set to true course information will also be printed.
+ * @param array|null $courses An array of courses belonging to the category, or null if you don't have it yet.
  */
-function print_category_info($category, $depth=0, $showcourses = false) {
+function print_category_info($category, $depth = 0, $showcourses = false, array $courses = null) {
     global $CFG, $DB, $OUTPUT;
 
     $strsummary = get_string('summary');
@@ -2264,7 +1538,9 @@ function print_category_info($category, $depth=0, $showcourses = false) {
         $catimage = "&nbsp;";
     }
 
-    $courses = get_courses($category->id, 'c.sortorder ASC', 'c.id,c.sortorder,c.visible,c.fullname,c.shortname,c.summary');
+    if (is_null($courses)) {
+        $courses = get_courses($category->id, 'c.sortorder ASC', 'c.id,c.sortorder,c.visible,c.fullname,c.shortname,c.summary');
+    }
     $context = context_coursecat::instance($category->id);
     $fullname = format_string($category->name, true, array('context' => $context));
 
@@ -2419,7 +1695,9 @@ function update_category_button($categoryid = 0) {
 }
 
 /**
- * Category is 0 (for all courses) or an object
+ * Print courses in category. If category is 0 then all courses are printed.
+ * @param int|stdClass $category category object or id.
+ * @return bool true if courses found and printed, else false.
  */
 function print_courses($category) {
     global $CFG, $OUTPUT;
@@ -2467,8 +1745,10 @@ function print_courses($category) {
             echo html_writer::start_tag('div', array('class'=>'addcoursebutton'));
             echo $OUTPUT->single_button(new moodle_url('/course/edit.php', $options), get_string("addnewcourse"));
             echo html_writer::end_tag('div');
+            return false;
         }
     }
+    return true;
 }
 
 /**
@@ -2566,6 +1846,7 @@ function print_course($course, $highlightterms = '') {
     if ($icons = enrol_get_course_info_icons($course)) {
         echo html_writer::start_tag('div', array('class'=>'enrolmenticons'));
         foreach ($icons as $icon) {
+            $icon->attributes["alt"] .= ": ". format_string($coursename, true, array('context'=>$context));
             echo $OUTPUT->render($icon);
         }
         echo html_writer::end_tag('div'); // End of enrolmenticons div
@@ -2868,6 +2149,13 @@ function set_coursemodule_visible($id, $visible) {
     if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
         return false;
     }
+
+    // Create events and propagate visibility to associated grade items if the value has changed.
+    // Only do this if it's changed to avoid accidently overwriting manual showing/hiding of student grades.
+    if ($cm->visible == $visible) {
+        return true;
+    }
+
     if (!$modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module))) {
         return false;
     }
@@ -2881,7 +2169,7 @@ function set_coursemodule_visible($id, $visible) {
         }
     }
 
-    // hide the associated grade items so the teacher doesn't also have to go to the gradebook and hide them there
+    // Hide the associated grade items so the teacher doesn't also have to go to the gradebook and hide them there.
     $grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename, 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course));
     if ($grade_items) {
         foreach ($grade_items as $grade_item) {
@@ -3155,19 +2443,15 @@ function moveto_module($mod, $section, $beforemod=NULL) {
 }
 
 /**
- * Produces the editing buttons for a module
+ * Returns the list of all editing actions that current user can perform on the module
  *
- * @global core_renderer $OUTPUT
- * @staticvar type $str
- * @param stdClass $mod The module to produce editing buttons for
- * @param bool $absolute_ignored ignored - all links are absolute
- * @param bool $moveselect If true a move seleciton process is used (default true)
- * @param int $indent The current indenting
- * @param int $section The section to link back to
- * @return string XHTML for the editing buttons
+ * @param cm_info $mod The module to produce editing buttons for
+ * @param int $indent The current indenting (default -1 means no move left-right actions)
+ * @param int $sr The section to link back to (used for creating the links)
+ * @return array array of action_link or pix_icon objects
  */
-function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $moveselect = true, $indent=-1, $section=null) {
-    global $CFG, $OUTPUT, $COURSE;
+function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
+    global $COURSE, $SITE;
 
     static $str;
 
@@ -3179,43 +2463,36 @@ function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $movesele
 
     // no permission to edit anything
     if (!has_any_capability($editcaps, $modcontext) and !has_all_capabilities($dupecaps, $coursecontext)) {
-        return false;
+        return array();
     }
 
     $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
 
     if (!isset($str)) {
-        $str = new stdClass;
-        $str->assign         = get_string("assignroles", 'role');
-        $str->delete         = get_string("delete");
-        $str->move           = get_string("move");
-        $str->moveup         = get_string("moveup");
-        $str->movedown       = get_string("movedown");
-        $str->moveright      = get_string("moveright");
-        $str->moveleft       = get_string("moveleft");
-        $str->update         = get_string("update");
-        $str->duplicate      = get_string("duplicate");
-        $str->hide           = get_string("hide");
-        $str->show           = get_string("show");
+        $str = get_strings(array('delete', 'move', 'moveright', 'moveleft',
+            'update', 'duplicate', 'hide', 'show', 'edittitle'), 'moodle');
+        $str->assign         = get_string('assignroles', 'role');
         $str->groupsnone     = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsnone"));
         $str->groupsseparate = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsseparate"));
         $str->groupsvisible  = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsvisible"));
         $str->forcedgroupsnone     = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsnone"));
         $str->forcedgroupsseparate = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsseparate"));
         $str->forcedgroupsvisible  = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsvisible"));
-        $str->edittitle = get_string('edittitle', 'moodle');
     }
 
     $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
 
-    if ($section !== null) {
-        $baseurl->param('sr', $section);
+    if ($sr !== null) {
+        $baseurl->param('sr', $sr);
     }
     $actions = array();
 
     // AJAX edit title
-    if ($mod->modname !== 'label' && $hasmanageactivities && course_ajax_enabled($COURSE)) {
-        $actions[] = new action_link(
+    if ($mod->modname !== 'label' && $hasmanageactivities &&
+                (($mod->course == $COURSE->id && course_ajax_enabled($COURSE)) ||
+                 ($mod->course == SITEID && course_ajax_enabled($SITE)))) {
+        // we will not display link if we are on some other-course page (where we should not see this module anyway)
+        $actions['title'] = new action_link(
             new moodle_url($baseurl, array('update' => $mod->id)),
             new pix_icon('t/editstring', $str->edittitle, 'moodle', array('class' => 'iconsmall visibleifjs', 'title' => '')),
             null,
@@ -3234,7 +2511,7 @@ function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $movesele
         }
 
         if ($indent > 0) {
-            $actions[] = new action_link(
+            $actions['moveleft'] = new action_link(
                 new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '-1')),
                 new pix_icon($leftarrow, $str->moveleft, 'moodle', array('class' => 'iconsmall', 'title' => '')),
                 null,
@@ -3242,7 +2519,7 @@ function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $movesele
             );
         }
         if ($indent >= 0) {
-            $actions[] = new action_link(
+            $actions['moveright'] = new action_link(
                 new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '1')),
                 new pix_icon($rightarrow, $str->moveright, 'moodle', array('class' => 'iconsmall', 'title' => '')),
                 null,
@@ -3253,32 +2530,17 @@ function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $movesele
 
     // move
     if ($hasmanageactivities) {
-        if ($moveselect) {
-            $actions[] = new action_link(
-                new moodle_url($baseurl, array('copy' => $mod->id)),
-                new pix_icon('t/move', $str->move, 'moodle', array('class' => 'iconsmall', 'title' => '')),
-                null,
-                array('class' => 'editing_move', 'title' => $str->move)
-            );
-        } else {
-            $actions[] = new action_link(
-                new moodle_url($baseurl, array('id' => $mod->id, 'move' => '-1')),
-                new pix_icon('t/up', $str->moveup, 'moodle', array('class' => 'iconsmall', 'title' => '')),
-                null,
-                array('class' => 'editing_moveup', 'title' => $str->moveup)
-            );
-            $actions[] = new action_link(
-                new moodle_url($baseurl, array('id' => $mod->id, 'move' => '1')),
-                new pix_icon('t/down', $str->movedown, 'moodle', array('class' => 'iconsmall', 'title' => '')),
-                null,
-                array('class' => 'editing_movedown', 'title' => $str->movedown)
-            );
-        }
+        $actions['move'] = new action_link(
+            new moodle_url($baseurl, array('copy' => $mod->id)),
+            new pix_icon('t/move', $str->move, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+            null,
+            array('class' => 'editing_move', 'title' => $str->move)
+        );
     }
 
     // Update
     if ($hasmanageactivities) {
-        $actions[] = new action_link(
+        $actions['update'] = new action_link(
             new moodle_url($baseurl, array('update' => $mod->id)),
             new pix_icon('t/edit', $str->update, 'moodle', array('class' => 'iconsmall', 'title' => '')),
             null,
@@ -3287,8 +2549,10 @@ function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $movesele
     }
 
     // Duplicate (require both target import caps to be able to duplicate and backup2 support, see modduplicate.php)
-    if (has_all_capabilities($dupecaps, $coursecontext) && plugin_supports('mod', $mod->modname, FEATURE_BACKUP_MOODLE2)) {
-        $actions[] = new action_link(
+    // note that restoring on front page is never allowed
+    if ($mod->course != SITEID && has_all_capabilities($dupecaps, $coursecontext) &&
+            plugin_supports('mod', $mod->modname, FEATURE_BACKUP_MOODLE2)) {
+        $actions['duplicate'] = new action_link(
             new moodle_url($baseurl, array('duplicate' => $mod->id)),
             new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall', 'title' => '')),
             null,
@@ -3298,7 +2562,7 @@ function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $movesele
 
     // Delete
     if ($hasmanageactivities) {
-        $actions[] = new action_link(
+        $actions['delete'] = new action_link(
             new moodle_url($baseurl, array('delete' => $mod->id)),
             new pix_icon('t/delete', $str->delete, 'moodle', array('class' => 'iconsmall', 'title' => '')),
             null,
@@ -3309,14 +2573,14 @@ function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $movesele
     // hideshow
     if (has_capability('moodle/course:activityvisibility', $modcontext)) {
         if ($mod->visible) {
-            $actions[] = new action_link(
+            $actions['hide'] = new action_link(
                 new moodle_url($baseurl, array('hide' => $mod->id)),
                 new pix_icon('t/hide', $str->hide, 'moodle', array('class' => 'iconsmall', 'title' => '')),
                 null,
                 array('class' => 'editing_hide', 'title' => $str->hide)
             );
         } else {
-            $actions[] = new action_link(
+            $actions['show'] = new action_link(
                 new moodle_url($baseurl, array('show' => $mod->id)),
                 new pix_icon('t/show', $str->show, 'moodle', array('class' => 'iconsmall', 'title' => '')),
                 null,
@@ -3326,61 +2590,54 @@ function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $movesele
     }
 
     // groupmode
-    if ($hasmanageactivities and $mod->groupmode !== false) {
-        if ($mod->groupmode == SEPARATEGROUPS) {
-            $groupmode = 0;
+    if ($hasmanageactivities and plugin_supports('mod', $mod->modname, FEATURE_GROUPS, 0)) {
+        if ($mod->coursegroupmodeforce) {
+            $modgroupmode = $mod->coursegroupmode;
+        } else {
+            $modgroupmode = $mod->groupmode;
+        }
+        if ($modgroupmode == SEPARATEGROUPS) {
+            $groupmode = NOGROUPS;
             $grouptitle = $str->groupsseparate;
             $forcedgrouptitle = $str->forcedgroupsseparate;
-            $groupclass = 'editing_groupsseparate';
+            $actionname = 'groupsseparate';
             $groupimage = 't/groups';
-        } else if ($mod->groupmode == VISIBLEGROUPS) {
-            $groupmode = 1;
+        } else if ($modgroupmode == VISIBLEGROUPS) {
+            $groupmode = SEPARATEGROUPS;
             $grouptitle = $str->groupsvisible;
             $forcedgrouptitle = $str->forcedgroupsvisible;
-            $groupclass = 'editing_groupsvisible';
+            $actionname = 'groupsvisible';
             $groupimage = 't/groupv';
         } else {
-            $groupmode = 2;
+            $groupmode = VISIBLEGROUPS;
             $grouptitle = $str->groupsnone;
             $forcedgrouptitle = $str->forcedgroupsnone;
-            $groupclass = 'editing_groupsnone';
+            $actionname = 'groupsnone';
             $groupimage = 't/groupn';
         }
-        if ($mod->groupmodelink) {
-            $actions[] = new action_link(
+        if (!$mod->coursegroupmodeforce) {
+            $actions[$actionname] = new action_link(
                 new moodle_url($baseurl, array('id' => $mod->id, 'groupmode' => $groupmode)),
                 new pix_icon($groupimage, $grouptitle, 'moodle', array('class' => 'iconsmall', 'title' => '')),
                 null,
-                array('class' => $groupclass, 'title' => $grouptitle)
+                array('class' => 'editing_'. $actionname, 'title' => $grouptitle)
             );
         } else {
-            $actions[] = new pix_icon($groupimage, $forcedgrouptitle, 'moodle', array('title' => $forcedgrouptitle, 'class' => 'iconsmall'));
+            $actions[$actionname] = new pix_icon($groupimage, $forcedgrouptitle, 'moodle', array('title' => $forcedgrouptitle, 'class' => 'iconsmall'));
         }
     }
 
     // Assign
     if (has_capability('moodle/role:assign', $modcontext)){
-        $actions[] = new action_link(
-            new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid' => $modcontext->id)),
+        $actions['assign'] = new action_link(
+            new moodle_url('/admin/roles/assign.php', array('contextid' => $modcontext->id)),
             new pix_icon('t/assignroles', $str->assign, 'moodle', array('class' => 'iconsmall', 'title' => '')),
             null,
             array('class' => 'editing_assign', 'title' => $str->assign)
         );
     }
 
-    // The space added before the <span> is a ugly hack but required to set the CSS property white-space: nowrap
-    // and having it to work without attaching the preceding text along with it. Hopefully the refactoring of
-    // the course page HTML will allow this to be removed.
-    $output = ' ' . html_writer::start_tag('span', array('class' => 'commands'));
-    foreach ($actions as $action) {
-        if ($action instanceof renderable) {
-            $output .= $OUTPUT->render($action);
-        } else {
-            $output .= $action;
-        }
-    }
-    $output .= html_writer::end_tag('span');
-    return $output;
+    return $actions;
 }
 
 /**
@@ -4548,17 +3805,6 @@ function include_course_ajax($course, $usedmodules = array(), $enabledmodules =
     // Load drag and drop upload AJAX.
     dndupload_add_to_course($course, $enabledmodules);
 
-    // Add the module chooser
-    $PAGE->requires->yui_module('moodle-course-modchooser',
-        'M.course.init_chooser',
-        array(array('courseid' => $course->id, 'closeButtonTitle' => get_string('close', 'editor')))
-    );
-    $PAGE->requires->strings_for_js(array(
-            'addresourceoractivity',
-            'modchooserenable',
-            'modchooserdisable',
-    ), 'moodle');
-
     return true;
 }
 
index d175a87..7247671 100644 (file)
@@ -45,7 +45,7 @@ $section    = $DB->get_record('course_sections', array('id' => $cm->section, 'co
 require_login($course);
 require_sesskey();
 require_capability('moodle/course:manageactivities', $context);
-// Require both target import caps to be able to duplicate, see make_editing_buttons()
+// Require both target import caps to be able to duplicate, see course_get_cm_edit_actions()
 require_capability('moodle/backup:backuptargetimport', $context);
 require_capability('moodle/restore:restoretargetimport', $context);
 
index f71c89b..b4feb9d 100644 (file)
@@ -341,7 +341,7 @@ abstract class moodleform_mod extends moodleform {
                     continue;
                 }
                 if ($gradedata['conditiongrademin'] !== '' && $gradedata['conditiongrademax'] !== '' &&
-                        unformat_float($gradedata['conditiongrademax']) < unformat_float($gradedata['conditiongrademin'])) {
+                        unformat_float($gradedata['conditiongrademax']) <= unformat_float($gradedata['conditiongrademin'])) {
                     $errors["conditiongradegroup[{$i}]"] = get_string('badgradelimits', 'condition');
                     continue;
                 }
index 33c15b4..ab89e16 100644 (file)
@@ -49,6 +49,53 @@ class core_course_renderer extends plugin_renderer_base {
     public function __construct(moodle_page $page, $target) {
         $this->strings = new stdClass;
         parent::__construct($page, $target);
+        $this->add_modchoosertoggle();
+    }
+
+    /**
+     * Adds the item in course settings navigation to toggle modchooser
+     *
+     * Theme can overwrite as an empty function to exclude it (for example if theme does not
+     * use modchooser at all)
+     */
+    protected function add_modchoosertoggle() {
+        global $CFG;
+        static $modchoosertoggleadded = false;
+        // Add the module chooser toggle to the course page
+        if ($modchoosertoggleadded || $this->page->state > moodle_page::STATE_PRINTING_HEADER ||
+                $this->page->course->id == SITEID ||
+                !$this->page->user_is_editing() ||
+                !($context = context_course::instance($this->page->course->id)) ||
+                !has_capability('moodle/course:update', $context) ||
+                !course_ajax_enabled($this->page->course) ||
+                !($coursenode = $this->page->settingsnav->find('courseadmin', navigation_node::TYPE_COURSE)) ||
+                !$coursenode->get('editsettings')) {
+            // too late or we are on site page or we could not find the course settings node
+            // or we are not allowed to edit
+            return;
+        }
+        $modchoosertoggleadded = true;
+        if ($this->page->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) {
+            // We are on the course page, retain the current page params e.g. section.
+            $modchoosertoggleurl = clone($this->page->url);
+        } else {
+            // Edit on the main course page.
+            $modchoosertoggleurl = new moodle_url('/course/view.php', array('id' => $this->page->course->id,
+                'return' => $this->page->url->out_as_local_url(false)));
+        }
+        $modchoosertoggleurl->param('sesskey', sesskey());
+        if ($usemodchooser = get_user_preferences('usemodchooser', $CFG->modchooserdefault)) {
+            $modchoosertogglestring = get_string('modchooserdisable', 'moodle');
+            $modchoosertoggleurl->param('modchooser', 'off');
+        } else {
+            $modchoosertogglestring = get_string('modchooserenable', 'moodle');
+            $modchoosertoggleurl->param('modchooser', 'on');
+        }
+        $modchoosertoggle = navigation_node::create($modchoosertogglestring, $modchoosertoggleurl, navigation_node::TYPE_SETTING);
+        $coursenode->add_node($modchoosertoggle, 'editsettings');
+        $modchoosertoggle->add_class('modchoosertoggle');
+        $modchoosertoggle->add_class('visibleifjs');
+        user_preference_allow_ajax_update('usemodchooser', PARAM_BOOL);
     }
 
     /**
@@ -70,7 +117,7 @@ class core_course_renderer extends plugin_renderer_base {
         if (!empty($CFG->coursecontact)) {
             $coursecontactroles = explode(',', $CFG->coursecontact);
             foreach ($coursecontactroles as $roleid) {
-                if ($users = get_role_users($roleid, $context, true)) {
+                if ($users = get_role_users($roleid, $context, true, '', null, false)) {
                     foreach ($users as $teacher) {
                         $role = new stdClass();
                         $role->id = $teacher->roleid;
@@ -211,7 +258,22 @@ class core_course_renderer extends plugin_renderer_base {
      * @return string The composed HTML for the module
      */
     public function course_modchooser($modules, $course) {
-        global $OUTPUT;
+        static $isdisplayed = false;
+        if ($isdisplayed) {
+            return '';
+        }
+        $isdisplayed = true;
+
+        // Add the module chooser
+        $this->page->requires->yui_module('moodle-course-modchooser',
+        'M.course.init_chooser',
+        array(array('courseid' => $course->id, 'closeButtonTitle' => get_string('close', 'editor')))
+        );
+        $this->page->requires->strings_for_js(array(
+                'addresourceoractivity',
+                'modchooserenable',
+                'modchooserdisable',
+        ), 'moodle');
 
         // Add the header
         $header = html_writer::tag('div', get_string('addresourceoractivity', 'moodle'),
@@ -354,4 +416,616 @@ class core_course_renderer extends plugin_renderer_base {
         $module->help = '';
         return $this->course_modchooser_module($module, array('moduletypetitle'));
     }
+
+    /**
+     * Renders HTML for displaying the sequence of course module editing buttons
+     *
+     * @see course_get_cm_edit_actions()
+     *
+     * @param array $actions array of action_link or pix_icon objects
+     * @return string
+     */
+    public function course_section_cm_edit_actions($actions) {
+        $output = html_writer::start_tag('span', array('class' => 'commands'));
+        foreach ($actions as $action) {
+            if ($action instanceof renderable) {
+                $output .= $this->output->render($action);
+            } else {
+                $output .= $action;
+            }
+        }
+        $output .= html_writer::end_tag('span');
+        return $output;
+    }
+
+    /**
+     * Renders HTML for the menus to add activities and resources to the current course
+     *
+     * Note, if theme overwrites this function and it does not use modchooser,
+     * see also {@link core_course_renderer::add_modchoosertoggle()}
+     *
+     * @param stdClass $course
+     * @param int $section relative section number (field course_sections.section)
+     * @param int $sectionreturn The section to link back to
+     * @param array $displayoptions additional display options, for example blocks add
+     *     option 'inblock' => true, suggesting to display controls vertically
+     * @return string
+     */
+    function course_section_add_cm_control($course, $section, $sectionreturn = null, $displayoptions = array()) {
+        global $CFG;
+
+        $vertical = !empty($displayoptions['inblock']);
+
+        // check to see if user can add menus and there are modules to add
+        if (!has_capability('moodle/course:manageactivities', context_course::instance($course->id))
+                || !$this->page->user_is_editing()
+                || !($modnames = get_module_types_names()) || empty($modnames)) {
+            return '';
+        }
+
+        // Retrieve all modules with associated metadata
+        $modules = get_module_metadata($course, $modnames, $sectionreturn);
+        $urlparams = array('section' => $section);
+
+        // We'll sort resources and activities into two lists
+        $activities = array(MOD_CLASS_ACTIVITY => array(), MOD_CLASS_RESOURCE => array());
+
+        foreach ($modules as $module) {
+            if (!array_key_exists($module->archetype, $activities)) {
+                // System modules cannot be added by user, do not add to dropdown
+            } else if (isset($module->types)) {
+                // This module has a subtype
+                // NOTE: this is legacy stuff, module subtypes are very strongly discouraged!!
+                $subtypes = array();
+                foreach ($module->types as $subtype) {
+                    $link = $subtype->link->out(true, $urlparams);
+                    $subtypes[$link] = $subtype->title;
+                }
+
+                // Sort module subtypes into the list
+                if (!empty($module->title)) {
+                    // This grouping has a name
+                    $activities[$module->archetype][] = array($module->title => $subtypes);
+                } else {
+                    // This grouping does not have a name
+                    $activities[$module->archetype] = array_merge($activities[$module->archetype], $subtypes);
+                }
+            } else {
+                // This module has no subtypes
+                $link = $module->link->out(true, $urlparams);
+                $activities[$module->archetype][$link] = $module->title;
+            }
+        }
+
+        $straddactivity = get_string('addactivity');
+        $straddresource = get_string('addresource');
+        $sectionname = get_section_name($course, $section);
+        $strresourcelabel = get_string('addresourcetosection', null, $sectionname);
+        $stractivitylabel = get_string('addactivitytosection', null, $sectionname);
+
+        $output = html_writer::start_tag('div', array('class' => 'section_add_menus', 'id' => 'add_menus-section-' . $section));
+
+        if (!$vertical) {
+            $output .= html_writer::start_tag('div', array('class' => 'horizontal'));
+        }
+
+        if (!empty($activities[MOD_CLASS_RESOURCE])) {
+            $select = new url_select($activities[MOD_CLASS_RESOURCE], '', array(''=>$straddresource), "ressection$section");
+            $select->set_help_icon('resources');
+            $select->set_label($strresourcelabel, array('class' => 'accesshide'));
+            $output .= $this->output->render($select);
+        }
+
+        if (!empty($activities[MOD_CLASS_ACTIVITY])) {
+            $select = new url_select($activities[MOD_CLASS_ACTIVITY], '', array(''=>$straddactivity), "section$section");
+            $select->set_help_icon('activities');
+            $select->set_label($stractivitylabel, array('class' => 'accesshide'));
+            $output .= $this->output->render($select);
+        }
+
+        if (!$vertical) {
+            $output .= html_writer::end_tag('div');
+        }
+
+        $output .= html_writer::end_tag('div');
+
+        if (course_ajax_enabled($course) && $course->id == $this->page->course->id) {
+            // modchooser can be added only for the current course set on the page!
+            $straddeither = get_string('addresourceoractivity');
+            // The module chooser link
+            $modchooser = html_writer::start_tag('div', array('class' => 'mdl-right'));
+            $modchooser.= html_writer::start_tag('div', array('class' => 'section-modchooser'));
+            $icon = $this->output->pix_icon('t/add', '');
+            $span = html_writer::tag('span', $straddeither, array('class' => 'section-modchooser-text'));
+            $modchooser .= html_writer::tag('span', $icon . $span, array('class' => 'section-modchooser-link'));
+            $modchooser.= html_writer::end_tag('div');
+            $modchooser.= html_writer::end_tag('div');
+
+            // Wrap the normal output in a noscript div
+            $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'));
+            } else {
+                // If the module chooser is disabled, we need to ensure that the dropdowns are shown even if javascript is disabled
+                $output = html_writer::tag('div', $output, array('class' => 'show addresourcedropdown'));
+                $modchooser = html_writer::tag('div', $modchooser, array('class' => 'hide addresourcemodchooser'));
+            }
+            $output = $this->course_modchooser($modules, $course) . $modchooser . $output;
+        }
+
+        return $output;
+    }
+
+    /**
+     * Renders html for completion box on course page
+     *
+     * If completion is disabled, returns empty string
+     * If completion is automatic, returns an icon of the current completion state
+     * If completion is manual, returns a form (with an icon inside) that allows user to
+     * toggle completion
+     *
+     * @param stdClass $course course object
+     * @param completion_info $completioninfo completion info for the course, it is recommended
+     *     to fetch once for all modules in course/section for performance
+     * @param cm_info $mod module to show completion for
+     * @param array $displayoptions display options, not used in core
+     * @return string
+     */
+    public function course_section_cm_completion($course, &$completioninfo, cm_info $mod, $displayoptions = array()) {
+        global $CFG;
+        $output = '';
+        if (!empty($displayoptions['hidecompletion']) || !isloggedin() || isguestuser() || !$mod->uservisible) {
+            return $output;
+        }
+        if ($completioninfo === null) {
+            $completioninfo = new completion_info($course);
+        }
+        $completion = $completioninfo->is_enabled($mod);
+        if ($completion == COMPLETION_TRACKING_NONE) {
+            return $output;
+        }
+
+        $completiondata = $completioninfo->get_data($mod, true);
+        $completionicon = '';
+
+        if ($this->page->user_is_editing()) {
+            switch ($completion) {
+                case COMPLETION_TRACKING_MANUAL :
+                    $completionicon = 'manual-enabled'; break;
+                case COMPLETION_TRACKING_AUTOMATIC :
+                    $completionicon = 'auto-enabled'; break;
+            }
+        } else if ($completion == COMPLETION_TRACKING_MANUAL) {
+            switch($completiondata->completionstate) {
+                case COMPLETION_INCOMPLETE:
+                    $completionicon = 'manual-n'; break;
+                case COMPLETION_COMPLETE:
+                    $completionicon = 'manual-y'; break;
+            }
+        } else { // Automatic
+            switch($completiondata->completionstate) {
+                case COMPLETION_INCOMPLETE:
+                    $completionicon = 'auto-n'; break;
+                case COMPLETION_COMPLETE:
+                    $completionicon = 'auto-y'; break;
+                case COMPLETION_COMPLETE_PASS:
+                    $completionicon = 'auto-pass'; break;
+                case COMPLETION_COMPLETE_FAIL:
+                    $completionicon = 'auto-fail'; break;
+            }
+        }
+        if ($completionicon) {
+            $formattedname = $mod->get_formatted_name();
+            $imgalt = get_string('completion-alt-' . $completionicon, 'completion', $formattedname);
+            if ($completion == COMPLETION_TRACKING_MANUAL && !$this->page->user_is_editing()) {
+                $imgtitle = get_string('completion-title-' . $completionicon, 'completion', $formattedname);
+                $newstate =
+                    $completiondata->completionstate == COMPLETION_COMPLETE
+                    ? COMPLETION_INCOMPLETE
+                    : COMPLETION_COMPLETE;
+                // In manual mode the icon is a toggle form...
+
+                // If this completion state is used by the
+                // conditional activities system, we need to turn
+                // off the JS.
+                $extraclass = '';
+                if (!empty($CFG->enableavailability) &&
+                        condition_info::completion_value_used_as_condition($course, $mod)) {
+                    $extraclass = ' preventjs';
+                }
+                $output .= html_writer::start_tag('form', array('method' => 'post',
+                    'action' => new moodle_url('/course/togglecompletion.php'),
+                    'class' => 'togglecompletion'. $extraclass));
+                $output .= html_writer::start_tag('div');
+                $output .= html_writer::empty_tag('input', array(
+                    'type' => 'hidden', 'name' => 'id', 'value' => $mod->id));
+                $output .= html_writer::empty_tag('input', array(
+                    'type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
+                $output .= html_writer::empty_tag('input', array(
+                    'type' => 'hidden', 'name' => 'modulename', 'value' => $mod->name));
+                $output .= html_writer::empty_tag('input', array(
+                    'type' => 'hidden', 'name' => 'completionstate', 'value' => $newstate));
+                $output .= html_writer::empty_tag('input', array(
+                    'type' => 'image',
+                    'src' => $this->output->pix_url('i/completion-'.$completionicon),
+                    'alt' => $imgalt, 'title' => $imgtitle));
+                $output .= html_writer::end_tag('div');
+                $output .= html_writer::end_tag('form');
+            } else {
+                // In auto mode, or when editing, the icon is just an image
+                $completionpixicon = new pix_icon('i/completion-'.$completionicon, $imgalt, '',
+                        array('title' => $imgalt));
+                $output .= html_writer::tag('span', $this->output->render($completionpixicon),
+                        array('class' => 'autocompletion'));
+            }
+        }
+        return $output;
+    }
+
+    /**
+     * Checks if course module has any conditions that may make it unavailable for
+     * all or some of the students
+     *
+     * This function is internal and is only used to create CSS classes for the module name/text
+     *
+     * @param cm_info $mod
+     * @return bool
+     */
+    protected function is_cm_conditionally_hidden(cm_info $mod) {
+        global $CFG;
+        $conditionalhidden = false;
+        if (!empty($CFG->enableavailability)) {
+            $conditionalhidden = $mod->availablefrom > time() ||
+                ($mod->availableuntil && $mod->availableuntil < time()) ||
+                count($mod->conditionsgrade) > 0 ||
+                count($mod->conditionscompletion) > 0;
+        }
+        return $conditionalhidden;
+    }
+
+    /**
+     * Renders html to display a name with the link to the course module on a course page
+     *
+     * If module is unavailable for user but still needs to be displayed
+     * in the list, just the name is returned without a link
+     *
+     * Note, that for course modules that never have separate pages (i.e. labels)
+     * this function return an empty string
+     *
+     * @param cm_info $mod
+     * @param array $displayoptions
+     * @return string
+     */
+    public function course_section_cm_name(cm_info $mod, $displayoptions = array()) {
+        global $CFG;
+        $output = '';
+        if (!$mod->uservisible &&
+                (empty($mod->showavailability) || empty($mod->availableinfo))) {
+            // nothing to be displayed to the user
+            return $output;
+        }
+        $url = $mod->get_url();
+        if (!$url) {
+            return $output;
+        }
+
+        //Accessibility: for files get description via icon, this is very ugly hack!
+        $instancename = $mod->get_formatted_name();
+        $altname = '';
+        $altname = $mod->modfullname;
+        // Avoid unnecessary duplication: if e.g. a forum name already
+        // includes the word forum (or Forum, etc) then it is unhelpful
+        // to include that in the accessible description that is added.
+        if (false !== strpos(textlib::strtolower($instancename),
+                textlib::strtolower($altname))) {
+            $altname = '';
+        }
+        // File type after name, for alphabetic lists (screen reader).
+        if ($altname) {
+            $altname = get_accesshide(' '.$altname);
+        }
+
+        $conditionalhidden = $this->is_cm_conditionally_hidden($mod);
+        $accessiblebutdim = !$mod->visible || $conditionalhidden;
+
+        $linkclasses = '';
+        $accesstext = '';
+        $textclasses = '';
+        if ($accessiblebutdim) {
+            $linkclasses .= ' dimmed';
+            $textclasses .= ' dimmed_text';
+            if ($conditionalhidden) {
+                $linkclasses .= ' conditionalhidden';
+                $textclasses .= ' conditionalhidden';
+            }
+            if ($mod->uservisible) {
+                // show accessibility note only if user can access the module himself
+                $accesstext = get_accesshide(get_string('hiddenfromstudents').': ');
+            }
+        }
+
+        // Get on-click attribute value if specified and decode the onclick - it
+        // has already been encoded for display (puke).
+        $onclick = htmlspecialchars_decode($mod->get_on_click(), ENT_QUOTES);
+
+        $groupinglabel = '';
+        if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', context_course::instance($mod->course))) {
+            $groupings = groups_get_all_groupings($mod->course);
+            $groupinglabel = html_writer::tag('span', '('.format_string($groupings[$mod->groupingid]->name).')',
+                    array('class' => 'groupinglabel '.$textclasses));
+        }
+
+        // Display link itself.
+        $activitylink = html_writer::empty_tag('img', array('src' => $mod->get_icon_url(),
+                'class' => 'iconlarge activityicon', 'alt' => $mod->modfullname)) . $accesstext .
+                html_writer::tag('span', $instancename . $altname, array('class' => 'instancename'));
+        if ($mod->uservisible) {
+            $output .= html_writer::link($url, $activitylink, array('class' => $linkclasses, 'onclick' => $onclick)) .
+                    $groupinglabel;
+        } else {
+            // We may be displaying this just in order to show information
+            // about visibility, without the actual link ($mod->uservisible)
+            $output .= html_writer::tag('div', $activitylink, array('class' => $textclasses)) .
+                    $groupinglabel;
+        }
+        return $output;
+    }
+
+    /**
+     * Renders html to display the module content on the course page (i.e. text of the labels)
+     *
+     * @param cm_info $mod
+     * @param array $displayoptions
+     * @return string
+     */
+    public function course_section_cm_text(cm_info $mod, $displayoptions = array()) {
+        $output = '';
+        if (!$mod->uservisible &&
+                (empty($mod->showavailability) || empty($mod->availableinfo))) {
+            // nothing to be displayed to the user
+            return $output;
+        }
+        $content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
+        $conditionalhidden = $this->is_cm_conditionally_hidden($mod);
+        $accessiblebutdim = !$mod->visible || $conditionalhidden;
+        $textclasses = '';
+        $accesstext = '';
+        if ($accessiblebutdim) {
+            $textclasses .= ' dimmed_text';
+            if ($conditionalhidden) {
+                $textclasses .= ' conditionalhidden';
+            }
+            if ($mod->uservisible) {
+                // show accessibility note only if user can access the module himself
+                $accesstext = get_accesshide(get_string('hiddenfromstudents').': ');
+            }
+        }
+        if ($mod->get_url()) {
+            if ($content) {
+                // If specified, display extra content after link.
+                $output = html_writer::tag('div', $content, array('class' =>
+                        trim('contentafterlink ' . $textclasses)));
+            }
+        } else {
+            // No link, so display only content.
+            $output = html_writer::tag('div', $accesstext . $content, array('class' => $textclasses));
+        }
+        return $output;
+    }
+
+    /**
+     * Renders HTML to show course module availability information (for someone who isn't allowed
+     * to see the activity itself, or for staff)
+     *
+     * @param cm_info $mod
+     * @param array $displayoptions
+     * @return string
+     */
+    public function course_section_cm_availability(cm_info $mod, $displayoptions = array()) {
+        global $CFG;
+        if (!$mod->uservisible) {
+            // this is a student who is not allowed to see the module but might be allowed
+            // to see availability info (i.e. "Available from ...")
+            if (!empty($mod->showavailability) && !empty($mod->availableinfo)) {
+                $output = html_writer::tag('div', $mod->availableinfo, array('class' => 'availabilityinfo'));
+            }
+            return $output;
+        }
+        // this is a teacher who is allowed to see module but still should see the
+        // information that module is not available to all/some students
+        $modcontext = context_module::instance($mod->id);
+        $canviewhidden = has_capability('moodle/course:viewhiddenactivities', $modcontext);
+        if ($canviewhidden && !empty($CFG->enableavailability)) {
+            // Don't add availability information if user is not editing and activity is hidden.
+            if ($mod->visible || $this->page->user_is_editing()) {
+                $hidinfoclass = '';
+                if (!$mod->visible) {
+                    $hidinfoclass = 'hide';
+                }
+                $ci = new condition_info($mod);
+                $fullinfo = $ci->get_full_information();
+                if($fullinfo) {
+                    return '<div class="availabilityinfo '.$hidinfoclass.'">'.get_string($mod->showavailability
+                        ? 'userrestriction_visible'
+                        : 'userrestriction_hidden','condition',
+                        $fullinfo).'</div>';
+                }
+            }
+        }
+        return '';
+    }
+
+    /**
+     * Renders HTML to display one course module in a course section
+     *
+     * This includes link, content, availability, completion info and additional information
+     * that module type wants to display (i.e. number of unread forum posts)
+     *
+     * This function calls:
+     * {@link core_course_renderer::course_section_cm_name()}
+     * {@link cm_info::get_after_link()}
+     * {@link core_course_renderer::course_section_cm_text()}
+     * {@link core_course_renderer::course_section_cm_availability()}
+     * {@link core_course_renderer::course_section_cm_completion()}
+     * {@link course_get_cm_edit_actions()}
+     * {@link core_course_renderer::course_section_cm_edit_actions()}
+     *
+     * @param stdClass $course
+     * @param completion_info $completioninfo
+     * @param cm_info $mod
+     * @param int|null $sectionreturn
+     * @param array $displayoptions
+     * @return string
+     */
+    public function course_section_cm($course, &$completioninfo, cm_info $mod, $sectionreturn, $displayoptions = array()) {
+        $output = '';
+        // We return empty string (because course module will not be displayed at all)
+        // if:
+        // 1) The activity is not visible to users
+        // and
+        // 2a) The 'showavailability' option is not set (if that is set,
+        //     we need to display the activity so we can show
+        //     availability info)
+        // or
+        // 2b) The 'availableinfo' is empty, i.e. the activity was
+        //     hidden in a way that leaves no info, such as using the
+        //     eye icon.
+        if (!$mod->uservisible &&
+            (empty($mod->showavailability) || empty($mod->availableinfo))) {
+            return $output;
+        }
+
+        $indentclasses = 'mod-indent';
+        if (!empty($mod->indent)) {
+            $indentclasses .= ' mod-indent-'.$mod->indent;
+            if ($mod->indent > 15) {
+                $indentclasses .= ' mod-indent-huge';
+            }
+        }
+        $output .= html_writer::start_tag('div', array('class' => $indentclasses));
+
+        // Start the div for the activity title, excluding the edit icons.
+        $output .= html_writer::start_tag('div', array('class' => 'activityinstance'));
+
+        // Display the link to the module (or do nothing if module has no url)
+        $output .= $this->course_section_cm_name($mod, $displayoptions);
+
+        // Module can put text after the link (e.g. forum unread)
+        $output .= $mod->get_after_link();
+
+        // Closing the tag which contains everything but edit icons. Content part of the module should not be part of this.
+        $output .= html_writer::end_tag('div'); // .activityinstance
+
+        // If there is content but NO link (eg label), then display the
+        // content here (BEFORE any icons). In this case cons must be
+        // displayed after the content so that it makes more sense visually
+        // and for accessibility reasons, e.g. if you have a one-line label
+        // it should work similarly (at least in terms of ordering) to an
+        // activity.
+        $contentpart = $this->course_section_cm_text($mod, $displayoptions);
+        $url = $mod->get_url();
+        if (empty($url)) {
+            $output .= $contentpart;
+        }
+
+        if ($this->page->user_is_editing()) {
+            $editactions = course_get_cm_edit_actions($mod, $mod->indent, $sectionreturn);
+            $output .= ' '. $this->course_section_cm_edit_actions($editactions);
+            $output .= $mod->get_after_edit_icons();
+        }
+
+        $output .= $this->course_section_cm_completion($course, $completioninfo, $mod, $displayoptions);
+
+        // If there is content AND a link, then display the content here
+        // (AFTER any icons). Otherwise it was displayed before
+        if (!empty($url)) {
+            $output .= $contentpart;
+        }
+
+        // show availability info (if module is not available)
+        $output .= $this->course_section_cm_availability($mod, $displayoptions);
+
+        $output .= html_writer::end_tag('div'); // $indentclasses
+        return $output;
+    }
+
+    /**
+     * Renders HTML to display a list of course modules in a course section
+     * Also displays "move here" controls in Javascript-disabled mode
+     *
+     * This function calls {@link core_course_renderer::course_section_cm()}
+     *
+     * @param stdClass $course course object
+     * @param int|stdClass|section_info $section relative section number or section object
+     * @param int $sectionreturn section number to return to
+     * @param int $displayoptions
+     * @return void
+     */
+    public function course_section_cm_list($course, $section, $sectionreturn = null, $displayoptions = array()) {
+        global $USER;
+
+        $output = '';
+        $modinfo = get_fast_modinfo($course);
+        if (is_object($section)) {
+            $section = $modinfo->get_section_info($section->section);
+        } else {
+            $section = $modinfo->get_section_info($section);
+        }
+        $completioninfo = new completion_info($course);
+
+        // check if we are currently in the process of moving a module with JavaScript disabled
+        $ismoving = $this->page->user_is_editing() && ismoving($course->id);
+        if ($ismoving) {
+            $movingpix = new pix_icon('movehere', get_string('movehere'), 'moodle', array('class' => 'movetarget'));
+            $strmovefull = strip_tags(get_string("movefull", "", "'$USER->activitycopyname'"));
+        }
+
+        // Get the list of modules visible to user (excluding the module being moved if there is one)
+        $moduleshtml = array();
+        if (!empty($modinfo->sections[$section->section])) {
+            foreach ($modinfo->sections[$section->section] as $modnumber) {
+                $mod = $modinfo->cms[$modnumber];
+
+                if ($ismoving and $mod->id == $USER->activitycopy) {
+                    // do not display moving mod
+                    continue;
+                }
+
+                if ($modulehtml = $this->course_section_cm($course,
+                        $completioninfo, $mod, $sectionreturn, $displayoptions)) {
+                    $moduleshtml[$modnumber] = $modulehtml;
+                }
+            }
+        }
+
+        if (!empty($moduleshtml) || $ismoving) {
+
+            $output .= html_writer::start_tag('ul', array('class' => 'section img-text'));
+
+            foreach ($moduleshtml as $modnumber => $modulehtml) {
+                if ($ismoving) {
+                    $movingurl = new moodle_url('/course/mod.php', array('moveto' => $modnumber, 'sesskey' => sesskey()));
+                    $output .= html_writer::tag('li', html_writer::link($movingurl, $this->output->render($movingpix)),
+                            array('class' => 'movehere', 'title' => $strmovefull));
+                }
+
+                $mod = $modinfo->cms[$modnumber];
+                $modclasses = 'activity '. $mod->modname. ' modtype_'.$mod->modname. ' '. $mod->get_extra_classes();
+                $output .= html_writer::start_tag('li', array('class' => $modclasses, 'id' => 'module-'. $mod->id));
+                $output .= $modulehtml;
+                $output .= html_writer::end_tag('li');
+            }
+
+            if ($ismoving) {
+                $movingurl = new moodle_url('/course/mod.php', array('movetosection' => $section->id, 'sesskey' => sesskey()));
+                $output .= html_writer::tag('li', html_writer::link($movingurl, $this->output->render($movingpix)),
+                        array('class' => 'movehere', 'title' => $strmovefull));
+            }
+
+            $output .= html_writer::end_tag('ul'); // .section
+        }
+
+        return $output;
+    }
 }
index 2b756b6..533537a 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
@@ -20,7 +19,7 @@
  * back to the page that they were on.
  *
  * This functionality is also supported in {@link /course/view.php} in order to comply
- * with backwards compatibility
+ * with backwards compatibility.
  * The reason that we created this file was so that user didn't get redirected back
  * to the course view page only to be redirected again.
  *
@@ -34,54 +33,43 @@ require_once('../config.php');
 require_once($CFG->dirroot.'/course/lib.php');
 
 $id         = required_param('id', PARAM_INT);
-$switchrole = optional_param('switchrole',-1, PARAM_INT);
-$returnurl  = optional_param('returnurl', false, PARAM_LOCALURL);
+$switchrole = optional_param('switchrole', -1, PARAM_INT);
+$returnurl  = optional_param('returnurl', '', PARAM_RAW);
+
+if (strpos($returnurl, '?') === false) {
+    // Looks like somebody did not set proper page url, better go to course page.
+    $returnurl = new moodle_url('/course/view.php', array('id' => $id));
+} else {
+    if (strpos($returnurl, $CFG->wwwroot) !== 0) {
+        $returnurl = $CFG->wwwroot.$returnurl;
+    }
+    $returnurl  = clean_param($returnurl, PARAM_URL);
+}
 
 $PAGE->set_url('/course/switchrole.php', array('id'=>$id));
 
-if (!confirm_sesskey()) {
-    print_error('confirmsesskeybad', 'error');
-}
+require_sesskey();
 
-if (! ($course = $DB->get_record('course', array('id'=>$id)))) {
-    print_error('invalidcourseid', 'error');
+if (!$course = $DB->get_record('course', array('id'=>$id))) {
+    redirect(new moodle_url('/'));
 }
 
 $context = context_course::instance($course->id);
 
-// Remove any switched roles before checking login
+// Remove any switched roles before checking login.
 if ($switchrole == 0) {
-    role_switch($switchrole, $context);
+    role_switch(0, $context);
 }
 require_login($course);
 
 // Switchrole - sanity check in cost-order...
 if ($switchrole > 0 && has_capability('moodle/role:switchroles', $context)) {
-    // is this role assignable in this context?
+    // Is this role assignable in this context?
     // inquiring minds want to know...
     $aroles = get_switchable_roles($context);
     if (is_array($aroles) && isset($aroles[$switchrole])) {
         role_switch($switchrole, $context);
-        // Double check that this role is allowed here
-        require_login($course);
     }
 }
 
-// TODO: Using SESSION->returnurl is deprecated and should be removed in the future.
-// Till then this code remains to support any external applications calling this script.
-if (!empty($returnurl) && is_numeric($returnurl)) {
-    $returnurl = false;
-    if (!empty($SESSION->returnurl) && strpos($SESSION->returnurl, 'moodle_url')!==false) {
-        debugging('Code calling switchrole should be passing a URL as a param.', DEBUG_DEVELOPER);
-        $returnurl = @unserialize($SESSION->returnurl);
-        if (!($returnurl instanceof moodle_url)) {
-            $returnurl = false;
-        }
-    }
-}
-
-if ($returnurl === false) {
-    $returnurl = new moodle_url('/course/view.php', array('id' => $course->id));
-}
-
 redirect($returnurl);
index 82cba69..b763b92 100644 (file)
@@ -76,6 +76,9 @@ class core_course_external_testcase extends externallib_advanced_testcase {
 
         $createdcats = core_course_external::create_categories($categories);
 
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $createdcats = external_api::clean_returnvalue(core_course_external::create_categories_returns(), $createdcats);
+
         // Initially confirm that base data was inserted correctly.
         $this->assertEquals($category1->name, $createdcats[0]['name']);
         $this->assertEquals($category2->name, $createdcats[1]['name']);
@@ -93,6 +96,9 @@ class core_course_external_testcase extends externallib_advanced_testcase {
 
         $createdsubcats = core_course_external::create_categories($subcategories);
 
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $createdsubcats = external_api::clean_returnvalue(core_course_external::create_categories_returns(), $createdsubcats);
+
         // Confirm that sub categories were inserted correctly.
         $this->assertEquals($category3->name, $createdsubcats[0]['name']);
 
@@ -197,6 +203,9 @@ class core_course_external_testcase extends externallib_advanced_testcase {
             array('key' => 'id', 'value' => $category1->id),
             array('key' => 'visible', 'value' => 1)), 1);
 
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $categories = external_api::clean_returnvalue(core_course_external::get_categories_returns(), $categories);
+
         // Check we retrieve the good total number of categories.
         $this->assertEquals(2, count($categories));
 
@@ -214,6 +223,10 @@ class core_course_external_testcase extends externallib_advanced_testcase {
             array('key' => 'id', 'value' => $category1->id),
             array('key' => 'idnumber', 'value' => $category1->idnumber),
             array('key' => 'visible', 'value' => 1)), 0);
+
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $categories = external_api::clean_returnvalue(core_course_external::get_categories_returns(), $categories);
+
         $this->assertEquals(1, count($categories));
 
         // Retrieve categories from parent.
@@ -223,6 +236,10 @@ class core_course_external_testcase extends externallib_advanced_testcase {
 
         // Retrieve all categories.
         $categories = core_course_external::get_categories();
+
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $categories = external_api::clean_returnvalue(core_course_external::get_categories_returns(), $categories);
+
         $this->assertEquals($DB->count_records('course_categories'), count($categories));
 
         // Call without required capability (it will fail cause of the search on idnumber).
@@ -354,6 +371,9 @@ class core_course_external_testcase extends externallib_advanced_testcase {
 
         $createdcourses = core_course_external::create_courses($courses);
 
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $createdcourses = external_api::clean_returnvalue(core_course_external::create_courses_returns(), $createdcourses);
+
         // Check that right number of courses were created.
         $this->assertEquals(2, count($createdcourses));
 
@@ -492,6 +512,9 @@ class core_course_external_testcase extends externallib_advanced_testcase {
         $courses = core_course_external::get_courses(array('ids' =>
             array($course1->id, $course2->id)));
 
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $courses = external_api::clean_returnvalue(core_course_external::get_courses_returns(), $courses);
+
         // Check we retrieve the good total number of categories.
         $this->assertEquals(2, count($courses));
 
@@ -532,6 +555,10 @@ class core_course_external_testcase extends externallib_advanced_testcase {
 
         // Get all courses in the DB
         $courses = core_course_external::get_courses(array());
+
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $courses = external_api::clean_returnvalue(core_course_external::get_courses_returns(), $courses);
+
         $this->assertEquals($DB->count_records('course'), count($courses));
     }
 
@@ -559,6 +586,9 @@ class core_course_external_testcase extends externallib_advanced_testcase {
 
         $courses = core_course_external::get_course_contents($course->id, array());
 
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $courses = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $courses);
+
         // Check that the course has the 3 created modules
         $this->assertEquals(3, count($courses[0]['modules']));
     }
@@ -602,6 +632,9 @@ class core_course_external_testcase extends externallib_advanced_testcase {
         $duplicate = core_course_external::duplicate_course($course->id, $newcourse['fullname'],
                 $newcourse['shortname'], $newcourse['categoryid'], $newcourse['visible'], $newcourse['options']);
 
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $duplicate = external_api::clean_returnvalue(core_course_external::duplicate_course_returns(), $duplicate);
+
         // Check that the course has been duplicated.
         $this->assertEquals($newcourse['shortname'], $duplicate['shortname']);
     }
index 3773890..2977f71 100644 (file)
@@ -123,7 +123,7 @@ switch($targetstate) {
 }
 
 // Get course-modules entry
-$cm = get_coursemodule_from_id(null, $cmid, null, false, MUST_EXIST);
+$cm = get_coursemodule_from_id(null, $cmid, null, true, MUST_EXIST);
 $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
 
 // Check user is logged in
@@ -152,8 +152,12 @@ if ($fromajax) {
 } else {
     // In case of use in other areas of code we allow a 'backto' parameter,
     // otherwise go back to course page
-    $backto = optional_param('backto', 'view.php?id='.$course->id, PARAM_URL);
-    redirect($backto);
+
+    if ($backto = optional_param('backto', null, PARAM_URL)) {
+        redirect($backto);
+    } else {
+        redirect(course_get_url($course, $cm->sectionnum));
+    }
 }
 
 // utility functions
index 00c6754..02ae982 100644 (file)
@@ -18,7 +18,7 @@
     $section     = optional_param('section', 0, PARAM_INT);
     $move        = optional_param('move', 0, PARAM_INT);
     $marker      = optional_param('marker',-1 , PARAM_INT);
-    $switchrole  = optional_param('switchrole',-1, PARAM_INT);
+    $switchrole  = optional_param('switchrole',-1, PARAM_INT); // Deprecated, use course/switchrole.php instead.
     $modchooser  = optional_param('modchooser', -1, PARAM_BOOL);
     $return      = optional_param('return', 0, PARAM_LOCALURL);
 
     }
     add_to_log($course->id, 'course', $loglabel, "view.php?". $logparam, $infoid);
 
-    $course->format = clean_param($course->format, PARAM_ALPHA);
-    if (!file_exists($CFG->dirroot.'/course/format/'.$course->format.'/format.php')) {
-        $course->format = 'weeks';  // Default format is weeks
-    }
+    // Fix course format if it is no longer installed
+    $course->format = course_get_format($course)->get_format();
 
     $PAGE->set_pagelayout('course');
     $PAGE->set_pagetype('course-view-' . $course->format);
     $PAGE->set_other_editing_capability('moodle/course:manageactivities');
 
+    // Preload course format renderer before output starts.
+    // This is a little hacky but necessary since
+    // format.php is not included until after output starts
+    if (file_exists($CFG->dirroot.'/course/format/'.$course->format.'/renderer.php')) {
+        require_once($CFG->dirroot.'/course/format/'.$course->format.'/renderer.php');
+        if (class_exists('format_'.$course->format.'_renderer')) {
+            // call get_renderer only if renderer is defined in format plugin
+            // otherwise an exception would be thrown
+            $PAGE->get_renderer('format_'. $course->format);
+        }
+    }
+
     if ($reset_user_allowed_editing) {
         // ugly hack
         unset($PAGE->_user_allowed_editing);
     echo html_writer::end_tag('div');
 
     // Include course AJAX
-    if (include_course_ajax($course, $modnamesused)) {
-        // Add the module chooser
-        $renderer = $PAGE->get_renderer('core', 'course');
-        echo $renderer->course_modchooser(get_module_metadata($course, $modnames, $displaysection), $course);
-    }
+    include_course_ajax($course, $modnamesused);
 
     echo $OUTPUT->footer();
index cdc93d0..377e5ae 100644 (file)
@@ -150,6 +150,9 @@ YUI.add('moodle-course-modchooser', function(Y) {
     {
         NAME : MODCHOOSERNAME,
         ATTRS : {
+            maxheight : {
+                value : 800
+            }
         }
     });
     M.course = M.course || {};
@@ -158,6 +161,6 @@ YUI.add('moodle-course-modchooser', function(Y) {
     }
 },
 '@VERSION@', {
-    requires:['base', 'overlay', 'moodle-core-chooserdialogue', 'transition']
+    requires:['base', 'overlay', 'moodle-core-chooserdialogue', 'transition', 'moodle-course-coursebase']
 }
 );
index 1b37f40..23a61a3 100644 (file)
@@ -64,7 +64,7 @@ YUI.add('moodle-course-toolboxes', function(Y) {
             var toggle_class;
             if (this.is_label(element)) {
                 toggle_class = CSS.DIMMEDTEXT;
-                dimarea = element.one(CSS.MODINDENTDIV + ' div');
+                dimarea = element.all(CSS.MODINDENTDIV + ' > div').item(1);
             } else {
                 toggle_class = CSS.DIMCLASS;
                 dimarea = element.one('a');
@@ -259,15 +259,6 @@ YUI.add('moodle-course-toolboxes', function(Y) {
         },
         _setup_for_resource : function(toolboxtarget) {
             toolboxtarget = Y.one(toolboxtarget);
-            // "Disable" show/hide icons (change cursor to not look clickable) if section is hidden
-            var showhide = toolboxtarget.all(CSS.COMMANDSPAN + ' ' + CSS.HIDE);
-            showhide.concat(toolboxtarget.all(CSS.COMMANDSPAN + ' ' + CSS.SHOW));
-            showhide.each(function(node) {
-                var section = node.ancestor(CSS.SECTIONLI);
-                if (section && section.hasClass(CSS.SECTIONHIDDENCLASS)) {
-                    node.setStyle('cursor', 'auto');
-                }
-            });
 
             // Set groupmode attribute for use by this.toggle_groupmode()
             var groups;
@@ -708,12 +699,6 @@ YUI.add('moodle-course-toolboxes', function(Y) {
                 if (Y.Array.indexOf(response.resourcestotoggle, activityid) != -1) {
                     this.toggle_hide_resource_ui(button);
                 }
-
-                if (value == 0) {
-                    button.setStyle('cursor', 'auto');
-                } else {
-                    button.setStyle('cursor', 'pointer');
-                }
             }, this);
         },
         toggle_highlight : function(e) {
index 42cc37e..e987f87 100644 (file)
@@ -77,6 +77,8 @@ switch ($action) {
         $offset = optional_param('offset', 0, PARAM_INT);
         $search  = optional_param('search', '', PARAM_RAW);
         $outcome->response = enrol_cohort_search_cohorts($manager, $offset, 25, $search);
+        // Some browsers reorder collections by key.
+        $outcome->response['cohorts'] = array_values($outcome->response['cohorts']);
         break;
     case 'enrolcohort':
         require_capability('moodle/course:enrolconfig', $context);
index 52727e6..7dc8ffd 100644 (file)
@@ -229,7 +229,7 @@ YUI.add('moodle-enrol_cohort-quickenrolment', function(Y) {
             var rawcohorts = response.cohorts;
             var cohorts = [], i=0;
             for (i in rawcohorts) {
-                cohorts[rawcohorts[i].cohortid] = new COHORT(rawcohorts[i]);
+                cohorts[i] = new COHORT(rawcohorts[i]);
             }
             this.set(COHORTS, cohorts);
         },
similarity index 61%
rename from enrol/self/editenrolment.php
rename to enrol/editenrolment.php
index 8af223f..8b6f54e 100644 (file)
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Self user enrolment edit script.
+ * User enrolment edit script.
  *
- * This page allows the current user to edit a self user enrolment.
+ * This page allows the current user to edit a user enrolment.
  * It is not compatible with the frontpage.
  *
- * @package    enrol_self
+ * NOTE: plugins are free to implement own edit scripts with extra logic.
+ *
+ * @package    core_enrol
  * @copyright  2011 Sam Hemelryk
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-require('../../config.php');
+require('../config.php');
 require_once("$CFG->dirroot/enrol/locallib.php"); // Required for the course enrolment manager.
 require_once("$CFG->dirroot/enrol/renderer.php"); // Required for the course enrolment users table.
-require_once("$CFG->dirroot/enrol/self/editenrolment_form.php"); // Forms for this page.
+require_once("$CFG->dirroot/enrol/editenrolment_form.php"); // Forms for this page.
 
 $ueid   = required_param('ue', PARAM_INT);
 $filter = optional_param('ifilter', 0, PARAM_INT); // Table filter for return url.
 
-// Get the user enrolment object.
 $ue = $DB->get_record('user_enrolments', array('id' => $ueid), '*', MUST_EXIST);
-// Get the user for whom the enrolment is.
 $user = $DB->get_record('user', array('id'=>$ue->userid), '*', MUST_EXIST);
-// Get the course the enrolment is to.
-$sql = "SELECT c.*
-          FROM {course} c
-          JOIN {enrol} e ON e.courseid = c.id
-         WHERE e.id = :enrolid";
-$params = array('enrolid' => $ue->enrolid);
-$course = $DB->get_record_sql($sql, $params, MUST_EXIST);
-
-// Make sure the course isn't the front page.
-if ($course->id == SITEID) {
-    redirect(new moodle_url('/'));
-}
+$instance = $DB->get_record('enrol', array('id'=>$ue->enrolid), '*', MUST_EXIST);
+$course = $DB->get_record('course', array('id'=>$instance->courseid), '*', MUST_EXIST);
+
+// The URL of the enrolled users page for the course.
+$usersurl = new moodle_url('/enrol/users.php', array('id' => $course->id));
 
-// Do not allow any changes if plugin disabled.
-if (!enrol_is_enabled('self')) {
-    redirect(new moodle_url('/course/view.php', array('id'=>$course->id)));
+// Do not allow any changes if plugin disabled, not available or not suitable.
+if (!$plugin = enrol_get_plugin($instance->enrol)) {
+    redirect($usersurl);
+}
+if (!$plugin->allow_manage($instance)) {
+    redirect($usersurl);
 }
 
 // Obviously.
 require_login($course);
-// The user must be able to manage self enrolments within the course.
-require_capability("enrol/self:manage", context_course::instance($course->id, MUST_EXIST));
+// The user must be able to manage enrolments within the course.
+require_capability('enrol/'.$instance->enrol.':manage', context_course::instance($course->id, MUST_EXIST));
 
 // Get the enrolment manager for this course.
 $manager = new course_enrolment_manager($PAGE, $course, $filter);
@@ -67,26 +63,17 @@ $manager = new course_enrolment_manager($PAGE, $course, $filter);
 // exact page of the users screen they can from.
 $table = new course_enrolment_users_table($manager, $PAGE);
 
-// The URL of the enrolled users page for the course.
-$usersurl = new moodle_url('/enrol/users.php', array('id' => $course->id));
 // The URl to return the user too after this screen.
 $returnurl = new moodle_url($usersurl, $manager->get_url_params()+$table->get_url_params());
 // The URL of this page.
-$url = new moodle_url('/enrol/self/editenrolment.php', $returnurl->params());
+$url = new moodle_url('/enrol/editenrolment.php', $returnurl->params());
 
 $PAGE->set_url($url);
 $PAGE->set_pagelayout('admin');
 navigation_node::override_active_url($usersurl);
 
-// Gets the components of the user enrolment.
-list($instance, $plugin) = $manager->get_user_enrolment_components($ue);
-// Check that the user can manage this instance, and that the instance is of the correct type.
-if (!$plugin->allow_manage($instance) || $instance->enrol != 'self' || !($plugin instanceof enrol_self_plugin)) {
-    print_error('erroreditenrolment', 'enrol');
-}
-
-// Get the self enrolment edit form.
-$mform = new enrol_self_user_enrolment_form($url, array('user'=>$user, 'course'=>$course, 'ue'=>$ue));
+// Get the enrolment edit form.
+$mform = new enrol_user_enrolment_form($url, array('user'=>$user, 'course'=>$course, 'ue'=>$ue));
 $mform->set_data($PAGE->url->params());
 
 if ($mform->is_cancelled()) {
@@ -99,7 +86,7 @@ if ($mform->is_cancelled()) {
 }
 
 $fullname = fullname($user);
-$title = get_string('editenrolment', 'enrol_self');
+$title = get_string('editenrolment', 'core_enrol');
 
 $PAGE->set_title($title);
 $PAGE->set_heading($title);
similarity index 94%
rename from enrol/self/editenrolment_form.php
rename to enrol/editenrolment_form.php
index 24d6525..8bdc466 100644 (file)
@@ -15,9 +15,9 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Contains the form used to edit self enrolments for a user.
+ * Contains the form used to edit enrolments for a user.
  *
- * @package    enrol_self
+ * @package    core_enrol
  * @copyright  2011 Sam Hemelryk
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die();
 
 require_once("$CFG->libdir/formslib.php");
 
-class enrol_self_user_enrolment_form extends moodleform {
+class enrol_user_enrolment_form extends moodleform {
     function definition() {
         global $CFG, $DB;
 
diff --git a/enrol/flatfile/cli/sync.php b/enrol/flatfile/cli/sync.php
new file mode 100644 (file)
index 0000000..59df7e0
--- /dev/null
@@ -0,0 +1,74 @@
+<?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/>.
+
+/**
+ * Flatfile CLI tool.
+ *
+ * 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_flatfile
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', true);
+
+require(__DIR__.'/../../../config.php');
+require_once("$CFG->libdir/clilib.php");
+
+// 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 =
+        "Process flat file enrolments, update expiration and send notifications.
+
+Options:
+-v, --verbose         Print verbose progress information
+-h, --help            Print out this help
+
+Example:
+\$ sudo -u www-data /usr/bin/php enrol/flatfile/cli/sync.php
+";
+
+    echo $help;
+    die;
+}
+
+if (!enrol_is_enabled('flatfile')) {
+    cli_error('enrol_flatfile plugin is disabled, synchronisation stopped', 2);
+}
+
+/** @var $plugin enrol_flatfile_plugin */
+$plugin = enrol_get_plugin('flatfile');
+
+if (empty($options['verbose'])) {
+    $trace = new null_progress_trace();
+} else {
+    $trace = new text_progress_trace();
+}
+
+$result = $plugin->sync($trace);
+
+exit($result);
diff --git a/enrol/flatfile/db/access.php b/enrol/flatfile/db/access.php
new file mode 100644 (file)
index 0000000..bb87e11
--- /dev/null
@@ -0,0 +1,43 @@
+<?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/>.
+
+/**
+ * Capabilities for manual enrolment plugin.
+ *
+ * @package    enrol_flatfile
+ * @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();
+
+$capabilities = array(
+    /* Manage enrolments of users - requires allowmodifications enabled. */
+    'enrol/flatfile:manage' => array(
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_COURSE,
+        'archetypes' => array(
+        )
+    ),
+
+    /* Unenrol anybody (including self) - requires allowmodifications enabled */
+    'enrol/flatfile:unenrol' => array(
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_COURSE,
+        'archetypes' => array(
+        )
+    ),
+);
index 0b1295c..992c6a9 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 @@
 /**
  * Keeps track of upgrades to the enrol_flatfile plugin
  *
- * @package    enrol
- * @subpackage flatfile
+ * @package    enrol_flatfile
  * @copyright  2010 Aparup Banerjee <aparup@moodle.com>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
index 9d992cf..7fd1901 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_flatfile', language 'en', branch 'MOODLE_20_STABLE'
+ * Strings for component 'enrol_flatfile', language 'en'.
  *
- * @package    enrol
- * @subpackage flatfile
+ * @package    enrol_flatfile
  * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+$string['encoding'] = 'File encoding';
+$string['expiredaction'] = 'Enrolment expiration action';
+$string['expiredaction_help'] = 'Select action to carry out when user enrolment expires. Please note that some user data and settings are purged from course during course unenrolment.';
 $string['filelockedmail'] = 'The text file you are using for file-based enrolments ({$a}) can not be deleted by the cron process.  This usually means the permissions are wrong on it.  Please fix the permissions so that Moodle can delete the file, otherwise it might be processed repeatedly.';
 $string['filelockedmailsubject'] = 'Important error: Enrolment file';
+$string['flatfile:manage'] = 'Manage user enrolments manually';
+$string['flatfile:unenrol'] = 'Unenrol users from the course manually';
 $string['location'] = 'File location';
-$string['mailadmin'] = 'Notify admin by email';
-$string['mailstudents'] = 'Notify students by email';
-$string['mailteachers'] = 'Notify teachers by email';
+$string['location_desc'] = 'Specify full path to the enrolment file. The file is automatically deleted after processing.';
+$string['notifyadmin'] = 'Notify administrator';
+$string['notifyenrolled'] = 'Notify enrolled users';
+$string['notifyenroller'] = 'Notify user responsible for enrolments';
 $string['messageprovider:flatfile_enrolment'] = 'Flat file enrolment messages';
-$string['mapping'] = 'Flat file mapping';
+$string['mapping'] = 'Flat file role mapping';
 $string['pluginname'] = 'Flat file (CSV)';
 $string['pluginname_desc'] = 'This method will repeatedly check for and process a specially-formatted text file in the location that you specify.
 The file is a comma separated file assumed to have four or six fields per line:
-<pre class="informationbox">
-*  operation, role, idnumber(user), idnumber(course) [, starttime, endtime]
+
+    operation, role, user idnumber, course idnumber [, starttime [, endtime]]
+
 where:
-*  operation        = add | del
-*  role             = student | teacher | teacheredit
-*  idnumber(user)   = idnumber in the user table NB not id
-*  idnumber(course) = idnumber in the course table NB not id
-*  starttime        = start time (in seconds since epoch) - optional
-*  endtime          = end time (in seconds since epoch) - optional
-</pre>
+
+* operation - add | del
+* role - student | teacher | teacheredit
+* user idnumber - idnumber in the user table NB not id
+* course idnumber - idnumber in the course table NB not id
+* starttime - start time (in seconds since epoch) - optional
+* endtime - end time (in seconds since epoch) - optional
+
 It could look something like this:
 <pre class="informationbox">
    add, student, 5, CF101
index 7e01825..29b2d44 100644 (file)
  * This plugin lets the user specify a "flatfile" (CSV) containing enrolment information.
  * On a regular cron cycle, the specified file is parsed and then deleted.
  *
- * @package    enrol
- * @subpackage flatfile
+ * @package    enrol_flatfile
  * @copyright  2010 Eugene Venter
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
 defined('MOODLE_INTERNAL') || die();
 
+
 /**
  * Flatfile enrolment plugin implementation.
+ *
+ * Comma separated file assumed to have four or six fields per line:
+ *   operation, role, idnumber(user), idnumber(course) [, starttime [, endtime]]
+ * where:
+ *   operation        = add | del
+ *   role             = student | teacher | teacheredit
+ *   idnumber(user)   = idnumber in the user table NB not id
+ *   idnumber(course) = idnumber in the course table NB not id
+ *   starttime        = start time (in seconds since epoch) - optional
+ *   endtime          = end time (in seconds since epoch) - optional
+ *
  * @author  Eugene Venter - based on code by Petr Skoda, Martin Dougiamas, Martin Langhoff and others
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class enrol_flatfile_plugin extends enrol_plugin {
+    protected $lasternoller = null;
+    protected $lasternollercourseid = 0;
+
+    /**
+     * Does this plugin assign protected roles are can they be manually removed?
+     * @return bool - false means anybody may tweak roles, it does not use itemid and component when assigning roles
+     */
+    public function roles_protected() {
+        return false;
+    }
+
+    /**
+     * Does this plugin allow manual unenrolment of all users?
+     * All plugins allowing this must implement 'enrol/xxx:unenrol' capability
+     *
+     * @param stdClass $instance course enrol instance
+     * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol others freely, false means nobody may touch user_enrolments
+     */
+    public function allow_unenrol(stdClass $instance) {
+        return true;
+    }
 
     /**
-     * Override the base cron() function to read in a file
+     * Does this plugin allow manual unenrolment of a specific user?
+     * All plugins allowing this must implement 'enrol/xxx:unenrol' capability
+     *
+     * This is useful especially for synchronisation plugins that
+     * do suspend instead of full unenrolment.
      *
-     * Comma separated file assumed to have four or six fields per line:
-     *   operation, role, idnumber(user), idnumber(course) [, starttime, endtime]
-     * where:
-     *   operation        = add | del
-     *   role             = student | teacher | teacheredit
-     *   idnumber(user)   = idnumber in the user table NB not id
-     *   idnumber(course) = idnumber in the course table NB not id
-     *   starttime        = start time (in seconds since epoch) - optional
-     *   endtime          = end time (in seconds since epoch) - optional
+     * @param stdClass $instance course enrol instance
+     * @param stdClass $ue record from user_enrolments table, specifies user
+     *
+     * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol this user, false means nobody may touch this user enrolment
      */
-    private $log;
+    public function allow_unenrol_user(stdClass $instance, stdClass $ue) {
+        return true;
+    }
+
+    /**
+     * Does this plugin allow manual changes in user_enrolments table?
+     *
+     * All plugins allowing this must implement 'enrol/xxx:manage' capability
+     *
+     * @param stdClass $instance course enrol instance
+     * @return bool - true means it is possible to change enrol period and status in user_enrolments table
+     */
+    public function allow_manage(stdClass $instance) {
+        return true;
+    }
+
+    /**
+     * Is it possible to delete enrol instance via standard UI?
+     *
+     * @param object $instance
+     * @return bool
+     */
+    public function instance_deleteable($instance) {
+        return true;
+    }
+
+    /**
+     * Gets an array of the user enrolment actions.
+     *
+     * @param course_enrolment_manager $manager
+     * @param stdClass $ue A user enrolment object
+     * @return array An array of user_enrolment_actions
+     */
+    public function get_user_enrolment_actions(course_enrolment_manager $manager, $ue) {
+        $actions = array();
+        $context = $manager->get_context();
+        $instance = $ue->enrolmentinstance;
+        $params = $manager->get_moodlepage()->url->params();
+        $params['ue'] = $ue->id;
+        if ($this->allow_unenrol_user($instance, $ue) && has_capability("enrol/flatfile:unenrol", $context)) {
+            $url = new moodle_url('/enrol/unenroluser.php', $params);
+            $actions[] = new user_enrolment_action(new pix_icon('t/delete', ''), get_string('unenrol', 'enrol'), $url, array('class'=>'unenrollink', 'rel'=>$ue->id));
+        }
+        if ($this->allow_manage($instance) && has_capability("enrol/flatfile:manage", $context)) {
+            $url = new moodle_url('/enrol/editenrolment.php', $params);
+            $actions[] = new user_enrolment_action(new pix_icon('t/edit', ''), get_string('edit'), $url, array('class'=>'editenrollink', 'rel'=>$ue->id));
+        }
+        return $actions;
+    }
+
+    /**
+     * Enrol user into course via enrol instance.
+     *
+     * @param stdClass $instance
+     * @param int $userid
+     * @param int $roleid optional role id
+     * @param int $timestart 0 means unknown
+     * @param int $timeend 0 means forever
+     * @param int $status default to ENROL_USER_ACTIVE for new enrolments, no change by default in updates
+     * @return void
+     */
+    public function enrol_user(stdClass $instance, $userid, $roleid = null, $timestart = 0, $timeend = 0, $status = null) {
+        parent::enrol_user($instance, $userid, null, $timestart, $timeend, $status);
+        if ($roleid) {
+            $context = context_course::instance($instance->courseid, MUST_EXIST);
+            role_assign($roleid, $userid, $context->id, 'enrol_'.$this->get_name(), $instance->id);
+        }
+    }
 
     public function cron() {
-        $this->process_file();
+        $trace = new text_progress_trace();
+        $this->sync($trace);
+    }
+
+    /**
+     * Execute synchronisation.
+     * @param progress_trace
+     * @return int exit code, 0 means ok, 2 means plugin disabled
+     */
+    public function sync(progress_trace $trace) {
+        if (!enrol_is_enabled('flatfile')) {
+            return 2;
+        }
+
+        $mailadmins = $this->get_config('mailadmins', 0);
+
+        if ($mailadmins) {
+            $buffer = new progress_trace_buffer(new text_progress_trace(), false);
+            $trace = new combined_progress_trace(array($trace, $buffer));
+        }
+
+        $processed = false;
+
+        $processed = $this->process_file($trace) || $processed;
+        $processed = $this->process_buffer($trace) || $processed;
+        $processed = $this->process_expirations($trace) || $processed;
+
+        if ($processed and $mailadmins) {
+            if ($log = $buffer->get_buffer()) {
+                $eventdata = new stdClass();
+                $eventdata->modulename        = 'moodle';
+                $eventdata->component         = 'enrol_flatfile';
+                $eventdata->name              = 'flatfile_enrolment';
+                $eventdata->userfrom          = get_admin();
+                $eventdata->userto            = get_admin();
+                $eventdata->subject           = 'Flatfile Enrolment Log';
+                $eventdata->fullmessage       = $log;
+                $eventdata->fullmessageformat = FORMAT_PLAIN;
+                $eventdata->fullmessagehtml   = '';
+                $eventdata->smallmessage      = '';
+                message_send($eventdata);
+            }
+            $buffer->reset_buffer();
+        }
+
+        return 0;
+    }
+
+    /**
+     * Sorry, we do not want to show paths in cron output.
+     *
+     * @param string $filepath
+     * @return string
+     */
+    protected function obfuscate_filepath($filepath) {
+        global $CFG;
 
-        $this->process_buffer();
+        if (strpos($filepath, $CFG->dataroot.'/') === 0 or strpos($filepath, $CFG->dataroot.'\\') === 0) {
+            $disclosefile = '$CFG->dataroot'.substr($filepath, strlen($CFG->dataroot));
 
-        echo $this->log;
-    } // end of function
+        } else if (strpos($filepath, $CFG->dirroot.'/') === 0 or strpos($filepath, $CFG->dirroot.'\\') === 0) {
+            $disclosefile = '$CFG->dirroot'.substr($filepath, strlen($CFG->dirroot));
 
-    protected function process_file() {
+        } else {
+            $disclosefile = basename($filepath);
+        }
+
+        return $disclosefile;
+    }
+
+    /**
+     * Process flatfile.
+     * @param progress_trace $trace
+     * @return bool true if any data processed, false if not
+     */
+    protected function process_file(progress_trace $trace) {
         global $CFG, $DB;
 
+        // We may need more memory here.
+        @set_time_limit(0);
+        raise_memory_limit(MEMORY_HUGE);
+
         $filelocation = $this->get_config('location');
-        $mailadmins   = $this->get_config('mailadmins');
         if (empty($filelocation)) {
-            $filename = "$CFG->dataroot/1/enrolments.txt";  // Default location
-        } else {
-            $filename = $filelocation;
+            // Default legacy location.
+            $filelocation = "$CFG->dataroot/1/enrolments.txt";
         }
+        $disclosefile = $this->obfuscate_filepath($filelocation);
 
-        if ( file_exists($filename) ) {
-            $this->log  = userdate(time()) . "\n";
-            $this->log .= "Flatfile enrol cron found file: $filename\n\n";
+        if (!file_exists($filelocation)) {
+            $trace->output("Flatfile enrolments file not found: $disclosefile");
+            $trace->finished();
+            return false;
+        }
+        $trace->output("Processing flat file enrolments from: $disclosefile ...");
 
-            if (($fh = fopen($filename, "r")) != false) {
+        $content = file_get_contents($filelocation);
 
-                list($roles, $rolemap) = $this->get_roles();
+        if ($content !== false) {
 
-                $line = 0;
-                while (!feof($fh)) {
+            $rolemap = $this->get_role_map($trace);
 
-                    $line++;
-                    $fields = explode( ",", str_replace( "\r", "", fgets($fh) ) );
+            $content = textlib::convert($content, $this->get_config('encoding', 'utf-8'), 'utf-8');
+            $content = str_replace("\r", '', $content);
+            $content = explode("\n", $content);
 
-                /// If a line is incorrectly formatted ie does not have 4 comma separated fields then ignore it
-                    if (count($fields) != 4 and count($fields) !=6) {
-                        if ( count($fields) > 1 or strlen($fields[0]) > 1) { // no error for blank lines
-                            $this->log .= "$line: Line incorrectly formatted - ignoring\n";
-                        }
-                        continue;
-                    }
+            $line = 0;
+            foreach($content as $fields) {
+                $line++;
 
-                    $fields[0] = trim(strtolower($fields[0]));
-                    $fields[1] = trim(strtolower($fields[1]));
-                    $fields[2] = trim($fields[2]);
-                    $fields[3] = trim($fields[3]);
+                if (trim($fields) === '') {
+                    // Empty lines are ignored.
+                    continue;
+                }
 
-                    $this->log .= "$line: $fields[0] $fields[1] $fields[2] $fields[3] ";
+                // Deal with different separators.
+                if (strpos($fields, ',') !== false) {
+                    $fields = explode(',', $fields);
+                } else {
+                    $fields = explode(';', $fields);
+                }
+
+                // If a line is incorrectly formatted ie does not have 4 comma separated fields then ignore it.
+                if (count($fields) < 4 or count($fields) > 6) {
+                    $trace->output("Line incorrectly formatted - ignoring $line", 1);
+                    continue;
+                }
 
-                    if (!empty($fields[5])) {
-                        $fields[4] = (int)trim($fields[4]);
-                        $fields[5] = (int)trim($fields[5]);
-                        $this->log .= "$fields[4] $fields[5]";
-                    } else {
-                        $fields[4] = 0;
-                        $fields[5] = 0;
+                $fields[0] = trim(textlib::strtolower($fields[0]));
+                $fields[1] = trim(textlib::strtolower($fields[1]));
+                $fields[2] = trim($fields[2]);
+                $fields[3] = trim($fields[3]);
+                $fields[4] = isset($fields[4]) ? (int)trim($fields[4]) : 0;
+                $fields[5] = isset($fields[5]) ? (int)trim($fields[5]) : 0;
+
+                // Deal with quoted values - all or nothing, we need to support "' in idnumbers, sorry.
+                if (strpos($fields[0], "'") === 0) {
+                    foreach ($fields as $k=>$v) {
+                        $fields[$k] = trim($v, "'");
                     }
+                } else if (strpos($fields[0], '"') === 0) {
+                    foreach ($fields as $k=>$v) {
+                        $fields[$k] = trim($v, '"');
+                    }
+                }
 
-                    $this->log .= ":";
+                $trace->output("$line: $fields[0], $fields[1], $fields[2], $fields[3], $fields[4], $fields[5]", 1);
 
-                /// check correct formatting of operation field
-                    if ($fields[0] != "add" and $fields[0] != "del") {
-                        $this->log .= "Unknown operation in field 1 - ignoring line\n";
-                        continue;
-                    }
+                // Check correct formatting of operation field.
+                if ($fields[0] !== "add" and $fields[0] !== "del") {
+                    $trace->output("Unknown operation in field 1 - ignoring line $line", 1);
+                    continue;
+                }
 
-                /// check correct formatting of role field
-                    if (!isset($rolemap[$fields[1]]) && !isset($roles[$fields[1]])) {
-                        $this->log .= "Unknown role in field2 - ignoring line\n";
-                        continue;
-                    }
+                // Check correct formatting of role field.
+                if (!isset($rolemap[$fields[1]])) {
+                    $trace->output("Unknown role in field2 - ignoring line $line", 1);
+                    continue;
+                }
+                $roleid = $rolemap[$fields[1]];
 
-                    if (! $user = $DB->get_record("user", array("idnumber"=>$fields[2]))) {
-                        $this->log .= "Unknown user idnumber in field 3 - ignoring line\n";
-                        continue;
-                    }
+                if (!$user = $DB->get_record("user", array("idnumber"=>$fields[2]))) {
+                    $trace->output("Unknown user idnumber in field 3 - ignoring line $line", 1);
+                    continue;
+                }
 
-                    if (! $course = $DB->get_record("course", array("idnumber"=>$fields[3]))) {
-                        $this->log .= "Unknown course idnumber in field 4 - ignoring line\n";
-                        continue;
-                    }
+                if (!$course = $DB->get_record("course", array("idnumber"=>$fields[3]))) {
+                    $trace->output("Unknown course idnumber in field 4 - ignoring line $line", 1);
+                    continue;
+                }
 
-                    // Either field[1] is a name that appears in the mapping,
-                    // or it's an actual short name. It has to be one or the
-                    // other, or we don't get to this point.
-                    $roleid = isset($rolemap[$fields[1]]) ? $roles[$rolemap[$fields[1]]] : $roles[$fields[1]];
+                if ($fields[4] > $fields[5] and $fields[5] != 0) {
+                    $trace->output("Start time was later than end time - ignoring line $line", 1);
+                    continue;
+                }
 
-                    if ($fields[4] > $fields[5]) {
-                        $this->log .= "Start time was later than end time - ignoring line\n";
-                        continue;
-                    }
+                $this->process_records($trace, $fields[0], $roleid, $user, $course, $fields[4], $fields[5]);
+            }
+
+            unset($content);
+        }
+
+        if (!unlink($filelocation)) {
+            $eventdata = new stdClass();
+            $eventdata->modulename        = 'moodle';
+            $eventdata->component         = 'enrol_flatfile';
+            $eventdata->name              = 'flatfile_enrolment';
+            $eventdata->userfrom          = get_admin();
+            $eventdata->userto            = get_admin();
+            $eventdata->subject           = get_string('filelockedmailsubject', 'enrol_flatfile');
+            $eventdata->fullmessage       = get_string('filelockedmail', 'enrol_flatfile', $filelocation);
+            $eventdata->fullmessageformat = FORMAT_PLAIN;
+            $eventdata->fullmessagehtml   = '';
+            $eventdata->smallmessage      = '';
+            message_send($eventdata);
+            $trace->output("Error deleting enrolment file: $disclosefile", 1);
+        } else {
+            $trace->output("Deleted enrolment file", 1);
+        }
+
+        $trace->output("...finished enrolment file processing.");
+        $trace->finished();
+
+        return true;
+    }
+
+    /**
+     * Process any future enrollments stored in the buffer.
+     * @param progress_trace $trace
+     * @return bool true if any data processed, false if not
+     */
+    protected function process_buffer(progress_trace $trace) {
+        global $DB;
 
-                    $this->process_records($fields[0],$roleid,$user,$course,$fields[4],$fields[5]);
+        if (!$future_enrols = $DB->get_records_select('enrol_flatfile', "timestart < ?", array(time()))) {
+            $trace->output("No enrolments to be processed in flatfile buffer");
+            $trace->finished();
+            return false;
+        }
 
-                 } // end of while loop
+        $trace->output("Starting processing of flatfile buffer");
+        foreach($future_enrols as $en) {
+            $user = $DB->get_record('user', array('id'=>$en->userid));
+            $course = $DB->get_record('course', array('id'=>$en->courseid));
+            if ($user and $course) {
+                $trace->output("buffer: $en->action $en->roleid $user->id $course->id $en->timestart $en->timeend", 1);
+                $this->process_records($trace, $en->action, $en->roleid, $user, $course, $en->timestart, $en->timeend, false);
+            }
+            $DB->delete_records('enrol_flatfile', array('id'=>$en->id));
+        }
+        $trace->output("Finished processing of flatfile buffer");
+        $trace->finished();
 
-            fclose($fh);
-            } // end of if(file_open)
+        return true;
+    }
+
+    /**
+     * Process user enrolment line.
+     *
+     * @param progress_trace $trace
+     * @param string $action
+     * @param int $roleid
+     * @param stdClass $user
+     * @param stdClass $course
+     * @param int $timestart
+     * @param int $timeend
+     * @param bool $buffer_if_future
+     */
+    protected function process_records(progress_trace $trace, $action, $roleid, $user, $course, $timestart, $timeend, $buffer_if_future = true) {
+        global $CFG, $DB, $SESSION;
+
+        // Check if timestart is for future processing.
+        if ($timestart > time() and $buffer_if_future) {
+            // Populate into enrol_flatfile table as a future role to be assigned by cron.
+            // Note: since 2.0 future enrolments do not cause problems if you disable guest access.
+            $future_en = new stdClass();
+            $future_en->action       = $action;
+            $future_en->roleid       = $roleid;
+            $future_en->userid       = $user->id;
+            $future_en->courseid     = $course->id;
+            $future_en->timestart    = $timestart;
+            $future_en->timeend      = $timeend;
+            $future_en->timemodified = time();
+            $DB->insert_record('enrol_flatfile', $future_en);
+            $trace->output("User $user->id will be enrolled later into course $course->id using role $roleid ($timestart, $timeend)", 1);
+            return;
+        }
+
+        $context = context_course::instance($course->id);
+
+        if ($action === 'add') {
+            // Clear the buffer just in case there were some future enrolments.
+            $DB->delete_records('enrol_flatfile', array('userid'=>$user->id, 'courseid'=>$course->id, 'roleid'=>$roleid));
+
+            $instance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'flatfile'));
+            if (empty($instance)) {
+                // Only add an enrol instance to the course if non-existent.
+                $enrolid = $this->add_instance($course);
+                $instance = $DB->get_record('enrol', array('id' => $enrolid));
+            }
+
+            $notify = false;
+            if ($ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$user->id))) {
+                // Update only.
+                $this->update_user_enrol($instance, $user->id, ENROL_USER_ACTIVE, $roleid, $timestart, $timeend);
+                if (!$DB->record_exists('role_assignments', array('contextid'=>$context->id, 'roleid'=>$roleid, 'userid'=>$user->id, 'component'=>'enrol_flatfile', 'itemid'=>$instance->id))) {
+                    role_assign($roleid, $user->id, $context->id, 'enrol_flatfile', $instance->id);
+                }
+                $trace->output("User $user->id enrolment updated in course $course->id using role $roleid ($timestart, $timeend)", 1);
+
+  &