Merge branch 'MDL-40010_master' of https://github.com/markn86/moodle
authorDan Poltawski <dan@moodle.com>
Tue, 11 Jun 2013 05:05:25 +0000 (13:05 +0800)
committerDan Poltawski <dan@moodle.com>
Tue, 11 Jun 2013 05:05:25 +0000 (13:05 +0800)
145 files changed:
auth/cas/auth.php
auth/ldap/auth.php
backup/moodle2/restore_course_task.class.php
backup/moodle2/restore_final_task.class.php
backup/moodle2/restore_root_task.class.php
backup/moodle2/restore_stepslib.php
blocks/badges/block_badges.php
blocks/course_overview/renderer.php
cohort/index.php
config-dist.php
course/edit_form.php
course/externallib.php
course/format/lib.php
course/format/topics/lib.php
course/format/topics/renderer.php
course/format/weeks/lib.php
course/lib.php
course/manage.php
course/moodleform_mod.php
course/renderer.php
course/tests/externallib_test.php
course/view.php
enrol/manual/lib.php
enrol/manual/settings.php
enrol/manual/tests/lib_test.php
grade/edit/tree/grade.php
grade/grading/form/guide/lib.php
grade/grading/form/rubric/lib.php
grade/report/grader/ajax_callbacks.php
grade/report/grader/lib.php
index.php
install/lang/ca_valencia/error.php
install/lang/ca_valencia/install.php
install/lang/it/error.php
install/lang/sr_cr/langconfig.php
install/lang/sr_lt/langconfig.php
lang/en/admin.php
lang/en/group.php
lang/en/hub.php
lib/accesslib.php
lib/adminlib.php
lib/blocklib.php
lib/completionlib.php
lib/coursecatlib.php
lib/csslib.php
lib/datalib.php
lib/db/upgrade.php
lib/deprecatedlib.php
lib/dml/oci_native_moodle_database.php
lib/filelib.php
lib/form/dateselector.php
lib/form/editor.php
lib/form/hidden.php
lib/form/yui/dateselector/dateselector.js
lib/jslib.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputlib.php
lib/outputrenderers.php
lib/pagelib.php
lib/portfolio/exporter.php
lib/tests/accesslib_test.php
lib/tests/completionlib_advanced_test.php [new file with mode: 0644]
lib/tests/completionlib_test.php
lib/tests/datalib_test.php
lib/tests/environment_test.php [new file with mode: 0644]
lib/tests/navigationlib_test.php
lib/tests/weblib_test.php
lib/upgrade.txt
lib/weblib.php
lib/xsendfilelib.php
lib/yui/build/moodle-core-blocks/moodle-core-blocks-debug.js
lib/yui/build/moodle-core-blocks/moodle-core-blocks-min.js
lib/yui/build/moodle-core-blocks/moodle-core-blocks.js
lib/yui/dragdrop/dragdrop.js
lib/yui/src/blocks/build.json
lib/yui/src/blocks/js/blockregion.js [new file with mode: 0644]
lib/yui/src/blocks/js/blocks.js
lib/yui/src/blocks/js/manager.js [new file with mode: 0644]
login/index.php
login/signup.php
mod/assign/backup/moodle2/backup_assign_stepslib.php
mod/assign/db/install.xml
mod/assign/db/upgrade.php
mod/assign/gradingbatchoperationsform.php
mod/assign/gradingtable.php
mod/assign/lang/en/assign.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/mod_form.php
mod/assign/tests/generator/lib.php
mod/assign/upgradelib.php
mod/assign/version.php
mod/feedback/mapcourse.php
mod/forum/lib.php
mod/imscp/locallib.php
mod/quiz/startattempt.php
mod/scorm/module.js
mod/scorm/report/basic/lang/en/scormreport_basic.php
mod/scorm/styles.css
notes/edit_form.php
question/engine/datalib.php
question/engine/lib.php
question/type/multichoice/question.php
question/type/multichoice/tests/question_multi_test.php [new file with mode: 0644]
question/type/multichoice/tests/question_single_test.php [moved from question/type/multichoice/tests/question_test.php with 56% similarity]
question/type/questiontypebase.php
report/log/graph.php
report/progress/lib.php
theme/bootstrapbase/config.php
theme/bootstrapbase/layout/columns1.php [new file with mode: 0644]
theme/bootstrapbase/layout/columns2.php [new file with mode: 0644]
theme/bootstrapbase/layout/columns3.php [new file with mode: 0644]
theme/bootstrapbase/layout/embedded.php
theme/bootstrapbase/layout/general.php [deleted file]
theme/bootstrapbase/layout/secure.php [new file with mode: 0644]
theme/bootstrapbase/less/moodle.less
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/less/moodle/filemanager.less
theme/bootstrapbase/less/moodle/modules.less
theme/bootstrapbase/less/moodle/question.less
theme/bootstrapbase/less/moodle/responsive.less
theme/bootstrapbase/renderers/core_renderer.php
theme/bootstrapbase/style/moodle.css
theme/clean/config.php
theme/clean/layout/columns1.php [new file with mode: 0644]
theme/clean/layout/columns2.php [new file with mode: 0644]
theme/clean/layout/columns3.php [new file with mode: 0644]
theme/clean/layout/embedded.php [new file with mode: 0644]
theme/clean/layout/general.php [deleted file]
theme/clean/layout/secure.php [new file with mode: 0644]
theme/clean/lib.php
theme/image.php
theme/jquery.php
theme/sky_high/pix/footer-rtl.jpg [deleted file]
theme/sky_high/pix/footer-rtl.png [new file with mode: 0644]
theme/sky_high/pix/footer.png
theme/sky_high/style/admin.css
theme/sky_high/style/pagelayout.css
theme/sky_high/style/report.css
theme/upgrade.txt
theme/yui_combo.php
theme/yui_image.php
user/editlib.php
version.php

index 7e8e5d0..95ab725 100644 (file)
@@ -174,7 +174,7 @@ class auth_plugin_cas extends auth_plugin_ldap {
      *
      */
     function connectCAS() {
-        global $PHPCAS_CLIENT;
+        global $CFG, $PHPCAS_CLIENT;
 
         if (!is_object($PHPCAS_CLIENT)) {
             // Make sure phpCAS doesn't try to start a new PHP session when connecting to the CAS server.
@@ -185,6 +185,27 @@ class auth_plugin_cas extends auth_plugin_ldap {
             }
         }
 
+        // If Moodle is configured to use a proxy, phpCAS needs some curl options set.
+        if (!empty($CFG->proxyhost) && !is_proxybypass($this->config->hostname)) {
+            phpCAS::setExtraCurlOption(CURLOPT_PROXY, $CFG->proxyhost);
+            if (!empty($CFG->proxyport)) {
+                phpCAS::setExtraCurlOption(CURLOPT_PROXYPORT, $CFG->proxyport);
+            }
+            if (!empty($CFG->proxytype)) {
+                // Only set CURLOPT_PROXYTYPE if it's something other than the curl-default http
+                if ($CFG->proxytype == 'SOCKS5') {
+                    phpCAS::setExtraCurlOption(CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
+                }
+            }
+            if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
+                phpCAS::setExtraCurlOption(CURLOPT_PROXYUSERPWD, $CFG->proxyuser.':'.$CFG->proxypassword);
+                if (defined('CURLOPT_PROXYAUTH')) {
+                    // any proxy authentication if PHP 5.1
+                    phpCAS::setExtraCurlOption(CURLOPT_PROXYAUTH, CURLAUTH_BASIC | CURLAUTH_NTLM);
+                }
+            }
+        }
+
         if($this->config->certificate_check && $this->config->certificate_path){
             phpCAS::setCasServerCACert($this->config->certificate_path);
         }else{
index d32f6bd..54a987e 100644 (file)
@@ -598,6 +598,8 @@ class auth_plugin_ldap extends auth_plugin_base {
                 if ($user->firstaccess == 0) {
                     $DB->set_field('user', 'firstaccess', time(), array('id'=>$user->id));
                 }
+                $euser = $DB->get_record('user', array('id' => $user->id));
+                events_trigger('user_updated', $euser);
                 return AUTH_CONFIRM_OK;
             }
         } else {
@@ -770,6 +772,8 @@ class auth_plugin_ldap extends auth_plugin_base {
                         $updateuser->auth = 'nologin';
                         $DB->update_record('user', $updateuser);
                         echo "\t"; print_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)); echo "\n";
+                        $euser = $DB->get_record('user', array('id' => $user->id));
+                        events_trigger('user_updated', $euser);
                     }
                 }
             } else {
@@ -795,6 +799,8 @@ class auth_plugin_ldap extends auth_plugin_base {
                     $updateuser->auth = $this->authtype;
                     $DB->update_record('user', $updateuser);
                     echo "\t"; print_string('auth_dbreviveduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)); echo "\n";
+                    $euser = $DB->get_record('user', array('id' => $user->id));
+                    events_trigger('user_updated', $euser);
                 }
             } else {
                 print_string('nouserentriestorevive', 'auth_ldap');
@@ -908,6 +914,8 @@ class auth_plugin_ldap extends auth_plugin_base {
 
                 $id = $DB->insert_record('user', $user);
                 echo "\t"; print_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)); echo "\n";
+                $euser = $DB->get_record('user', array('id' => $user->id));
+                events_trigger('user_created', $euser);
                 if (!empty($this->config->forcechangepassword)) {
                     set_user_preference('auth_forcepasswordchange', 1, $id);
                 }
@@ -978,6 +986,10 @@ class auth_plugin_ldap extends auth_plugin_base {
                     }
                 }
             }
+            if (!empty($updatekeys)) {
+                $euser = $DB->get_record('user', array('id' => $userid));
+                events_trigger('user_updated', $euser);
+            }
         } else {
             return false;
         }
index 5f231e9..74bfef4 100644 (file)
@@ -73,8 +73,20 @@ class restore_course_task extends restore_task {
 
         $this->add_step(new restore_course_legacy_files_step('legacy_files'));
 
-        // Restore course enrolments (plugins and membership). Conditionally prevented for any IMPORT/HUB operation
-        if ($this->plan->get_mode() != backup::MODE_IMPORT && $this->plan->get_mode() != backup::MODE_HUB) {
+        // Deal with enrolment methods and user enrolments.
+        if ($this->plan->get_mode() == backup::MODE_IMPORT) {
+            // No need to do anything with enrolments.
+
+        } else if (!$this->get_setting_value('users') or $this->plan->get_mode() == backup::MODE_HUB) {
+            if ($this->get_target() == backup::TARGET_CURRENT_ADDING or $this->get_target() == backup::TARGET_EXISTING_ADDING) {
+                // Keep current enrolments unchanged.
+            } else {
+                // If no instances yet add default enrol methods the same way as when creating new course in UI.
+                $this->add_step(new restore_default_enrolments_step('default_enrolments'));
+            }
+
+        } else {
+            // Restore course enrolment data.
             $this->add_step(new restore_enrolments_structure_step('course_enrolments', 'enrolments.xml'));
         }
 
index d336631..f4a2d46 100644 (file)
@@ -143,6 +143,9 @@ class restore_final_task extends restore_task {
         $rules[] = new restore_log_rule('course', 'report stats', 'report/stats/index.php?id={course}', '{course}');
         $rules[] = new restore_log_rule('course', 'view section', 'view.php?id={course}&sectionid={course_section}', '{course_section}');
 
+        // module 'grade' rules
+        $rules[] = new restore_log_rule('grade', 'update', 'report/grader/index.php?id={course}', null);
+
         // module 'user' rules
         $rules[] = new restore_log_rule('user', 'view', 'view.php?id={user}&course={course}', '{user}');
         $rules[] = new restore_log_rule('user', 'change password', 'view.php?id={user}&course={course}', '{user}');
index f95a5b9..a0b90b8 100644 (file)
@@ -115,6 +115,7 @@ class restore_root_task extends restore_task {
         $rootenrolmanual = new restore_users_setting('enrol_migratetomanual', base_setting::IS_BOOLEAN, false);
         $rootenrolmanual->set_ui(new backup_setting_ui_checkbox($rootenrolmanual, get_string('rootenrolmanual', 'backup')));
         $rootenrolmanual->get_ui()->set_changeable(enrol_is_enabled('manual'));
+        $rootenrolmanual->get_ui()->set_changeable($changeable);
         $this->add_setting($rootenrolmanual);
         $users->add_dependency($rootenrolmanual);
 
index 8b02dc1..8797ee5 100644 (file)
@@ -477,6 +477,8 @@ class restore_rebuild_course_cache extends restore_execution_step {
 
         // Rebuild cache now that all sections are in place
         rebuild_course_cache($this->get_courseid());
+        cache_helper::purge_by_event('changesincourse');
+        cache_helper::purge_by_event('changesincoursecat');
     }
 }
 
@@ -1623,6 +1625,29 @@ class restore_ras_and_caps_structure_step extends restore_structure_step {
     }
 }
 
+/**
+ * If no instances yet add default enrol methods the same way as when creating new course in UI.
+ */
+class restore_default_enrolments_step extends restore_execution_step {
+    public function define_execution() {
+        global $DB;
+
+        $course = $DB->get_record('course', array('id'=>$this->get_courseid()), '*', MUST_EXIST);
+
+        if ($DB->record_exists('enrol', array('courseid'=>$this->get_courseid(), 'enrol'=>'manual'))) {
+            // Something already added instances, do not add default instances.
+            $plugins = enrol_get_plugins(true);
+            foreach ($plugins as $plugin) {
+                $plugin->restore_sync_course($course);
+            }
+
+        } else {
+            // Looks like a newly created course.
+            enrol_course_updated(true, $course, null);
+        }
+    }
+}
+
 /**
  * This structure steps restores the enrol plugins and their underlying
  * enrolments, performing all the mappings and/or movements required
index 2968a23..2af1c5d 100644 (file)
@@ -52,7 +52,7 @@ class block_badges extends block_base {
         return array(
                 'admin' => false,
                 'site-index' => true,
-                'course-view' => false,
+                'course-view' => true,
                 'mod' => false,
                 'my' => true
         );
@@ -88,8 +88,16 @@ class block_badges extends block_base {
 
         if (empty($CFG->enablebadges)) {
             $this->content->text .= get_string('badgesdisabled', 'badges');
-        } else if ($badges = badges_get_user_badges($USER->id, null, 0, $this->config->numberofbadges)) {
-            $output = $PAGE->get_renderer('core', 'badges');
+            return $this->content;
+        }
+
+        $courseid = $this->page->course->id;
+        if ($courseid == SITEID) {
+            $courseid = null;
+        }
+
+        if ($badges = badges_get_user_badges($USER->id, $courseid, 0, $this->config->numberofbadges)) {
+            $output = $this->page->get_renderer('core', 'badges');
             $this->content->text = $output->print_badges_list($badges, $USER->id, true);
         } else {
             $this->content->text .= get_string('nothingtodisplay', 'block_badges');
index 1ac4e90..bb29569 100644 (file)
@@ -103,6 +103,9 @@ class block_course_overview_renderer extends plugin_renderer_base {
 
             // No need to pass title through s() here as it will be done automatically by html_writer.
             $attributes = array('title' => $course->fullname);
+            if (empty($course->visible)) {
+                $attributes['class'] = 'dimmed';
+            }
             if ($course->id > 0) {
                 $courseurl = new moodle_url('/course/view.php', array('id' => $course->id));
                 $coursefullname = format_string($course->fullname, true, $course->id);
index 38ecfe6..3c31556 100644 (file)
@@ -86,6 +86,7 @@ $search .= html_writer::start_tag('div');
 $search .= html_writer::label(get_string('searchcohort', 'cohort'), 'cohort_search_q'); // No : in form labels!
 $search .= html_writer::empty_tag('input', array('id'=>'cohort_search_q', 'type'=>'text', 'name'=>'search', 'value'=>$searchquery));
 $search .= html_writer::empty_tag('input', array('type'=>'submit', 'value'=>get_string('search', 'cohort')));
+$search .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'contextid', 'value'=>$contextid));
 $search .= html_writer::end_tag('div');
 $search .= html_writer::end_tag('form');
 echo $search;
index 8955f58..e74d2eb 100644 (file)
@@ -520,6 +520,10 @@ $CFG->admin = 'admin';
 // Example:
 //   $CFG->forced_plugin_settings = array('pluginname'  => array('settingname' => 'value', 'secondsetting' => 'othervalue'),
 //                                        'otherplugin' => array('mysetting' => 'myvalue', 'thesetting' => 'thevalue'));
+// Module default settings with advanced/locked checkboxes can be set too. To do this, add
+// an extra config with '_adv' or '_locked' as a suffix and set the value to true or false.
+// Example:
+//   $CFG->forced_plugin_settings = array('pluginname'  => array('settingname' => 'value', 'settingname_locked' => true, 'settingname_adv' => true));
 //
 //=========================================================================
 // 9. PHPUNIT SUPPORT
@@ -554,6 +558,11 @@ $CFG->admin = 'admin';
 //=========================================================================
 // 11. BEHAT SUPPORT
 //=========================================================================
+// Behat needs a separate data directory and unique database prefix:
+//
+// $CFG->behat_prefix = 'bht_';
+// $CFG->behat_dataroot = '/home/example/bht_moodledata';
+//
 // Behat uses http://localhost:8000 as default URL to run
 // the acceptance tests, you can override this value.
 // Example:
index 2fc377d..c845331 100644 (file)
@@ -81,7 +81,7 @@ class course_edit_form extends moodleform {
                 $displaylist = coursecat::make_categories_list('moodle/course:create');
                 if (!isset($displaylist[$course->category])) {
                     //always keep current
-                    $displaylist[$course->category] = coursecat::get($course->category)->get_formatted_name();
+                    $displaylist[$course->category] = coursecat::get($course->category, MUST_EXIST, true)->get_formatted_name();
                 }
                 $mform->addElement('select', 'category', get_string('coursecategory'), $displaylist);
                 $mform->addHelpButton('category', 'coursecategory');
@@ -248,7 +248,7 @@ class course_edit_form extends moodleform {
         enrol_course_edit_form($mform, $course, $context);
 
 //--------------------------------------------------------------------------------
-        $mform->addElement('header','groups', get_string('groups', 'group'));
+        $mform->addElement('header','groups', get_string('groupsettingsheader', 'group'));
 
         $choices = array();
         $choices[NOGROUPS] = get_string('groupsnone', 'group');
index c174ce3..75b400c 100644 (file)
@@ -902,9 +902,9 @@ class core_course_external extends external_api {
                                             "users" (int) Include users (default to 0 that is equal to no),
                                             "role_assignments" (int) Include role assignments  (default to 0 that is equal to no),
                                             "comments" (int) Include user comments  (default to 0 that is equal to no),
-                                            "completion_information" (int) Include user course completion information  (default to 0 that is equal to no),
+                                            "userscompletion" (int) Include user course completion information  (default to 0 that is equal to no),
                                             "logs" (int) Include course logs  (default to 0 that is equal to no),
-                                            "histories" (int) Include histories  (default to 0 that is equal to no)'
+                                            "grade_histories" (int) Include histories  (default to 0 that is equal to no)'
                                             ),
                                 'value' => new external_value(PARAM_RAW, 'the value for the option 1 (yes) or 0 (no)'
                             )
@@ -966,9 +966,9 @@ class core_course_external extends external_api {
             'users' => 0,
             'role_assignments' => 0,
             'comments' => 0,
-            'completion_information' => 0,
+            'userscompletion' => 0,
             'logs' => 0,
-            'histories' => 0
+            'grade_histories' => 0
         );
 
         $backupsettings = array();
index a12f187..5723eff 100644 (file)
@@ -235,7 +235,7 @@ abstract class format_base {
             return null;
         }
         if ($this->course === false) {
-            $this->course = $DB->get_record('course', array('id' => $this->courseid));
+            $this->course = get_course($this->courseid);
             $options = $this->get_format_options();
             foreach ($options as $optionname => $optionvalue) {
                 if (!isset($this->course->$optionname)) {
index debe045..71a0452 100644 (file)
@@ -257,6 +257,37 @@ class format_topics extends format_base {
         return $courseformatoptions;
     }
 
+    /**
+     * Adds format options elements to the course/section edit form.
+     *
+     * This function is called from {@link course_edit_form::definition_after_data()}.
+     *
+     * @param MoodleQuickForm $mform form the elements are added to.
+     * @param bool $forsection 'true' if this is a section edit form, 'false' if this is course edit form.
+     * @return array array of references to the added form elements.
+     */
+    public function create_edit_form_elements(&$mform, $forsection = false) {
+        $elements = parent::create_edit_form_elements($mform, $forsection);
+
+        // Increase the number of sections combo box values if the user has increased the number of sections
+        // using the icon on the course page beyond course 'maxsections' or course 'maxsections' has been
+        // reduced below the number of sections already set for the course on the site administration course
+        // defaults page.  This is so that the number of sections is not reduced leaving unintended orphaned
+        // activities / resources.
+        if (!$forsection) {
+            $maxsections = get_config('moodlecourse', 'maxsections');
+            $numsections = $mform->getElementValue('numsections');
+            $numsections = $numsections[0];
+            if ($numsections > $maxsections) {
+                $element = $mform->getElement('numsections');
+                for ($i = $maxsections+1; $i <= $numsections; $i++) {
+                    $element->addOption("$i", $i);
+                }
+            }
+        }
+        return $elements;
+    }
+
     /**
      * Updates format options for a course
      *
index e293a9d..ffc7be6 100644 (file)
@@ -35,6 +35,20 @@ require_once($CFG->dirroot.'/course/format/renderer.php');
  */
 class format_topics_renderer extends format_section_renderer_base {
 
+    /**
+     * 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);
+
+        // Since format_topics_renderer::section_edit_controls() only displays the 'Set current section' control when editing mode is on
+        // we need to be sure that the link 'Turn editing mode on' is available for a user who does not have any other managing capability.
+        $page->set_other_editing_capability('moodle/course:setcurrentsection');
+    }
+
     /**
      * Generate the starting container html for a list of sections
      * @return string HTML to output.
index 40686d4..fb3b0ee 100644 (file)
@@ -262,6 +262,37 @@ class format_weeks extends format_base {
         return $courseformatoptions;
     }
 
+    /**
+     * Adds format options elements to the course/section edit form.
+     *
+     * This function is called from {@link course_edit_form::definition_after_data()}.
+     *
+     * @param MoodleQuickForm $mform form the elements are added to.
+     * @param bool $forsection 'true' if this is a section edit form, 'false' if this is course edit form.
+     * @return array array of references to the added form elements.
+     */
+    public function create_edit_form_elements(&$mform, $forsection = false) {
+        $elements = parent::create_edit_form_elements($mform, $forsection);
+
+        // Increase the number of sections combo box values if the user has increased the number of sections
+        // using the icon on the course page beyond course 'maxsections' or course 'maxsections' has been
+        // reduced below the number of sections already set for the course on the site administration course
+        // defaults page.  This is so that the number of sections is not reduced leaving unintended orphaned
+        // activities / resources.
+        if (!$forsection) {
+            $maxsections = get_config('moodlecourse', 'maxsections');
+            $numsections = $mform->getElementValue('numsections');
+            $numsections = $numsections[0];
+            if ($numsections > $maxsections) {
+                $element = $mform->getElement('numsections');
+                for ($i = $maxsections+1; $i <= $numsections; $i++) {
+                    $element->addOption("$i", $i);
+                }
+            }
+        }
+        return $elements;
+    }
+
     /**
      * Updates format options for a course
      *
index f1663f7..9f2bb26 100644 (file)
@@ -105,6 +105,9 @@ function make_log_url($module, $url) {
         case 'role':
             $url = '/'.$url;
             break;
+        case 'grade':
+            $url = "/grade/$url";
+            break;
         default:
             $url = "/mod/$module/$url";
             break;
@@ -1578,38 +1581,6 @@ function delete_mod_from_section($modid, $sectionid) {
     return false;
 }
 
-/**
- * Moves a section up or down by 1. CANNOT BE USED DIRECTLY BY AJAX!
- *
- * @param object $course course object
- * @param int $section Section number (not id!!!)
- * @param int $move (-1 or 1)
- * @return boolean true if section moved successfully
- * @todo MDL-33379 remove this function in 2.5
- */
-function move_section($course, $section, $move) {
-    debugging('This function will be removed before 2.5 is released please use move_section_to', DEBUG_DEVELOPER);
-
-/// Moves a whole course section up and down within the course
-    global $USER;
-
-    if (!$move) {
-        return true;
-    }
-
-    $sectiondest = $section + $move;
-
-    // compartibility with course formats using field 'numsections'
-    $courseformatoptions = course_get_format($course)->get_format_options();
-    if (array_key_exists('numsections', $courseformatoptions) &&
-            $sectiondest > $courseformatoptions['numsections'] or $sectiondest < 1) {
-        return false;
-    }
-
-    $retval = move_section_to($course, $section, $sectiondest);
-    return $retval;
-}
-
 /**
  * Moves a section within a course, from a position to another.
  * Be very careful: $section and $destination refer to section number,
index fbc384d..df8c5f4 100644 (file)
@@ -183,6 +183,7 @@ if ((!empty($moveupcat) or !empty($movedowncat)) and confirm_sesskey()) {
     if ($swapcategory and $movecategory) {
         $DB->set_field('course_categories', 'sortorder', $swapcategory->sortorder, array('id' => $movecategory->id));
         $DB->set_field('course_categories', 'sortorder', $movecategory->sortorder, array('id' => $swapcategory->id));
+        cache_helper::purge_by_event('changesincoursecat');
         add_to_log(SITEID, "category", "move", "editcategory.php?id=$movecategory->id", $movecategory->id);
     }
 
index d9345e3..c7d36db 100644 (file)
@@ -864,6 +864,59 @@ abstract class moodleform_mod extends moodleform {
         $mform->setType('buttonar', PARAM_RAW);
         $mform->closeHeaderBefore('buttonar');
     }
+
+    /**
+     * Get the list of admin settings for this module and apply any defaults/advanced/locked settings.
+     *
+     * @param $datetimeoffsets array - If passed, this is an array of fieldnames => times that the
+     *                         default date/time value should be relative to. If not passed, all
+     *                         date/time fields are set relative to the users current midnight.
+     * @return void
+     */
+    function apply_admin_defaults($datetimeoffsets = array()) {
+        global $OUTPUT;
+
+        $settings = get_config($this->_modname);
+        $mform = $this->_form;
+        $lockedicon = html_writer::tag('span',
+                                       $OUTPUT->pix_icon('t/locked', get_string('locked', 'admin')),
+                                       array('class' => 'action-icon'));
+        $usermidnight = usergetmidnight(time());
+
+        foreach ($settings as $name => $value) {
+            if (strpos('_', $name) !== false) {
+                continue;
+            }
+            if ($mform->elementExists($name)) {
+                $element = $mform->getElement($name);
+                if ($element->getType() == 'date_time_selector') {
+                    $enabledsetting = $name . '_enabled';
+                    if (empty($settings->$enabledsetting)) {
+                        $mform->setDefault($name, 0);
+                    } else {
+                        $relativetime = $usermidnight;
+                        if (isset($datetimeoffsets[$name])) {
+                            $relativetime = $datetimeoffsets[$name];
+                        }
+                        $mform->setDefault($name, $relativetime + $settings->$name);
+                    }
+                } else {
+                    $mform->setDefault($name, $settings->$name);
+                }
+                $advancedsetting = $name . '_adv';
+                if (!empty($settings->$advancedsetting)) {
+                    $mform->setAdvanced($name);
+                }
+                $lockedsetting = $name . '_locked';
+                if (!empty($settings->$lockedsetting)) {
+                    $mform->setConstant($name, $settings->$name);
+                    $element->setLabel($element->getLabel() . $lockedicon);
+                    // Do not use hardfreeze because we need the hidden input to check dependencies.
+                    $element->freeze();
+                }
+            }
+        }
+    }
 }
 
 
index 0e3da60..9bc279d 100644 (file)
@@ -72,11 +72,11 @@ class core_course_renderer extends plugin_renderer_base {
                 $this->page->course->id == SITEID ||
                 !$this->page->user_is_editing() ||
                 !($context = context_course::instance($this->page->course->id)) ||
-                !has_capability('moodle/course:update', $context) ||
+                !has_capability('moodle/course:manageactivities', $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
+                !($turneditingnode = $coursenode->get('turneditingonoff'))) {
+            // too late or we are on site page or we could not find the adjacent nodes in course settings menu
             // or we are not allowed to edit
             return;
         }
@@ -97,8 +97,13 @@ class core_course_renderer extends plugin_renderer_base {
             $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 = navigation_node::create($modchoosertogglestring, $modchoosertoggleurl, navigation_node::TYPE_SETTING, null, 'modchoosertoggle');
+
+        // Insert the modchoosertoggle after the settings node 'turneditingonoff' (navigation_node only has function to insert before, so we insert before and then swap).
+        $coursenode->add_node($modchoosertoggle, 'turneditingonoff');
+        $turneditingnode->remove();
+        $coursenode->add_node($turneditingnode, 'modchoosertoggle');
+
         $modchoosertoggle->add_class('modchoosertoggle');
         $modchoosertoggle->add_class('visibleifjs');
         user_preference_allow_ajax_update('usemodchooser', PARAM_BOOL);
@@ -369,9 +374,7 @@ class core_course_renderer extends plugin_renderer_base {
         $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)) {
+            if (isset($module->types)) {
                 // This module has a subtype
                 // NOTE: this is legacy stuff, module subtypes are very strongly discouraged!!
                 $subtypes = array();
@@ -381,17 +384,28 @@ class core_course_renderer extends plugin_renderer_base {
                 }
 
                 // Sort module subtypes into the list
+                $activityclass = MOD_CLASS_ACTIVITY;
+                if ($module->archetype == MOD_CLASS_RESOURCE) {
+                    $activityclass = MOD_CLASS_RESOURCE;
+                }
                 if (!empty($module->title)) {
                     // This grouping has a name
-                    $activities[$module->archetype][] = array($module->title => $subtypes);
+                    $activities[$activityclass][] = array($module->title => $subtypes);
                 } else {
                     // This grouping does not have a name
-                    $activities[$module->archetype] = array_merge($activities[$module->archetype], $subtypes);
+                    $activities[$activityclass] = array_merge($activities[$activityclass], $subtypes);
                 }
             } else {
                 // This module has no subtypes
+                $activityclass = MOD_CLASS_ACTIVITY;
+                if ($module->archetype == MOD_ARCHETYPE_RESOURCE) {
+                    $activityclass = MOD_CLASS_RESOURCE;
+                } else if ($module->archetype === MOD_ARCHETYPE_SYSTEM) {
+                    // System modules cannot be added by user, do not add to dropdown
+                    continue;
+                }
                 $link = $module->link->out(true, $urlparams);
-                $activities[$module->archetype][$link] = $module->title;
+                $activities[$activityclass][$link] = $module->title;
             }
         }
 
index 59e0c60..5fdbd41 100644 (file)
@@ -671,7 +671,15 @@ class core_course_external_testcase extends externallib_advanced_testcase {
      * Test update_courses
      */
     public function test_update_courses() {
-        global $DB, $CFG, $USER;
+        global $DB, $CFG, $USER, $COURSE;
+
+        // Get current $COURSE to be able to restore it later (defaults to $SITE). We need this
+        // trick because we are both updating and getting (for testing) course information
+        // in the same request and core_course_external::update_courses()
+        // is overwriting $COURSE all over the time with OLD values, so later
+        // use of get_course() fetches those OLD values instead of the updated ones.
+        // See MDL-39723 for more info.
+        $origcourse = clone($COURSE);
 
         $this->resetAfterTest(true);
 
@@ -724,6 +732,7 @@ class core_course_external_testcase extends externallib_advanced_testcase {
         $courses = array($course1, $course2);
 
         $updatedcoursewarnings = core_course_external::update_courses($courses);
+        $COURSE = $origcourse; // Restore $COURSE. Instead of using the OLD one set by the previous line.
 
         // Check that right number of courses were created.
         $this->assertEquals(0, count($updatedcoursewarnings['warnings']));
index 02ae982..467c390 100644 (file)
 
     $PAGE->set_pagelayout('course');
     $PAGE->set_pagetype('course-view-' . $course->format);
+    $PAGE->set_other_editing_capability('moodle/course:update');
     $PAGE->set_other_editing_capability('moodle/course:manageactivities');
+    $PAGE->set_other_editing_capability('moodle/course:activityvisibility');
+    if (course_format_uses_sections($course->format)) {
+        $PAGE->set_other_editing_capability('moodle/course:sectionvisibility');
+        $PAGE->set_other_editing_capability('moodle/course:movesections');
+    }
 
     // Preload course format renderer before output starts.
     // This is a little hacky but necessary since
             }
         }
 
-        if (has_capability('moodle/course:update', $context)) {
-            if (!empty($section)) {
-                if (!empty($move) and has_capability('moodle/course:movesections', $context) and confirm_sesskey()) {
-                    $destsection = $section + $move;
-                    if (move_section_to($course, $section, $destsection)) {
-                        if ($course->id == SITEID) {
-                            redirect($CFG->wwwroot . '/?redirect=0');
-                        } else {
-                            redirect(course_get_url($course));
-                        }
-                    } else {
-                        echo $OUTPUT->notification('An error occurred while moving a section');
-                    }
+        if (!empty($section) && !empty($move) &&
+                has_capability('moodle/course:movesections', $context) && confirm_sesskey()) {
+            $destsection = $section + $move;
+            if (move_section_to($course, $section, $destsection)) {
+                if ($course->id == SITEID) {
+                    redirect($CFG->wwwroot . '/?redirect=0');
+                } else {
+                    redirect(course_get_url($course));
                 }
+            } else {
+                echo $OUTPUT->notification('An error occurred while moving a section');
             }
         }
     } else {
index ab6f719..10e12e1 100644 (file)
@@ -340,7 +340,7 @@ class enrol_manual_plugin extends enrol_plugin {
             $rs->close();
             unset($instances);
 
-        } else if ($action == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
+        } else if ($action == ENROL_EXT_REMOVED_SUSPENDNOROLES or $action == ENROL_EXT_REMOVED_SUSPEND) {
             $instances = array();
             $sql = "SELECT ue.*, e.courseid, c.id AS contextid
                       FROM {user_enrolments} ue
@@ -355,10 +355,15 @@ class enrol_manual_plugin extends enrol_plugin {
                     $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
                 }
                 $instance = $instances[$ue->enrolid];
-                // Always remove all manually assigned roles here, this may break enrol_self roles but we do not want hardcoded hacks here.
-                role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$ue->contextid, 'component'=>'', 'itemid'=>0), true);
-                $this->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
-                $trace->output("suspending expired user $ue->userid in course $instance->courseid", 1);
+                if ($action == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
+                    // Remove all manually assigned roles here, this may break enrol_self roles but we do not want hardcoded hacks here.
+                    role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$ue->contextid, 'component'=>'', 'itemid'=>0), true);
+                    $this->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
+                    $trace->output("suspending expired user $ue->userid in course $instance->courseid, roles unassigned", 1);
+                } else {
+                    $this->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
+                    $trace->output("suspending expired user $ue->userid in course $instance->courseid, roles kept", 1);
+                }
             }
             $rs->close();
             unset($instances);
index 8905c1f..dbfc766 100644 (file)
@@ -33,6 +33,7 @@ if ($ADMIN->fulltree) {
     //       it describes what should happend when users are not supposed to be enerolled any more.
     $options = array(
         ENROL_EXT_REMOVED_KEEP           => get_string('extremovedkeep', 'enrol'),
+        ENROL_EXT_REMOVED_SUSPEND        => get_string('extremovedsuspend', 'enrol'),
         ENROL_EXT_REMOVED_SUSPENDNOROLES => get_string('extremovedsuspendnoroles', 'enrol'),
         ENROL_EXT_REMOVED_UNENROL        => get_string('extremovedunenrol', 'enrol'),
     );
index 77ffc6b..4f4c6d2 100644 (file)
@@ -304,6 +304,30 @@ class enrol_manual_lib_testcase extends advanced_testcase {
         $this->assertEquals(4, $DB->count_records('role_assignments', array('roleid'=>$studentrole->id)));
         $this->assertEquals(0, $DB->count_records('role_assignments', array('roleid'=>$teacherrole->id)));
         $this->assertEquals(1, $DB->count_records('role_assignments', array('roleid'=>$managerrole->id)));
+
+
+        $manualplugin->set_config('expiredaction', ENROL_EXT_REMOVED_SUSPEND);
+        $manualplugin->enrol_user($instance1, $user3->id, $studentrole->id, 0, $now-60);
+        $manualplugin->enrol_user($instance3, $user3->id, $teacherrole->id, 0, $now-60*60);
+        $maninstance1 = $DB->get_record('enrol', array('courseid'=>$course1->id, 'enrol'=>'manual'), '*', MUST_EXIST);
+        $maninstance2 = $DB->get_record('enrol', array('courseid'=>$course3->id, 'enrol'=>'manual'), '*', MUST_EXIST);
+
+        $this->assertEquals(6, $DB->count_records('user_enrolments'));
+        $this->assertEquals(7, $DB->count_records('role_assignments'));
+        $this->assertEquals(5, $DB->count_records('role_assignments', array('roleid'=>$studentrole->id)));
+        $this->assertEquals(1, $DB->count_records('role_assignments', array('roleid'=>$teacherrole->id)));
+        $this->assertTrue($DB->record_exists('user_enrolments', array('enrolid'=>$maninstance1->id, 'userid'=>$user3->id, 'status'=>ENROL_USER_ACTIVE)));
+        $this->assertTrue($DB->record_exists('user_enrolments', array('enrolid'=>$maninstance2->id, 'userid'=>$user3->id, 'status'=>ENROL_USER_ACTIVE)));
+
+        $manualplugin->sync($trace, null);
+        $this->assertEquals(6, $DB->count_records('user_enrolments'));
+        $this->assertTrue($DB->record_exists('user_enrolments', array('enrolid'=>$instance1->id, 'userid'=>$user3->id)));
+        $this->assertTrue($DB->record_exists('user_enrolments', array('enrolid'=>$instance3->id, 'userid'=>$user3->id)));
+        $this->assertEquals(7, $DB->count_records('role_assignments'));
+        $this->assertEquals(5, $DB->count_records('role_assignments', array('roleid'=>$studentrole->id)));
+        $this->assertEquals(1, $DB->count_records('role_assignments', array('roleid'=>$teacherrole->id)));
+        $this->assertTrue($DB->record_exists('user_enrolments', array('enrolid'=>$maninstance1->id, 'userid'=>$user3->id, 'status'=>ENROL_USER_SUSPENDED)));
+        $this->assertTrue($DB->record_exists('user_enrolments', array('enrolid'=>$maninstance2->id, 'userid'=>$user3->id, 'status'=>ENROL_USER_SUSPENDED)));
     }
 
     public function test_send_expiry_notifications() {
index 8850622..9287986 100644 (file)
@@ -200,6 +200,18 @@ if ($mform->is_cancelled()) {
         $data->feedback       = $old_grade_grade->feedback;
         $data->feedbackformat = $old_grade_grade->feedbackformat;
     }
+
+    // Only log a grade override if they actually changed the student grade.
+    if ($data->finalgrade != $old_grade_grade->finalgrade) {
+        $url = '/report/grader/index.php?id=' . $course->id;
+
+        $user = $DB->get_record('user', array('id'=>$data->userid), '*', MUST_EXIST);
+        $fullname = fullname($user);
+
+        $info = "{$grade_item->itemname}: $fullname";
+        add_to_log($course->id, 'grade', 'update', $url, $info);
+    }
+
     // update final grade or feedback
     // when we set override grade the first time, it happens here
     $grade_item->update_final_grade($data->userid, $data->finalgrade, 'editgrade', $data->feedback, $data->feedbackformat);
@@ -213,6 +225,19 @@ if ($mform->is_cancelled()) {
             $data->overridden = 0; // checkbox unticked
         }
         $grade_grade->set_overridden($data->overridden);
+
+        if ($data->overridden == 0 && $data->overridden != $old_grade_grade->overridden) {
+            // Log removing an override.
+            // The addition of an override is logged above.
+            // One or the other will happen but never both.
+            $url = '/report/grader/index.php?id=' . $course->id;
+
+            $user = $DB->get_record('user', array('id'=>$data->userid), '*', MUST_EXIST);
+            $fullname = fullname($user);
+
+            $info = "{$grade_item->itemname}: $fullname";
+            add_to_log($course->id, 'grade', 'update', $url, $info);
+        }
     }
 
     if (has_capability('moodle/grade:manage', $context) or has_capability('moodle/grade:hide', $context)) {
index b6b9ec2..c5292ba 100644 (file)
@@ -501,10 +501,16 @@ class gradingform_guide_controller extends gradingform_controller {
             throw new coding_exception('It is the caller\'s responsibility to make sure that the form is actually defined');
         }
 
-        $output = $this->get_renderer($page);
+        // Check if current user is able to see preview
+        $options = $this->get_options();
+        if (empty($options['alwaysshowdefinition']) && !has_capability('moodle/grade:managegradingforms', $page->context))  {
+            return '';
+        }
+
         $criteria = $this->definition->guide_criteria;
         $comments = $this->definition->guide_comment;
-        $options = $this->get_options();
+        $output = $this->get_renderer($page);
+
         $guide = '';
         $guide .= $output->box($this->get_formatted_description(), 'gradingform_guide-description');
         if (has_capability('moodle/grade:managegradingforms', $page->context)) {
index 7f08a4e..ae4f5d5 100644 (file)
@@ -505,15 +505,19 @@ class gradingform_rubric_controller extends gradingform_controller {
             throw new coding_exception('It is the caller\'s responsibility to make sure that the form is actually defined');
         }
 
-        $output = $this->get_renderer($page);
         $criteria = $this->definition->rubric_criteria;
         $options = $this->get_options();
         $rubric = '';
         if (has_capability('moodle/grade:managegradingforms', $page->context)) {
             $showdescription = true;
         } else {
+            if (empty($options['alwaysshowdefinition']))  {
+                // ensure we don't display unless show rubric option enabled
+                return '';
+            }
             $showdescription = $options['showdescriptionstudent'];
         }
+        $output = $this->get_renderer($page);
         if ($showdescription) {
             $rubric .= $output->box($this->get_formatted_description(), 'gradingform_rubric-description');
         }
index f0c7188..b61f579 100644 (file)
@@ -118,6 +118,14 @@ switch ($action) {
                 echo json_encode($json_object);
                 die();
             } else {
+                $url = '/report/grader/index.php?id=' . $course->id;
+
+                $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
+                $fullname = fullname($user);
+
+                $info = "{$grade_item->itemname}: $fullname";
+                add_to_log($course->id, 'grade', 'update', $url, $info);
+
                 $json_object->gradevalue = $finalvalue;
 
                 if ($grade_item->update_final_grade($userid, $finalgrade, 'gradebook', $feedback, FORMAT_MOODLE)) {
index 0734134..f1dd17e 100644 (file)
@@ -297,6 +297,12 @@ class grade_report_grader extends grade_report {
                         }
                     }
 
+                    $url = '/report/grader/index.php?id=' . $this->course->id;
+                    $fullname = fullname($this->users[$userid]);
+
+                    $info = "{$gradeitem->itemname}: $fullname";
+                    add_to_log($this->course->id, 'grade', 'update', $url, $info);
+
                     $gradeitem->update_final_grade($userid, $finalgrade, 'gradebook', $feedback, FORMAT_MOODLE);
 
                     // We can update feedback without reloading the grade item as it doesn't affect grade calculations
index b216a69..ce56de5 100644 (file)
--- a/index.php
+++ b/index.php
@@ -40,6 +40,9 @@
     }
     $PAGE->set_url('/', $urlparams);
     $PAGE->set_course($SITE);
+    $PAGE->set_other_editing_capability('moodle/course:update');
+    $PAGE->set_other_editing_capability('moodle/course:manageactivities');
+    $PAGE->set_other_editing_capability('moodle/course:activityvisibility');
 
     // Prevent caching of this page to stop confusion when changing page after making AJAX changes
     $PAGE->set_cacheable(false);
@@ -89,7 +92,6 @@
     }
 
     $PAGE->set_pagetype('site-index');
-    $PAGE->set_other_editing_capability('moodle/course:manageactivities');
     $PAGE->set_docs_path('');
     $PAGE->set_pagelayout('frontpage');
     $editing = $PAGE->user_is_editing();
 
             echo format_text($summarytext, $section->summaryformat, $summaryformatoptions);
 
-            if ($editing) {
+            if ($editing && has_capability('moodle/course:update', $context)) {
                 $streditsummary = get_string('editsummary');
                 echo "<a title=\"$streditsummary\" ".
                      " href=\"course/editsection.php?id=$section->id\"><img src=\"" . $OUTPUT->pix_url('t/edit') . "\" ".
index 9df0f04..3301110 100644 (file)
@@ -30,5 +30,5 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$string['cannotsavemd5file'] = 'No s\'ha pogut alçar el fitxer md5';
-$string['cannotsavezipfile'] = 'No s\'ha pogut alçar el fitxer zip';
+$string['cannotsavemd5file'] = 'No s\'ha pogut guardar el fitxer md5';
+$string['cannotsavezipfile'] = 'No s\'ha pogut guardar el fitxer zip';
index d8be051..ec61cf9 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
-$string['langdownloaderror'] = 'Dissortadament l\'idioma "{$a}" no està instal·lat. La instal·lació prosseguirà en anglés.';
+$string['clialreadyconfigured'] = 'El fitxer config.php ja existeix, feu servir dmin/cli/install_database.php si voleu instal·lar este lloc web.';
+$string['clialreadyinstalled'] = 'El fitxer config.php ja existeix, feu servir admin/cli/upgrade.php si voleu actualitzar este lloc web.';
+$string['cliinstallheader'] = 'Programa d\'instal·lació de línia d\'ordes de Moodle {$a}';
+$string['langdownloaderror'] = 'Dissortadament l\'idioma "{$a}" no es pot baixar. La instal·lació prosseguirà en anglés.';
 $string['memorylimithelp'] = '<p>El límit de memòria del PHP del vostre servidor actualment està definit en {$a}.</p>
 
 <p>Això pot causar que Moodle tinga problemes de memòria més avant, especialment si teniu molts mòduls habilitats i/o molts usuaris.</p>
@@ -41,14 +44,20 @@ $string['memorylimithelp'] = '<p>El límit de memòria del PHP del vostre servid
 <li>Si teniu accés al fitxer php.ini, podeu canviar el paràmetre <b>memory_limit</b> a 40 MB. Si no hi teniu accés podeu demanar al vostre administrador que ho faça ell.</li>
 <li>En alguns servidors PHP podeu crear un fitxer .htaccess dins del directori de Moodle amb esta línia:
 <p><blockquote>php_value memory_limit 40M</blockquote></p>
-<p>Tanmateix, en alguns servidors això farà que no funcioni <b>cap</b> pàgina PHP (es visualitzaran errors) en el qual cas hauríeu de suprimir el fitxer .htaccess.</p></li>
+<p>Tanmateix, en alguns servidors això farà que no funcione <b>cap</b> pàgina PHP (es visualitzaran errors) en el qual cas hauríeu de suprimir el fitxer .htaccess.</p></li>
 </ol>';
-$string['pathssubdataroot'] = 'Necessiteu un espai on Moodle puga alçar els fitxers penjats. Este directori hauria de tindre permisos de lectura I ESCRIPTURA per a l\'usuari del servidor web (normalment \'nobody\' o \'apache\'), però no cal que siga accessible directament via web. L\'instal·lador provarà de crear-lo si no existeix.';
-$string['phpversionhelp'] = '<p>Moodle necessita la versió de PHP 4.1.0 o posterior.</p>
+$string['pathssubadmindir'] = 'Alguns serveis d\'allotjament web (pocs) utilitzen un URL especial /admin p. ex. per a accedir a un tauler de control o quelcom paregut. Malauradament això entra en conflicte amb la ubicació estàndard de les pàgines d\'administració de Moodle. Podeu arreglar este problema canviant el nom del directori d\'administració de Moodle en la vostra instal·lació i posant el nou nom ací. Per exemple <em>moodleadmin</em>. Això modificarà els enllaços d\'administració de Moodle.';
+$string['pathssubdataroot'] = 'Necessiteu un espai on Moodle puga guardar els fitxers penjats. Este directori hauria de tindre permisos de lectura I ESCRIPTURA per a l\'usuari del servidor web (normalment \'nobody\' o \'apache\'), però no cal que siga accessible directament via web. L\'instal·lador provarà de crear-lo si no existeix.';
+$string['pathssubwwwroot'] = 'L\'adreça web completa on s\'accedirà a Moodle.
+No és possible accedir a Moodle en diferents adreces.
+Si el vostre lloc té múltiples adreces públiques haureu de configurar redireccions permanents per a totes excepte esta.
+Si el vostre lloc és accessible tant des d\'Internet com des d\'una intranet, utilitzeu ací l\'adreça pública i configureu el DNS de manera que els usuaris de la intranet puguen utilitzar també l\'adreça pública.
+Si l\'adreça no és correcta, canvieu l\'URL en el vostre navegador per reiniciar la instal·lació amb un altre valor.';
+$string['phpversionhelp'] = '<p>Moodle necessita una versió de PHP 4.3.0 o 5.1.0 (les versions 5.0.x tenien uns quants problemes coneguts).</p>
 <p>A hores d\'ara esteu utilitzant la versió {$a}.</p>
-<p>Vos caldrà actualitzar el PHP o traslladar Moodle a un ordinador amb una versió de PHP més recent.</p>';
-$string['welcomep20'] = 'Esteu veient esta pàgina perquè heu instal·lat amb èxit i heu executat el paquet <strong>{$a->packname} {$a->packversion}</strong>. Felicitacions!';
-$string['welcomep30'] = 'Esta versió de <strong>{$a->installername}</strong> inclou les aplicacions necessàries per crear un entorn en el qual funcioni <strong>Moodle</strong>:';
+<p>Vos cal actualitzar el PHP o traslladar Moodle a un ordinador amb una versió de PHP més recent.<br />(Si esteu utilitzant la versió 5.0.x, alternativament també podríeu tornar arrere a la 4.4.x)</p>';
+$string['welcomep20'] = 'Esteu veient esta pàgina perquè heu instal·lat amb èxit i heu executat el paquet <strong>{$a->packname} {$a->packversion}</strong>. Felicitacions.';
+$string['welcomep30'] = 'Esta versió de <strong>{$a->installername}</strong> inclou les aplicacions necessàries per crear un entorn en el qual funcione <strong>Moodle</strong>:';
 $string['welcomep50'] = 'L\'ús de totes les aplicacions d\'este paquet és governat per les seues llicències respectives. El paquet <strong>{$a->installername}</strong> complet és
 <a href="http://www.opensource.org/docs/definition_plain.html">codi font obert</a> i es distribueix
 sota llicència <a href="http://www.gnu.org/copyleft/gpl.html">GPL</a>.';
index 267e449..61197a5 100644 (file)
@@ -30,6 +30,8 @@
 
 defined('MOODLE_INTERNAL') || die();
 
+$string['cannotcreatedboninstall'] = '<p>Non è possibile creare il database </p> <p>Il database non esiste o l\'utente non è autorizzato a crearlo.</p>
+<p>E\' necessario che l\'amministratore del sito  verifichi  la configurazione del database.</p>';
 $string['cannotcreatelangdir'] = 'Non è possibile creare la cartella lang';
 $string['cannotcreatetempdir'] = 'Non è possibile creare la cartella temp';
 $string['cannotdownloadcomponents'] = 'Non è possibile scaricare componenti.';
index 9e1d4de..1ede2d0 100644 (file)
@@ -30,5 +30,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
+$string['parentlanguage'] = 'en';
 $string['thisdirection'] = 'ltr';
 $string['thislanguage'] = 'Српски';
index c11ac67..01b82fc 100644 (file)
@@ -30,5 +30,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
+$string['parentlanguage'] = 'en';
 $string['thisdirection'] = 'ltr';
 $string['thislanguage'] = 'Srpski';
index b1c5126..ffea8a9 100644 (file)
@@ -479,6 +479,7 @@ $string['enablecomments'] = 'Enable comments';
 $string['enablecourserequests'] = 'Enable course requests';
 $string['enablecssoptimiser'] = 'Enable CSS optimiser';
 $string['enablecssoptimiser_desc'] = 'When enabled CSS will be run through an optimisation process before being cached. The optimiser processes the CSS removing duplicate rules and styles, as well as white space removable and reformatting. Please note turning this on at the same time as theme designer mode is awful for performance but will help theme designers create optimised CSS.';
+$string['enabled'] = 'Enabled';
 $string['enabledevicedetection'] = 'Enable device detection';
 $string['enablegravatar'] = 'Enable Gravatar';
 $string['enablegravatar_help'] = 'When enabled Moodle will attempt to fetch a user profile picture from Gravatar if the user has not uploaded an image.';
@@ -635,7 +636,7 @@ $string['localetext'] = 'Sitewide locale';
 $string['localstringcustomization'] = 'Local string customization';
 $string['location'] = 'Location';
 $string['locationsettings'] = 'Location settings';
-$string['locked'] = 'locked';
+$string['locked'] = 'Locked';
 $string['lockoutduration'] = 'Account lockout duration';
 $string['lockoutduration_desc'] = 'Locked out account is automatically unlocked after this duration.';
 $string['lockoutemailbody'] = 'Your account with username {$a->username} on server \'{$a->sitename}\'
index 3aabdfc..4edb0a0 100644 (file)
@@ -111,6 +111,7 @@ $string['groupnameexists'] = 'The group name \'{$a}\' already exists in this cou
 $string['groupnotamember'] = 'Sorry, you are not a member of that group';
 $string['groups'] = 'Groups';
 $string['groupscount'] = 'Groups ({$a})';
+$string['groupsettingsheader'] = 'Groups';
 $string['groupsgroupings'] = 'Groups &amp; groupings';
 $string['groupsinselectedgrouping'] = 'Groups in:';
 $string['groupsnone'] = 'No groups';
index 6ee12c8..6a91a6b 100644 (file)
@@ -154,7 +154,6 @@ $string['publisheremail_help'] = 'The publisher email address allows the hub adm
 $string['publishername'] = 'Publisher';
 $string['publishername_help'] = 'The publisher is the person or organisation that is the official publisher of the course.  Unless you are publishing it on behalf of someone else, it will usually be you.';
 $string['publishon'] = 'Publish on';
-$string['publishonmoodleorg'] = 'Publish on MOOCH';
 $string['publishonspecifichub'] = 'Publish on another Hub';
 $string['questionsnumber'] = 'Number of questions ({$a})';
 $string['registeredcourses'] = 'Registered courses';
@@ -192,7 +191,6 @@ $string['share'] = 'Share this course for people to download';
 $string['shared'] = 'Shared';
 $string['shareon'] = 'Upload this course to {$a}';
 $string['shareonhub'] = 'Upload this course to a hub';
-$string['shareonmoodleorg'] = 'Upload this course to MOOCH';
 $string['sharepublication_help'] = 'Uploading this course to a community hub server will enable people to download it and install it on their own Moodle sites.';
 $string['siteadmin'] = 'Administrator';
 $string['siteadmin_help'] = 'The full name of the site administrator.';
index f9e4e71..dbb2031 100644 (file)
@@ -572,8 +572,21 @@ function is_siteadmin($user_or_id = null) {
         $userid = $user_or_id;
     }
 
+    // Because this script is called many times (150+ for course page) with
+    // the same parameters, it is worth doing minor optimisations. This static
+    // cache stores the value for a single userid, saving about 2ms from course
+    // page load time without using significant memory. As the static cache
+    // also includes the value it depends on, this cannot break unit tests.
+    static $knownid, $knownresult, $knownsiteadmins;
+    if ($knownid === $userid && $knownsiteadmins === $CFG->siteadmins) {
+        return $knownresult;
+    }
+    $knownid = $userid;
+    $knownsiteadmins = $CFG->siteadmins;
+
     $siteadmins = explode(',', $CFG->siteadmins);
-    return in_array($userid, $siteadmins);
+    $knownresult = in_array($userid, $siteadmins);
+    return $knownresult;
 }
 
 /**
index af6bab7..a47b1a6 100644 (file)
@@ -1506,6 +1506,8 @@ abstract class admin_setting {
     public $nosave = false;
     /** @var bool if set, indicates that a change to this setting requires rebuild course cache */
     public $affectsmodinfo = false;
+    /** @var array of admin_setting_flag - These are extra checkboxes attached to a setting. */
+    private $flags = array();
 
     /**
      * Constructor
@@ -1522,6 +1524,114 @@ abstract class admin_setting {
         $this->defaultsetting = $defaultsetting;
     }
 
+    /**
+     * Generic function to add a flag to this admin setting.
+     *
+     * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
+     * @param bool $default - The default for the flag
+     * @param string $shortname - The shortname for this flag. Used as a suffix for the setting name.
+     * @param string $displayname - The display name for this flag. Used as a label next to the checkbox.
+     */
+    protected function set_flag_options($enabled, $default, $shortname, $displayname) {
+        if (empty($this->flags[$shortname])) {
+            $this->flags[$shortname] = new admin_setting_flag($enabled, $default, $shortname, $displayname);
+        } else {
+            $this->flags[$shortname]->set_options($enabled, $default);
+        }
+    }
+
+    /**
+     * Set the enabled options flag on this admin setting.
+     *
+     * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
+     * @param bool $default - The default for the flag
+     */
+    public function set_enabled_flag_options($enabled, $default) {
+        $this->set_flag_options($enabled, $default, 'enabled', new lang_string('enabled', 'core_admin'));
+    }
+
+    /**
+     * Set the advanced options flag on this admin setting.
+     *
+     * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
+     * @param bool $default - The default for the flag
+     */
+    public function set_advanced_flag_options($enabled, $default) {
+        $this->set_flag_options($enabled, $default, 'adv', new lang_string('advanced'));
+    }
+
+
+    /**
+     * Set the locked options flag on this admin setting.
+     *
+     * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
+     * @param bool $default - The default for the flag
+     */
+    public function set_locked_flag_options($enabled, $default) {
+        $this->set_flag_options($enabled, $default, 'locked', new lang_string('locked', 'core_admin'));
+    }
+
+    /**
+     * Get the currently saved value for a setting flag
+     *
+     * @param admin_setting_flag $flag - One of the admin_setting_flag for this admin_setting.
+     * @return bool
+     */
+    public function get_setting_flag_value(admin_setting_flag $flag) {
+        $value = $this->config_read($this->name . '_' . $flag->get_shortname());
+        if (!isset($value)) {
+            $value = $flag->get_default();
+        }
+
+        return !empty($value);
+    }
+
+    /**
+     * Get the list of defaults for the flags on this setting.
+     *
+     * @param array of strings describing the defaults for this setting. This is appended to by this function.
+     */
+    public function get_setting_flag_defaults(& $defaults) {
+        foreach ($this->flags as $flag) {
+            if ($flag->is_enabled() && $flag->get_default()) {
+                $defaults[] = $flag->get_displayname();
+            }
+        }
+    }
+
+    /**
+     * Output the input fields for the advanced and locked flags on this setting.
+     *
+     * @param bool $adv - The current value of the advanced flag.
+     * @param bool $locked - The current value of the locked flag.
+     * @return string $output - The html for the flags.
+     */
+    public function output_setting_flags() {
+        $output = '';
+
+        foreach ($this->flags as $flag) {
+            if ($flag->is_enabled()) {
+                $output .= $flag->output_setting_flag($this);
+            }
+        }
+
+        return $output;
+    }
+
+    /**
+     * Write the values of the flags for this admin setting.
+     *
+     * @param array $data - The data submitted from the form.
+     * @return bool - true if successful.
+     */
+    public function write_setting_flags($data) {
+        $result = true;
+        foreach ($this->flags as $flag) {
+            $result = $result && $flag->write_setting_flag($this, $data);
+        }
+        return $result;
+    }
+
     /**
      * Set up $this->name and potentially $this->plugin
      *
@@ -1746,6 +1856,126 @@ abstract class admin_setting {
     }
 }
 
+/**
+ * An additional option that can be applied to an admin setting.
+ * The currently supported options are 'ADVANCED' and 'LOCKED'.
+ *
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class admin_setting_flag {
+    /** @var bool Flag to indicate if this option can be toggled for this setting */
+    private $enabled = false;
+    /** @var bool Flag to indicate if this option defaults to true or false */
+    private $default = false;
+    /** @var string Short string used to create setting name - e.g. 'adv' */
+    private $shortname = '';
+    /** @var string String used as the label for this flag */
+    private $displayname = '';
+    /** @const Checkbox for this flag is displayed in admin page */
+    const ENABLED = true;
+    /** @const Checkbox for this flag is not displayed in admin page */
+    const DISABLED = false;
+
+    /**
+     * Constructor
+     *
+     * @param bool $enabled Can this option can be toggled.
+     *                      Should be one of admin_setting_flag::ENABLED or admin_setting_flag::DISABLED.
+     * @param bool $default The default checked state for this setting option.
+     * @param string $shortname The shortname of this flag. Currently supported flags are 'locked' and 'adv'
+     * @param string $displayname The displayname of this flag. Used as a label for the flag.
+     */
+    public function __construct($enabled, $default, $shortname, $displayname) {
+        $this->shortname = $shortname;
+        $this->displayname = $displayname;
+        $this->set_options($enabled, $default);
+    }
+
+    /**
+     * Update the values of this setting options class
+     *
+     * @param bool $enabled Can this option can be toggled.
+     *                      Should be one of admin_setting_flag::ENABLED or admin_setting_flag::DISABLED.
+     * @param bool $default The default checked state for this setting option.
+     */
+    public function set_options($enabled, $default) {
+        $this->enabled = $enabled;
+        $this->default = $default;
+    }
+
+    /**
+     * Should this option appear in the interface and be toggleable?
+     *
+     * @return bool Is it enabled?
+     */
+    public function is_enabled() {
+        return $this->enabled;
+    }
+
+    /**
+     * Should this option be checked by default?
+     *
+     * @return bool Is it on by default?
+     */
+    public function get_default() {
+        return $this->default;
+    }
+
+    /**
+     * Return the short name for this flag. e.g. 'adv' or 'locked'
+     *
+     * @return string
+     */
+    public function get_shortname() {
+        return $this->shortname;
+    }
+
+    /**
+     * Return the display name for this flag. e.g. 'Advanced' or 'Locked'
+     *
+     * @return string
+     */
+    public function get_displayname() {
+        return $this->displayname;
+    }
+
+    /**
+     * Save the submitted data for this flag - or set it to the default if $data is null.
+     *
+     * @param admin_setting $setting - The admin setting for this flag
+     * @param array $data - The data submitted from the form.
+     * @return bool
+     */
+    public function write_setting_flag(admin_setting $setting, $data) {
+        $result = true;
+        if ($this->is_enabled()) {
+            $value = !empty($data[$setting->get_full_name() . '_' . $this->get_shortname()]);
+            $result = $setting->config_write($setting->name . '_' . $this->get_shortname(), $value);
+        }
+
+        return $result;
+
+    }
+
+    /**
+     * Output the checkbox for this setting flag. Should only be called if the flag is enabled.
+     *
+     * @param admin_setting $setting - The admin setting for this flag
+     * @return string - The html for the checkbox.
+     */
+    public function output_setting_flag(admin_setting $setting) {
+        $value = $setting->get_setting_flag_value($this);
+        $output = ' <input type="checkbox" class="form-checkbox" ' .
+                        ' id="' .  $setting->get_id() . '_' . $this->get_shortname() . '" ' .
+                        ' name="' . $setting->get_full_name() .  '_' . $this->get_shortname() . '" ' .
+                        ' value="1" ' . ($value ? 'checked="checked"' : '') . ' />' .
+                        ' <label for="' . $setting->get_id() . '_' . $this->get_shortname() . '">' .
+                        $this->get_displayname() .
+                        ' </label> ';
+        return $output;
+    }
+}
+
 
 /**
  * No setting - just heading and text.
@@ -4230,73 +4460,8 @@ class admin_setting_configtext_with_advanced extends admin_setting_configtext {
      * @param int $size default field size
      */
     public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
-        parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size);
-    }
-
-    /**
-     * Loads the current setting and returns array
-     *
-     * @return array Returns array value=>xx, __construct=>xx
-     */
-    public function get_setting() {
-        $value = parent::get_setting();
-        $adv = $this->config_read($this->name.'_adv');
-        if (is_null($value) or is_null($adv)) {
-            return NULL;
-        }
-        return array('value' => $value, 'adv' => $adv);
-    }
-
-    /**
-     * Saves the new settings passed in $data
-     *
-     * @todo Add vartype handling to ensure $data is an array
-     * @param array $data
-     * @return mixed string or Array
-     */
-    public function write_setting($data) {
-        $error = parent::write_setting($data['value']);
-        if (!$error) {
-            $value = empty($data['adv']) ? 0 : 1;
-            $this->config_write($this->name.'_adv', $value);
-        }
-        return $error;
-    }
-
-    /**
-     * Return XHTML for the control
-     *
-     * @param array $data Default data array
-     * @param string $query
-     * @return string XHTML to display control
-     */
-    public function output_html($data, $query='') {
-        $default = $this->get_defaultsetting();
-        $defaultinfo = array();
-        if (isset($default['value'])) {
-            if ($default['value'] === '') {
-                $defaultinfo[] = "''";
-            } else {
-                $defaultinfo[] = $default['value'];
-            }
-        }
-        if (!empty($default['adv'])) {
-            $defaultinfo[] = get_string('advanced');
-        }
-        $defaultinfo = implode(', ', $defaultinfo);
-
-        $adv = !empty($data['adv']);
-        $return = '<div class="form-text defaultsnext">' .
-            '<input type="text" size="' . $this->size . '" id="' . $this->get_id() .
-            '" name="' . $this->get_full_name() . '[value]" value="' . s($data['value']) . '" />' .
-            ' <input type="checkbox" class="form-checkbox" id="' .
-            $this->get_id() . '_adv" name="' . $this->get_full_name() .
-            '[adv]" value="1" ' . ($adv ? 'checked="checked"' : '') . ' />' .
-            ' <label for="' . $this->get_id() . '_adv">' .
-            get_string('advanced') . '</label></div>';
-
-        return format_admin_setting($this, $this->visiblename, $return,
-        $this->description, true, '', $defaultinfo, $query);
+        parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $paramtype, $size);
+        $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
     }
 }
 
@@ -4319,90 +4484,10 @@ class admin_setting_configcheckbox_with_advanced extends admin_setting_configche
      * @param string $no value used when not checked
      */
     public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
-        parent::__construct($name, $visiblename, $description, $defaultsetting, $yes, $no);
-    }
-
-    /**
-     * Loads the current setting and returns array
-     *
-     * @return array Returns array value=>xx, adv=>xx
-     */
-    public function get_setting() {
-        $value = parent::get_setting();
-        $adv = $this->config_read($this->name.'_adv');
-        if (is_null($value) or is_null($adv)) {
-            return NULL;
-        }
-        return array('value' => $value, 'adv' => $adv);
+        parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $yes, $no);
+        $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
     }
 
-    /**
-     * Sets the value for the setting
-     *
-     * Sets the value for the setting to either the yes or no values
-     * of the object by comparing $data to yes
-     *
-     * @param mixed $data Gets converted to str for comparison against yes value
-     * @return string empty string or error
-     */
-    public function write_setting($data) {
-        $error = parent::write_setting($data['value']);
-        if (!$error) {
-            $value = empty($data['adv']) ? 0 : 1;
-            $this->config_write($this->name.'_adv', $value);
-        }
-        return $error;
-    }
-
-    /**
-     * Returns an XHTML checkbox field and with extra advanced cehckbox
-     *
-     * @param string $data If $data matches yes then checkbox is checked
-     * @param string $query
-     * @return string XHTML field
-     */
-    public function output_html($data, $query='') {
-        $defaults = $this->get_defaultsetting();
-        $defaultinfo = array();
-        if (!is_null($defaults)) {
-            if ((string)$defaults['value'] === $this->yes) {
-                $defaultinfo[] = get_string('checkboxyes', 'admin');
-            } else {
-                $defaultinfo[] = get_string('checkboxno', 'admin');
-            }
-            if (!empty($defaults['adv'])) {
-                $defaultinfo[] = get_string('advanced');
-            }
-        }
-        $defaultinfo = implode(', ', $defaultinfo);
-
-        if ((string)$data['value'] === $this->yes) { // convert to strings before comparison
-            $checked = 'checked="checked"';
-        } else {
-            $checked = '';
-        }
-        if (!empty($data['adv'])) {
-            $advanced = 'checked="checked"';
-        } else {
-            $advanced = '';
-        }
-
-        $fullname    = $this->get_full_name();
-        $novalue     = s($this->no);
-        $yesvalue    = s($this->yes);
-        $id          = $this->get_id();
-        $stradvanced = get_string('advanced');
-        $return = <<<EOT
-<div class="form-checkbox defaultsnext" >
-<input type="hidden" name="{$fullname}[value]" value="$novalue" />
-<input type="checkbox" id="$id" name="{$fullname}[value]" value="$yesvalue" $checked />
-<input type="checkbox" class="form-checkbox" id="{$id}_adv" name="{$fullname}[adv]" value="1" $advanced />
-<label for="{$id}_adv">$stradvanced</label>
-</div>
-EOT;
-        return format_admin_setting($this, $this->visiblename, $return, $this->description,
-        true, '', $defaultinfo, $query);
-    }
 }
 
 
@@ -4425,86 +4510,10 @@ class admin_setting_configcheckbox_with_lock extends admin_setting_configcheckbo
      * @param string $no value used when not checked
      */
     public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
-        parent::__construct($name, $visiblename, $description, $defaultsetting, $yes, $no);
-    }
-
-    /**
-     * Loads the current setting and returns array
-     *
-     * @return array Returns array value=>xx, adv=>xx
-     */
-    public function get_setting() {
-        $value = parent::get_setting();
-        $locked = $this->config_read($this->name.'_locked');
-        if (is_null($value) or is_null($locked)) {
-            return NULL;
-        }
-        return array('value' => $value, 'locked' => $locked);
+        parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $yes, $no);
+        $this->set_locked_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['locked']));
     }
 
-    /**
-     * Sets the value for the setting
-     *
-     * Sets the value for the setting to either the yes or no values
-     * of the object by comparing $data to yes
-     *
-     * @param mixed $data Gets converted to str for comparison against yes value
-     * @return string empty string or error
-     */
-    public function write_setting($data) {
-        $error = parent::write_setting($data['value']);
-        if (!$error) {
-            $value = empty($data['locked']) ? 0 : 1;
-            $this->config_write($this->name.'_locked', $value);
-        }
-        return $error;
-    }
-
-    /**
-     * Returns an XHTML checkbox field and with extra locked checkbox
-     *
-     * @param string $data If $data matches yes then checkbox is checked
-     * @param string $query
-     * @return string XHTML field
-     */
-    public function output_html($data, $query='') {
-        $defaults = $this->get_defaultsetting();
-        $defaultinfo = array();
-        if (!is_null($defaults)) {
-            if ((string)$defaults['value'] === $this->yes) {
-                $defaultinfo[] = get_string('checkboxyes', 'admin');
-            } else {
-                $defaultinfo[] = get_string('checkboxno', 'admin');
-            }
-            if (!empty($defaults['locked'])) {
-                $defaultinfo[] = get_string('locked', 'admin');
-            }
-        }
-        $defaultinfo = implode(', ', $defaultinfo);
-
-        $fullname    = $this->get_full_name();
-        $novalue     = s($this->no);
-        $yesvalue    = s($this->yes);
-        $id          = $this->get_id();
-
-        $checkboxparams = array('type'=>'checkbox', 'id'=>$id,'name'=>$fullname.'[value]', 'value'=>$yesvalue);
-        if ((string)$data['value'] === $this->yes) { // convert to strings before comparison
-            $checkboxparams['checked'] = 'checked';
-        }
-
-        $lockcheckboxparams = array('type'=>'checkbox', 'id'=>$id.'_locked','name'=>$fullname.'[locked]', 'value'=>1, 'class'=>'form-checkbox locked-checkbox');
-        if (!empty($data['locked'])) { // convert to strings before comparison
-            $lockcheckboxparams['checked'] = 'checked';
-        }
-
-        $return  = html_writer::start_tag('div', array('class'=>'form-checkbox defaultsnext'));
-        $return .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>$fullname.'[value]', 'value'=>$novalue));
-        $return .= html_writer::empty_tag('input', $checkboxparams);
-        $return .= html_writer::empty_tag('input', $lockcheckboxparams);
-        $return .= html_writer::tag('label', get_string('locked', 'admin'), array('for'=>$id.'_locked'));
-        $return .= html_writer::end_tag('div');
-        return format_admin_setting($this, $this->visiblename, $return, $this->description, true, '', $defaultinfo, $query);
-    }
 }
 
 
@@ -4518,79 +4527,10 @@ class admin_setting_configselect_with_advanced extends admin_setting_configselec
      * Calls parent::__construct with specific arguments
      */
     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
-        parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
+        parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $choices);
+        $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
     }
 
-    /**
-     * Loads the current setting and returns array
-     *
-     * @return array Returns array value=>xx, adv=>xx
-     */
-    public function get_setting() {
-        $value = parent::get_setting();
-        $adv = $this->config_read($this->name.'_adv');
-        if (is_null($value) or is_null($adv)) {
-            return NULL;
-        }
-        return array('value' => $value, 'adv' => $adv);
-    }
-
-    /**
-     * Saves the new settings passed in $data
-     *
-     * @todo Add vartype handling to ensure $data is an array
-     * @param array $data
-     * @return mixed string or Array
-     */
-    public function write_setting($data) {
-        $error = parent::write_setting($data['value']);
-        if (!$error) {
-            $value = empty($data['adv']) ? 0 : 1;
-            $this->config_write($this->name.'_adv', $value);
-        }
-        return $error;
-    }
-
-    /**
-     * Return XHTML for the control
-     *
-     * @param array $data Default data array
-     * @param string $query
-     * @return string XHTML to display control
-     */
-    public function output_html($data, $query='') {
-        $default = $this->get_defaultsetting();
-        $current = $this->get_setting();
-
-        list($selecthtml, $warning) = $this->output_select_html($data['value'],
-            $current['value'], $default['value'], '[value]');
-        if (!$selecthtml) {
-            return '';
-        }
-
-        if (!is_null($default) and array_key_exists($default['value'], $this->choices)) {
-            $defaultinfo = array();
-            if (isset($this->choices[$default['value']])) {
-                $defaultinfo[] = $this->choices[$default['value']];
-            }
-            if (!empty($default['adv'])) {
-                $defaultinfo[] = get_string('advanced');
-            }
-            $defaultinfo = implode(', ', $defaultinfo);
-        } else {
-            $defaultinfo = '';
-        }
-
-        $adv = !empty($data['adv']);
-        $return = '<div class="form-select defaultsnext">' . $selecthtml .
-            ' <input type="checkbox" class="form-checkbox" id="' .
-            $this->get_id() . '_adv" name="' . $this->get_full_name() .
-            '[adv]" value="1" ' . ($adv ? 'checked="checked"' : '') . ' />' .
-            ' <label for="' . $this->get_id() . '_adv">' .
-            get_string('advanced') . '</label></div>';
-
-        return format_admin_setting($this, $this->visiblename, $return, $this->description, true, $warning, $defaultinfo, $query);
-    }
 }
 
 
@@ -6466,6 +6406,8 @@ function admin_write_settings($formdata) {
             $adminroot->errors[$fullname]->data  = $data[$fullname];
             $adminroot->errors[$fullname]->id    = $setting->get_id();
             $adminroot->errors[$fullname]->error = $error;
+        } else {
+            $setting->write_setting_flags($data);
         }
         if ($setting->post_write_settings($original)) {
             $count++;
@@ -6654,6 +6596,7 @@ function format_admin_setting($setting, $title='', $form='', $description='', $l
     } else {
         $labelfor = '';
     }
+    $form .= $setting->output_setting_flags();
 
     $override = '';
     if (empty($setting->plugin)) {
@@ -6670,12 +6613,18 @@ function format_admin_setting($setting, $title='', $form='', $description='', $l
         $warning = '<div class="form-warning">'.$warning.'</div>';
     }
 
-    if (is_null($defaultinfo)) {
-        $defaultinfo = '';
-    } else {
+    $defaults = array();
+    if (!is_null($defaultinfo)) {
         if ($defaultinfo === '') {
             $defaultinfo = get_string('emptysettingvalue', 'admin');
         }
+        $defaults[] = $defaultinfo;
+    }
+
+    $setting->get_setting_flag_defaults($defaults);
+
+    if (!empty($defaults)) {
+        $defaultinfo = implode(', ', $defaults);
         $defaultinfo = highlight($query, nl2br(s($defaultinfo)));
         $defaultinfo = '<div class="form-defaultinfo">'.get_string('defaultsettinginfo', 'admin', $defaultinfo).'</div>';
     }
index f7ab795..54f08a8 100644 (file)
@@ -341,7 +341,7 @@ class block_manager {
      * output the blocks anyway, so we are not doing wasted effort.)
      *
      * @param string $region a block region that exists on this page.
-     * @param object $output a core_renderer. normally the global $OUTPUT.
+     * @param core_renderer $output a core_renderer. normally the global $OUTPUT.
      * @return boolean Whether there is anything in this region.
      */
     public function region_has_content($region, $output) {
index 389312f..78f9eb8 100644 (file)
@@ -1045,40 +1045,36 @@ class completion_info {
         }
     }
 
+     /**
+     * Return whether or not the course has activities with completion enabled.
+     *
+     * @return boolean true when there is at least one activity with completion enabled.
+     */
+    public function has_activities() {
+        $modinfo = get_fast_modinfo($this->course);
+        foreach ($modinfo->get_cms() as $cm) {
+            if ($cm->completion != COMPLETION_TRACKING_NONE) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Obtains a list of activities for which completion is enabled on the
      * course. The list is ordered by the section order of those activities.
      *
-     * @param array $modinfo For unit testing only, supply the value
-     *   here. Otherwise the method calls get_fast_modinfo
      * @return array Array from $cmid => $cm of all activities with completion enabled,
      *   empty array if none
      */
-    public function get_activities($modinfo=null) {
-        global $DB;
-
-        // Obtain those activities which have completion turned on
-        $withcompletion = $DB->get_records_select('course_modules', 'course='.$this->course->id.
-          ' AND completion<>'.COMPLETION_TRACKING_NONE);
-        if (!$withcompletion) {
-            return array();
-        }
-
-        // Use modinfo to get section order and also add in names
-        if (empty($modinfo)) {
-            $modinfo = get_fast_modinfo($this->course);
-        }
+    public function get_activities() {
+        $modinfo = get_fast_modinfo($this->course);
         $result = array();
-        foreach ($modinfo->sections as $sectioncms) {
-            foreach ($sectioncms as $cmid) {
-                if (array_key_exists($cmid, $withcompletion)) {
-                    $result[$cmid] = $withcompletion[$cmid];
-                    $result[$cmid]->modname = $modinfo->cms[$cmid]->modname;
-                    $result[$cmid]->name    = $modinfo->cms[$cmid]->name;
-                }
+        foreach ($modinfo->get_cms() as $cm) {
+            if ($cm->completion != COMPLETION_TRACKING_NONE) {
+                $result[$cm->id] = $cm;
             }
         }
-
         return $result;
     }
 
index 6cf2ea1..2a5e06d 100644 (file)
@@ -833,7 +833,7 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
             $fields[] = 'c.summary';
             $fields[] = 'c.summaryformat';
         } else {
-            $fields[] = $DB->sql_substr('c.summary', 1, 1). ' hassummary';
+            $fields[] = $DB->sql_substr('c.summary', 1, 1). ' as hassummary';
         }
         $sql = "SELECT ". join(',', $fields). ", $ctxselect
                 FROM {course} c
index 8551999..cb6faf3 100644 (file)
@@ -228,7 +228,7 @@ function css_send_ie_css($themename, $rev, $etag, $slasharguments) {
         $css .= "\n@import url($relroot/styles.php?theme=$themename&rev=$rev&type=theme);";
     }
 
-    header('Etag: '.$etag);
+    header('Etag: "'.$etag.'"');
     header('Content-Disposition: inline; filename="styles.php"');
     header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
     header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
@@ -254,7 +254,7 @@ function css_send_ie_css($themename, $rev, $etag, $slasharguments) {
 function css_send_cached_css($csspath, $etag) {
     $lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
 
-    header('Etag: '.$etag);
+    header('Etag: "'.$etag.'"');
     header('Content-Disposition: inline; filename="styles.php"');
     header('Last-Modified: '. gmdate('D, d M Y H:i:s', filemtime($csspath)) .' GMT');
     header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
@@ -312,7 +312,7 @@ function css_send_unmodified($lastmodified, $etag) {
     header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
     header('Cache-Control: public, max-age='.$lifetime);
     header('Content-Type: text/css; charset=utf-8');
-    header('Etag: '.$etag);
+    header('Etag: "'.$etag.'"');
     if ($lastmodified) {
         header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');
     }
index 7db9eba..3f918e8 100644 (file)
@@ -556,6 +556,30 @@ function get_site() {
     }
 }
 
+/**
+ * Gets a course object from database. If the course id corresponds to an
+ * already-loaded $COURSE or $SITE object, then the loaded object will be used,
+ * saving a database query.
+ *
+ * If it reuses an existing object, by default the object will be cloned. This
+ * means you can modify the object safely without affecting other code.
+ *
+ * @param int $courseid Course id
+ * @param bool $clone If true (default), makes a clone of the record
+ * @return stdClass A course object
+ * @throws dml_exception If not found in database
+ */
+function get_course($courseid, $clone = true) {
+    global $DB, $COURSE, $SITE;
+    if (!empty($COURSE->id) && $COURSE->id == $courseid) {
+        return $clone ? clone($COURSE) : $COURSE;
+    } else if (!empty($SITE->id) && $SITE->id == $courseid) {
+        return $clone ? clone($SITE) : $SITE;
+    } else {
+        return $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
+    }
+}
+
 /**
  * Returns list of courses, for whole site, or category
  *
index 64ecd3b..8b2c407 100644 (file)
@@ -2139,6 +2139,9 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2013042300.00);
     }
 
+    // Moodle v2.5.0 release upgrade line.
+    // Put any upgrade step following this.
+
     if ($oldversion < 2013051400.01) {
         // Fix incorrect cc-nc url. Unfortunately the license 'plugins' do
         // not give a mechanism to do this.
@@ -2160,9 +2163,5 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2013051400.01);
     }
 
-    // Moodle v2.5.0 release upgrade line.
-    // Put any upgrade step following this.
-
-
     return true;
 }
index 5f72a73..3aab9f4 100644 (file)
@@ -2838,24 +2838,10 @@ function show_event($event) {
 }
 
 /**
- * Converts string to lowercase using most compatible function available.
- *
  * @deprecated Use textlib::strtolower($text) instead.
- *
- * @param string $string The string to convert to all lowercase characters.
- * @param string $encoding The encoding on the string.
- * @return string
  */
 function moodle_strtolower($string, $encoding='') {
-
-    debugging('moodle_strtolower() is deprecated. Please use textlib::strtolower() instead.', DEBUG_DEVELOPER);
-
-    //If not specified use utf8
-    if (empty($encoding)) {
-        $encoding = 'UTF-8';
-    }
-    //Use text services
-    return textlib::strtolower($string, $encoding);
+    throw new coding_exception('moodle_strtolower() cannot be used any more. Please use textlib::strtolower() instead.');
 }
 
 /**
@@ -4676,3 +4662,10 @@ function convert_tabrows_to_tree($tabrows, $selected, $inactive, $activated) {
 
     return $subtree;
 }
+
+/**
+ * @deprecated since Moodle 2.3
+ */
+function move_section($course, $section, $move) {
+    throw new coding_exception('move_section() can not be used any more, please see move_section_to().');
+}
index 96f14fc..f146088 100644 (file)
@@ -51,8 +51,6 @@ class oci_native_moodle_database extends moodle_database {
     private $last_error_reporting;
     /** @var To store unique_session_id. Needed for temp tables unique naming.*/
     private $unique_session_id;
-    /** @var To cache locks support along the connection life.*/
-    private $oci_package_installed = null;
 
     /**
      * Detects if all needed PHP stuff installed.
@@ -127,9 +125,6 @@ class oci_native_moodle_database extends moodle_database {
      * @return string null means everything ok, string means problem found.
      */
     public function diagnose() {
-        if (!$this->oci_package_installed()) {
-            return 'Oracle PL/SQL Moodle support packages are not installed! Database administrator has to execute /lib/dml/oci_native_moodle_package.sql script.';
-        }
         return null;
     }
 
@@ -203,6 +198,19 @@ class oci_native_moodle_database extends moodle_database {
             throw new dml_connection_exception($dberr);
         }
 
+        // Make sure moodle package is installed - now required.
+        if (!$this->oci_package_installed()) {
+            try {
+                $this->attempt_oci_package_install();
+            } catch (Exception $e) {
+                // Ignore problems, only the result counts,
+                // admins have to fix it manually if necessary.
+            }
+            if (!$this->oci_package_installed()) {
+                throw new dml_exception('dbdriverproblem', 'Oracle PL/SQL Moodle support package MOODLELIB is not installed! Database administrator has to execute /lib/dml/oci_native_moodle_package.sql script.');
+            }
+        }
+
         // get unique session id, to be used later for temp tables stuff
         $sql = 'SELECT DBMS_SESSION.UNIQUE_SESSION_ID() FROM DUAL';
         $this->query_start($sql, null, SQL_QUERY_AUX);
@@ -1476,21 +1484,11 @@ class oci_native_moodle_database extends moodle_database {
     }
 
     public function sql_bitor($int1, $int2) {
-        // Use the MOODLELIB package if available
-        if ($this->oci_package_installed()) {
-            return 'MOODLELIB.BITOR(' . $int1 . ', ' . $int2 . ')';
-        }
-        // fallback to PHP bool operations, can break if using placeholders
-        return '((' . $int1 . ') + (' . $int2 . ') - ' . $this->sql_bitand($int1, $int2) . ')';
+        return 'MOODLELIB.BITOR(' . $int1 . ', ' . $int2 . ')';
     }
 
     public function sql_bitxor($int1, $int2) {
-        // Use the MOODLELIB package if available
-        if ($this->oci_package_installed()) {
-            return 'MOODLELIB.BITXOR(' . $int1 . ', ' . $int2 . ')';
-        }
-        // fallback to PHP bool operations, can break if using placeholders
-        return '(' . $this->sql_bitor($int1, $int2) . ' - ' . $this->sql_bitand($int1, $int2) . ')';
+        return 'MOODLELIB.BITXOR(' . $int1 . ', ' . $int2 . ')';
     }
 
     /**
@@ -1703,9 +1701,6 @@ class oci_native_moodle_database extends moodle_database {
      * @return bool
      */
     protected function oci_package_installed() {
-        if (isset($this->oci_package_installed)) { // Use cached value if available.
-            return $this->oci_package_installed;
-        }
         $sql = "SELECT 1
                 FROM user_objects
                 WHERE object_type = 'PACKAGE BODY'
@@ -1718,8 +1713,22 @@ class oci_native_moodle_database extends moodle_database {
         $records = null;
         oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW);
         oci_free_statement($stmt);
-        $this->oci_package_installed = isset($records[0]) && reset($records[0]) ? true : false;
-        return $this->oci_package_installed;
+        return isset($records[0]) && reset($records[0]) ? true : false;
+    }
+
+    /**
+     * Try to add required moodle package into oracle server.
+     */
+    protected function attempt_oci_package_install() {
+        $sqls = file_get_contents(__DIR__.'/oci_native_moodle_package.sql');
+        $sqls = preg_split('/^\/$/sm', $sqls);
+        foreach ($sqls as $sql) {
+            $sql = trim($sql);
+            if ($sql === '' or $sql === 'SHOW ERRORS') {
+                continue;
+            }
+            $this->change_database_structure($sql);
+        }
     }
 
     /**
@@ -1729,9 +1738,6 @@ class oci_native_moodle_database extends moodle_database {
      * @return void
      */
     public function get_session_lock($rowid, $timeout) {
-        if (!$this->oci_package_installed()) {
-            return;
-        }
         parent::get_session_lock($rowid, $timeout);
 
         $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid;
@@ -1749,9 +1755,6 @@ class oci_native_moodle_database extends moodle_database {
     }
 
     public function release_session_lock($rowid) {
-        if (!$this->oci_package_installed()) {
-            return;
-        }
         if (!$this->used_for_db_sessions) {
             return;
         }
index 0db12ba..d73604a 100644 (file)
@@ -2021,11 +2021,7 @@ function readfile_accel($file, $mimetype, $accelerate) {
     header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');
 
     if (is_object($file)) {
-        if (empty($_SERVER['HTTP_RANGE'])) {
-            // Use Etag only when not byteserving,
-            // is it tag of this range or whole file?
-            header('Etag: ' . $file->get_contenthash());
-        }
+        header('Etag: "' . $file->get_contenthash() . '"');
         if (isset($_SERVER['HTTP_IF_NONE_MATCH']) and $_SERVER['HTTP_IF_NONE_MATCH'] === $file->get_contenthash()) {
             header('HTTP/1.1 304 Not Modified');
             return;
@@ -2675,10 +2671,6 @@ function byteserving_send_file($handle, $mimetype, $ranges, $filesize) {
     // better turn off any kind of compression and buffering
     @ini_set('zlib.output_compression', 'Off');
 
-    // Remove Etag because is is not strictly defined for byteserving,
-    // is it tag of this range or whole file?
-    header_remove('Etag');
-
     $chunksize = 1*(1024*1024); // 1MB chunks - must be less than 2MB!
     if ($handle === false) {
         die;
index 1a0e432..f4cb2c5 100644 (file)
@@ -119,12 +119,12 @@ class MoodleQuickForm_date_selector extends MoodleQuickForm_group
         $this->_elements[] = @MoodleQuickForm::createElement('select', 'day', get_string('day', 'form'), $days, $this->getAttributes(), true);
         $this->_elements[] = @MoodleQuickForm::createElement('select', 'month', get_string('month', 'form'), $months, $this->getAttributes(), true);
         $this->_elements[] = @MoodleQuickForm::createElement('select', 'year', get_string('year', 'form'), $years, $this->getAttributes(), true);
+        $this->_elements[] = @MoodleQuickForm::createElement('image', 'calendar', $OUTPUT->pix_url('i/calendar', 'moodle'),
+            array('title' => get_string('calendar', 'calendar'), 'class' => 'visibleifjs'));
         // If optional we add a checkbox which the user can use to turn if on
         if($this->_options['optional']) {
             $this->_elements[] = @MoodleQuickForm::createElement('checkbox', 'enabled', null, get_string('enable'), $this->getAttributes(), true);
         }
-        $this->_elements[] = @MoodleQuickForm::createElement('image', 'calendar', $OUTPUT->pix_url('i/calendar', 'moodle'),
-            array('title' => get_string('calendar', 'calendar'), 'class' => 'visibleifjs'));
         foreach ($this->_elements as $element){
             if (method_exists($element, 'setHiddenLabel')){
                 $element->setHiddenLabel(true);
index f2003d2..f56eb03 100644 (file)
@@ -227,28 +227,10 @@ class MoodleQuickForm_editor extends HTML_QuickForm_element {
     }
 
     /**
-     * Sets help button for editor
-     *
-     * @param mixed $_helpbuttonargs arguments to create help button
-     * @param string $function name of the callback function
-     * @deprecated since Moodle 2.0. Please do not call this function any more.
-     * @todo MDL-34508 this api will be removed.
-     * @see MoodleQuickForm::addHelpButton()
+     * @deprecated since Moodle 2.0
      */
     function setHelpButton($_helpbuttonargs, $function='_helpbutton') {
-        debugging('setHelpButton() is deprecated, please use $mform->addHelpButton() instead');
-        if (!is_array($_helpbuttonargs)) {
-            $_helpbuttonargs = array($_helpbuttonargs);
-        } else {
-            $_helpbuttonargs = $_helpbuttonargs;
-        }
-        //we do this to to return html instead of printing it
-        //without having to specify it in every call to make a button.
-        if ('_helpbutton' == $function){
-            $defaultargs = array('', '', 'moodle', true, false, '', true);
-            $_helpbuttonargs = $_helpbuttonargs + $defaultargs ;
-        }
-        $this->_helpbutton=call_user_func_array($function, $_helpbuttonargs);
+        throw new coding_exception('setHelpButton() can not be used any more, please see MoodleQuickForm::addHelpButton().');
     }
 
     /**
index b0e78b4..6c2f239 100644 (file)
@@ -54,16 +54,10 @@ class MoodleQuickForm_hidden extends HTML_QuickForm_hidden{
     }
 
     /**
-     * set html for help button
-     *
-     * @param array $helpbuttonargs array of arguments to make a help button
-     * @param string $function function name to call to get html
-     * @deprecated since Moodle 2.0. Please do not call this function any more.
-     * @todo MDL-34508 this api will be removed.
-     * @see MoodleQuickForm::addHelpButton()
+     * @deprecated since Moodle 2.0
      */
     function setHelpButton($helpbuttonargs, $function='helpbutton'){
-        debugging('setHelpButton() is deprecated, please use $mform->addHelpButton() instead');
+        throw new coding_exception('setHelpButton() can not be used any more, please see MoodleQuickForm::addHelpButton().');
     }
 
     /**
index 5c48075..e1394f3 100644 (file)
@@ -166,6 +166,7 @@ YUI.add('moodle-form-dateselector', function(Y) {
             if (!this.enablecheckbox.get('checked')) {
                 this.calendarimage.set('disabled', 'disabled');
                 this.calendarimage.setStyle('cursor', 'default');
+                this.release_calendar();
             } else {
                 this.calendarimage.set('disabled', false);
                 this.calendarimage.setStyle('cursor', 'auto');
index 1d20bbd..bbfb895 100644 (file)
@@ -36,7 +36,7 @@ function js_send_cached($jspath, $etag, $filename = 'javascript.php') {
 
     $lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
 
-    header('Etag: '.$etag);
+    header('Etag: "'.$etag.'"');
     header('Content-Disposition: inline; filename="'.$filename.'"');
     header('Last-Modified: '. gmdate('D, d M Y H:i:s', filemtime($jspath)) .' GMT');
     header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
@@ -86,7 +86,7 @@ function js_send_unmodified($lastmodified, $etag) {
     header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
     header('Cache-Control: public, max-age='.$lifetime);
     header('Content-Type: application/javascript; charset=utf-8');
-    header('Etag: '.$etag);
+    header('Etag: "'.$etag.'"');
     if ($lastmodified) {
         header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');
     }
index 0d396a4..4637875 100644 (file)
@@ -10609,8 +10609,8 @@ function get_performance_info() {
     $info['html'] .= '<span class="included">Included '.$info['includecount'].' files</span> ';
     $info['txt']  .= 'includecount: '.$info['includecount'].' ';
 
-    if (!empty($CFG->early_install_lang)) {
-        // We can not track more performance before installation, sorry.
+    if (!empty($CFG->early_install_lang) or empty($PAGE)) {
+        // We can not track more performance before installation or before PAGE init, sorry.
         return $info;
     }
 
index 7e1576b..cb30e27 100644 (file)
@@ -899,8 +899,9 @@ class navigation_node_collection implements IteratorAggregate {
         $child = $this->get($key, $type);
         if ($child !== false) {
             foreach ($this->collection as $colkey => $node) {
-                if ($node->key == $key && $node->type == $type) {
+                if ($node->key === $key && $node->type == $type) {
                     unset($this->collection[$colkey]);
+                    $this->collection = array_values($this->collection);
                     break;
                 }
             }
@@ -3489,7 +3490,7 @@ class settings_navigation extends navigation_node {
             $coursenode->force_open();
         }
 
-        if (has_capability('moodle/course:update', $coursecontext)) {
+        if ($this->page->user_allowed_editing()) {
             // Add the turn on/off settings
 
             if ($this->page->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) {
@@ -3509,8 +3510,10 @@ class settings_navigation extends navigation_node {
                 $editurl->param('edit', 'on');
                 $editstring = get_string('turneditingon');
             }
-            $coursenode->add($editstring, $editurl, self::TYPE_SETTING, null, null, new pix_icon('i/edit', ''));
+            $coursenode->add($editstring, $editurl, self::TYPE_SETTING, null, 'turneditingonoff', new pix_icon('i/edit', ''));
+        }
 
+        if (has_capability('moodle/course:update', $coursecontext)) {
             // Add the course settings link
             $url = new moodle_url('/course/edit.php', array('id'=>$course->id));
             $coursenode->add(get_string('editsettings'), $url, self::TYPE_SETTING, null, 'editsettings', new pix_icon('i/settings', ''));
@@ -4079,7 +4082,7 @@ class settings_navigation extends navigation_node {
             $reportfunction($reporttab, $user, $course);
         }
         $anyreport = has_capability('moodle/user:viewuseractivitiesreport', $usercontext);
-        if ($anyreport || ($course->showreports && $currentuser && $forceforcontext)) {
+        if ($anyreport || ($course->showreports && $currentuser)) {
             // Add grade hardcoded grade report if necessary.
             $gradeaccess = false;
             if (has_capability('moodle/grade:viewall', $coursecontext)) {
@@ -4105,7 +4108,6 @@ class settings_navigation extends navigation_node {
         // Check the number of nodes in the report node... if there are none remove the node
         $reporttab->trim_if_empty();
 
-
         // Login as ...
         if (!$user->deleted and !$currentuser && !session_is_loggedinas() && has_capability('moodle/user:loginas', $coursecontext) && !is_siteadmin($user->id)) {
             $url = new moodle_url('/course/loginas.php', array('id'=>$course->id, 'user'=>$user->id, 'sesskey'=>sesskey()));
@@ -4244,7 +4246,7 @@ class settings_navigation extends navigation_node {
         }
         $frontpage->id = 'frontpagesettings';
 
-        if (has_capability('moodle/course:update', $coursecontext)) {
+        if ($this->page->user_allowed_editing()) {
 
             // Add the turn on/off settings
             $url = new moodle_url('/course/view.php', array('id'=>$course->id, 'sesskey'=>sesskey()));
@@ -4256,7 +4258,9 @@ class settings_navigation extends navigation_node {
                 $editstring = get_string('turneditingon');
             }
             $frontpage->add($editstring, $url, self::TYPE_SETTING, null, null, new pix_icon('i/edit', ''));
+        }
 
+        if (has_capability('moodle/course:update', $coursecontext)) {
             // Add the course settings link
             $url = new moodle_url('/admin/settings.php', array('section'=>'frontpagesettings'));
             $frontpage->add(get_string('editsettings'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/settings', ''));
index 414f51d..35282f9 100644 (file)
@@ -325,6 +325,14 @@ class theme_config {
      */
     public $yuicssmodules = array('cssreset', 'cssfonts', 'cssgrids', 'cssbase');
 
+    /**
+     * An associative array of block manipulations that should be made if the user is using an rtl language.
+     * The key is the original block region, and the value is the block region to change to.
+     * This is used when displaying blocks for regions only.
+     * @var array
+     */
+    public $blockrtlmanipulations = array();
+
     /**
      * @var renderer_factory Instance of the renderer_factory implementation
      * we are using. Implementation detail.
@@ -418,7 +426,7 @@ class theme_config {
         $configurable = array('parents', 'sheets', 'parents_exclude_sheets', 'plugins_exclude_sheets', 'javascripts', 'javascripts_footer',
                               'parents_exclude_javascripts', 'layouts', 'enable_dock', 'enablecourseajax', 'supportscssoptimisation',
                               'rendererfactory', 'csspostprocess', 'editor_sheets', 'rarrow', 'larrow', 'hidefromselector', 'doctype',
-                              'yuicssmodules');
+                              'yuicssmodules', 'blockrtlmanipulations');
 
         foreach ($config as $key=>$value) {
             if (in_array($key, $configurable)) {
index 8b27876..0e5c959 100644 (file)
@@ -921,7 +921,7 @@ class core_renderer extends renderer_base {
         $functioncalled = true;
         $courseformat = course_get_format($this->page->course);
         if (($obj = $courseformat->course_content_header()) !== null) {
-            return $courseformat->get_renderer($this->page)->render($obj);
+            return html_writer::div($courseformat->get_renderer($this->page)->render($obj), 'course-content-header');
         }
         return '';
     }
@@ -948,7 +948,7 @@ class core_renderer extends renderer_base {
         require_once($CFG->dirroot.'/course/lib.php');
         $courseformat = course_get_format($this->page->course);
         if (($obj = $courseformat->course_content_footer()) !== null) {
-            return $courseformat->get_renderer($this->page)->render($obj);
+            return html_writer::div($courseformat->get_renderer($this->page)->render($obj), 'course-content-footer');
         }
         return '';
     }
@@ -1228,6 +1228,7 @@ class core_renderer extends renderer_base {
      * @return string the HTML to be output.
      */
     public function blocks_for_region($region) {
+        $region = $this->page->apply_theme_region_manipulations($region);
         $blockcontents = $this->page->blocks->get_content_for_region($region, $this);
         $blocks = $this->page->blocks->get_blocks_for_region($region);
         $lastblock = null;
@@ -2675,10 +2676,13 @@ EOD;
      */
     public function navbar() {
         $items = $this->page->navbar->get_items();
+        $itemcount = count($items);
+        if ($itemcount === 0) {
+            return '';
+        }
 
         $htmlblocks = array();
         // Iterate the navarray and display each node
-        $itemcount = count($items);
         $separator = get_separator();
         for ($i=0;$i < $itemcount;$i++) {
             $item = $items[$i];
@@ -2827,7 +2831,7 @@ EOD;
         $jscode = "(function(){{$jscode}})";
         $this->page->requires->yui_module('node-menunav', $jscode);
         // Build the root nodes as required by YUI
-        $content = html_writer::start_tag('div', array('id'=>'custom_menu_'.$menucount, 'class'=>'yui3-menu yui3-menu-horizontal javascript-disabled'));
+        $content = html_writer::start_tag('div', array('id'=>'custom_menu_'.$menucount, 'class'=>'yui3-menu yui3-menu-horizontal javascript-disabled custom-menu'));
         $content .= html_writer::start_tag('div', array('class'=>'yui3-menu-content'));
         $content .= html_writer::start_tag('ul');
         // Render each child
@@ -3028,6 +3032,146 @@ EOD;
 
         return $str;
     }
+
+    /**
+     * Get the HTML for blocks in the given region.
+     *
+     * @since 2.5.1 2.6
+     * @param string $region The region to get HTML for.
+     * @return string HTML.
+     */
+    public function blocks($region, $classes = array(), $tag = 'aside') {
+        $displayregion = $this->page->apply_theme_region_manipulations($region);
+        $classes = (array)$classes;
+        $classes[] = 'block-region';
+        $attributes = array(
+            'id' => 'block-region-'.preg_replace('#[^a-zA-Z0-9_\-]+#', '-', $displayregion),
+            'class' => join(' ', $classes),
+            'data-blockregion' => $displayregion,
+            'data-droptarget' => '1'
+        );
+        return html_writer::tag($tag, $this->blocks_for_region($region), $attributes);
+    }
+
+    /**
+     * Returns the CSS classes to apply to the body tag.
+     *
+     * @since 2.5.1 2.6
+     * @param array $additionalclasses Any additional classes to apply.
+     * @return string
+     */
+    public function body_css_classes(array $additionalclasses = array()) {
+        // Add a class for each block region on the page.
+        // We use the block manager here because the theme object makes get_string calls.
+        foreach ($this->page->blocks->get_regions() as $region) {
+            $additionalclasses[] = 'has-region-'.$region;
+            if ($this->page->blocks->region_has_content($region, $this)) {
+                $additionalclasses[] = 'used-region-'.$region;
+            } else {
+                $additionalclasses[] = 'empty-region-'.$region;
+            }
+        }
+        foreach ($this->page->layout_options as $option => $value) {
+            if ($value) {
+                $additionalclasses[] = 'layout-option-'.$option;
+            }
+        }
+        $css = $this->page->bodyclasses .' '. join(' ', $additionalclasses);
+        return $css;
+    }
+
+    /**
+     * The ID attribute to apply to the body tag.
+     *
+     * @since 2.5.1 2.6
+     * @return string
+     */
+    public function body_id() {
+        return $this->page->bodyid;
+    }
+
+    /**
+     * Returns HTML attributes to use within the body tag. This includes an ID and classes.
+     *
+     * @since 2.5.1 2.6
+     * @param string|array $additionalclasses Any additional classes to give the body tag,
+     * @return string
+     */
+    public function body_attributes($additionalclasses = array()) {
+        if (!is_array($additionalclasses)) {
+            $additionalclasses = explode(' ', $additionalclasses);
+        }
+        return ' id="'. $this->body_id().'" class="'.$this->body_css_classes($additionalclasses).'"';
+    }
+
+    /**
+     * Gets HTML for the page heading.
+     *
+     * @since 2.5.1 2.6
+     * @param string $tag The tag to encase the heading in. h1 by default.
+     * @return string HTML.
+     */
+    public function page_heading($tag = 'h1') {
+        return html_writer::tag($tag, $this->page->heading);
+    }
+
+    /**
+     * Gets the HTML for the page heading button.
+     *
+     * @since 2.5.1 2.6
+     * @return string HTML.
+     */
+    public function page_heading_button() {
+        return $this->page->button;
+    }
+
+    /**
+     * Returns the Moodle docs link to use for this page.
+     *
+     * @since 2.5.1 2.6
+     * @param string $text
+     * @return string
+     */
+    public function page_doc_link($text = null) {
+        if ($text === null) {
+            $text = get_string('moodledocslink');
+        }
+        $path = page_get_doc_link_path($this->page);
+        if (!$path) {
+            return '';
+        }
+        return $this->doc_link($path, $text);
+    }
+
+    /**
+     * Returns the page heading menu.
+     *
+     * @since 2.5.1 2.6
+     * @return string HTML.
+     */
+    public function page_heading_menu() {
+        return $this->page->headingmenu;
+    }
+
+    /**
+     * Returns the title to use on the page.
+     *
+     * @since 2.5.1 2.6
+     * @return string
+     */
+    public function page_title() {
+        return $this->page->title;
+    }
+
+    /**
+     * Returns the URL for the favicon.
+     *
+     * @since 2.5.1 2.6
+     * @return string The favicon URL
+     */
+    public function favicon() {
+        return $this->pix_url('favicon', 'theme');
+    }
 }
 
 /**
index 9f05f63..19940fb 100644 (file)
@@ -51,7 +51,8 @@ defined('MOODLE_INTERNAL') || die();
  *      the forum or quiz table) that this page belongs to. Will be null
  *      if this page is not within a module.
  * @property-read array $alternativeversions Mime type => object with ->url and ->title.
- * @property-read blocks_manager $blocks The blocks manager object for this page.
+ * @property-read block_manager $blocks The blocks manager object for this page.
+ * @property-read array $blockmanipulations
  * @property-read string $bodyclasses A string to use within the class attribute on the body tag.
  * @property-read string $bodyid A string to use as the id of the body tag.
  * @property-read string $button The HTML to go where the Turn editing on button normally goes.
@@ -637,6 +638,22 @@ class moodle_page {
         return $this->_theme;
     }
 
+    /**
+     * Returns an array of minipulations or false if there are none to make.
+     *
+     * @since 2.5.1 2.6
+     * @return bool|array
+     */
+    protected function magic_get_blockmanipulations() {
+        if (!right_to_left()) {
+            return false;
+        }
+        if (is_null($this->_theme)) {
+            $this->initialise_theme_and_output();
+        }
+        return $this->_theme->blockrtlmanipulations;
+    }
+
     /**
      * Please do not call this method directly, use the ->devicetypeinuse syntax. {@link moodle_page::__get()}.
      * @return string The device type being used.
@@ -1825,4 +1842,18 @@ class moodle_page {
     public function set_popup_notification_allowed($allowed) {
         $this->_popup_notification_allowed = $allowed;
     }
+
+    /**
+     * Returns the block region having made any required theme manipulations.
+     *
+     * @since 2.5.1 2.6
+     * @param string $region
+     * @return string
+     */
+    public function apply_theme_region_manipulations($region) {
+        if ($this->blockmanipulations && isset($this->blockmanipulations[$region])) {
+            return $this->blockmanipulations[$region];
+        }
+        return $region;
+    }
 }
index cc73d37..879f68b 100644 (file)
@@ -835,7 +835,7 @@ class portfolio_exporter {
             if ($f->get_filename() == $skipfile) {
                 continue;
             }
-            $returnfiles[$f->get_filepath() . '/' . $f->get_filename()] = $f;
+            $returnfiles[$f->get_filepath() . $f->get_filename()] = $f;
         }
         return $returnfiles;
     }
index 847724d..03a40c6 100644 (file)
@@ -123,7 +123,7 @@ class accesslib_testcase extends advanced_testcase {
      * @return void
      */
     public function test_is_siteadmin() {
-        global $DB;
+        global $DB, $CFG;
 
         $this->resetAfterTest();
 
@@ -145,6 +145,20 @@ class accesslib_testcase extends advanced_testcase {
                 $this->assertFalse(is_siteadmin(null));
             }
         }
+
+        // Change the site admin list and check that it still works with
+        // multiple admins. We do this with userids only (not real user
+        // accounts) because it makes the test simpler.
+        $before = $CFG->siteadmins;
+        set_config('siteadmins', '666,667,668');
+        $this->assertTrue(is_siteadmin(666));
+        $this->assertTrue(is_siteadmin(667));
+        $this->assertTrue(is_siteadmin(668));
+        $this->assertFalse(is_siteadmin(669));
+        set_config('siteadmins', '13');
+        $this->assertTrue(is_siteadmin(13));
+        $this->assertFalse(is_siteadmin(666));
+        set_config('siteadmins', $before);
     }
 
     /**
diff --git a/lib/tests/completionlib_advanced_test.php b/lib/tests/completionlib_advanced_test.php
new file mode 100644 (file)
index 0000000..e259554
--- /dev/null
@@ -0,0 +1,91 @@
+<?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/>.
+
+/**
+ * Completion lib advanced test case.
+ *
+ * This file contains the advanced test suite for completion lib.
+ *
+ * @package    core_completion
+ * @category   phpunit
+ * @copyright  2013 Frédéric Massart
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir.'/completionlib.php');
+
+class completionlib_advanced_testcase extends advanced_testcase {
+
+    function test_get_activities() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        // Create a course with mixed auto completion data.
+        $course = $this->getDataGenerator()->create_course();
+        $completionauto = array('completion' => COMPLETION_TRACKING_AUTOMATIC);
+        $completionmanual = array('completion' => COMPLETION_TRACKING_MANUAL);
+        $completionnone = array('completion' => COMPLETION_TRACKING_NONE);
+        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), $completionauto);
+        $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id), $completionauto);
+        $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id), $completionmanual);
+
+        $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), $completionnone);
+        $page2 = $this->getDataGenerator()->create_module('page', array('course' => $course->id), $completionnone);
+        $data2 = $this->getDataGenerator()->create_module('data', array('course' => $course->id), $completionnone);
+
+        // Create data in another course to make sure it's not considered.
+        $course2 = $this->getDataGenerator()->create_course();
+        $c2forum = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id), $completionauto);
+        $c2page = $this->getDataGenerator()->create_module('page', array('course' => $course2->id), $completionmanual);
+        $c2data = $this->getDataGenerator()->create_module('data', array('course' => $course2->id), $completionnone);
+
+        $c = new completion_info($course);
+        $activities = $c->get_activities();
+        $this->assertEquals(3, count($activities));
+        $this->assertTrue(isset($activities[$forum->cmid]));
+        $this->assertEquals($activities[$forum->cmid]->name, $forum->name);
+        $this->assertTrue(isset($activities[$page->cmid]));
+        $this->assertEquals($activities[$page->cmid]->name, $page->name);
+        $this->assertTrue(isset($activities[$data->cmid]));
+        $this->assertEquals($activities[$data->cmid]->name, $data->name);
+
+        $this->assertFalse(isset($activities[$forum2->cmid]));
+        $this->assertFalse(isset($activities[$page2->cmid]));
+        $this->assertFalse(isset($activities[$data2->cmid]));
+    }
+
+    function test_has_activities() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        // Create a course with mixed auto completion data.
+        $course = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $completionauto = array('completion' => COMPLETION_TRACKING_AUTOMATIC);
+        $completionnone = array('completion' => COMPLETION_TRACKING_NONE);
+        $c1forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), $completionauto);
+        $c2forum = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id), $completionnone);
+
+        $c1 = new completion_info($course);
+        $c2 = new completion_info($course2);
+
+        $this->assertTrue($c1->has_activities());
+        $this->assertFalse($c2->has_activities());
+    }
+}
index 5092831..2298460 100644 (file)
@@ -514,31 +514,6 @@ WHERE
         $c->internal_set_data($cm, $data);
     }
 
-    function test_get_activities() {
-        global $DB;
-
-        $c = new completion_info((object)array('id'=>42));
-
-        // Try with no activities
-        $DB->expects($this->at(0))
-            ->method('get_records_select')
-            ->with('course_modules', 'course=42 AND completion<>'.COMPLETION_TRACKING_NONE)
-            ->will($this->returnValue(array()));
-        $result = $c->get_activities();
-        $this->assertEquals(array(), $result);
-
-        // Try with an activity (need to fake up modinfo for it as well)
-        $DB->expects($this->at(0))
-            ->method('get_records_select')
-            ->with('course_modules', 'course=42 AND completion<>'.COMPLETION_TRACKING_NONE)
-            ->will($this->returnValue(array(13=>(object)array('id'=>13))));
-        $modinfo = new stdClass;
-        $modinfo->sections = array(array(1, 2, 3), array(12, 13, 14));
-        $modinfo->cms[13] = (object)array('modname'=>'frog', 'name'=>'kermit');
-        $result = $c->get_activities($modinfo);
-        $this->assertEquals(array(13=>(object)array('id'=>13, 'modname'=>'frog', 'name'=>'kermit')), $result);
-    }
-
     // get_tracked_users() cannot easily be tested because it uses
     // get_role_users, so skipping that
 
index cce2090..8a97db4 100644 (file)
@@ -267,4 +267,30 @@ class datalib_testcase extends advanced_testcase {
         get_admins(); // This should make just one query.
         $this->assertEquals($odlread+1, $DB->perf_get_reads());
     }
+
+    public function test_get_course() {
+        global $DB, $PAGE, $SITE;
+
+        $this->resetAfterTest(true);
+
+        // First test course will be current course ($COURSE).
+        $course1obj = $this->getDataGenerator()->create_course(array('shortname' => 'FROGS'));
+        $PAGE->set_course($course1obj);
+
+        // Second test course is not current course.
+        $course2obj = $this->getDataGenerator()->create_course(array('shortname' => 'ZOMBIES'));
+
+        // Check it does not make any queries when requesting the $COURSE/$SITE
+        $before = $DB->perf_get_queries();
+        $result = get_course($course1obj->id);
+        $this->assertEquals($before, $DB->perf_get_queries());
+        $this->assertEquals('FROGS', $result->shortname);
+        $result = get_course($SITE->id);
+        $this->assertEquals($before, $DB->perf_get_queries());
+
+        // Check it makes 1 query to request other courses.
+        $result = get_course($course2obj->id);
+        $this->assertEquals('ZOMBIES', $result->shortname);
+        $this->assertEquals($before + 1, $DB->perf_get_queries());
+    }
 }
diff --git a/lib/tests/environment_test.php b/lib/tests/environment_test.php
new file mode 100644 (file)
index 0000000..9dea45d
--- /dev/null
@@ -0,0 +1,45 @@
+<?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/>.
+
+/**
+ * Moodle environment test.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2013 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * Do standard environment.xml tests.
+ */
+class environment_testcase extends advanced_testcase {
+
+    public function test_environment() {
+        global $CFG;
+
+        require_once($CFG->libdir.'/environmentlib.php');
+        list($envstatus, $environment_results) = check_moodle_environment(normalize_version($CFG->release), ENV_SELECT_RELEASE);
+
+        $this->assertNotEmpty($envstatus);
+        foreach ($environment_results as $environment_result) {
+            $this->assertTrue($environment_result->getStatus(), "Problem detected in environment ($environment_result->part:$environment_result->info), fix all warnings and errors!");
+        }
+    }
+}
index 430ec86..e9157e6 100644 (file)
@@ -60,6 +60,8 @@ class navigation_node_testcase extends basic_testcase {
 
         $this->node = new navigation_node('Test Node');
         $this->node->type = navigation_node::TYPE_SYSTEM;
+        // We add the first child without key. This way we make sure all keys search by comparision is performed using ===
+        $this->node->add('first child without key', null, navigation_node::TYPE_CUSTOM);
         $demo1 = $this->node->add('demo1', $this->inactiveurl, navigation_node::TYPE_COURSE, null, 'demo1', new pix_icon('i/course', ''));
         $demo2 = $this->node->add('demo2', $this->inactiveurl, navigation_node::TYPE_COURSE, null, 'demo2', new pix_icon('i/course', ''));
         $demo3 = $this->node->add('demo3', $this->inactiveurl, navigation_node::TYPE_CATEGORY, null, 'demo3',new pix_icon('i/course', ''));
@@ -251,8 +253,24 @@ class navigation_node_testcase extends basic_testcase {
         $this->assertInstanceOf('navigation_node', $this->node->get('remove2'));
         $this->assertInstanceOf('navigation_node', $remove2->get('remove3'));
 
+        // Remove element and make sure this is no longer a child.
         $this->assertTrue($remove1->remove());
+        $this->assertFalse($this->node->get('remove1'));
+        $this->assertFalse(in_array('remove1', $this->node->get_children_key_list(), true));
+
+        // Make sure that we can insert element after removal
+        $insertelement = navigation_node::create('extra element 4', null, navigation_node::TYPE_CUSTOM, null, 'element4');
+        $this->node->add_node($insertelement, 'remove2');
+        $this->assertNotEmpty($this->node->get('element4'));
+
+        // Remove more elements
         $this->assertTrue($this->node->get('remove2')->remove());
+        $this->assertFalse($this->node->get('remove2'));
+
+        // Make sure that we can add element after removal
+        $this->node->add('extra element 5', null, navigation_node::TYPE_CUSTOM, null, 'element5');
+        $this->assertNotEmpty($this->node->get('element5'));
+
         $this->assertTrue($remove2->get('remove3')->remove());
 
         $this->assertFalse($this->node->get('remove1'));
index 74362a4..8ba4a4d 100644 (file)
@@ -106,6 +106,22 @@ class web_testcase extends advanced_testcase {
             format_text_email('&#x7fd2;&#x7FD2;',FORMAT_HTML));
     }
 
+    function test_obfuscate_email() {
+        $email = 'some.user@example.com';
+        $obfuscated = obfuscate_email($email);
+        $this->assertNotSame($email, $obfuscated);
+        $back = textlib::entities_to_utf8(urldecode($email), true);
+        $this->assertSame($email, $back);
+    }
+
+    function test_obfuscate_text() {
+        $text = 'Žluťoučký koníček 32131';
+        $obfuscated = obfuscate_text($text);
+        $this->assertNotSame($text, $obfuscated);
+        $back = textlib::entities_to_utf8($obfuscated, true);
+        $this->assertSame($text, $back);
+    }
+
     function test_highlight() {
         $this->assertEquals(highlight('good', 'This is good'), 'This is <span class="highlight">good</span>');
         $this->assertEquals(highlight('SpaN', 'span'), '<span class="highlight">span</span>');
index b999955..e33f0c8 100644 (file)
@@ -1,6 +1,11 @@
 This files describes API changes in core libraries and APIs,
 information provided here is intended especially for developers.
 
+=== 2.5.1 ===
+
+* New get_course() function for use when obtaining the course record from database. Will
+  reuse existing $COURSE or $SITE globals if possible to improve performance.
+
 === 2.5 ===
 
 * The database drivers (moodle_database and subclasses) aren't using anymore the ::columns property
index 1985b7e..66e02eb 100644 (file)
@@ -909,20 +909,37 @@ function close_window($delay = 0, $reloadopener = false) {
  * @return string The link to user documentation for this current page
  */
 function page_doc_link($text='') {
-    global $CFG, $PAGE, $OUTPUT;
+    global $OUTPUT, $PAGE;
+    $path = page_get_doc_link_path($PAGE);
+    if (!$path) {
+        return '';
+    }
+    return $OUTPUT->doc_link($path, $text);
+}
+
+/**
+ * Returns the path to use when constructing a link to the docs.
+ *
+ * @since 2.5.1 2.6
+ * @global stdClass $CFG
+ * @param moodle_page $page
+ * @return string
+ */
+function page_get_doc_link_path(moodle_page $page) {
+    global $CFG;
 
     if (empty($CFG->docroot) || during_initial_install()) {
         return '';
     }
-    if (!has_capability('moodle/site:doclinks', $PAGE->context)) {
+    if (!has_capability('moodle/site:doclinks', $page->context)) {
         return '';
     }
 
-    $path = $PAGE->docspath;
+    $path = $page->docspath;
     if (!$path) {
         return '';
     }
-    return $OUTPUT->doc_link($path, $text);
+    return $path;
 }
 
 
@@ -1178,6 +1195,12 @@ function format_text($text, $format = FORMAT_MOODLE, $options = NULL, $courseid_
         // The only potential problem is that somebody might try to format
         // the text before storing into database which would be itself big bug.
         $text = str_replace("\"$CFG->httpswwwroot/draftfile.php", "\"$CFG->httpswwwroot/brokenfile.php#", $text);
+
+        if (debugging('', DEBUG_DEVELOPER)) {
+            if (strpos($text, '@@PLUGINFILE@@/') !== false) {
+                debugging('Before calling format_text(), the content must be processed with file_rewrite_pluginfile_urls()', DEBUG_DEVELOPER);
+            }
+        }
     }
 
     // Warn people that we have removed this old mechanism, just in case they
@@ -2577,19 +2600,20 @@ function redirect($url, $message='', $delay=-1) {
 function obfuscate_text($plaintext) {
 
     $i=0;
-    $length = strlen($plaintext);
+    $length = textlib::strlen($plaintext);
     $obfuscated='';
     $prev_obfuscated = false;
     while ($i < $length) {
-        $c = ord($plaintext{$i});
-        $numerical = ($c >= ord('0')) && ($c <= ord('9'));
+        $char = textlib::substr($plaintext, $i, 1);
+        $ord = textlib::utf8ord($char);
+        $numerical = ($ord >= ord('0')) && ($ord <= ord('9'));
         if ($prev_obfuscated and $numerical ) {
-            $obfuscated.='&#'.ord($plaintext{$i}).';';
+            $obfuscated.='&#'.$ord.';';
         } else if (rand(0,2)) {
-            $obfuscated.='&#'.ord($plaintext{$i}).';';
+            $obfuscated.='&#'.$ord.';';
             $prev_obfuscated = true;
         } else {
-            $obfuscated.=$plaintext{$i};
+            $obfuscated.=$char;
             $prev_obfuscated = false;
         }
       $i++;
index 5c25485..3a563fd 100644 (file)
@@ -68,12 +68,6 @@ function xsendfile($filepath) {
         }
     }
 
-    // Remove Etag because is is not strictly defined for byteserving,
-    // is it tag of this range or whole file?
-    if (!empty($_SERVER['HTTP_RANGE'])) {
-        header_remove('Etag');
-    }
-
     if ($CFG->xsendfile === 'X-LIGHTTPD-send-file') {
         // http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file says 1.4 it does not support byteserving
         header('Accept-Ranges: none');
index 8430a9b..ea694d0 100644 (file)
Binary files a/lib/yui/build/moodle-core-blocks/moodle-core-blocks-debug.js and b/lib/yui/build/moodle-core-blocks/moodle-core-blocks-debug.js differ
index 24f8901..31b92ae 100644 (file)
Binary files a/lib/yui/build/moodle-core-blocks/moodle-core-blocks-min.js and b/lib/yui/build/moodle-core-blocks/moodle-core-blocks-min.js differ
index 8430a9b..bc3bb52 100644 (file)
Binary files a/lib/yui/build/moodle-core-blocks/moodle-core-blocks.js and b/lib/yui/build/moodle-core-blocks/moodle-core-blocks.js differ
index 6d7fd45..b8904bb 100644 (file)
@@ -158,7 +158,7 @@ YUI.add('moodle-core-dragdrop', function(Y) {
                 }
                 //Add the node
                 e.drop.get('node').ancestor().insertBefore(drag, drop);
-            } else if (drop.hasClass(this.parentnodeclass) && !drop.contains(drag)) {
+            } else if ((drop.hasClass(this.parentnodeclass) || drop.test('[data-droptarget="1"]')) && !drop.contains(drag)) {
                 // We are dropping on parent node and it is empty
                 if (this.goingup) {
                     drop.append(drag);
index 41689c9..2644ec9 100644 (file)
@@ -3,7 +3,9 @@
   "builds": {
     "moodle-core-blocks": {
       "jsfiles": [
-        "blocks.js"
+        "blocks.js",
+        "manager.js",
+        "blockregion.js"
       ]
     }
   }
diff --git a/lib/yui/src/blocks/js/blockregion.js b/lib/yui/src/blocks/js/blockregion.js
new file mode 100644 (file)
index 0000000..1d854b7
--- /dev/null
@@ -0,0 +1,216 @@
+/**
+ * This file contains the Block Region class used by the drag and drop manager.
+ *
+ * Provides drag and drop functionality for blocks.
+ *
+ * @module moodle-core-blockdraganddrop
+ */
+
+/**
+ * Constructs a new block region object.
+ *
+ * @namespace M.core.blockdraganddrop
+ * @class BlockRegion
+ * @constructor
+ * @extends Y.Base
+ */
+var BLOCKREGION = function() {
+    BLOCKREGION.superclass.constructor.apply(this, arguments);
+};
+BLOCKREGION.prototype = {
+    /**
+     * Called during the initialisation process of the object.
+     * @method initializer
+     */
+    initializer : function() {
+        var node = this.get('node');
+        Y.log('Block region `'+this.get('region')+'` initialising', 'info');
+        if (!node) {
+            Y.log('block region known about but no HTML structure found for it. Guessing structure.', 'warn');
+            this.create_and_add_node();
+        }
+        var body = Y.one('body'),
+            hasblocks = node.all('.'+CSS.BLOCK).size() > 0,
+            hasregionclass = this.get_has_region_class();
+        this.set('hasblocks', hasblocks);
+        if (!body.hasClass(hasregionclass)) {
+            body.addClass(hasregionclass);
+        }
+        body.addClass((hasblocks) ? this.get_used_region_class() : this.get_empty_region_class());
+        body.removeClass((hasblocks) ? this.get_empty_region_class() : this.get_used_region_class());
+    },
+    /**
+     * Creates a generic block region node and adds it to the DOM at the best guess location.
+     * Any calling of this method is an unfortunate circumstance.
+     * @method create_and_add_node
+     */
+    create_and_add_node : function() {
+        var c = Y.Node.create,
+            region = this.get('region'),
+            node = c('<div id="block-region-'+region+'" data-droptarget="1"></div>')
+                .addClass(CSS.BLOCKREGION)
+                .setData('blockregion', region),
+            regions = this.get('manager').get('regions'),
+            i,
+            haspre = false,
+            haspost = false,
+            added = false,
+            pre,
+            post;
+
+        for (i in regions) {
+            if (regions[i].match(/(pre|left)/)) {
+                haspre = regions[i];
+            } else if (regions[i].match(/(post|right)/)) {
+                haspost = regions[i];
+            }
+        }
+
+        if (haspre !== false && haspost !== false) {
+            if (region === haspre) {
+                post = Y.one('#block-region-'+haspost);
+                if (post) {
+                    post.insert(node, 'before');
+                    added = true;
+                }
+            } else {
+                pre = Y.one('#block-region-'+haspre);
+                if (pre) {
+                    pre.insert(node, 'after');
+                    added = true;
+                }
+            }
+        }
+        if (added === false) {
+            Y.one('body').append(node);
+        }
+        this.set('node', node);
+    },
+
+    /**
+     * Removes the move icons and changes the cursor to a move icon when over the header.
+     * @method remove_block_move_icons
+     */
+    remove_block_move_icons : function() {
+        this.get('node').all('.'+CSS.BLOCK+' a.'+CSS.EDITINGMOVE).each(function(moveicon){
+            moveicon.ancestor('.'+CSS.BLOCK).one('.'+CSS.HEADER).setStyle('cursor', 'move');
+            moveicon.remove();
+        });
+    },
+
+    /**
+     * Returns the class name on the body that signifies the document knows about this region.
+     * @method get_has_region_class
+     * @return String
+     */
+    get_has_region_class : function() {
+        return 'has-region-'+this.get('region');
+    },
+
+    /**
+     * Returns the class name to use on the body if the region contains no blocks.
+     * @method get_empty_region_class
+     * @return String
+     */
+    get_empty_region_class : function() {
+        return 'empty-region-'+this.get('region');
+    },
+
+    /**
+     * Returns the class name to use on the body if the region contains blocks.
+     * @method get_used_region_class
+     * @return String
+     */
+    get_used_region_class : function() {
+        return 'used-region-'+this.get('region');
+    },
+
+    /**
+     * Returns the node to use as the drop target for this region.
+     * @method get_droptarget
+     * @return Node
+     */
+    get_droptarget : function() {
+        var node = this.get('node');
+        if (node.test('[data-droptarget="1"]')) {
+            return node;
+        }
+        return node.one('[data-droptarget="1"]');
+    },
+
+    /**
+     * Enables the block region so that we can be sure the user can see it.
+     * This is done even if it is empty.
+     * @method enable
+     */
+    enable : function() {
+        Y.one('body').addClass(this.get_used_region_class()).removeClass(this.get_empty_region_class());
+    },
+
+    /**
+     * Disables the region if it contains no blocks, essentially hiding it from the user.
+     * @method disable_if_required
+     */
+    disable_if_required : function() {
+        if (this.get('node').all('.'+CSS.BLOCK).size() === 0) {
+            Y.one('body').addClass(this.get_empty_region_class()).removeClass(this.get_used_region_class());
+        }
+    }
+};
+Y.extend(BLOCKREGION, Y.Base, BLOCKREGION.prototype, {
+    NAME : 'core-blocks-dragdrop-blockregion',
+    ATTRS : {
+
+        /**
+         * The drag and drop manager that created this block region instance.
+         * @attribute manager
+         * @type M.core.blockdraganddrop.Manager
+         * @writeOnce
+         */
+        manager : {
+            // Can only be set during initialisation and must be set then.
+            writeOnce : 'initOnly',
+            validator : function (value) {
+                return Y.Lang.isObject(value) && value instanceof MANAGER;
+            }
+        },
+
+        /**
+         * The name of the block region this object represents.
+         * @attribute region
+         * @type String
+         * @writeOnce
+         */
+        region : {
+            // Can only be set during initialisation and must be set then.
+            writeOnce : 'initOnly',
+            validator : function (value) {
+                return Y.Lang.isString(value);
+            }
+        },
+
+        /**
+         * The node the block region HTML starts at.s
+         * @attribute region
+         * @type Y.Node
+         */
+        node : {
+            validator : function (value) {
+                return Y.Lang.isObject(value) || Y.Lang.isNull(value);
+            }
+        },
+
+        /**
+         * True if the block region currently contains blocks.
+         * @attribute hasblocks
+         * @type Boolean
+         * @default false
+         */
+        hasblocks : {
+            value : false,
+            validator : function (value) {
+                return Y.Lang.isBoolean(value);
+            }
+        }
+    }
+});
\ No newline at end of file
index ff2809d..4597acc 100644 (file)
@@ -1,3 +1,9 @@
+/**
+ * Provides drag and drop functionality for blocks.
+ *
+ * @module moodle-core-blockdraganddrop
+ */
+
 var AJAXURL = '/lib/ajax/blocks.php',
 CSS = {
     BLOCK : 'block',
@@ -13,6 +19,16 @@ CSS = {
     REGIONMAIN : 'region-main'
 };
 
+/**
+ * Legacy drag and drop manager.
+ * This drag and drop manager is specifically designed for themes using side-pre and side-post
+ * that do not make use of the block output methods introduced by MDL-39824.
+ *
+ * @namespace M.core.blockdraganddrop
+ * @class LegacyManager
+ * @constructor
+ * @extends M.core.dragdrop
+ */
 var DRAGBLOCK = function() {
     DRAGBLOCK.superclass.constructor.apply(this, arguments);
 };
@@ -299,7 +315,66 @@ Y.extend(DRAGBLOCK, M.core.dragdrop, {
     }
 });
 
+/**
+ * Core namespace.
+ * @static
+ * @class core
+ */
+M.core = M.core || {};
+
+/**
+ * Block drag and drop static class.
+ * @namespace M.core
+ * @class blockdraganddrop
+ * @static
+ */
+M.core.blockdraganddrop = M.core.blockdraganddrop || {};
+
+/**
+ * True if the page is using the new blocks methods.
+ * @private
+ * @static
+ * @property _isusingnewblocksmethod
+ * @type Boolean
+ * @default null
+ */
+M.core.blockdraganddrop._isusingnewblocksmethod = null;
+
+/**
+ * Returns true if the page is using the new blocks methods.
+ * @static
+ * @method is_using_blocks_render_method
+ * @return Boolean
+ */
+M.core.blockdraganddrop.is_using_blocks_render_method = function() {
+    if (this._isusingnewblocksmethod === null) {
+        var goodregions = Y.all('.block-region[data-blockregion]').size();
+        var allregions = Y.all('.block-region').size();
+        this._isusingnewblocksmethod = (allregions === goodregions);
+    }
+    return this._isusingnewblocksmethod;
+};
+
+/**
+ * Initialises a drag and drop manager.
+ * This should only ever be called once for a page.
+ * @static
+ * @method init
+ * @param {Object} params
+ * @return Manager
+ */
+M.core.blockdraganddrop.init = function(params) {
+    if (this.is_using_blocks_render_method()) {
+        new MANAGER(params);
+    } else {
+        new DRAGBLOCK(params);
+    }
+};
+
+/**
+ * Legacy code to keep things working.
+ */
 M.core_blocks = M.core_blocks || {};
 M.core_blocks.init_dragdrop = function(params) {
-    new DRAGBLOCK(params);
-};
+    M.core.blockdraganddrop.init(params);
+};
\ No newline at end of file
diff --git a/lib/yui/src/blocks/js/manager.js b/lib/yui/src/blocks/js/manager.js
new file mode 100644 (file)
index 0000000..40490af
--- /dev/null
@@ -0,0 +1,393 @@
+/**
+ * This file contains the drag and drop manager class.
+ *
+ * Provides drag and drop functionality for blocks.
+ *
+ * @module moodle-core-blockdraganddrop
+ */
+
+/**
+ * Constructs a new Block drag and drop manager.
+ *
+ * @namespace M.core.blockdraganddrop
+ * @class Manager
+ * @constructor
+ * @extends M.core.dragdrop
+ */
+var MANAGER = function() {
+    MANAGER.superclass.constructor.apply(this, arguments);
+};
+MANAGER.prototype = {
+
+    /**
+     * The skip block link from above the block being dragged while a drag is in progress.
+     * Required by the M.core.dragdrop from whom this class extends.
+     * @private
+     * @property skipnodetop
+     * @type Node
+     * @default null
+     */
+    skipnodetop : null,
+
+    /**
+     * The skip block link from below the block being dragged while a drag is in progress.
+     * Required by the M.core.dragdrop from whom this class extends.
+     * @private
+     * @property skipnodebottom
+     * @type Node
+     * @default null
+     */
+    skipnodebottom : null,
+
+    /**
+     * An associative object of regions and the
+     * @property regionobjects
+     * @type {Object} Primitive object mocking an associative array.
+     * @type {BLOCKREGION} [regionname]* Each item uses the region name as the key with the value being
+     *      an instance of the BLOCKREGION class.
+     */
+    regionobjects : {},
+
+    /**
+     * Called during the initialisation process of the object.
+     * @method initializer
+     */
+    initializer : function() {
+        Y.log('Initialising drag and drop for blocks.', 'info');
+        var regionnames = this.get('regions'),
+            i = 0,
+            region,
+            regionname,
+            droptarget,
+            dragdelegation;
+
+        // Evil required by M.core.dragdrop.
+        this.groups = ['block'];
+        this.samenodeclass = CSS.BLOCK;
+        this.parentnodeclass = CSS.BLOCKREGION;
+
+        // Add relevant classes and ID to 'content' block region on My Home page.
+        var myhomecontent = Y.Node.all('body#'+CSS.MYINDEX+' #'+CSS.REGIONMAIN+' > .'+CSS.REGIONCONTENT);
+        if (myhomecontent.size() > 0) {
+            var contentregion = myhomecontent.item(0);
+            contentregion.addClass(CSS.BLOCKREGION);
+            contentregion.set('id', CSS.REGIONCONTENT);
+            contentregion.one('div').addClass(CSS.REGIONCONTENT);
+        }
+
+        for (i in regionnames) {
+            regionname = regionnames[i];
+            region = new BLOCKREGION({
+                manager : this,
+                region : regionname,
+                node : Y.one('#block-region-'+regionname)
+            });
+            this.regionobjects[regionname] = region;
+
+            // Setting blockregion as droptarget (the case when it is empty)
+            // The region-post (the right one)
+            // is very narrow, so add extra padding on the left to drop block on it.
+            droptarget = new Y.DD.Drop({
+                node: region.get_droptarget(),
+                groups: this.groups,
+                padding: '40 240 40 240'
+            });
+
+            // Make each div element in the list of blocks draggable
+            dragdelegation = new Y.DD.Delegate({
+                container: region.get_droptarget(),
+                nodes: '.'+CSS.BLOCK,
+                target: true,
+                handles: ['.'+CSS.HEADER],
+                invalid: '.block-hider-hide, .block-hider-show, .moveto',
+                dragConfig: {groups: this.groups}
+            });
+            dragdelegation.dd.plug(Y.Plugin.DDProxy, {
+                // Don't move the node at the end of the drag
+                moveOnEnd: false
+            });
+            dragdelegation.dd.plug(Y.Plugin.DDWinScroll);
+            // On the mouse down event we will enable all block regions so that they can be dragged to.
+            // This is VERY important as without it dnd won't work for empty block regions.
+            dragdelegation.on('drag:mouseDown', this.enable_all_regions, this);
+
+            region.remove_block_move_icons();
+        }
+        Y.log('Initialisation of drag and drop for blocks complete.', 'info');
+    },
+
+    /**
+     * Returns the ID of the block the given node represents.
+     * @method get_block_id
+     * @param {Node} node
+     * @returns {int} The blocks ID in the database.
+     */
+    get_block_id : function(node) {
+        return Number(node.get('id').replace(/inst/i, ''));
+    },
+
+    /**
+     * Returns the block region that the node is part of or belonging to.
+     * @method get_block_region
+     * @param {Y.Node} node
+     * @returns {string} The region name.
+     */
+    get_block_region : function(node) {
+        if (!node.test('[data-blockregion]')) {
+            node = node.ancestor('[data-blockregion]');
+        }
+        return node.getData('blockregion');
+    },
+
+    /**
+     * Returns the BLOCKREGION instance that represents the block region the given node is part of.
+     * @method get_region_object
+     * @param {Y.Node} node
+     * @returns {BLOCKREGION}
+     */
+    get_region_object : function(node) {
+        return this.regionobjects[this.get_block_region(node)];
+    },
+
+    /**
+     * Enables all fo the regions so that they are all visible while dragging is occuring.
+     * @method enable_all_regions
+     * @returns {undefined}
+     */
+    enable_all_regions : function() {
+        var i = 0;
+        for (i in this.regionobjects) {
+            this.regionobjects[i].enable();
+        }
+    },
+
+    /**
+     * Disables enabled regions if they contain no blocks.
+     * @method disable_regions_if_required
+     * @returns {undefined}
+     */
+    disable_regions_if_required : function() {
+        var i = 0;
+        for (i in this.regionobjects) {
+            this.regionobjects[i].disable_if_required();
+        }
+    },
+
+    /**
+     * Called by M.core.dragdrop.global_drag_start when dragging starts.
+     * @method drag_start
+     * @param {Event} e
+     * @returns {undefined}
+     */
+    drag_start : function(e) {
+        // Get our drag object
+        var drag = e.target;
+
+        // Store the parent node of original drag node (block)
+        // we will need it later for show/hide empty regions
+
+        // Determine skipnodes and store them
+        if (drag.get('node').previous() && drag.get('node').previous().hasClass(CSS.SKIPBLOCK)) {
+            this.skipnodetop = drag.get('node').previous();
+        }
+        if (drag.get('node').next() && drag.get('node').next().hasClass(CSS.SKIPBLOCKTO)) {
+            this.skipnodebottom = drag.get('node').next();
+        }
+    },
+
+    /**
+     * Called by M.core.dragdrop.global_drop_over when something is dragged over a drop target.
+     * @method drop_over
+     * @param {Event} e
+     * @returns {undefined}
+     */
+    drop_over : function(e) {
+        // Get a reference to our drag and drop nodes
+        var drag = e.drag.get('node');
+        var drop = e.drop.get('node');
+
+        // We need to fix the case when parent drop over event has determined
+        // 'goingup' and appended the drag node after admin-block.
+        if (drop.hasClass(CSS.REGIONCONTENT) && drop.one('.'+CSS.BLOCKADMINBLOCK) && drop.one('.'+CSS.BLOCKADMINBLOCK).next('.'+CSS.BLOCK)) {
+            drop.prepend(drag);
+        }
+    },
+
+    /**
+     * Called by M.core.dragdrop.global_drop_end when a drop has been completed.
+     * @method drop_end
+     * @returns {undefined}
+     */
+    drop_end : function() {
+        // Clear variables.
+        this.skipnodetop = null;
+        this.skipnodebottom = null;
+        this.disable_regions_if_required();
+    },
+
+    /**
+     * Called by M.core.dragdrop.global_drag_dropmiss when something has been dropped on a node that isn't contained by a drop target.
+     * @method drag_dropmiss
+     * @param {Event} e
+     * @returns {undefined}
+     */
+    drag_dropmiss : function(e) {
+        // Missed the target, but we assume the user intended to drop it
+        // on the last ghost node location, e.drag and e.drop should be
+        // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
+        this.drop_hit(e);
+    },
+
+    /**
+     * Called by M.core.dragdrop.global_drag_hit when something has been dropped on a drop target.
+     * @method drop_hit
+     * @param {Event} e
+     * @returns {undefined}
+     */
+    drop_hit : function(e) {
+        // Get a reference to our drag node
+        var dragnode = e.drag.get('node');
+        var dropnode = e.drop.get('node');
+
+        // Amend existing skipnodes
+        if (dragnode.previous() && dragnode.previous().hasClass(CSS.SKIPBLOCK)) {
+            // the one that belongs to block below move below
+            dragnode.insert(dragnode.previous(), 'after');
+        }
+        // Move original skipnodes
+        if (this.skipnodetop) {
+            dragnode.insert(this.skipnodetop, 'before');
+        }
+        if (this.skipnodebottom) {
+            dragnode.insert(this.skipnodebottom, 'after');
+        }
+
+        // Add lightbox if it not there
+        var lightbox = M.util.add_lightbox(Y, dragnode);
+
+        // Prepare request parameters
+        var params = {
+            sesskey : M.cfg.sesskey,
+            courseid : this.get('courseid'),
+            pagelayout : this.get('pagelayout'),
+            pagetype : this.get('pagetype'),
+            subpage : this.get('subpage'),
+            contextid : this.get('contextid'),
+            action : 'move',
+            bui_moveid : this.get_block_id(dragnode),
+            bui_newregion : this.get_block_region(dropnode)
+        };
+
+        if (this.get('cmid')) {
+            params.cmid = this.get('cmid');
+        }
+
+        if (dragnode.next('.'+CSS.BLOCK) && !dragnode.next('.'+CSS.BLOCK).hasClass(CSS.BLOCKADMINBLOCK)) {
+            params.bui_beforeid = this.get_block_id(dragnode.next('.'+CSS.BLOCK));
+        }
+
+        // Do AJAX request
+        Y.io(M.cfg.wwwroot+AJAXURL, {
+            method: 'POST',
+            data: params,
+            on: {
+                start : function() {
+                    lightbox.show();
+                },
+                success: function(tid, response) {
+                    window.setTimeout(function() {
+                        lightbox.hide();
+                    }, 250);
+                    try {
+                        var responsetext = Y.JSON.parse(response.responseText);
+                        if (responsetext.error) {
+                            new M.core.ajaxException(responsetext);
+                        }
+                    } catch (e) {}
+                },
+                failure: function(tid, response) {
+                    this.ajax_failure(response);
+                    lightbox.hide();
+                },
+                complete : function() {
+                    this.disable_regions_if_required();
+                }
+            },
+            context:this
+        });
+    }
+};
+Y.extend(MANAGER, M.core.dragdrop, MANAGER.prototype, {
+    NAME : 'core-blocks-dragdrop-manager',
+    ATTRS : {
+        /**
+         * The Course ID if there is one.
+         * @attribute courseid
+         * @type int|null
+         * @default null
+         */
+        courseid : {
+            value : null
+        },
+
+        /**
+         * The Course Module ID if there is one.
+         * @attribute cmid
+         * @type int|null
+         * @default null
+         */
+        cmid : {
+            value : null
+        },
+
+        /**
+         * The Context ID.
+         * @attribute contextid
+         * @type int|null
+         * @default null
+         */
+        contextid : {
+            value : null
+        },
+
+        /**
+         * The current page layout.
+         * @attribute pagelayout
+         * @type string|null
+         * @default null
+         */
+        pagelayout : {
+            value : null
+        },
+
+        /**
+         * The page type string, should be used as the id for the body tag in the theme.
+         * @attribute pagetype
+         * @type string|null
+         * @default null
+         */
+        pagetype : {
+            value : null
+        },
+
+        /**
+         * The subpage identifier, if any.
+         * @attribute subpage
+         * @type string|null
+         * @default null
+         */
+        subpage : {
+            value : null
+        },
+
+        /**
+         * An array of block regions that are present on the page.
+         * @attribute regions
+         * @type array|null
+         * @default Array[]
+         */
+        regions : {
+            value : []
+        }
+    }
+});
\ No newline at end of file
index a98af01..e4293dc 100644 (file)
 
 require('../config.php');
 
+// Try to prevent searching for sites that allow sign-up.
+if (!isset($CFG->additionalhtmlhead)) {
+    $CFG->additionalhtmlhead = '';
+}
+$CFG->additionalhtmlhead .= '<meta name="robots" content="noindex" />';
+
 redirect_if_major_upgrade_required();
 
 $testsession = optional_param('testsession', 0, PARAM_INT); // test session works properly
index 331f9ef..a35641f 100644 (file)
 
 require('../config.php');
 
+// Try to prevent searching for sites that allow sign-up.
+if (!isset($CFG->additionalhtmlhead)) {
+    $CFG->additionalhtmlhead = '';
+}
+$CFG->additionalhtmlhead .= '<meta name="robots" content="noindex" />';
+
 if (empty($CFG->registerauth)) {
     print_error('notlocalisederrormessage', 'error', '', 'Sorry, you may not use this page.');
 }
index 8696fa8..7ff7bc6 100644 (file)
@@ -52,6 +52,7 @@ class backup_assign_activity_structure_step extends backup_activity_structure_st
                                                   'sendnotifications',
                                                   'sendlatenotifications',
                                                   'duedate',
+                                                  'displayduedate',
                                                   'cutoffdate',
                                                   'allowsubmissionsfromdate',
                                                   'grade',
index 271062e..1d7a476 100644 (file)
@@ -17,6 +17,7 @@
         <FIELD NAME="sendnotifications" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Allows the disabling of email notifications in the assign module."/>
         <FIELD NAME="sendlatenotifications" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Allows separate enabling of notifications for late assignment submissions."/>
         <FIELD NAME="duedate" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The due date for the assignment. Displayed to students."/>
+        <FIELD NAME="displayduedate" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="If true the due date will be displayed."/>
         <FIELD NAME="allowsubmissionsfromdate" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="If set, submissions will only be accepted after this date."/>
         <FIELD NAME="grade" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The maximum grade for this assignment. Can be negative to indicate the use of a scale."/>
         <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The time the settings for this assign module instance were last modified."/>
index d7136de..f7709a1 100644 (file)
@@ -206,8 +206,6 @@ function xmldb_assign_upgrade($oldversion) {
     }
 
     // Moodle v2.4.0 release upgrade line.
-    // Put any upgrade step following this.
-
     if ($oldversion < 2013030600) {
         upgrade_set_timeout(60*20);
 
@@ -422,7 +420,17 @@ function xmldb_assign_upgrade($oldversion) {
     }
 
     // Moodle v2.5.0 release upgrade line.
-    // Put any upgrade step following this.
+    if ($oldversion < 2013050100) {
+        // Define field displayduedate to be added to assign.
+        $table = new xmldb_table('assign');
+        $field = new xmldb_field('displayduedate', XMLDB_TYPE_INTEGER, '2', null, null, null, '0', 'duedate');
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // Assign savepoint reached.
+        upgrade_mod_savepoint(true, 2013050100, 'assign');
+    }
 
 
     return true;
index 4be18c3..21788ad 100644 (file)
@@ -49,7 +49,7 @@ class mod_assign_grading_batch_operations_form extends moodleform {
         if ($instance['submissiondrafts']) {
             $options['reverttodraft'] = get_string('reverttodraft', 'assign');
         }
-        if ($instance['duedate']) {
+        if ($instance['duedate'] && has_capability('mod/assign:grantextension', $instance['context'])) {
             $options['grantextension'] = get_string('grantextension', 'assign');
         }
         if ($instance['attemptreopenmethod'] == ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL) {
index a168a0b..57df5a0 100644 (file)
@@ -166,15 +166,17 @@ class assign_grading_table extends table_sql implements renderable {
         // The filters do not make sense when there are no submissions, so do not apply them.
         if ($this->assignment->is_any_submission_plugin_enabled()) {
             if ($filter == ASSIGN_FILTER_SUBMITTED) {
-                $where .= ' AND s.timecreated > 0 ';
-            }
-            if ($filter == ASSIGN_FILTER_REQUIRE_GRADING) {
+                $where .= ' AND (s.timemodified IS NOT NULL AND
+                                 s.status = :submitted) ';
+                $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
+
+            } else if ($filter == ASSIGN_FILTER_REQUIRE_GRADING) {
                 $where .= ' AND (s.timemodified IS NOT NULL AND
                                  s.status = :submitted AND
                                  (s.timemodified > g.timemodified OR g.timemodified IS NULL))';
                 $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
-            }
-            if (strpos($filter, ASSIGN_FILTER_SINGLE_USER) === 0) {
+
+            } else if (strpos($filter, ASSIGN_FILTER_SINGLE_USER) === 0) {
                 $userfilter = (int) array_pop(explode('=', $filter));
                 $where .= ' AND (u.id = :userid)';
                 $params['userid'] = $userfilter;
index f9cfea2..b3261aa 100644 (file)
@@ -115,6 +115,8 @@ $string['deletepluginareyousure'] = 'Delete assignment plugin {$a}: are you sure
 $string['deletepluginareyousuremessage'] = 'You are about to completely delete the assignment plugin {$a}. This will completely delete everything in the database associated with this assignment plugin. Are you SURE you want to continue?';
 $string['deletingplugin'] = 'Deleting plugin {$a}.';
 $string['description'] = 'Description';
+$string['displayduedate'] = 'Display on course page';
+$string['displayduedate_help'] = 'If enabled the due date will be displayed on the course page if one has been set and enabled.';
 $string['downloadall'] = 'Download all submissions';
 $string['download all submissions'] = 'Download all submissions in a zip file.';
 $string['duedate'] = 'Due date';
index 0cff70d..d43e61d 100644 (file)
@@ -257,7 +257,7 @@ function assign_get_coursemodule_info($coursemodule) {
     global $CFG, $DB;
 
     $dbparams = array('id'=>$coursemodule->instance);
-    $fields = 'id, name, alwaysshowdescription, allowsubmissionsfromdate, intro, introformat';
+    $fields = 'id, name, alwaysshowdescription, allowsubmissionsfromdate, intro, introformat, duedate, displayduedate';
     if (! $assignment = $DB->get_record('assign', $dbparams, $fields)) {
         return false;
     }
@@ -270,6 +270,14 @@ function assign_get_coursemodule_info($coursemodule) {
             $result->content = format_module_intro('assign', $assignment, $coursemodule->id, false);
         }
     }
+
+    if (($assignment->duedate > 0) && ($assignment->displayduedate)) {
+        if (empty($result->content)){
+            $result->content = '';
+        }
+        $result->content .= html_writer::tag('p', get_string('duedate', 'assign') . ': ' . userdate($assignment->duedate));
+    }
+
     return $result;
 }
 
index bce42d2..811fa51 100644 (file)
@@ -524,7 +524,16 @@ class assign {
         $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
         $update->sendnotifications = $formdata->sendnotifications;
         $update->sendlatenotifications = $formdata->sendlatenotifications;
-        $update->duedate = $formdata->duedate;
+        if (empty($formdata->duedateenable)) {
+            $update->duedate = 0;
+        } else {
+            $update->duedate = $formdata->duedate;
+        }
+        if (empty($formdata->displayduedate)) {
+            $update->displayduedate = 0;
+        } else {
+            $update->displayduedate = $formdata->displayduedate;
+        }
         $update->cutoffdate = $formdata->cutoffdate;
         $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
         $update->grade = $formdata->grade;
@@ -845,7 +854,16 @@ class assign {
         $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
         $update->sendnotifications = $formdata->sendnotifications;
         $update->sendlatenotifications = $formdata->sendlatenotifications;
-        $update->duedate = $formdata->duedate;
+        if (empty($formdata->duedateenable)) {
+            $update->duedate = 0;
+        } else {
+            $update->duedate = $formdata->duedate;
+        }
+        if (empty($formdata->displayduedate)) {
+            $update->displayduedate = 0;
+        } else {
+            $update->displayduedate = $formdata->displayduedate;
+        }
         $update->cutoffdate = $formdata->cutoffdate;
         $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
         $update->grade = $formdata->grade;
@@ -2876,7 +2894,8 @@ class assign {
                                  'submissiondrafts'=>$this->get_instance()->submissiondrafts,
                                  'duedate'=>$this->get_instance()->duedate,
                                  'attemptreopenmethod'=>$this->get_instance()->attemptreopenmethod,
-                                 'feedbackplugins'=>$this->get_feedback_plugins());
+                                 'feedbackplugins'=>$this->get_feedback_plugins(),
+                                 'context'=>$this->get_context());
         $classoptions = array('class'=>'gradingbatchoperationsform');
 
         $gradingbatchoperationsform = new mod_assign_grading_batch_operations_form(null,
@@ -3152,7 +3171,8 @@ class assign {
                                  'submissiondrafts'=>$this->get_instance()->submissiondrafts,
                                  'duedate'=>$this->get_instance()->duedate,
                                  'attemptreopenmethod'=>$this->get_instance()->attemptreopenmethod,
-                                 'feedbackplugins'=>$this->get_feedback_plugins());
+                                 'feedbackplugins'=>$this->get_feedback_plugins(),
+                                 'context'=>$this->get_context());
         $formclasses = array('class'=>'gradingbatchoperationsform');
         $mform = new mod_assign_grading_batch_operations_form(null,
                                                               $batchformparams,
@@ -3675,7 +3695,7 @@ class assign {
             return false;
         }
         $assign = clone $this->get_instance();
-        $assign->cmidnumber = $this->get_course_module()->id;
+        $assign->cmidnumber = $this->get_course_module()->idnumber;
 
         return assign_grade_item_update($assign, $gradebookgrade);
     }
@@ -4958,6 +4978,7 @@ class assign {
                 $gradingelement->freeze();
             } else {
                 $mform->addElement('hidden', 'advancedgradinginstanceid', $gradinginstance->get_id());
+                $mform->setType('advancedgradinginstanceid', PARAM_INT);
             }
         } else {
             // Use simple direct grading.
index 4101ab1..1b39c7e 100644 (file)
@@ -83,10 +83,30 @@ class mod_assign_mod_form extends moodleform_mod {
         $mform->addHelpButton('allowsubmissionsfromdate', 'allowsubmissionsfromdate', 'assign');
         $mform->setDefault('allowsubmissionsfromdate', time());
 
-        $name = get_string('duedate', 'assign');
-        $mform->addElement('date_time_selector', 'duedate', $name, array('optional'=>true));
-        $mform->addHelpButton('duedate', 'duedate', 'assign');
-        $mform->setDefault('duedate', time()+7*24*3600);
+        $name = get_string('duedate', 'assign').$assignment->get_renderer()->help_icon('duedate', 'assign');
+        $duedateelements[] = $mform->createElement('date_time_selector', 'duedate', $name);
+        $duedateelements[] = $mform->createElement('checkbox', 'duedateenable', null, get_string('enable'));
+        try {
+            $duedate = $assignment->get_instance()->duedate;
+        } catch (Exception $e) {
+            $duedate = 0;
+        }
+        if ($duedate > 0) {
+            $mform->setDefault('duedate', $duedate);
+            $mform->setDefault('duedateenable', 1);
+        } else {
+            $mform->setDefault('duedate', time()+7*24*3600);
+            $mform->setDefault('duedateenable', 0);
+            $mform->setType('duedateenable', PARAM_BOOL);
+        }
+
+        $dddname = get_string('displayduedate', 'assign').$assignment->get_renderer()->help_icon('displayduedate', 'assign');
+        $duedateelements[] = $mform->createElement('checkbox', 'displayduedate', null, $dddname);
+        $mform->setDefault('displayduedate', 0);
+
+        $mform->addGroup($duedateelements, 'duedategrp', $name, null, false);
+        $mform->setType('displayduedate', PARAM_BOOL);
+        $mform->disabledIf('duedategrp', 'duedateenable');
 
         $name = get_string('cutoffdate', 'assign');
         $mform->addElement('date_time_selector', 'cutoffdate', $name, array('optional'=>true));
index 6c22581..daf1eed 100644 (file)
@@ -56,6 +56,7 @@ class mod_assign_generator extends testing_module_generator {
             'sendnotifications'                 => 0,
             'sendlatenotifications'             => 0,
             'duedate'                           => 0,
+            'displayduedate'                    => 0,
             'allowsubmissionsfromdate'          => 0,
             'grade'                             => 100,
             'cutoffdate'                        => 0,
index e3899dd..2d76aea 100644 (file)
@@ -86,6 +86,7 @@ class assign_upgrade_manager {
         $data->sendnotifications = $oldassignment->emailteachers;
         $data->sendlatenotifications = $oldassignment->emailteachers;
         $data->duedate = $oldassignment->timedue;
+        $data->displayduedate = 0;
         $data->allowsubmissionsfromdate = $oldassignment->timeavailable;
         $data->grade = $oldassignment->grade;
         $data->submissiondrafts = $oldassignment->resubmit;
index 3b979b4..00ab2c6 100644 (file)
@@ -28,5 +28,3 @@ $module->component = 'mod_assign'; // Full name of the plugin (used for diagnost
 $module->version  = 2013050100;    // The current module version (Date: YYYYMMDDXX).
 $module->requires = 2013050100;    // Requires this Moodle version.
 $module->cron     = 60;
-
-
index 4e0d669..ba33874 100644 (file)
@@ -68,6 +68,7 @@ require_login($course, true, $cm);
 require_capability('mod/feedback:mapcourse', $context);
 
 if ($coursefilter) {
+    $map = new stdClass;
     $map->feedbackid = $feedback->id;
     $map->courseid = $coursefilter;
     // insert a map only if it does exists yet
@@ -123,6 +124,7 @@ echo '</form>';
 
 if ($coursemap = feedback_get_courses_from_sitecourse_map($feedback->id)) {
     $table = new flexible_table('coursemaps');
+    $table->baseurl = $url;
     $table->define_columns( array('course'));
     $table->define_headers( array(get_string('mappedcourses', 'feedback')));
 
@@ -130,9 +132,8 @@ if ($coursemap = feedback_get_courses_from_sitecourse_map($feedback->id)) {
 
     $unmapurl = new moodle_url('/mod/feedback/unmapcourse.php');
     foreach ($coursemap as $cmap) {
-        $cmapcontext = context_course::instance($cmap->id);
-        $cmapshortname = format_string($cmap->shortname, true, array('context' => $cmapcontext));
         $coursecontext = context_course::instance($cmap->courseid);
+        $cmapshortname = format_string($cmap->shortname, true, array('context' => $coursecontext));
         $cmapfullname = format_string($cmap->fullname, true, array('context' => $coursecontext));
         $unmapurl->params(array('id'=>$id, 'cmapid'=>$cmap->id));
         $anker = '<a href="'.$unmapurl->out().'">';
index 0e3935a..9473987 100644 (file)
@@ -6760,7 +6760,11 @@ function forum_tp_count_forum_unread_posts($cm, $course) {
         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
     }
 
-    $mygroups = $modinfo->groups[$cm->groupingid];
+    if (array_key_exists($cm->groupingid, $modinfo->groups)) {
+        $mygroups = $modinfo->groups[$cm->groupingid];
+    } else {
+        $mygroups = false; // Will be set below
+    }
 
     // add all groups posts
     if (empty($mygroups)) {
index f2755cf..d51ecf7 100644 (file)
@@ -61,10 +61,14 @@ function imscp_print_content($imscp, $cm, $course) {
 function imscp_htmllize_item($item, $imscp, $cm) {
     global $CFG;
 
-    $context = context_module::instance($cm->id);
-    $urlbase = "$CFG->wwwroot/pluginfile.php";
-    $path = '/'.$context->id.'/mod_imscp/content/'.$imscp->revision.'/'.$item['href'];
-    $url = file_encode_url($urlbase, $path, false);
+    if (preg_match('|^https?://|', $item['href'])) {
+        $url = $item['href'];
+    } else {
+        $context = context_module::instance($cm->id);
+        $urlbase = "$CFG->wwwroot/pluginfile.php";
+        $path = '/'.$context->id.'/mod_imscp/content/'.$imscp->revision.'/'.$item['href'];
+        $url = file_encode_url($urlbase, $path, false);
+    }
     $result = "<li><a href=\"$url\">".$item['title'].'</a>';
     if ($item['subitems']) {
         $result .= '<ul>';
index f1f08ca..4a47194 100644 (file)
@@ -93,7 +93,7 @@ if ($lastattempt && ($lastattempt->state == quiz_attempt::IN_PROGRESS ||
 
     // And, if the attempt is now no longer in progress, redirect to the appropriate place.
     if ($lastattempt->state == quiz_attempt::OVERDUE) {
-         redirect($quizobj->summary_url($lastattempt->id));
+        redirect($quizobj->summary_url($lastattempt->id));
     } else if ($lastattempt->state != quiz_attempt::IN_PROGRESS) {
         redirect($quizobj->review_url($lastattempt->id));
     }
@@ -104,6 +104,10 @@ if ($lastattempt && ($lastattempt->state == quiz_attempt::IN_PROGRESS ||
     }
 
 } else {
+    while ($lastattempt && $lastattempt->preview) {
+        $lastattempt = array_pop($attempts);
+    }
+
     // Get number for the next or unfinished attempt.
     if ($lastattempt) {
         $attemptnumber = $lastattempt->attempt + 1;
index d803485..10f078f 100644 (file)
@@ -504,7 +504,10 @@ M.mod_scorm.init = function(Y, hide_nav, hide_toc, toc_title, window_name, launc
 
         // navigation
         if (scorm_hide_nav == false) {
-            scorm_nav_panel = new Y.YUI2.widget.Panel('scorm_navpanel', { visible:true, draggable:true, close:false, xy: [250, 450],
+            var left = scorm_layout_widget.getUnitByPosition('left');
+            navposition = Y.YUI2.util.Dom.getXY(left);
+            navposition[1] += 200;
+            scorm_nav_panel = new Y.YUI2.widget.Panel('scorm_navpanel', { visible:true, draggable:true, close:false, xy: navposition,
                                                                     autofillheight: "body"} );
             scorm_nav_panel.setHeader(M.str.scorm.navigation);
 
index 6286973..7a03152 100644 (file)
@@ -23,5 +23,4 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$string['pluginname'] = 'Graph report';
-
+$string['pluginname'] = 'Basic report';
index 63ef9cf..e77f988 100644 (file)
@@ -12,6 +12,7 @@
 #page-mod-scorm-player #scormpage {position: relative;width: 100%;height: 100%;}
 #page-mod-scorm-player #scormpage #toctree {position:relative;width:100%;overflow-x: auto;overflow-y: auto;}
 #page-mod-scorm-player #tocbox {position: relative;left: 0px;width: 100%;height: 100%;font-size: 0.8em;}
+#page-mod-scorm-player #toctree { overflow: visible; }
 #page-mod-scorm-player #tochead {position: relative;text-align: center;top: 3px;height: 30px;}
 #page-mod-scorm-player #scormpage .scoframe {frameborder: 0;}
 
@@ -39,3 +40,4 @@
 .path-mod-scorm.jsenabled .scorm-center { display:block;}
 .path-mod-scorm.jsenabled .toc { display:block;}
 .path-mod-scorm.jsenabled #scormpage #tocbox { display:block;}
+
index 979b432..e66c434 100644 (file)
@@ -24,10 +24,10 @@ class note_edit_form extends moodleform {
         $this->add_action_buttons();
 
         $mform->addElement('hidden', 'courseid');
-        $mform->setType('course', PARAM_INT);
+        $mform->setType('courseid', PARAM_INT);
 
         $mform->addElement('hidden', 'userid');
-        $mform->setType('user', PARAM_INT);
+        $mform->setType('userid', PARAM_INT);
 
         $mform->addElement('hidden', 'id');
         $mform->setType('id', PARAM_INT);
index cfd0af2..9c0c7cf 100644 (file)
@@ -152,6 +152,8 @@ class question_engine_data_mapper {
         foreach ($step->get_all_data() as $name => $value) {
             if ($value instanceof question_file_saver) {
                 $value->save_files($stepid, $context);
+            }
+            if ($value instanceof question_response_files) {
                 $value = (string) $value;
             }
 
index df13890..a5e5d12 100644 (file)
@@ -759,16 +759,16 @@ abstract class question_utils {
     public static function arrays_same_at_key_integer(
             array $array1, array $array2, $key) {
         if (array_key_exists($key, $array1)) {
-            $value1 = $array1[$key];
+            $value1 = (int) $array1[$key];
         } else {
             $value1 = 0;
         }
         if (array_key_exists($key, $array2)) {
-            $value2 = $array2[$key];
+            $value2 = (int) $array2[$key];
         } else {
             $value2 = 0;
         }
-        return ((integer) $value1) === ((integer) $value2);
+        return $value1 === $value2;
     }
 
     private static $units     = array('', 'i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii', 'viii', 'ix');
index c3ac2ca..2888a30 100644 (file)
@@ -338,7 +338,7 @@ class qtype_multichoice_multi_question extends qtype_multichoice_base {
     public function is_same_response(array $prevresponse, array $newresponse) {
         foreach ($this->order as $key => $notused) {
             $fieldname = $this->field($key);
-            if (!question_utils::arrays_same_at_key($prevresponse, $newresponse, $fieldname)) {
+            if (!question_utils::arrays_same_at_key_integer($prevresponse, $newresponse, $fieldname)) {
                 return false;
             }
         }
diff --git a/question/type/multichoice/tests/question_multi_test.php b/question/type/multichoice/tests/question_multi_test.php
new file mode 100644 (file)
index 0000000..11fe16f
--- /dev/null
@@ -0,0 +1,152 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for the multiple choice, multi-response question definition classes.
+ *
+ * @package   qtype_multichoice
+ * @copyright 2009 The Open University
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
+
+
+/**
+ * Unit tests for the multiple choice, multi-response question definition class.
+ *
+ * @copyright 2009 The Open University
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_multichoice_multi_question_test extends advanced_testcase {
+
+    public function test_get_expected_data() {
+        $question = test_question_maker::make_a_multichoice_multi_question();
+        $question->start_attempt(new question_attempt_step(), 1);
+
+        $this->assertEquals(array('choice0' => PARAM_BOOL, 'choice1' => PARAM_BOOL,
+                'choice2' => PARAM_BOOL, 'choice3' => PARAM_BOOL), $question->get_expected_data());
+    }
+
+    public function test_is_complete_response() {
+        $question = test_question_maker::make_a_multichoice_multi_question();
+        $question->start_attempt(new question_attempt_step(), 1);
+
+        $this->assertFalse($question->is_complete_response(array()));
+        $this->assertFalse($question->is_complete_response(
+                array('choice0' => '0', 'choice1' => '0', 'choice2' => '0', 'choice3' => '0')));
+        $this->assertTrue($question->is_complete_response(array('choice1' => '1')));
+        $this->assertTrue($question->is_complete_response(
+                array('choice0' => '1', 'choice1' => '1', 'choice2' => '1', 'choice3' => '1')));
+    }
+
+    public function test_is_gradable_response() {
+        $question = test_question_maker::make_a_multichoice_multi_question();
+        $question->start_attempt(new question_attempt_step(), 1);
+
+        $this->assertFalse($question->is_gradable_response(array()));
+        $this->assertFalse($question->is_gradable_response(
+                array('choice0' => '0', 'choice1' => '0', 'choice2' => '0', 'choice3' => '0')));
+        $this->assertTrue($question->is_gradable_response(array('choice1' => '1')));
+        $this->assertTrue($question->is_gradable_response(
+                array('choice0' => '1', 'choice1' => '1', 'choice2' => '1', 'choice3' => '1')));
+    }
+
+    public function test_is_same_response() {
+        $question = test_question_maker::make_a_multichoice_multi_question();
+        $question->start_attempt(new question_attempt_step(), 1);
+
+        $this->assertTrue($question->is_same_response(
+                array(),
+                array('choice0' => '0', 'choice1' => '0', 'choice2' => '0', 'choice3' => '0')));
+
+        $this->assertTrue($question->is_same_response(
+                array('choice0' => '0', 'choice1' => '0', 'choice2' => '0', 'choice3' => '0'),
+                array('choice0' => '0', 'choice1' => '0', 'choice2' => '0', 'choice3' => '0')));
+
+        $this->assertFalse($question->is_same_response(
+                array('choice0' => '0', 'choice1' => '0', 'choice2' => '0', 'choice3' => '0'),
+                array('choice0' => '1', 'choice1' => '0', 'choice2' => '0', 'choice3' => '0')));
+
+        $this->assertTrue($question->is_same_response(
+                array('choice0' => '1', 'choice1' => '0', 'choice2' => '1', 'choice3' => '0'),
+                array('choice0' => '1', 'choice1' => '0', 'choice2' => '1', 'choice3' => '0')));
+    }
+
+    public function test_grading() {
+        $question = test_question_maker::make_a_multichoice_multi_question();
+        $question->shuffleanswers = false;
+        $question->start_attempt(new question_attempt_step(), 1);
+
+        $this->assertEquals(array(1, question_state::$gradedright),
+                $question->grade_response(array('choice0' => '1', 'choice2' => '1')));
+        $this->assertEquals(array(0.5, question_state::$gradedpartial),
+                $question->grade_response(array('choice0' => '1')));
+        $this->assertEquals(array(0, question_state::$gradedwrong),
+                $question->grade_response(
+                        array('choice0' => '1', 'choice1' => '1', 'choice2' => '1')));
+        $this->assertEquals(array(0, question_state::$gradedwrong),
+                $question->grade_response(array('choice1' => '1')));
+    }
+
+    public function test_get_correct_response() {
+        $question = test_question_maker::make_a_multichoice_multi_question();
+        $question->shuffleanswers = false;
+        $question->start_attempt(new question_attempt_step(), 1);
+
+        $this->assertEquals(array('choice0' => '1', 'choice2' => '1'),
+                $question->get_correct_response());
+    }
+
+    public function test_get_question_summary() {
+        $mc = test_question_maker::make_a_multichoice_single_question();
+        $mc->start_attempt(new question_attempt_step(), 1);
+
+        $qsummary = $mc->get_question_summary();
+
+        $this->assertRegExp('/' . preg_quote($mc->questiontext, '/') . '/', $qsummary);
+        foreach ($mc->answers as $answer) {
+            $this->assertRegExp('/' . preg_quote($answer->answer, '/') . '/', $qsummary);
+        }
+    }
+
+    public function test_summarise_response() {
+        $mc = test_question_maker::make_a_multichoice_multi_question();
+        $mc->shuffleanswers = false;
+        $mc->start_attempt(new question_attempt_step(), 1);
+
+        $summary = $mc->summarise_response(array('choice1' => 1, 'choice2' => 1),
+                test_question_maker::get_a_qa($mc));
+
+        $this->assertEquals('B; C', $summary);
+    }
+
+    public function test_classify_response() {
+        $mc = test_question_maker::make_a_multichoice_multi_question();
+        $mc->shuffleanswers = false;
+        $mc->start_attempt(new question_attempt_step(), 1);
+
+        $this->assertEquals(array(
+                    13 => new question_classified_response(13, 'A', 0.5),
+                    14 => new question_classified_response(14, 'B', -1.0),
+                ), $mc->classify_response(array('choice0' => 1, 'choice1' => 1)));
+
+        $this->assertEquals(array(), $mc->classify_response(array()));
+    }
+}
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Unit tests for the multiple choice question definition classes.
+ * Unit tests for the multiple choice, single response question definition classes.
  *
- * @package    qtype
- * @subpackage multichoice
- * @copyright  2009 The Open University
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package   qtype_multichoice
+ * @copyright 2009 The Open University
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-
 defined('MOODLE_INTERNAL') || die();
 
 global $CFG;
@@ -31,10 +29,10 @@ require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
 
 
 /**
- * Unit tests for the multiple choice, multiple response question definition class.
+ * Unit tests for the multiple choice, single response question definition class.
  *
- * @copyright  2009 The Open University
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @copyright 2009 The Open University
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class qtype_multichoice_single_question_test extends advanced_testcase {
 
@@ -59,6 +57,31 @@ class qtype_multichoice_single_question_test extends advanced_testcase {
         $this->assertTrue($question->is_gradable_response(array('answer' => '2')));
     }
 
+    public function test_is_same_response() {
+        $question = test_question_maker::make_a_multichoice_single_question();
+        $question->start_attempt(new question_attempt_step(), 1);
+
+        $this->assertTrue($question->is_same_response(
+                array(),
+                array()));
+
+        $this->assertFalse($question->is_same_response(
+                array(),
+                array('answer' => '0')));
+
+        $this->assertTrue($question->is_same_response(
+                array('answer' => '0'),
+                array('answer' => '0')));
+
+        $this->assertFalse($question->is_same_response(
+                array('answer' => '0'),
+                array('answer' => '1')));
+
+        $this->assertTrue($question->is_same_response(
+                array('answer' => '2'),
+                array('answer' => '2')));
+    }
+
     public function test_grading() {
         $question = test_question_maker::make_a_multichoice_single_question();
         $question->shuffleanswers = false;
@@ -151,106 +174,3 @@ class qtype_multichoice_single_question_test extends advanced_testcase {
         $this->assertEquals('Frog<br />†', $mc->make_html_inline('<p>Frog</p><p>†</p>'));
     }
 }
-
-
-/**
- * Unit tests for the multiple choice, single response question definition class.
- *
- * @copyright  2009 The Open University
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class qtype_multichoice_multi_question_test extends advanced_testcase {
-
-    public function test_get_expected_data() {
-        $question = test_question_maker::make_a_multichoice_multi_question();
-        $question->start_attempt(new question_attempt_step(), 1);
-
-        $this->assertEquals(array('choice0' => PARAM_BOOL, 'choice1' => PARAM_BOOL,
-                'choice2' => PARAM_BOOL, 'choice3' => PARAM_BOOL), $question->get_expected_data());
-    }
-
-    public function test_is_complete_response() {
-        $question = test_question_maker::make_a_multichoice_multi_question();
-        $question->start_attempt(new question_attempt_step(), 1);
-
-        $this->assertFalse($question->is_complete_response(array()));
-        $this->assertFalse($question->is_complete_response(
-                array('choice0' => '0', 'choice1' => '0', 'choice2' => '0', 'choice3' => '0')));
-        $this->assertTrue($question->is_complete_response(array('choice1' => '1')));
-        $this->assertTrue($question->is_complete_response(
-                array('choice0' => '1', 'choice1' => '1', 'choice2' => '1', 'choice3' => '1')));
-    }
-
-    public function test_is_gradable_response() {
-        $question = test_question_maker::make_a_multichoice_multi_question();
-        $question->start_attempt(new question_attempt_step(), 1);
-
-        $this->assertFalse($question->is_gradable_response(array()));
-        $this->assertFalse($question->is_gradable_response(
-                array('choice0' => '0', 'choice1' => '0', 'choice2' => '0', 'choice3' => '0')));
-        $this->assertTrue($question->is_gradable_response(array('choice1' => '1')));
-        $this->assertTrue($question->is_gradable_response(
-                array('choice0' => '1', 'choice1' => '1', 'choice2' => '1', 'choice3' => '1')));
-    }
-
-    public function test_grading() {
-        $question = test_question_maker::make_a_multichoice_multi_question();
-        $question->shuffleanswers = false;
-        $question->start_attempt(new question_attempt_step(), 1);
-
-        $this->assertEquals(array(1, question_state::$gradedright),
-                $question->grade_response(array('choice0' => '1', 'choice2' => '1')));
-        $this->assertEquals(array(0.5, question_state::$gradedpartial),
-                $question->grade_response(array('choice0' => '1')));
-        $this->assertEquals(array(0, question_state::$gradedwrong),
-                $question->grade_response(
-                        array('choice0' => '1', 'choice1' => '1', 'choice2' => '1')));
-        $this->assertEquals(array(0, question_state::$gradedwrong),
-                $question->grade_response(array('choice1' => '1')));
-    }
-
-    public function test_get_correct_response() {
-        $question = test_question_maker::make_a_multichoice_multi_question();
-        $question->shuffleanswers = false;
-        $question->start_attempt(new question_attempt_step(), 1);
-
-        $this->assertEquals(array('choice0' => '1', 'choice2' => '1'),
-                $question->get_correct_response());
-    }
-
-    public function test_get_question_summary() {
-        $mc = test_question_maker::make_a_multichoice_single_question();
-        $mc->start_attempt(new question_attempt_step(), 1);
-
-        $qsummary = $mc->get_question_summary();
-
-        $this->assertRegExp('/' . preg_quote($mc->questiontext, '/') . '/', $qsummary);
-        foreach ($mc->answers as $answer) {
-            $this->assertRegExp('/' . preg_quote($answer->answer, '/') . '/', $qsummary);
-        }
-    }
-
-    public function test_summarise_response() {
-        $mc = test_question_maker::make_a_multichoice_multi_question();
-        $mc->shuffleanswers = false;
-        $mc->start_attempt(new question_attempt_step(), 1);
-
-        $summary = $mc->summarise_response(array('choice1' => 1, 'choice2' => 1),
-                test_question_maker::get_a_qa($mc));
-
-        $this->assertEquals('B; C', $summary);
-    }
-
-    public function test_classify_response() {
-        $mc = test_question_maker::make_a_multichoice_multi_question();
-        $mc->shuffleanswers = false;
-        $mc->start_attempt(new question_attempt_step(), 1);
-
-        $this->assertEquals(array(
-                    13 => new question_classified_response(13, 'A', 0.5),
-                    14 => new question_classified_response(14, 'B', -1.0),
-                ), $mc->classify_response(array('choice0' => 1, 'choice1' => 1)));
-
-        $this->assertEquals(array(), $mc->classify_response(array()));
-    }
-}
index 6d61971..35075ba 100644 (file)
@@ -473,25 +473,8 @@ class question_type {
         $oldhints = $DB->get_records('question_hints',
                 array('questionid' => $formdata->id), 'id ASC');
 
-        if (!empty($formdata->hint)) {
-            $numhints = max(array_keys($formdata->hint)) + 1;
-        } else {
-            $numhints = 0;
-        }
 
-        if ($withparts) {
-            if (!empty($formdata->hintclearwrong)) {
-                $numclears = max(array_keys($formdata->hintclearwrong)) + 1;
-            } else {
-                $numclears = 0;
-            }
-            if (!empty($formdata->hintshownumcorrect)) {
-                $numshows = max(array_keys($formdata->hintshownumcorrect)) + 1;
-            } else {
-                $numshows = 0;
-            }
-            $numhints = max($numhints, $numclears, $numshows);
-        }
+        $numhints = $this->count_hints_on_form($formdata, $withparts);
 
         for ($i = 0; $i < $numhints; $i += 1) {
             if (html_is_blank($formdata->hint[$i]['text'])) {
@@ -503,8 +486,7 @@ class question_type {
                 $shownumcorrect = !empty($formdata->hintshownumcorrect[$i]);
             }
 
-            if (empty($formdata->hint[$i]['text']) && empty($clearwrong) &&
-                    empty($shownumcorrect)) {
+            if ($this->is_hint_empty_in_form_data($formdata, $i, $withparts)) {
                 continue;
             }
 
@@ -524,6 +506,7 @@ class question_type {
                 $hint->clearwrong = $clearwrong;
                 $hint->shownumcorrect = $shownumcorrect;
             }
+            $hint->options = $this->save_hint_options($formdata, $i, $withparts);
             $DB->update_record('question_hints', $hint);
         }
 
@@ -535,6 +518,65 @@ class question_type {
         }
     }
 
+    /**
+     * Count number of hints on the form.
+     * Overload if you use custom hint controls.
+     * @param object $formdata the data from the form.
+     * @param bool $withparts whether to take into account clearwrong and shownumcorrect options.
+     * @return int count of hints on the form.
+     */
+    protected function count_hints_on_form($formdata, $withparts) {
+        if (!empty($formdata->hint)) {
+            $numhints = max(array_keys($formdata->hint)) + 1;
+        } else {
+            $numhints = 0;
+        }
+
+        if ($withparts) {
+            if (!empty($formdata->hintclearwrong)) {
+                $numclears = max(array_keys($formdata->hintclearwrong)) + 1;
+            } else {
+                $numclears = 0;
+            }
+            if (!empty($formdata->hintshownumcorrect)) {
+                $numshows = max(array_keys($formdata->hintshownumcorrect)) + 1;
+            } else {
+                $numshows = 0;
+            }
+            $numhints = max($numhints, $numclears, $numshows);
+        }
+        return $numhints;
+    }
+
+    /**
+     * Determine if the hint with specified number is not empty and should be saved.
+     * Overload if you use custom hint controls.
+     * @param object $formdata the data from the form.
+     * @param int $number number of hint under question.
+     * @param bool $withparts whether to take into account clearwrong and shownumcorrect options.
+     * @return bool is this particular hint data empty.
+     */
+    protected function is_hint_empty_in_form_data($formdata, $number, $withparts) {
+        if ($withparts) {
+            return empty($formdata->hint[$number]['text']) && empty($formdata->hintclearwrong[$number]) &&
+                    empty($formdata->hintshownumcorrect[$number]);
+        } else {
+            return  empty($formdata->hint[$number]['text']);
+        }
+    }
+
+    /**
+     * Save additional question type data into the hint optional field.
+     * Overload if you use custom hint information.
+     * @param object $formdata the data from the form.
+     * @param int $number number of hint to get options from.
+     * @param bool $withparts whether question have parts.
+     * @return string value to save into the options field of question_hints table.
+     */
+    protected function save_hint_options($formdata, $number, $withparts) {
+        return null;    // By default, options field is unused.
+    }
+
     /**
      * Can be used to {@link save_question_options()} to transfer the combined
      * feedback fields from $formdata to $options.
index 842f847..58a49cb 100644 (file)
@@ -130,6 +130,7 @@ if ($type === "usercourse.png") {
 
    $graph = new graph(750, 400);
 
+   $a = new stdClass();
    $a->coursename = format_string($course->shortname, true, array('context' => $coursecontext));
    $a->username = fullname($user, true);
    $graph->parameter['title'] = get_string("hitsoncourse", "", $a);
@@ -191,6 +192,7 @@ if ($type === "usercourse.png") {
 
    $graph = new graph(750, 400);
 
+   $a = new stdClass();
    $a->coursename = format_string($course->shortname, true, array('context' => $coursecontext));
    $a->username = fullname($user, true);
    $graph->parameter['title'] = get_string("hitsoncoursetoday", "", $a);
index c8ec1e4..3931e71 100644 (file)
@@ -44,7 +44,7 @@ function report_progress_extend_navigation_course($navigation, $course, $context
     }
 
     $completion = new completion_info($course);
-    $showonnavigation = ($showonnavigation && $completion->is_enabled() && count($completion->get_activities())>0);
+    $showonnavigation = ($showonnavigation && $completion->is_enabled() && $completion->has_activities());
     if ($showonnavigation) {
         $url = new moodle_url('/report/progress/index.php', array('course'=>$course->id));
         $navigation->add(get_string('pluginname','report_progress'), $url, navigation_node::TYPE_SETTING, null, null, new pix_icon('i/report', ''));
index aba395f..8ddf895 100644 (file)
@@ -32,7 +32,7 @@
 $THEME->doctype = 'html5';
 $THEME->yuicssmodules = array();
 $THEME->name = 'bootstrapbase';
-$THEME->parents = array('');
+$THEME->parents = array();
 $THEME->sheets = array('moodle');
 $THEME->supportscssoptimisation = false;
 
@@ -52,115 +52,112 @@ $THEME->rendererfactory = 'theme_overridden_renderer_factory';
 $THEME->layouts = array(
     // Most backwards compatible layout without the blocks - this is the layout used by default.
     'base' => array(
-        'file' => 'general.php',
+        'file' => 'columns1.php',
         'regions' => array(),
     ),
     // Standard layout with blocks, this is recommended for most pages with general information.
     'standard' => array(
-        'file' => 'general.php',
+        'file' => 'columns3.php',
         'regions' => array('side-pre', 'side-post'),
         'defaultregion' => 'side-pre',
     ),
     // Main course page.
     'course' => array(
-        'file' => 'general.php',
+        'file' => 'columns3.php',
         'regions' => array('side-pre', 'side-post'),
         'defaultregion' => 'side-pre',
         'options' => array('langmenu'=>true),
     ),
     'coursecategory' => array(
-        'file' => 'general.php',
+        'file' => 'columns3.php',
         'regions' => array('side-pre', 'side-post'),
         'defaultregion' => 'side-pre',
     ),
     // part of course, typical for modules - default page layout if $cm specified in require_login()
     'incourse' => array(
-        'file' => 'general.php',
+        'file' => 'columns3.php',
         'regions' => array('side-pre', 'side-post'),
         'defaultregion' => 'side-pre',
     ),
     // The site home page.
     'frontpage' => array(
-        'file' => 'general.php',
+        'file' => 'columns3.php',
         'regions' => array('side-pre', 'side-post'),
         'defaultregion' => 'side-pre',
         'options' => array('nonavbar'=>true),
     ),
     // Server administration scripts.
     'admin' => array(
-        'file' => 'general.php',
+        'file' => 'columns2.php',
         'regions' => array('side-pre'),
         'defaultregion' => 'side-pre',
     ),
     // My dashboard page.
     'mydashboard' => array(
-        'file' => 'general.php',
+        'file' => 'columns3.php',
         'regions' => array('side-pre', 'side-post'),
         'defaultregion' => 'side-pre',
         'options' => array('langmenu'=>true),
     ),
     // My public page.
     'mypublic' => array(
-        'file' => 'general.php',
+        'file' => 'columns3.php',
         'regions' => array('side-pre', 'side-post'),
         'defaultregion' => 'side-pre',
     ),
     'login' => array(
-        'file' => 'general.php',
+        'file' => 'columns1.php',
         'regions' => array(),
         'options' => array('langmenu'=>true),
     ),
 
     // Pages that appear in pop-up windows - no navigation, no blocks, no header.
     'popup' => array(
-        'file' => 'general.php',
+        'file' => 'columns1.php',
         'regions' => array(),
-        'options' => array('nofooter'=>true, 'nonavbar'=>true, 'nocustommenu'=>true, 'nologininfo'=>true, 'nocourseheaderfooter'=>true),
+        'options' => array('nofooter'=>true, 'nonavbar'=>true),
     ),
     // No blocks and minimal footer - used for legacy frame layouts only!
     'frametop' => array(
-        'file' => 'general.php',
+        'file' => 'columns1.php',
         'regions' => array(),
         'options' => array('nofooter'=>true, 'nocoursefooter'=>true),
     ),
     // Embeded pages, like iframe/object embeded in moodleform - it needs as much space as possible
     'embedded' => array(
         'file' => 'embedded.php',
-        'regions' => array(),
-        'options' => array('nofooter'=>true, 'nonavbar'=>true, 'nocustommenu'=>true, 'nocourseheaderfooter'=>true),
+        'regions' => array()
     ),
     // Used during upgrade and install, and for the 'This site is undergoing maintenance' message.
     // This must not have any blocks, and it is good idea if it does not have links to
     // other places - for example there should not be a home link in the footer...
     'maintenance' => array(
-        'file' => 'general.php',
+        'file' => 'columns1.php',
         'regions' => array(),
-        'options' => array('noblocks'=>true, 'nofooter'=>true, 'nonavbar'=>true, 'nocustommenu'=>true, 'nocourseheaderfooter'=>true),
+        'options' => array('nofooter'=>true, 'nonavbar'=>true, 'nocoursefooter'=>true, 'nocourseheader'=>true),
     ),
     // Should display the content and basic headers only.
     'print' => array(
-        'file' => 'general.php',
+        'file' => 'columns1.php',
         'regions' => array(),
-        'options' => array('noblocks'=>true, 'nofooter'=>true, 'nonavbar'=>false, 'nocustommenu'=>true, 'nocourseheaderfooter'=>true),
+        'options' => array('nofooter'=>true, 'nonavbar'=>false),
     ),
     // The pagelayout used when a redirection is occuring.
     'redirect' => array(
         'file' => 'embedded.php',
         'regions' => array(),
-        'options' => array('nofooter'=>true, 'nonavbar'=>true, 'nocustommenu'=>true, 'nocourseheaderfooter'=>true),
     ),
     // The pagelayout used for reports.
     'report' => array(
-        'file' => 'general.php',
+        'file' => 'columns2.php',
         'regions' => array('side-pre'),
         'defaultregion' => 'side-pre',
     ),
     // The pagelayout used for safebrowser and securewindow.
     'secure' => array(
-        'file' => 'general.php',
+        'file' => 'secure.php',
         'regions' => array('side-pre', 'side-post'),
-        'defaultregion' => 'side-pre',
-        'options' => array('nofooter'=>true, 'nonavbar'=>true, 'nocustommenu'=>true, 'nologinlinks'=>true, 'nocourseheaderfooter'=>true),
+        'defaultregion' => 'side-pre'
     ),
 );
 
@@ -170,12 +167,13 @@ $THEME->javascripts_footer = array(
     'moodlebootstrap',
 );
 
-$useragent = '';
-if (!empty($_SERVER['HTTP_USER_AGENT'])) {
-    $useragent = $_SERVER['HTTP_USER_AGENT'];
-}
-if (strpos($useragent, 'MSIE 8') || strpos($useragent, 'MSIE 7')) {
+if (check_browser_version('MSIE') && !check_browser_version('MSIE', '9.0')) {
     $THEME->javascripts[] = 'html5shiv';
 }
 
 $THEME->hidefromselector = true;
+
+$THEME->blockrtlmanipulations = array(
+    'side-pre' => 'side-post',
+    'side-post' => 'side-pre'
+);
diff --git a/theme/bootstrapbase/layout/columns1.php b/theme/bootstrapbase/layout/columns1.php
new file mode 100644 (file)
index 0000000..2d3f417
--- /dev/null
@@ -0,0 +1,89 @@
+<?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/>.
+
+echo $OUTPUT->doctype() ?>
+<html <?php echo $OUTPUT->htmlattributes(); ?>>
+<head>
+    <title><?php echo $OUTPUT->page_title(); ?></title>
+    <link rel="shortcut icon" href="<?php echo $OUTPUT->favicon(); ?>" />
+    <?php echo $OUTPUT->standard_head_html() ?>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+</head>
+
+<body <?php echo $OUTPUT->body_attributes(); ?>>
+
+<?php echo $OUTPUT->standard_top_of_body_html() ?>
+
+<header role="banner" class="navbar navbar-fixed-top">
+    <nav role="navigation" class="navbar-inner">
+        <div class="container-fluid">
+            <a class="brand" href="<?php echo $CFG->wwwroot;?>"><?php echo $SITE->shortname; ?></a>
+            <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+                <span class="icon-bar"></span>
+                <span class="icon-bar"></span>
+                <span class="icon-bar"></span>
+            </a>
+            <div class="nav-collapse collapse">
+                <?php echo $OUTPUT->custom_menu(); ?>
+                <ul class="nav pull-right">
+                    <li><?php echo $OUTPUT->page_heading_menu(); ?></li>
+                    <li class="navbar-text"><?php echo $OUTPUT->login_info() ?></li>
+                </ul>
+            </div>
+        </div>
+    </nav>
+</header>
+
+<div id="page" class="container-fluid">
+
+    <header id="page-header" class="clearfix">
+        <div id="page-navbar">
+            <nav class="breadcrumb-button"><?php echo $OUTPUT->page_heading_button(); ?></nav>
+            <?php echo $OUTPUT->navbar(); ?>
+        </div>
+        <?php echo $OUTPUT->page_heading(); ?>
+        <div id="course-header">
+            <?php echo $OUTPUT->course_header(); ?>
+        </div>
+    </header>
+
+    <div id="page-content">
+        <div id="region-bs-main-and-pre">
+            <section id="region-main">
+                <?php
+                echo $OUTPUT->course_content_header();
+                echo $OUTPUT->main_content();
+                echo $OUTPUT->course_content_footer();
+                ?>
+            </section>
+        </div>
+    </div>
+
+    <footer id="page-footer">
+        <div id="course-footer"><?php echo $OUTPUT->course_footer(); ?></div>
+        <p class="helplink"><?php echo $OUTPUT->page_doc_link(); ?></p>
+        <?php
+        echo $OUTPUT->login_info();
+        echo $OUTPUT->home_link();
+        echo $OUTPUT->standard_footer_html();
+        ?>
+    </footer>
+
+    <?php echo $OUTPUT->standard_end_of_body_html() ?>
+
+</div>
+</body>
+</html>
diff --git a/theme/bootstrapbase/layout/columns2.php b/theme/bootstrapbase/layout/columns2.php
new file mode 100644 (file)
index 0000000..eba9f85
--- /dev/null
@@ -0,0 +1,100 @@
+<?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/>.
+
+echo $OUTPUT->doctype() ?>
+<html <?php echo $OUTPUT->htmlattributes(); ?>>
+<head>
+    <title><?php echo $OUTPUT->page_title(); ?></title>
+    <link rel="shortcut icon" href="<?php echo $OUTPUT->favicon(); ?>" />
+    <?php echo $OUTPUT->standard_head_html() ?>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+</head>
+
+<body <?php echo $OUTPUT->body_attributes('two-column'); ?>>
+
+<?php echo $OUTPUT->standard_top_of_body_html() ?>
+
+<header role="banner" class="navbar navbar-fixed-top">
+    <nav role="navigation" class="navbar-inner">
+        <div class="container-fluid">
+            <a class="brand" href="<?php echo $CFG->wwwroot;?>"><?php echo $SITE->shortname; ?></a>
+            <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+                <span class="icon-bar"></span>
+                <span class="icon-bar"></span>
+                <span class="icon-bar"></span>
+            </a>
+            <div class="nav-collapse collapse">
+                <?php echo $OUTPUT->custom_menu(); ?>
+                <ul class="nav pull-right">
+                    <li><?php echo $OUTPUT->page_heading_menu(); ?></li>
+                    <li class="navbar-text"><?php echo $OUTPUT->login_info() ?></li>
+                </ul>
+            </div>
+        </div>
+    </nav>
+</header>
+
+<div id="page" class="container-fluid">
+
+    <header id="page-header" class="clearfix">
+        <div id="page-navbar">
+            <nav class="breadcrumb-button"><?php echo $OUTPUT->page_heading_button(); ?></nav>
+            <?php echo $OUTPUT->navbar(); ?>
+        </div>
+        <?php echo $OUTPUT->page_heading(); ?>
+        <div id="course-header">
+            <?php echo $OUTPUT->course_header(); ?>
+        </div>
+    </header>
+
+    <div id="page-content" class="row-fluid">
+        <div id="region-bs-main-and-pre" class="span9">
+            <div class="row-fluid">
+                <section id="region-main" class="span9 pull-right">
+                    <?php
+                    echo $OUTPUT->course_content_header();
+                    echo $OUTPUT->main_content();
+                    echo $OUTPUT->course_content_footer();
+                    ?>
+                </section>
+                <?php
+                if (!right_to_left()) {
+                    echo $OUTPUT->blocks('side-pre', 'span3 desktop-first-column');
+                } ?>
+            </div>
+        </div>
+        <?php
+        if (right_to_left()) {
+            echo $OUTPUT->blocks('side-post', 'span3');
+        }
+        ?>
+    </div>
+
+    <footer id="page-footer">
+        <div id="course-footer"><?php echo $OUTPUT->course_footer(); ?></div>
+        <p class="helplink"><?php echo $OUTPUT->page_doc_link(); ?></p>
+        <?php
+        echo $OUTPUT->login_info();
+        echo $OUTPUT->home_link();
+        echo $OUTPUT->standard_footer_html();
+        ?>
+    </footer>
+
+    <?php echo $OUTPUT->standard_end_of_body_html() ?>
+
+</div>
+</body>
+</html>
diff --git a/theme/bootstrapbase/layout/columns3.php b/theme/bootstrapbase/layout/columns3.php
new file mode 100644 (file)
index 0000000..3df8db2
--- /dev/null
@@ -0,0 +1,93 @@
+<?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/>.
+
+echo $OUTPUT->doctype() ?>
+<html <?php echo $OUTPUT->htmlattributes(); ?>>
+<head>
+    <title><?php echo $OUTPUT->page_title(); ?></title>
+    <link rel="shortcut icon" href="<?php echo $OUTPUT->favicon(); ?>" />
+    <?php echo $OUTPUT->standard_head_html() ?>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+</head>
+
+<body <?php echo $OUTPUT->body_attributes(); ?>>
+
+<?php echo $OUTPUT->standard_top_of_body_html() ?>
+
+<header role="banner" class="navbar navbar-fixed-top">
+    <nav role="navigation" class="navbar-inner">
+        <div class="container-fluid">
+            <a class="brand" href="<?php echo $CFG->wwwroot;?>"><?php echo $SITE->shortname; ?></a>
+            <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+                <span class="icon-bar"></span>
+                <span class="icon-bar"></span>
+                <span class="icon-bar"></span>
+            </a>
+            <div class="nav-collapse collapse">
+                <?php echo $OUTPUT->custom_menu(); ?>
+                <ul class="nav pull-right">
+                    <li><?php echo $OUTPUT->page_heading_menu(); ?></li>
+                    <li class="navbar-text"><?php echo $OUTPUT->login_info() ?></li>
+                </ul>
+            </div>
+        </div>
+    </nav>
+</header>
+
+<div id="page" class="container-fluid">
+
+    <header id="page-header" class="clearfix">
+        <div id="page-navbar">
+            <nav class="breadcrumb-button"><?php echo $OUTPUT->page_heading_button(); ?></nav>
+            <?php echo $OUTPUT->navbar(); ?>
+        </div>
+        <?php echo $OUTPUT->page_heading(); ?>
+        <div id="course-header">
+            <?php echo $OUTPUT->course_header(); ?>
+        </div>
+    </header>
+
+    <div id="page-content" class="row-fluid">
+        <div id="region-bs-main-and-pre" class="span9">
+            <div class="row-fluid">
+                <section id="region-main" class="span8 pull-right">
+                    <?php
+                    echo $OUTPUT->course_content_header();
+                    echo $OUTPUT->main_content();
+                    echo $OUTPUT->course_content_footer();
+                    ?>
+                </section>
+                <?php echo $OUTPUT->blocks('side-pre', 'span4 desktop-first-column'); ?>
+            </div>
+        </div>
+        <?php echo $OUTPUT->blocks('side-post', 'span3'); ?>
+    </div>
+
+    <footer id="page-footer">
+        <div id="course-footer"><?php echo $OUTPUT->course_footer(); ?></div>
+        <p class="helplink"><?php echo $OUTPUT->page_doc_link(); ?></p>
+        <?php
+        echo $OUTPUT->login_info();
+        echo $OUTPUT->home_link();
+        echo $OUTPUT->standard_footer_html();
+        ?>
+    </footer>
+
+    <?php echo $OUTPUT->standard_end_of_body_html() ?>
+
+</div>
+</body>
+</html>
index a802fbb..821ae91 100644 (file)
@@ -1,23 +1,35 @@
-<?php echo $OUTPUT->doctype() ?>
-<html <?php echo $OUTPUT->htmlattributes() ?>>
+<?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/>.
+
+echo $OUTPUT->doctype() ?>
+<html <?php echo $OUTPUT->htmlattributes(); ?>>
 <head>
-    <title><?php echo $PAGE->title ?></title>
-    <link rel="shortcut icon" href="<?php echo $OUTPUT->pix_url('favicon', 'theme')?>" />
+    <title><?php echo $OUTPUT->page_title(); ?></title>
+    <link rel="shortcut icon" href="<?php echo $OUTPUT->favicon(); ?>" />
     <?php echo $OUTPUT->standard_head_html() ?>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 </head>
-<body id="<?php p($PAGE->bodyid) ?>" class="<?php p($PAGE->bodyclasses) ?>">
-<?php echo $OUTPUT->standard_top_of_body_html() ?>
 
+<body <?php echo $OUTPUT->body_attributes(); ?>>
+<?php echo $OUTPUT->standard_top_of_body_html() ?>
 <div id="page">
-
-<!-- END OF HEADER -->
-
-    <div id="content" class="clearfix">
-        <?php echo $OUTPUT->main_content() ?>
+    <div id="page-content" class="clearfix">
+        <?php echo $OUTPUT->main_content(); ?>
     </div>
-
-<!-- START OF FOOTER -->
 </div>
 <?php echo $OUTPUT->standard_end_of_body_html() ?>
 </body>
-</html>
+</html>
\ No newline at end of file
diff --git a/theme/bootstrapbase/layout/general.php b/theme/bootstrapbase/layout/general.php
deleted file mode 100644 (file)
index fe59117..0000000
+++ /dev/null
@@ -1,189 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-$hasheading = ($PAGE->heading);
-$hasnavbar = (empty($PAGE->layout_options['nonavbar']) && $PAGE->has_navbar());
-$hasfooter = (empty($PAGE->layout_options['nofooter']));
-$hasheader = (empty($PAGE->layout_options['noheader']));
-
-$hassidepre = (empty($PAGE->layout_options['noblocks']) && $PAGE->blocks->region_has_content('side-pre', $OUTPUT));
-$hassidepost = (empty($PAGE->layout_options['noblocks']) && $PAGE->blocks->region_has_content('side-post', $OUTPUT));
-
-$showsidepre = ($hassidepre && !$PAGE->blocks->region_completely_docked('side-pre', $OUTPUT));
-$showsidepost = ($hassidepost && !$PAGE->blocks->region_completely_docked('side-post', $OUTPUT));
-
-// If there can be a sidepost region on this page and we are editing, always
-// show it so blocks can be dragged into it.
-if ($PAGE->user_is_editing()) {
-    if ($PAGE->blocks->is_known_region('side-pre')) {
-        $showsidepre = true;
-    }
-    if ($PAGE->blocks->is_known_region('side-post')) {
-        $showsidepost = true;
-    }
-}
-
-$custommenu = $OUTPUT->custom_menu();
-$hascustommenu = (empty($PAGE->layout_options['nocustommenu']) && !empty($custommenu));
-
-$courseheader = $coursecontentheader = $coursecontentfooter = $coursefooter = '';
-
-if (empty($PAGE->layout_options['nocourseheaderfooter'])) {
-    $courseheader = $OUTPUT->course_header();
-    $coursecontentheader = $OUTPUT->course_content_header();
-    if (empty($PAGE->layout_options['nocoursefooter'])) {
-        $coursecontentfooter = $OUTPUT->course_content_footer();
-        $coursefooter = $OUTPUT->course_footer();
-    }
-}
-
-$layout = 'pre-and-post';
-if ($showsidepre && !$showsidepost) {
-    if (!right_to_left()) {
-        $layout = 'side-pre-only';
-    } else {
-        $layout = 'side-post-only';
-    }
-} else if ($showsidepost && !$showsidepre) {
-    if (!right_to_left()) {
-        $layout = 'side-post-only';
-    } else {
-        $layout = 'side-pre-only';
-    }
-} else if (!$showsidepost && !$showsidepre) {
-    $layout = 'content-only';
-}
-$bodyclasses[] = $layout;
-
-echo $OUTPUT->doctype() ?>
-<html <?php echo $OUTPUT->htmlattributes() ?>>
-<head>
-    <title><?php echo $PAGE->title ?></title>
-    <link rel="shortcut icon" href="<?php echo $OUTPUT->pix_url('favicon', 'theme')?>" />
-    <?php echo $OUTPUT->standard_head_html() ?>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-</head>
-
-<body id="<?php p($PAGE->bodyid) ?>" class="<?php p($PAGE->bodyclasses.' '.join($bodyclasses)) ?>">
-
-<?php echo $OUTPUT->standard_top_of_body_html() ?>
-
-<header role="banner" class="navbar navbar-fixed-top">
-    <nav role="navigation" class="navbar-inner">
-        <div class="container-fluid">
-            <a class="brand" href="<?php echo $CFG->wwwroot;?>"><?php echo $SITE->shortname; ?></a>
-            <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
-                <span class="icon-bar"></span>
-                <span class="icon-bar"></span>
-                <span class="icon-bar"></span>
-            </a>
-            <div class="nav-collapse collapse">
-            <?php if ($hascustommenu) {
-                echo $custommenu;
-            } ?>
-            <ul class="nav pull-right">
-            <li><?php echo $PAGE->headingmenu ?></li>
-            <li class="navbar-text"><?php echo $OUTPUT->login_info() ?></li>
-            </ul>
-            </div>
-        </div>
-    </nav>
-</header>
-
-<div id="page" class="container-fluid">
-
-<?php if ($hasheader) { ?>
-<header id="page-header" class="clearfix">
-    <?php if ($hasnavbar) { ?>
-        <nav class="breadcrumb-button"><?php echo $PAGE->button; ?></nav>
-        <?php echo $OUTPUT->navbar(); ?>
-    <?php } ?>
-    <h1><?php echo $PAGE->heading ?></h1>
-
-    <?php if (!empty($courseheader)) { ?>
-        <div id="course-header"><?php echo $courseheader; ?></div>
-    <?php } ?>
-</header>
-<?php } ?>
-
-<div id="page-content" class="row-fluid">
-
-<?php if ($layout === 'pre-and-post') { ?>
-    <div id="region-bs-main-and-pre" class="span9">
-    <div class="row-fluid">
-    <section id="region-main" class="span8 pull-right">
-<?php } else if ($layout === 'side-post-only') { ?>
-    <section id="region-main" class="span9">
-<?php } else if ($layout === 'side-pre-only') { ?>
-    <section id="region-main" class="span9 pull-right">
-<?php } else if ($layout === 'content-only') { ?>
-    <section id="region-main" class="span12">
-<?php } ?>
-
-
-    <?php echo $coursecontentheader; ?>
-    <?php echo $OUTPUT->main_content() ?>
-    <?php echo $coursecontentfooter; ?>
-    </section>
-
-
-<?php if ($layout !== 'content-only') {
-          if ($layout === 'pre-and-post') { ?>
-            <aside class="span4 desktop-first-column">
-    <?php } else if ($layout === 'side-pre-only') { ?>
-            <aside class="span3 desktop-first-column">
-    <?php } ?>
-          <div id="region-pre" class="block-region">
-          <div class="region-content">
-          <?php
-                if (!right_to_left()) {
-                    echo $OUTPUT->blocks_for_region('side-pre');
-                } else if ($hassidepost) {
-                    echo $OUTPUT->blocks_for_region('side-post');
-                } ?>
-          </div>
-          </div>
-          </aside>
-    <?php if ($layout === 'pre-and-post') {
-          ?></div></div><?php // Close row-fluid and span9.
-   }
-
-    if ($layout === 'side-post-only' OR $layout === 'pre-and-post') { ?>
-        <aside class="span3">
-        <div id="region-post" class="block-region">
-        <div class="region-content">
-        <?php if (!right_to_left()) {
-                  echo $OUTPUT->blocks_for_region('side-post');
-              } else {
-                  echo $OUTPUT->blocks_for_region('side-pre');
-              } ?>
-        </div>
-        </div>
-        </aside>
-    <?php } ?>
-<?php } ?>
-</div>
-
-<footer id="page-footer">
-    <p class="helplink"><?php echo page_doc_link(get_string('moodledocslink')) ?></p>
-    <?php echo $OUTPUT->standard_footer_html(); ?>
-</footer>
-
-<?php echo $OUTPUT->standard_end_of_body_html() ?>
-
-</div>
-</body>
-</html>
diff --git a/theme/bootstrapbase/layout/secure.php b/theme/bootstrapbase/layout/secure.php
new file mode 100644 (file)
index 0000000..eb3746b
--- /dev/null
@@ -0,0 +1,70 @@
+<?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/>.
+
+echo $OUTPUT->doctype() ?>
+<html <?php echo $OUTPUT->htmlattributes(); ?>>
+<head>
+    <title><?php echo $OUTPUT->page_title(); ?></title>
+    <link rel="shortcut icon" href="<?php echo $OUTPUT->favicon(); ?>" />
+    <?php echo $OUTPUT->standard_head_html() ?>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+</head>
+
+<body <?php echo $OUTPUT->body_attributes(); ?>>
+
+<?php echo $OUTPUT->standard_top_of_body_html() ?>
+
+<header role="banner" class="navbar navbar-fixed-top">
+    <nav role="navigation" class="navbar-inner">
+        <div class="container-fluid">
+            <a class="brand" href="<?php echo $CFG->wwwroot;?>"><?php echo $SITE->shortname; ?></a>
+            <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+                <span class="icon-bar"></span>
+                <span class="icon-bar"></span>
+                <span class="icon-bar"></span>
+            </a>
+            <div class="nav-collapse collapse">
+                <ul class="nav pull-right">
+                    <li><?php echo $OUTPUT->page_heading_menu(); ?></li>
+                </ul>
+            </div>
+        </div>
+    </nav>
+</header>
+
+<div id="page" class="container-fluid">
+
+    <header id="page-header" class="clearfix">
+        <?php echo $OUTPUT->page_heading(); ?>
+    </header>
+
+    <div id="page-content" class="row-fluid">
+        <div id="region-bs-main-and-pre" class="span9">
+            <div class="row-fluid">
+                <section id="region-main" class="span8 pull-right">
+                    <?php echo $OUTPUT->main_content(); ?>
+                </section>
+                <?php echo $OUTPUT->blocks('side-pre', 'span4 desktop-first-column'); ?>
+            </div>
+        </div>
+        <?php echo $OUTPUT->blocks('side-post', 'span3'); ?>
+    </div>
+
+    <?php echo $OUTPUT->standard_end_of_body_html() ?>
+
+</div>
+</body>
+</html>
\ No newline at end of file
index 4ed8537..0384629 100644 (file)
@@ -1,3 +1,6 @@
+// Import the bootstrap variables.
+@import "bootstrap/variables.less";
+
 // Old Moodle stuff from base theme.
 // Massive, needs broken up.
 @import "moodle/core";
@@ -21,8 +24,9 @@
 // Increase form label width.
 @horizontalComponentOffset: 200px;
 // On Wider screens.
-@horizontalComponentOffset768: 220px;
-@horizontalComponentOffset980: 265px;
+@horizontalComponentOffset980: 220px;
+@horizontalComponentOffset1200: 265px;
+
 
 // Roll back nameclashes.
 @import "moodle/undo";
index d33480d..7b34db3 100644 (file)
@@ -1,4 +1,45 @@
 /* core.less */
+
+/** Page layout CSS starts **/
+.layout-option-noheader #page-header,
+.layout-option-nonavbar #page-navbar,
+.layout-option-nofooter #page-footer,
+.layout-option-nocourseheader .course-content-header,
+.layout-option-nocoursefooter .course-content-footer {
+    display:none;
+}
+
+.empty-region-side-pre #block-region-side-pre,
+.empty-region-side-post #block-region-side-post {
+    display:none;
+}
+
+.dir-ltr.two-column #region-bs-main-and-pre.span9,
+.dir-rtl.two-column #region-main.span9,
+.empty-region-side-post #region-bs-main-and-pre.span9 {
+    width:100%;
+}
+
+.empty-region-side-pre #region-main {
+    float:none;
+    width:100%;
+}
+
+.fluid-span (@columns) {
+    width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1));
+    *width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%);
+}
+
+.empty-region-side-post.used-region-side-pre #region-main.span8 {
+    /** increase the span size by 1 **/
+    .fluid-span(9);
+}
+.empty-region-side-post.used-region-side-pre #block-region-side-pre.span4 {
+    /** decrease the span size by 1 **/
+    .fluid-span(3);
+}
+/** Page layout CSS ends **/
+
 .dir-ltr,
 .mdl-left,
 .dir-rtl .mdl-right {
@@ -1845,3 +1886,9 @@ div.badge .expireimage {
 .addcourse {
     float: right;
 }
+.invisiblefieldset {
+    display: inline;
+    margin: 0;
+    padding: 0;
+    border-width: 0;
+}
index d142f3b..5904827 100644 (file)
@@ -813,6 +813,7 @@ a.ygtvspacer:hover {
 .filemanager-toolbar{
     padding: 5px 8px;
     min-height: 22px;
+    overflow: hidden;
 }
 .fp-pathbar {
     border-top: 1px solid #BBBBBB;
index d5bc457..0b1c034 100644 (file)
@@ -82,13 +82,6 @@ select {
     float: right;
 }
 
-.navbar .logininfo a {
-    color: @navbarLinkColor;
-}
-.navbar .logininfo a:hover {
-    background-color: @navbarLinkBackgroundHover; // "transparent" is default to differentiate :hover/:focus from .active
-    color: @navbarLinkColorHover;
-}
 .navbar-inverse .logininfo a {
     color: @navbarInverseLinkColor;
 }
@@ -99,7 +92,7 @@ select {
 
 .navbar-fixed-top,
 .navbar-fixed-bottom {
-        z-index: 4030;
+    z-index: 4030;
 }
 
 .dir-rtl .breadcrumb-button,
@@ -108,7 +101,7 @@ select {
 }
 
 .ie .row-fluid .desktop-first-column {
-      margin-left: 0;
+    margin-left: 0;
 }
 .langmenu form {
     margin: 0;
index ba341ee..939a174 100644 (file)
 }
 .que .formulation,
 .que .outcome,
-.que .comment,
-.que .history {
+.que .comment {
     .alert
 }
 .que .formulation {
 .formulation select {
     width: auto;
 }
-
 .path-mod-quiz input[size] {
     width: auto;
 }
     .alert-success;
 }
 .que .history {
-    color: @textColor;
-    background: @grayLighter;
+    .well
 }
 .que .ablock {
     margin: 0.7em 0 0.3em 0;
index 9971e2f..578b5a0 100644 (file)
@@ -14,6 +14,9 @@
             float: right;
         }
     }
+}
+
+@media (min-width: 980px) and (max-width: 1199px) {
     // Wider form labels.
     .form-item .form-label,
     .mform .fitem div.fitemtitle,
         width: 48.717948717948715%;
        *width: 48.664757228587014%;
    }
+    // Wider form labels.
+    .form-item .form-label,
+    .mform .fitem div.fitemtitle,
+    .userprofile dl.list dt,
+    .form-horizontal .control-label {
+        width: @horizontalComponentOffset1200 - 20px;
+    }
+    .form-item .form-setting,
+    .form-item .form-description,
+    .mform .fitem .felement,
+    #page-mod-forum-search .c1,
+    .mform .fdescription.required,
+    .userprofile dl.list dd,
+    .form-horizontal .controls {
+        margin-left: @horizontalComponentOffset1200;
+    }
+    .path-admin .buttons,
+    .form-buttons {
+        padding-left: @horizontalComponentOffset1200;
+    }
+
 }
 
 @media (min-width: 980px) {
     .path-mod-forum .forumsearch #search {
         width: 120px;
     }
+    .path-mod-forum .forumheaderlist .picture {
+        display: none;
+    }
 }
 
 // Stuart's 2,1,3 layout
         padding-left: 0;
         padding-right: 0;
     }
-    // Wider form labels.
-    .form-item .form-label,
-    .mform .fitem div.fitemtitle,
-    .userprofile dl.list dt,
-    .form-horizontal .control-label {
-        width: @horizontalComponentOffset768 - 20px;
-    }
-    .form-item .form-setting,
-    .form-item .form-description,
-    .mform .fitem .felement,
-    #page-mod-forum-search .c1,
-    .mform .fdescription.required,
-    .userprofile dl.list dd,
-    .form-horizontal .controls {
-        margin-left: @horizontalComponentOffset768;
-    }
-    .path-admin .buttons,
-    .form-buttons {
-        padding-left: @horizontalComponentOffset768;
-    }
 }
 
 @media (max-width: 979px) {
index 0b0bdcc..192af4c 100644 (file)
@@ -138,8 +138,8 @@ class theme_bootstrapbase_core_renderer extends core_renderer {
             } else {
                 $url = '#cm_submenu_'.$submenucount;
             }
-            $content .= html_writer::start_tag('a', array('href'=>$url, 'class'=>'dropdown-toggle', 'data-toggle'=>'dropdown'));
-            $content .= $menunode->get_title();
+            $content .= html_writer::start_tag('a', array('href'=>$url, 'class'=>'dropdown-toggle', 'data-toggle'=>'dropdown', 'title'=>$menunode->get_title()));
+            $content .= $menunode->get_text();
             if ($level == 1) {
                 $content .= '<b class="caret"></b>';
             }
index fb9fb58..8f5d1ce 100644 (file)
@@ -1,4 +1,4 @@
-.dir-ltr,.mdl-left,.dir-rtl .mdl-right{text-align:left}.dir-rtl,.mdl-right,.dir-rtl .mdl-left{text-align:right}#add,#remove,.centerpara,.mdl-align{text-align:center}a.dimmed,a.dimmed:link,a.dimmed:visited,a.dimmed_text,a.dimmed_text:link,a.dimmed_text:visited,.dimmed_text,.dimmed_text a,.dimmed_text a:link,.dimmed_text a:visited,.usersuspended,.usersuspended a,.usersuspended a:link,.usersuspended a:visited,.dimmed_category,.dimmed_category a{color:#999}.activity.label .dimmed_text{opacity:.5;filter:alpha(opacity=50)}.unlist,.unlist li,.inline-list,.inline-list li,.block .list,.block .list li,.section li.activity,.section li.movehere,.tabtree li{padding:0;margin:0;list-style:none}.inline,.inline-list li{display:inline}.notifytiny{font-size:10.5px}.notifytiny li,.notifytiny td{font-size:100%}.red,.notifyproblem{color:#b94a48}.green,.notifysuccess{color:#468847}.reportlink{text-align:right}a.autolink.glossary:hover{cursor:help}.collapsibleregioncaption{white-space:nowrap}.collapsibleregioncaption img{vertical-align:middle}.jsenabled .hiddenifjs{display:none}.visibleifjs{display:none}.jsenabled .visibleifjs{display:inline}.jsenabled .collapsibleregion{overflow:hidden}.jsenabled .collapsed .collapsibleregioninner{visibility:hidden}.yui-overlay .yui-widget-bd{position:relative;top:0;left:0;z-index:1;padding:2px 5px;color:#000;background-color:#ffee69;border:1px solid #a6982b;border-top-color:#d4c237}.clearer{display:block;height:1px;padding:0;margin:0;clear:both;background:transparent;border-width:0}.bold,.warning,.errorbox .title,.pagingbar .title,.pagingbar .thispage,.headingblock{font-weight:bold}img.resize{width:1em;height:1em}.block img.resize,.breadcrumb img.resize{width:.8em;height:.9em}img.icon{width:16px;height:16px;padding-right:6px;vertical-align:text-bottom}.dir-rtl img.icon{padding-right:0;padding-left:6px}img.iconsmall{width:12px;height:12px;margin-right:3px;vertical-align:middle}img.iconhelp,.helplink img{width:16px;height:16px;padding-left:3px;vertical-align:text-bottom}.dir-rtl img.iconhelp,.dir-rtl .helplink img{padding-right:3px;padding-left:0}img.iconlarge{width:24px;height:24px;vertical-align:middle}img.iconsort{padding-left:.3em;margin-bottom:.15em;vertical-align:text-bottom}.dir-rtl img.iconsort{padding-right:.3em;padding-left:0}img.icontoggle{width:50px;height:17px;vertical-align:middle}img.iconkbhelp{width:49px;height:17px}img.icon-pre,.dir-rtl img.icon-post{padding-right:3px;padding-left:0}img.icon-post,.dir-rtl img.icon-pre{padding-right:0;p