Merge branch 'MDL-15377-master' of git://github.com/raymanuk/moodle
authorSam Hemelryk <sam@moodle.com>
Mon, 12 Sep 2011 01:42:28 +0000 (13:42 +1200)
committerSam Hemelryk <sam@moodle.com>
Mon, 12 Sep 2011 01:42:28 +0000 (13:42 +1200)
80 files changed:
admin/blocks.php
admin/localplugins.php
admin/registration/forms.php
admin/settings/courses.php
auth/ldap/README-LDAP
backup/controller/backup_controller.class.php
backup/converter/moodle1/handlerlib.php
backup/moodle2/backup_stepslib.php
backup/moodle2/restore_stepslib.php
backup/util/checks/backup_check.class.php
backup/util/checks/restore_check.class.php
backup/util/dbops/backup_controller_dbops.class.php
backup/util/ui/import_extensions.php
backup/util/ui/restore_ui_components.php
blocks/activity_modules/block_activity_modules.php
blocks/community/forms.php
blocks/community/lang/en/block_community.php
blocks/html/edit_form.php
course/edit_form.php
course/editcategory_form.php
course/lib.php
course/modduplicate.php
course/modedit.php
course/moodleform_mod.php
course/publish/forms.php
lang/en/moodle.php
lib/blocklib.php
lib/db/access.php
lib/db/install.xml
lib/db/upgrade.php
lib/dml/moodle_database.php
lib/dml/oci_native_moodle_database.php
lib/dml/simpletest/testdml.php
lib/editor/tinymce/lib.php
lib/editor/tinymce/module.js
lib/filterlib.php
lib/form/editor.php
lib/formslib.php
lib/gradelib.php
lib/modinfolib.php
lib/moodlelib.php
lib/outputcomponents.php
lib/outputlib.php
lib/outputrenderers.php
lib/questionlib.php
lib/simpletest/testtextlib.php
lib/textlib.class.php
mod/assignment/lib.php
mod/chat/gui_ajax/theme/compact/input.png [new file with mode: 0644]
mod/chat/lib.php
mod/choice/lib.php
mod/data/lib.php
mod/feedback/lib.php
mod/folder/lib.php
mod/forum/lib.php
mod/glossary/lib.php
mod/imscp/lib.php
mod/imscp/locallib.php
mod/lesson/report.php
mod/page/lib.php
mod/quiz/lib.php
mod/resource/lib.php
mod/scorm/lib.php
mod/survey/lib.php
mod/upgrade.txt
mod/url/lib.php
mod/wiki/admin.php [new file with mode: 0644]
mod/wiki/lang/en/wiki.php
mod/wiki/lib.php
mod/wiki/locallib.php
mod/wiki/module.js
mod/wiki/pagelib.php
mod/wiki/renderer.php
mod/workshop/lib.php
pluginfile.php
question/engine/bank.php
theme/formal_white/style/pagelayout.css
user/editlib.php
user/selector/lib.php
version.php

index 55170d2..8f4fdc9 100644 (file)
         $tablerows[] = array(strip_tags($strblockname), $row); // first element will be used for sorting
     }
 
-    textlib_get_instance()->asort($tablerows);
+    collatorlib::asort($tablerows);
     foreach ($tablerows as $row) {
         $table->add_data($row[1]);
     }
index ea95388..674138b 100644 (file)
@@ -89,7 +89,7 @@ foreach (get_plugin_list('local') as $plugin => $plugindir) {
     }
     $plugins[$plugin] = $strpluginname;
 }
-textlib_get_instance()->asort($plugins);
+collatorlib::asort($plugins);
 
 foreach ($plugins as $plugin => $name) {
     $delete = new moodle_url($PAGE->url, array('delete' => $plugin, 'sesskey' => sesskey()));
index 23634f5..30ec47c 100644 (file)
@@ -283,7 +283,7 @@ class site_registration_form extends moodleform {
         $mform->addHelpButton('urlstring', 'siteurl', 'hub');
 
         $languages = get_string_manager()->get_list_of_languages();
-        textlib_get_instance()->asort($languages);
+        collatorlib::asort($languages);
         $mform->addElement('select', 'language', get_string('sitelang', 'hub'),
                 $languages);
         $mform->setType('language', PARAM_ALPHANUMEXT);
index 45b1ab3..f94775c 100644 (file)
@@ -142,7 +142,7 @@ if ($hassiteconfig
 
 
     $temp->add(new admin_setting_heading('automatedsettings', get_string('automatedsettings','backup'), ''));
-    $temp->add(new admin_setting_configcheckbox('backup/backup_auto_users', get_string('users'), get_string('backupusershelp'), 1));
+    $temp->add(new admin_setting_configcheckbox('backup/backup_auto_users', get_string('generalusers', 'backup'), get_string('configgeneralusers', 'backup'), 1));
     $temp->add(new admin_setting_configcheckbox('backup/backup_auto_role_assignments', get_string('generalroleassignments','backup'), get_string('configgeneralroleassignments','backup'), 1));
     $temp->add(new admin_setting_configcheckbox('backup/backup_auto_user_files', get_string('generaluserfiles', 'backup'), get_string('configgeneraluserfiles','backup'), 1));
     $temp->add(new admin_setting_configcheckbox('backup/backup_auto_activities', get_string('generalactivities','backup'), get_string('configgeneralactivities','backup'), 1));
@@ -150,7 +150,7 @@ if ($hassiteconfig
     $temp->add(new admin_setting_configcheckbox('backup/backup_auto_filters', get_string('generalfilters','backup'), get_string('configgeneralfilters','backup'), 1));
     $temp->add(new admin_setting_configcheckbox('backup/backup_auto_comments', get_string('generalcomments','backup'), get_string('configgeneralcomments','backup'), 1));
     $temp->add(new admin_setting_configcheckbox('backup/backup_auto_userscompletion', get_string('generaluserscompletion','backup'), get_string('configgeneraluserscompletion','backup'), 1));
-    $temp->add(new admin_setting_configcheckbox('backup/backup_auto_logs', get_string('logs'), get_string('backuplogshelp'), 0));
+    $temp->add(new admin_setting_configcheckbox('backup/backup_auto_logs', get_string('generallogs', 'backup'), get_string('configgenerallogs', 'backup'), 0));
     $temp->add(new admin_setting_configcheckbox('backup/backup_auto_histories', get_string('generalhistories','backup'), get_string('configgeneralhistories','backup'), 0));
 
 
index 11efdf5..fc8f28a 100644 (file)
@@ -1,3 +1,3 @@
 LDAP-module README
 
-Please read comments from lib.php
+Please read comments from auth.php in this directory.
index cd05481..1bab62b 100644 (file)
@@ -339,7 +339,7 @@ class backup_controller extends backup implements loggable {
 
     protected function apply_defaults() {
         $this->log('applying plan defaults', backup::LOG_DEBUG);
-        backup_controller_dbops::apply_general_config_defaults($this);
+        backup_controller_dbops::apply_config_defaults($this);
         $this->set_status(backup::STATUS_CONFIGURED);
     }
 }
index dc10857..1b38074 100644 (file)
@@ -800,6 +800,7 @@ class moodle1_course_outline_handler extends moodle1_xml_handler {
                         'showavailability'          => 0,
                         'availability_info'         => array(),
                         'visibleold'                => 1,
+                        'showdescription'           => 0,
                     ),
                     'dropfields' => array(
                         'instance',
index d1b5d24..987bb9f 100644 (file)
@@ -310,7 +310,7 @@ class backup_module_structure_step extends backup_structure_step {
             'added', 'score', 'indent', 'visible',
             'visibleold', 'groupmode', 'groupingid', 'groupmembersonly',
             'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected',
-            'availablefrom', 'availableuntil', 'showavailability'));
+            'availablefrom', 'availableuntil', 'showavailability', 'showdescription'));
 
         $availinfo = new backup_nested_element('availability_info');
         $availability = new backup_nested_element('availability', array('id'), array(
index 0b02c01..0dfc5a0 100644 (file)
@@ -2164,6 +2164,12 @@ class restore_module_structure_step extends restore_structure_step {
             $data->availablefrom = $this->apply_date_offset($data->availablefrom);
             $data->availableuntil= $this->apply_date_offset($data->availableuntil);
         }
+        // Backups that did not include showdescription, set it to default 0
+        // (this is not totally necessary as it has a db default, but just to
+        // be explicit).
+        if (!isset($data->showdescription)) {
+            $data->showdescription = 0;
+        }
         $data->instance = 0; // Set to 0 for now, going to create it soon (next step)
 
         // course_module record ready, insert it
index 39d0e96..881c73c 100644 (file)
@@ -102,45 +102,31 @@ abstract class backup_check {
         // Note: all the checks along the function MUST be performed for $userid, that
         // is the user who "requested" the course backup, not current $USER at all!!
 
-        // First of all, check the main backup[course|section|activity] principal caps
-        // Lacking the corresponding one makes this to break with exception always
+        // First of all, decide which caps/contexts are we going to check
+        // for common backups (general, automated...) based exclusively
+        // in the type (course, section, activity). And store them into
+        // one capability => context array structure
+        $typecapstocheck = array();
         switch ($type) {
             case backup::TYPE_1COURSE :
                 $DB->get_record('course', array('id' => $id), '*', MUST_EXIST); // course exists
-                if (!has_capability('moodle/backup:backupcourse', $coursectx, $userid)) {
-                    $a = new stdclass();
-                    $a->userid = $userid;
-                    $a->courseid = $courseid;
-                    $a->capability = 'moodle/backup:backupcourse';
-                    throw new backup_controller_exception('backup_user_missing_capability', $a);
-                }
+                $typecapstocheck['moodle/backup:backupcourse'] = $coursectx;
                 break;
             case backup::TYPE_1SECTION :
                 $DB->get_record('course_sections', array('course' => $courseid, 'id' => $id), '*', MUST_EXIST); // sec exists
-                if (!has_capability('moodle/backup:backupsection', $coursectx, $userid)) {
-                    $a = new stdclass();
-                    $a->userid = $userid;
-                    $a->courseid = $courseid;
-                    $a->capability = 'moodle/backup:backupsection';
-                    throw new backup_controller_exception('backup_user_missing_capability', $a);
-                }
+                $typecapstocheck['moodle/backup:backupsection'] = $coursectx;
                 break;
             case backup::TYPE_1ACTIVITY :
                 get_coursemodule_from_id(null, $id, $courseid, false, MUST_EXIST); // cm exists
                 $modulectx = get_context_instance(CONTEXT_MODULE, $id);
-                if (!has_capability('moodle/backup:backupactivity', $modulectx, $userid)) {
-                    $a = new stdclass();
-                    $a->userid = $userid;
-                    $a->cmid = $id;
-                    $a->capability = 'moodle/backup:backupactivity';
-                    throw new backup_controller_exception('backup_user_missing_capability', $a);
-                }
+                $typecapstocheck['moodle/backup:backupactivity'] = $modulectx;
                 break;
             default :
-                print_error('unknownbackuptype');
+                throw new backup_controller_exception('backup_unknown_backup_type', $type);
         }
 
         // Now, if backup mode is hub or import, check userid has permissions for those modes
+        // other modes will perform common checks only (backupxxxx capabilities in $typecapstocheck)
         switch ($mode) {
             case backup::MODE_HUB:
                 if (!has_capability('moodle/backup:backuptargethub', $coursectx, $userid)) {
@@ -160,6 +146,18 @@ abstract class backup_check {
                     throw new backup_controller_exception('backup_user_missing_capability', $a);
                 }
                 break;
+            // Common backup (general, automated...), let's check all the $typecapstocheck
+            // capability => context pairs
+            default:
+                foreach ($typecapstocheck as $capability => $context) {
+                    if (!has_capability($capability, $context, $userid)) {
+                        $a = new stdclass();
+                        $a->userid = $userid;
+                        $a->courseid = $courseid;
+                        $a->capability = $capability;
+                        throw new backup_controller_exception('backup_user_missing_capability', $a);
+                    }
+                }
         }
 
         // Now, enforce 'moodle/backup:userinfo' to 'users' setting, applying changes if allowed,
@@ -219,15 +217,19 @@ abstract class backup_check {
         }
 
         // Check the user has the ability to configure the backup. If not then we need
-        // to lock all settings by permission so that no changes can be made.
-        $hasconfigcap = has_capability('moodle/backup:configure', $coursectx, $userid);
-        if (!$hasconfigcap) {
-            $settings = $backup_controller->get_plan()->get_settings();
-            foreach ($settings as $setting) {
-                if ($setting->get_name()=='filename') {
-                    continue;
+        // to lock all settings by permission so that no changes can be made. This does
+        // not apply to the import facility, where the activities must be always enabled
+        // to be able to pick them
+        if ($mode != backup::MODE_IMPORT) {
+            $hasconfigcap = has_capability('moodle/backup:configure', $coursectx, $userid);
+            if (!$hasconfigcap) {
+                $settings = $backup_controller->get_plan()->get_settings();
+                foreach ($settings as $setting) {
+                    if ($setting->get_name() == 'filename') {
+                        continue;
+                    }
+                    $setting->set_status(base_setting::LOCKED_BY_PERMISSION);
                 }
-                $setting->set_status(base_setting::LOCKED_BY_PERMISSION);
             }
         }
 
index 20e1f94..915112c 100644 (file)
@@ -68,41 +68,27 @@ abstract class restore_check {
         // Note: all the checks along the function MUST be performed for $userid, that
         // is the user who "requested" the course restore, not current $USER at all!!
 
-        // First of all, check the main restore[course|section|activity] principal caps
-        // Lacking the corresponding one makes this to break with exception always
+        // First of all, decide which caps/contexts are we going to check
+        // for common backups (general, automated...) based exclusively
+        // in the type (course, section, activity). And store them into
+        // one capability => context array structure
+        $typecapstocheck = array();
         switch ($type) {
             case backup::TYPE_1COURSE :
-                if (!has_capability('moodle/restore:restorecourse', $coursectx, $userid)) {
-                    $a = new stdclass();
-                    $a->userid = $userid;
-                    $a->courseid = $courseid;
-                    $a->capability = 'moodle/restore:restorecourse';
-                    throw new restore_controller_exception('restore_user_missing_capability', $a);
-                }
+                $typecapstocheck['moodle/restore:restorecourse'] = $coursectx;
                 break;
             case backup::TYPE_1SECTION :
-                if (!has_capability('moodle/restore:restoresection', $coursectx, $userid)) {
-                    $a = new stdclass();
-                    $a->userid = $userid;
-                    $a->courseid = $courseid;
-                    $a->capability = 'moodle/restore:restoresection';
-                    throw new restore_controller_exception('restore_user_missing_capability', $a);
-                }
+                $typecapstocheck['moodle/restore:restoresection'] = $coursectx;
                 break;
             case backup::TYPE_1ACTIVITY :
-                if (!has_capability('moodle/restore:restoreactivity', $coursectx, $userid)) {
-                    $a = new stdclass();
-                    $a->userid = $userid;
-                    $a->courseid = $courseid;
-                    $a->capability = 'moodle/restore:restoreactivity';
-                    throw new restore_controller_exception('restore_user_missing_capability', $a);
-                }
+                $typecapstocheck['moodle/restore:restoreactivity'] = $coursectx;
                 break;
             default :
-                print_error('unknownrestoretype');
+                throw new restore_controller_exception('restore_unknown_restore_type', $type);
         }
 
         // Now, if restore mode is hub or import, check userid has permissions for those modes
+        // other modes will perform common checks only (restorexxxx capabilities in $typecapstocheck)
         switch ($mode) {
             case backup::MODE_HUB:
                 if (!has_capability('moodle/restore:restoretargethub', $coursectx, $userid)) {
@@ -122,6 +108,18 @@ abstract class restore_check {
                     throw new restore_controller_exception('restore_user_missing_capability', $a);
                 }
                 break;
+            // Common backup (general, automated...), let's check all the $typecapstocheck
+            // capability => context pairs
+            default:
+                foreach ($typecapstocheck as $capability => $context) {
+                    if (!has_capability($capability, $context, $userid)) {
+                        $a = new stdclass();
+                        $a->userid = $userid;
+                        $a->courseid = $courseid;
+                        $a->capability = $capability;
+                        throw new restore_controller_exception('restore_user_missing_capability', $a);
+                    }
+                }
         }
 
         // Now, enforce 'moodle/restore:userinfo' to 'users' setting, applying changes if allowed,
@@ -158,12 +156,16 @@ abstract class restore_check {
         }
 
         // Check the user has the ability to configure the restore. If not then we need
-        // to lock all settings by permission so that no changes can be made.
-        $hasconfigcap = has_capability('moodle/restore:configure', $coursectx, $userid);
-        if (!$hasconfigcap) {
-            $settings = $restore_controller->get_plan()->get_settings();
-            foreach ($settings as $setting) {
-                $setting->set_status(base_setting::LOCKED_BY_PERMISSION);
+        // to lock all settings by permission so that no changes can be made. This does
+        // not apply to the import facility, where all the activities (picked on backup)
+        // are restored automatically without restore UI
+        if ($mode != backup::MODE_IMPORT) {
+            $hasconfigcap = has_capability('moodle/restore:configure', $coursectx, $userid);
+            if (!$hasconfigcap) {
+                $settings = $restore_controller->get_plan()->get_settings();
+                foreach ($settings as $setting) {
+                    $setting->set_status(base_setting::LOCKED_BY_PERMISSION);
+                }
             }
         }
 
index d760871..cd89d82 100644 (file)
@@ -401,12 +401,40 @@ abstract class backup_controller_dbops extends backup_dbops {
         return $DB->get_record('course', array('id' => $courseid), 'fullname, shortname, startdate');
     }
 
+    /**
+     * Sets the default values for the settings in a backup operation
+     *
+     * Based on the mode of the backup it will delegate the process to
+     * other methods like {@link apply_general_config_defaults} ...
+     * to get proper defaults loaded
+     *
+     * @param backup_controller $controller
+     */
+    public static function apply_config_defaults(backup_controller $controller) {
+        // Based on the mode of the backup (general, automated, import, hub...)
+        // decide the action to perform to get defaults loaded
+        $mode = $controller->get_mode();
+
+        switch ($mode) {
+            case backup::MODE_GENERAL:
+                // Load the general defaults
+                self::apply_general_config_defaults($controller);
+                break;
+            case backup::MODE_AUTOMATED:
+                // TODO: Move the loading from automatic stuff to here
+                break;
+            default:
+                // Nothing to do for other modes (IMPORT/HUB...). Some day we
+                // can define defaults (admin UI...) for them if we want to
+        }
+    }
+
     /**
      * Sets the controller settings default values from the backup config.
      *
      * @param backup_controller $controller
      */
-    public static function apply_general_config_defaults(backup_controller $controller) {
+    private static function apply_general_config_defaults(backup_controller $controller) {
         $settings = array(
             // Config name                      => Setting name
             'backup_general_users'              => 'users',
index b4f9068..a389b93 100644 (file)
@@ -158,4 +158,13 @@ class import_ui_stage_final extends backup_ui_stage_final {}
 /**
  * Extends the restore course search to search for import courses.
  */
-class import_course_search extends restore_course_search {}
\ No newline at end of file
+class import_course_search extends restore_course_search {
+    /**
+     * Sets up any access restrictions for the courses to be displayed in the search.
+     *
+     * This will typically call $this->require_capability().
+     */
+    protected function setup_restrictions() {
+        $this->require_capability('moodle/backup:backuptargetimport');
+    }
+}
index 5daf231..adae38a 100644 (file)
@@ -226,10 +226,18 @@ class restore_course_search extends restore_search_base {
      */
     public function __construct(array $config=array(), $currentcouseid = null) {
         parent::__construct($config);
-        $this->require_capability('moodle/restore:restorecourse');
+        $this->setup_restrictions();
         $this->currentcourseid = $currentcouseid;
         $this->includecurrentcourse = false;
     }
+    /**
+     * Sets up any access restrictions for the courses to be displayed in the search.
+     *
+     * This will typically call $this->require_capability().
+     */
+    protected function setup_restrictions() {
+        $this->require_capability('moodle/restore:restorecourse');
+    }
     /**
      *
      * @global moodle_database $DB
index c44ceb4..c290b24 100644 (file)
@@ -46,7 +46,7 @@ class block_activity_modules extends block_list {
             }
         }
 
-        textlib_get_instance()->asort($modfullnames);
+        collatorlib::asort($modfullnames);
 
         foreach ($modfullnames as $modname => $modfullname) {
             if ($modname === 'resources') {
index 141bf66..fafab8a 100644 (file)
@@ -259,7 +259,7 @@ class community_hub_search_form extends moodleform {
             $mform->setDefault('licence', $licence);
 
             $languages = get_string_manager()->get_list_of_languages();
-            textlib_get_instance()->asort($languages);
+            collatorlib::asort($languages);
             $languages = array_merge(array('all' => get_string('any')), $languages);
             $mform->addElement('select', 'language', get_string('language'), $languages);
             $mform->setDefault('language', $language);
index 6885090..ec78b5a 100644 (file)
@@ -63,8 +63,8 @@ $string['enroldownload_help'] = 'Some courses listed in the selected hub are bei
 Others are course templates provided for you to download and use on your own Moodle site.';
 $string['enrollable'] = 'courses I can enrol in';
 $string['enrollablecourses'] = 'Enrollable courses';
-$string['errorcourselisting'] = 'An error occured when retrieving the course listing from the selected hub, please try again later. ({$a})';
-$string['errorhublisting'] = 'An error occured when retrieving the hub listing from Moodle.org, please try again later. ({$a})';
+$string['errorcourselisting'] = 'An error occurred when retrieving the course listing from the selected hub, please try again later. ({$a})';
+$string['errorhublisting'] = 'An error occurred when retrieving the hub listing from Moodle.org, please try again later. ({$a})';
 $string['fileinfo'] = 'Language: {$a->lang} - License: {$a->license} -  Time updated: {$a->timeupdated}';
 $string['hub'] = 'hub';
 $string['hubnottrusted'] = 'Not trusted';
index 05b52fd..35d6e94 100644 (file)
@@ -57,11 +57,24 @@ class block_html_edit_form extends block_edit_form {
         } else {
             $text = '';
         }
+
+        if (!$this->block->user_can_edit() && !empty($this->block->config->title)) {
+            // If a title has been set but the user cannot edit it format it nicely
+            $title = $this->block->config->title;
+            $defaults->config_title = format_string($title, true, $this->page->context);
+            // Remove the title from the config so that parent::set_data doesn't set it.
+            unset($this->block->config->title);
+        }
+
         // have to delete text here, otherwise parent::set_data will empty content
         // of editor
         unset($this->block->config->text);
         parent::set_data($defaults);
         // restore $text
         $this->block->config->text = $text;
+        if (isset($title)) {
+            // Reset the preserved title
+            $this->block->config->title = $title;
+        }
     }
 }
index 888e0c3..a5bad50 100644 (file)
@@ -176,7 +176,9 @@ class course_edit_form extends moodleform {
             $themes=array();
             $themes[''] = get_string('forceno');
             foreach ($themeobjects as $key=>$theme) {
-                $themes[$key] = $theme->name;
+                if (empty($theme->hidefromselector)) {
+                    $themes[$key] = get_string('pluginname', 'theme_'.$theme->name);
+                }
             }
             $mform->addElement('select', 'theme', get_string('forcetheme'), $themes);
         }
index 6318146..a708a02 100644 (file)
@@ -41,7 +41,9 @@ class editcategory_form extends moodleform {
             $themes = array(''=>get_string('forceno'));
             $allthemes = get_list_of_themes();
             foreach ($allthemes as $key=>$theme) {
-                $themes[$key] = $theme->name;
+                if (empty($theme->hidefromselector)) {
+                    $themes[$key] = get_string('pluginname', 'theme_'.$theme->name);
+                }
             }
             $mform->addElement('select', 'theme', get_string('forcetheme'), $themes);
         }
index a562887..f98e9b3 100644 (file)
@@ -1106,6 +1106,7 @@ function get_array_of_activities($courseid) {
                    $mod[$seq]->availablefrom    = $rawmods[$seq]->availablefrom;
                    $mod[$seq]->availableuntil   = $rawmods[$seq]->availableuntil;
                    $mod[$seq]->showavailability = $rawmods[$seq]->showavailability;
+                   $mod[$seq]->showdescription  = $rawmods[$seq]->showdescription;
                    if (!empty($CFG->enableavailability)) {
                        condition_info::fill_availability_conditions($rawmods[$seq]);
                        $mod[$seq]->conditionscompletion = $rawmods[$seq]->conditionscompletion;
@@ -1121,7 +1122,7 @@ function get_array_of_activities($courseid) {
 
                    include_once("$CFG->dirroot/mod/$modname/lib.php");
 
-                   if (function_exists($functionname)) {
+                   if ($hasfunction = function_exists($functionname)) {
                        if ($info = $functionname($rawmods[$seq])) {
                            if (!empty($info->icon)) {
                                $mod[$seq]->icon = $info->icon;
@@ -1156,17 +1157,33 @@ function get_array_of_activities($courseid) {
                            }
                        }
                    }
+                   // When there is no modname_get_coursemodule_info function,
+                   // but showdescriptions is enabled, then we use the 'intro'
+                   // and 'introformat' fields in the module table
+                   if (!$hasfunction && $rawmods[$seq]->showdescription) {
+                       if ($modvalues = $DB->get_record($rawmods[$seq]->modname,
+                               array('id' => $rawmods[$seq]->instance), 'name, intro, introformat')) {
+                           // Set content from intro and introformat. Filters are disabled
+                           // because we  filter it with format_text at display time
+                           $mod[$seq]->content = format_module_intro($rawmods[$seq]->modname,
+                                   $modvalues, $rawmods[$seq]->id, false);
+
+                           // To save making another query just below, put name in here
+                           $mod[$seq]->name = $modvalues->name;
+                       }
+                   }
                    if (!isset($mod[$seq]->name)) {
                        $mod[$seq]->name = $DB->get_field($rawmods[$seq]->modname, "name", array("id"=>$rawmods[$seq]->instance));
                    }
 
                    // Minimise the database size by unsetting default options when they are
                    // 'empty'. This list corresponds to code in the cm_info constructor.
-                   foreach(array('idnumber', 'groupmode', 'groupingid', 'groupmembersonly',
+                   foreach (array('idnumber', 'groupmode', 'groupingid', 'groupmembersonly',
                            'indent', 'completion', 'extra', 'extraclasses', 'onclick', 'content',
                            'icon', 'iconcomponent', 'customdata', 'showavailability', 'availablefrom',
                            'availableuntil', 'conditionscompletion', 'conditionsgrade',
-                           'completionview', 'completionexpected', 'score') as $property) {
+                           'completionview', 'completionexpected', 'score', 'showdescription')
+                           as $property) {
                        if (property_exists($mod[$seq], $property) &&
                                empty($mod[$seq]->{$property})) {
                            unset($mod[$seq]->{$property});
@@ -1206,7 +1223,7 @@ function get_all_mods($courseid, &$mods, &$modnames, &$modnamesplural, &$modname
                 $modnamesplural[$mod->name] = get_string("modulenameplural", "$mod->name");
             }
         }
-        textlib_get_instance()->asort($modnames);
+        collatorlib::asort($modnames);
     } else {
         print_error("nomodules", 'debug');
     }
@@ -1231,7 +1248,7 @@ function get_all_mods($courseid, &$mods, &$modnames, &$modnamesplural, &$modname
             $modnamesused[$mod->modname] = $modnames[$mod->modname];
         }
         if ($modnamesused) {
-            textlib_get_instance()->asort($modnamesused);
+            collatorlib::asort($modnamesused);
         }
     }
 }
@@ -3144,13 +3161,16 @@ function make_editing_buttons(stdClass $mod, $absolute = true, $moveselect = tru
         array('class' => 'editing_update', 'title' => $str->update)
     );
 
-    // Duplicate
-    $actions[] = new action_link(
-        new moodle_url($baseurl, array('duplicate' => $mod->id)),
-        new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall')),
-        null,
-        array('class' => 'editing_duplicate', 'title' => $str->duplicate)
-    );
+    // Duplicate (require both target import caps to be able to duplicate, see modduplicate.php)
+    $dupecaps = array('moodle/backup:backuptargetimport', 'moodle/restore:restoretargetimport');
+    if (has_all_capabilities($dupecaps, $coursecontext)) {
+        $actions[] = new action_link(
+            new moodle_url($baseurl, array('duplicate' => $mod->id)),
+            new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall')),
+            null,
+            array('class' => 'editing_duplicate', 'title' => $str->duplicate)
+        );
+    }
 
     // Delete
     $actions[] = new action_link(
index 632f191..a9006b1 100644 (file)
@@ -44,6 +44,7 @@ $section    = $DB->get_record('course_sections', array('id' => $cm->section, 'co
 require_login($course);
 require_sesskey();
 require_capability('moodle/course:manageactivities', $context);
+// Require both target import caps to be able to duplicate, see make_editing_buttons()
 require_capability('moodle/backup:backuptargetimport', $context);
 require_capability('moodle/restore:restoretargetimport', $context);
 
index cd84c00..0650de3 100644 (file)
@@ -135,6 +135,7 @@ if (!empty($add)) {
     $data->completionview     = $cm->completionview;
     $data->completionexpected = $cm->completionexpected;
     $data->completionusegrade = is_null($cm->completiongradeitemnumber) ? 0 : 1;
+    $data->showdescription    = $cm->showdescription;
     if (!empty($CFG->enableavailability)) {
         $data->availablefrom      = $cm->availablefrom;
         $data->availableuntil     = $cm->availableuntil;
@@ -310,6 +311,11 @@ if ($mform->is_cancelled()) {
             $cm->showavailability          = $fromform->showavailability;
             condition_info::update_cm_from_form($cm,$fromform,true);
         }
+        if (isset($fromform->showdescription)) {
+            $cm->showdescription = $fromform->showdescription;
+        } else {
+            $cm->showdescription = 0;
+        }
 
         $DB->update_record('course_modules', $cm);
 
@@ -395,6 +401,11 @@ if ($mform->is_cancelled()) {
             }
             $newcm->showavailability          = $fromform->showavailability;
         }
+        if (isset($fromform->showdescription)) {
+            $newcm->showdescription = $fromform->showdescription;
+        } else {
+            $newcm->showdescription = 0;
+        }
 
         if (!$fromform->coursemodule = add_course_module($newcm)) {
             print_error('cannotaddcoursemodule');
index e4eb740..bf7a1ec 100644 (file)
@@ -84,6 +84,7 @@ abstract class moodleform_mod extends moodleform {
         $this->_features->introeditor       = plugin_supports('mod', $this->_modname, FEATURE_MOD_INTRO, true);
         $this->_features->defaultcompletion = plugin_supports('mod', $this->_modname, FEATURE_MODEDIT_DEFAULT_COMPLETION, true);
         $this->_features->rating            = plugin_supports('mod', $this->_modname, FEATURE_RATE, false);
+        $this->_features->showdescription   = plugin_supports('mod', $this->_modname, FEATURE_SHOW_DESCRIPTION, false);
 
         $this->_features->gradecat          = ($this->_features->outcomes or $this->_features->hasgrades);
     }
@@ -674,6 +675,13 @@ abstract class moodleform_mod extends moodleform {
         if ($required) {
             $mform->addRule('introeditor', get_string('required'), 'required', null, 'client');
         }
+
+        // If the 'show description' feature is enabled, this checkbox appears
+        // below the intro.
+        if ($this->_features->showdescription) {
+            $mform->addElement('checkbox', 'showdescription', get_string('showdescription'));
+            $mform->addHelpButton('showdescription', 'showdescription');
+        }
     }
 
     /**
index 0ee8999..84a9852 100644 (file)
@@ -239,7 +239,7 @@ class course_publication_form extends moodleform {
         $mform->addHelpButton('description', 'description', 'hub');
 
         $languages = get_string_manager()->get_list_of_languages();
-        textlib_get_instance()->asort($languages);
+        collatorlib::asort($languages);
         $mform->addElement('select', 'language', get_string('language'), $languages);
         $mform->setDefault('language', $defaultlanguage);
         $mform->addHelpButton('language', 'language', 'hub');
index 07bb2e9..ecd7141 100644 (file)
@@ -178,7 +178,6 @@ $string['backupincludemoduleuserdatahelp'] = 'Choose whether you want to include
 $string['backupkeephelp'] = 'How many recent backups for each course do you want to keep? (older ones will be deleted automatically)';
 $string['backuplogdetailed'] = 'Detailed execution log';
 $string['backuploglaststatus'] = 'Last execution log';
-$string['backuplogshelp'] = 'If enabled, then course logs will be included in automated backups';
 $string['backupmissinguserinfoperms'] = 'Note: This backup contains no user data. Exercise and Workshop activities will not be included in the backup, since these modules are not compatible with this type of backup.';
 $string['backupnext'] = 'Next backup';
 $string['backupnonisowarning'] = 'Warning: this backup is from a non-Unicode version of Moodle (pre 1.6).  If this backup contains any non-ISO-8859-1 texts then they may be CORRUPTED if you try to restore them to this Unicode version of Moodle.  See the <a href="http://docs.moodle.org/en/Backup_FAQ">Backup FAQ</a> for more information about how to recover this backup correctly.';
@@ -189,7 +188,6 @@ $string['backupsitefileshelp'] = 'If enabled then site files used in courses wil
 $string['backuptakealook'] = 'Please take a look at your backup logs in:
   {$a}';
 $string['backupuserfileshelp'] = 'Choose whether user files (eg profile images) should be included in automated backups';
-$string['backupusershelp'] = 'Select whether you want to include all the users in the server or only the needed users for each course';
 $string['backupversion'] = 'Backup version';
 $string['block'] = 'Block';
 $string['blockconfiga'] = 'Configuring a {$a} block';
@@ -1487,6 +1485,8 @@ $string['showallweeks'] = 'Show all weeks';
 $string['showblockcourse'] = 'Show list of courses containing block';
 $string['showcomments'] = 'Show/hide comments';
 $string['showcommentsnonjs'] = 'Show comments';
+$string['showdescription'] = 'Display description on course page';
+$string['showdescription_help'] = 'If enabled, the introduction / description above will be displayed on the course page just below the link to the activity / resource.';
 $string['showgrades'] = 'Show gradebook to students';
 $string['showgrades_help'] = 'Many activities in the course allow grades to be set. This setting determines whether a student can view a list of all their grades for the course via a grades link in the course administration block.';
 $string['showlistofcourses'] = 'Show list of courses';
index 86fd35d..5586b3c 100644 (file)
@@ -1754,7 +1754,7 @@ function block_add_block_ui($page, $output) {
             $menu[$block->name] = $blockobject->get_title();
         }
     }
-    textlib_get_instance()->asort($menu);
+    collatorlib::asort($menu);
 
     $actionurl = new moodle_url($page->url, array('sesskey'=>sesskey()));
     $select = new single_select($actionurl, 'bui_addblock', $menu, null, array(''=>get_string('adddots')), 'add_block');
index bc42449..667e4b2 100644 (file)
@@ -1687,7 +1687,7 @@ $capabilities = array(
     ),
     'moodle/webservice:createmobiletoken' => array(
 
-        'riskbitmask' => RISK_CONFIG | RISK_DATALOSS | RISK_SPAM | RISK_PERSONAL | RISK_XSS,
+        'riskbitmask' => RISK_SPAM | RISK_PERSONAL,
         'captype' => 'write',
         'contextlevel' => CONTEXT_SYSTEM,
         'archetypes' => array(
index f72b59b..d23e2db 100644 (file)
         <FIELD NAME="completionexpected" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="Date at which students are expected to complete this activity. This field is used when displaying student progress." PREVIOUS="completionview" NEXT="availablefrom"/>
         <FIELD NAME="availablefrom" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="If set non-zero, the activity only becomes available from the time given here." PREVIOUS="completionexpected" NEXT="availableuntil"/>
         <FIELD NAME="availableuntil" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="If set non-zero, the activity is only available until the time given here." PREVIOUS="availablefrom" NEXT="showavailability"/>
-        <FIELD NAME="showavailability" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="If this field is set to 1 and the activity is not available because the 'availablefrom' date has not been reached, or one of the conditions in course_modules_availability is not matched, then the item will be displayed greyed-out (unclickable) with an information message such as 'Available from (date)'. Otherwise, the item will not be displayed to students at all." PREVIOUS="availableuntil"/>
+        <FIELD NAME="showavailability" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="If this field is set to 1 and the activity is not available because the 'availablefrom' date has not been reached, or one of the conditions in course_modules_availability is not matched, then the item will be displayed greyed-out (unclickable) with an information message such as 'Available from (date)'. Otherwise, the item will not be displayed to students at all." PREVIOUS="availableuntil" NEXT="showdescription"/>
+        <FIELD NAME="showdescription" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="Some module types support a 'description' which shows within the module pages. This option controls whether it also displays on the course main page. 0 = does not display (default), 1 = displays" PREVIOUS="showavailability"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id" NEXT="groupingid"/>
index 742faa8..a4f8809 100644 (file)
@@ -1230,8 +1230,14 @@ WHERE gradeitemid IS NOT NULL AND grademax IS NOT NULL");
         $field = new xmldb_field('backuptype', XMLDB_TYPE_CHAR, '50', null, XMLDB_NOTNULL, null, null, 'info');
     /// Conditionally Launch add field backuptype and set all old records as 'scheduledbackup' records.
         if (!$dbman->field_exists($table, $field)) {
+            // Set the default we want applied to any existing records
+            $field->setDefault('scheduledbackup');
+            // Add the field to the database
             $dbman->add_field($table, $field);
-            $DB->execute("UPDATE {backup_log} SET backuptype='scheduledbackup'");
+            // Remove the default
+            $field->setDefault(null);
+            // Update the database to remove the default
+            $dbman->change_field_default($table, $field);
         }
 
     /// Main savepoint reached
@@ -6675,6 +6681,20 @@ FROM
         upgrade_main_savepoint(true, 2011081700.02);
     }
 
+    if ($oldversion < 2011083100.02) {
+        // Define field showdescription to be added to course_modules
+        $table = new xmldb_table('course_modules');
+        $field = new xmldb_field('showdescription', XMLDB_TYPE_INTEGER, '1',
+                XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0', 'showavailability');
+
+        // Conditionally launch add field showdescription
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // Main savepoint reached
+        upgrade_main_savepoint(true, 2011083100.02);
+    }
 
     return true;
 }
index db88a9e..7c9384b 100644 (file)
@@ -747,6 +747,11 @@ abstract class moodle_database {
                 if (!array_key_exists($key, $params)) {
                     throw new dml_exception('missingkeyinsql', $key, '');
                 }
+                if (strlen($key) > 30) {
+                    throw new coding_exception(
+                            "Placeholder names must be 30 characters or shorter. '" .
+                            $key . "' is too long.", $sql);
+                }
                 $finalparams[$key] = $params[$key];
             }
             if ($count != count($finalparams)) {
@@ -1595,7 +1600,9 @@ abstract class moodle_database {
      * @throws dml_exception if error
      */
     public function delete_records($table, array $conditions=null) {
-        if (is_null($conditions)) {
+        // truncate is drop/create (DDL), not transactional safe,
+        // so we don't use the shortcut within them. MDL-29198
+        if (is_null($conditions) && empty($this->transactions)) {
             return $this->execute("TRUNCATE TABLE {".$table."}");
         }
         list($select, $params) = $this->where_clause($table, $conditions);
index 2197563..943dbbb 100644 (file)
@@ -364,11 +364,23 @@ class oci_native_moodle_database extends moodle_database {
         if (empty($params)) {
             return array($sql, $params);
         }
+
         $newparams = array();
-        foreach ($params as $name=>$value) {
-            $newparams['o_'.$name] = $value;
+        $searcharr = array(); // search => replace pairs
+        foreach ($params as $name => $value) {
+            // Keep the name within the 30 chars limit always (prefixing/replacing)
+            if (strlen($name) <= 28) {
+                $newname = 'o_' . $name;
+            } else {
+                $newname = 'o_' . substr($name, 2);
+            }
+            $newparams[$newname] = $value;
+            $searcharr[':' . $name] = ':' . $newname;
         }
-        $sql = preg_replace('/:([a-z0-9_-]+[$a-z0-9_-])/', ':o_$1', $sql);
+        // sort by length desc to avoid potential str_replace() overlap
+        uksort($searcharr, array('oci_native_moodle_database', 'compare_by_length_desc'));
+
+        $sql = str_replace(array_keys($searcharr), $searcharr, $sql);
         return array($sql, $newparams);
     }
 
@@ -821,6 +833,17 @@ class oci_native_moodle_database extends moodle_database {
         return $value;
     }
 
+    /**
+     * Helper function to order by string length desc
+     *
+     * @param $a string first element to compare
+     * @param $b string second element to compare
+     * @return int < 0 $a goes first (is less), 0 $b goes first, 0 doesn't matter
+     */
+    private function compare_by_length_desc($a, $b) {
+        return strlen($b) - strlen($a);
+    }
+
     /**
      * Is db in unicode mode?
      * @return bool
@@ -1164,12 +1187,13 @@ class oci_native_moodle_database extends moodle_database {
 
         $id = null;
 
-        list($sql, $params) = $this->tweak_param_names($sql, $params);
+        // note we don't need tweak_param_names() here. Placeholders are safe column names. MDL-28080
+        // list($sql, $params) = $this->tweak_param_names($sql, $params);
         $this->query_start($sql, $params, SQL_QUERY_INSERT);
         $stmt = $this->parse_query($sql);
         $descriptors = $this->bind_params($stmt, $params, $table);
         if ($returning) {
-            oci_bind_by_name($stmt, ":o_oracle_id", $id, 10, SQLT_INT); // :o_ prefix added in tweak_param_names()
+            oci_bind_by_name($stmt, ":oracle_id", $id, 10, SQLT_INT);
         }
         $result = oci_execute($stmt, $this->commit_status);
         $this->free_descriptors($descriptors);
@@ -1276,7 +1300,8 @@ class oci_native_moodle_database extends moodle_database {
         $sql = "UPDATE {" . $table . "} SET $sets WHERE id=:id";
         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
 
-        list($sql, $params) = $this->tweak_param_names($sql, $params);
+        // note we don't need tweak_param_names() here. Placeholders are safe column names. MDL-28080
+        // list($sql, $params) = $this->tweak_param_names($sql, $params);
         $this->query_start($sql, $params, SQL_QUERY_UPDATE);
         $stmt = $this->parse_query($sql);
         $descriptors = $this->bind_params($stmt, $params, $table);
index 40ba855..c9114c9 100644 (file)
@@ -465,6 +465,16 @@ class dml_test extends UnitTestCase {
             $this->fail("Unexpected ".get_class($e)." exception");
         }
 
+        // Params exceeding 30 chars length
+        $sql = "SELECT * FROM {{$tablename}} WHERE name = :long_placeholder_with_more_than_30";
+        $params = array('long_placeholder_with_more_than_30' => 'record1');
+        try {
+            $DB->fix_sql_params($sql, $params);
+            $this->fail("Expecting an exception, none occurred");
+        } catch (Exception $e) {
+            $this->assertTrue($e instanceof coding_exception);
+        }
+
         // Booleans in NAMED params are casting to 1/0 int
         $sql = "SELECT * FROM {{$tablename}} WHERE course = ? OR course = ?";
         $params = array(true, false);
@@ -493,6 +503,139 @@ class dml_test extends UnitTestCase {
         $this->assertIdentical(array_values($params), array_values($inparams));
     }
 
+    public function test_tweak_param_names() {
+        // Note the tweak_param_names() method is only available in the oracle driver,
+        // hence we look for expected results indirectly, by testing various DML methods
+        // with some "extreme" conditions causing the tweak to happen.
+        $DB = $this->tdb;
+        $dbman = $this->tdb->get_manager();
+
+        $table = $this->get_test_table();
+        $tablename = $table->getName();
+
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        // Add some columns with 28 chars in the name
+        $table->add_field('long_int_columnname_with_28c', XMLDB_TYPE_INTEGER, '10');
+        $table->add_field('long_dec_columnname_with_28c', XMLDB_TYPE_NUMBER, '10,2');
+        $table->add_field('long_str_columnname_with_28c', XMLDB_TYPE_CHAR, '100');
+        // Add some columns with 30 chars in the name
+        $table->add_field('long_int_columnname_with_30cxx', XMLDB_TYPE_INTEGER, '10');
+        $table->add_field('long_dec_columnname_with_30cxx', XMLDB_TYPE_NUMBER, '10,2');
+        $table->add_field('long_str_columnname_with_30cxx', XMLDB_TYPE_CHAR, '100');
+
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+        $dbman->create_table($table);
+
+        $this->assertTrue($dbman->table_exists($tablename));
+
+        // Test insert record
+        $rec1 = new stdClass();
+        $rec1->long_int_columnname_with_28c = 28;
+        $rec1->long_dec_columnname_with_28c = 28.28;
+        $rec1->long_str_columnname_with_28c = '28';
+        $rec1->long_int_columnname_with_30cxx = 30;
+        $rec1->long_dec_columnname_with_30cxx = 30.30;
+        $rec1->long_str_columnname_with_30cxx = '30';
+
+        // insert_record()
+        $rec1->id = $DB->insert_record($tablename, $rec1);
+        $this->assertEqual($rec1, $DB->get_record($tablename, array('id' => $rec1->id)));
+
+        // update_record()
+        $DB->update_record($tablename, $rec1);
+        $this->assertEqual($rec1, $DB->get_record($tablename, array('id' => $rec1->id)));
+
+        // set_field()
+        $rec1->long_int_columnname_with_28c = 280;
+        $DB->set_field($tablename, 'long_int_columnname_with_28c', $rec1->long_int_columnname_with_28c,
+            array('id' => $rec1->id, 'long_int_columnname_with_28c' => 28));
+        $rec1->long_dec_columnname_with_28c = 280.28;
+        $DB->set_field($tablename, 'long_dec_columnname_with_28c', $rec1->long_dec_columnname_with_28c,
+            array('id' => $rec1->id, 'long_dec_columnname_with_28c' => 28.28));
+        $rec1->long_str_columnname_with_28c = '280';
+        $DB->set_field($tablename, 'long_str_columnname_with_28c', $rec1->long_str_columnname_with_28c,
+            array('id' => $rec1->id, 'long_str_columnname_with_28c' => '28'));
+        $rec1->long_int_columnname_with_30cxx = 300;
+        $DB->set_field($tablename, 'long_int_columnname_with_30cxx', $rec1->long_int_columnname_with_30cxx,
+            array('id' => $rec1->id, 'long_int_columnname_with_30cxx' => 30));
+        $rec1->long_dec_columnname_with_30cxx = 300.30;
+        $DB->set_field($tablename, 'long_dec_columnname_with_30cxx', $rec1->long_dec_columnname_with_30cxx,
+            array('id' => $rec1->id, 'long_dec_columnname_with_30cxx' => 30.30));
+        $rec1->long_str_columnname_with_30cxx = '300';
+        $DB->set_field($tablename, 'long_str_columnname_with_30cxx', $rec1->long_str_columnname_with_30cxx,
+            array('id' => $rec1->id, 'long_str_columnname_with_30cxx' => '30'));
+        $this->assertEqual($rec1, $DB->get_record($tablename, array('id' => $rec1->id)));
+
+        // delete_records()
+        $rec2 = $DB->get_record($tablename, array('id' => $rec1->id));
+        $rec2->id = $DB->insert_record($tablename, $rec2);
+        $this->assertEqual(2, $DB->count_records($tablename));
+        $DB->delete_records($tablename, (array) $rec2);
+        $this->assertEqual(1, $DB->count_records($tablename));
+
+        // get_recordset()
+        $rs = $DB->get_recordset($tablename, (array) $rec1);
+        $iterations = 0;
+        foreach ($rs as $rec2) {
+            $iterations++;
+        }
+        $rs->close();
+        $this->assertEqual(1, $iterations);
+        $this->assertEqual($rec1, $rec2);
+
+        // get_records()
+        $recs = $DB->get_records($tablename, (array) $rec1);
+        $this->assertEqual(1, count($recs));
+        $this->assertEqual($rec1, reset($recs));
+
+        // get_fieldset_select()
+        $select = 'id = :id AND
+                   long_int_columnname_with_28c = :long_int_columnname_with_28c AND
+                   long_dec_columnname_with_28c = :long_dec_columnname_with_28c AND
+                   long_str_columnname_with_28c = :long_str_columnname_with_28c AND
+                   long_int_columnname_with_30cxx = :long_int_columnname_with_30cxx AND
+                   long_dec_columnname_with_30cxx = :long_dec_columnname_with_30cxx AND
+                   long_str_columnname_with_30cxx = :long_str_columnname_with_30cxx';
+        $fields = $DB->get_fieldset_select($tablename, 'long_int_columnname_with_28c', $select, (array)$rec1);
+        $this->assertEqual(1, count($fields));
+        $this->assertEqual($rec1->long_int_columnname_with_28c, reset($fields));
+        $fields = $DB->get_fieldset_select($tablename, 'long_dec_columnname_with_28c', $select, (array)$rec1);
+        $this->assertEqual($rec1->long_dec_columnname_with_28c, reset($fields));
+        $fields = $DB->get_fieldset_select($tablename, 'long_str_columnname_with_28c', $select, (array)$rec1);
+        $this->assertEqual($rec1->long_str_columnname_with_28c, reset($fields));
+        $fields = $DB->get_fieldset_select($tablename, 'long_int_columnname_with_30cxx', $select, (array)$rec1);
+        $this->assertEqual($rec1->long_int_columnname_with_30cxx, reset($fields));
+        $fields = $DB->get_fieldset_select($tablename, 'long_dec_columnname_with_30cxx', $select, (array)$rec1);
+        $this->assertEqual($rec1->long_dec_columnname_with_30cxx, reset($fields));
+        $fields = $DB->get_fieldset_select($tablename, 'long_str_columnname_with_30cxx', $select, (array)$rec1);
+        $this->assertEqual($rec1->long_str_columnname_with_30cxx, reset($fields));
+
+        // overlapping placeholders (progressive str_replace)
+        $overlapselect = 'id = :p AND
+                   long_int_columnname_with_28c = :param1 AND
+                   long_dec_columnname_with_28c = :param2 AND
+                   long_str_columnname_with_28c = :param_with_29_characters_long AND
+                   long_int_columnname_with_30cxx = :param_with_30_characters_long_ AND
+                   long_dec_columnname_with_30cxx = :param_ AND
+                   long_str_columnname_with_30cxx = :param__';
+        $overlapparams = array(
+                'p' => $rec1->id,
+                'param1' => $rec1->long_int_columnname_with_28c,
+                'param2' => $rec1->long_dec_columnname_with_28c,
+                'param_with_29_characters_long' => $rec1->long_str_columnname_with_28c,
+                'param_with_30_characters_long_' => $rec1->long_int_columnname_with_30cxx,
+                'param_' => $rec1->long_dec_columnname_with_30cxx,
+                'param__' => $rec1->long_str_columnname_with_30cxx);
+        $recs = $DB->get_records_select($tablename, $overlapselect, $overlapparams);
+        $this->assertEqual(1, count($recs));
+        $this->assertEqual($rec1, reset($recs));
+
+        // execute()
+        $DB->execute("DELETE FROM {{$tablename}} WHERE $select", (array)$rec1);
+        $this->assertEqual(0, $DB->count_records($tablename));
+    }
+
     public function test_get_tables() {
         $DB = $this->tdb;
         $dbman = $this->tdb->get_manager();
@@ -3851,6 +3994,25 @@ class dml_test extends UnitTestCase {
         $transaction->allow_commit();
         $this->assertEqual(2, $DB2->count_records($tablename));
 
+        // let's try delete all is also working on (this checks MDL-29198)
+        // initially both connections see all the records in the table (2)
+        $this->assertEqual(2, $DB->count_records($tablename));
+        $this->assertEqual(2, $DB2->count_records($tablename));
+        $transaction = $DB->start_delegated_transaction();
+
+        // delete all from within transaction
+        $DB->delete_records($tablename);
+
+        // transactional $DB, sees 0 records now
+        $this->assertEqual(0, $DB->count_records($tablename));
+
+        // others ($DB2) get no changes yet
+        $this->assertEqual(2, $DB2->count_records($tablename));
+
+        // now commit and we should see changes
+        $transaction->allow_commit();
+        $this->assertEqual(0, $DB2->count_records($tablename));
+
         $DB2->dispose();
     }
 
index 99c070a..5afc6ea 100644 (file)
@@ -172,7 +172,10 @@ class tinymce_texteditor extends texteditor {
                 $params['file_browser_callback'] = "M.editor_tinymce.filepicker";
             }
         }
-
+        //Add onblur event for client side text validation
+        if (!empty($options['required'])) {
+            $params['init_instance_callback'] = 'M.editor_tinymce.onblur_event';
+        }
         return $params;
     }
 }
index 7db0198..2754961 100644 (file)
@@ -72,3 +72,19 @@ M.editor_tinymce.filepicker = function(target_id, url, type, win) {
     });
 };
 
+M.editor_tinymce.onblur_event = function(ed) {
+    //Attach event only after tinymce is intialized.
+    if (ed.onInit != undefined) {
+        var s = ed.settings;
+        //Save before event is attached, so that if this event is not generated then textarea should
+        //have loaded contents and submitting form should not throw error.
+        ed.save();
+
+        //Attach blur event for tinymce to save contents to textarea
+        var doc = s.content_editable ? ed.getBody() : (tinymce.isGecko ? ed.getDoc() : ed.getWin());
+        tinymce.dom.Event.add(doc, 'blur', function() {
+            //save contents to textarea before calling validation script.
+            ed.save();
+        });
+    };
+};
index 743059b..a120277 100644 (file)
@@ -519,7 +519,7 @@ function filter_get_all_installed() {
             }
         }
     }
-    textlib_get_instance()->asort($filternames);
+    collatorlib::asort($filternames);
     return $filternames;
 }
 
index 6e1f12a..eeec855 100644 (file)
@@ -86,6 +86,24 @@ class MoodleQuickForm_editor extends HTML_QuickForm_element {
         $this->_options['subdirs'] = $allow;
     }
 
+    /**
+     * Returns editor format
+     *
+     * @return int.
+     */
+    function getFormat() {
+        return $this->_values['format'];
+    }
+
+    /**
+     * Checks if editor used is tinymce and is required field
+     *
+     * @return true if required field.
+     */
+    function isRequired() {
+        return (isset($this->_options['required']) && $this->_options['required']);
+    }
+
     function setHelpButton($_helpbuttonargs, $function='_helpbutton') {
         if (!is_array($_helpbuttonargs)) {
             $_helpbuttonargs = array($_helpbuttonargs);
@@ -198,13 +216,23 @@ class MoodleQuickForm_editor extends HTML_QuickForm_element {
             $fpoptions['link'] = $link_options;
         }
 
+        //If editor is required and tinymce, then set required_tinymce option to initalize tinymce validation.
+        if (($editor instanceof tinymce_texteditor)  && !is_null($this->getAttribute('onchange'))) {
+            $this->_options['required'] = true;
+        }
+
     /// print text area - TODO: add on-the-fly switching, size configuration, etc.
         $editor->use_editor($id, $this->_options, $fpoptions);
 
         $rows = empty($this->_attributes['rows']) ? 15 : $this->_attributes['rows'];
         $cols = empty($this->_attributes['cols']) ? 80 : $this->_attributes['cols'];
 
-        $str .= '<div><textarea id="'.$id.'" name="'.$elname.'[text]" rows="'.$rows.'" cols="'.$cols.'">';
+        //Apply editor validation if required field
+        $editorrules = '';
+        if (!is_null($this->getAttribute('onblur')) && !is_null($this->getAttribute('onchange'))) {
+            $editorrules = 'onblur="'.htmlspecialchars($this->getAttribute('onblur')).'" onchange="'.htmlspecialchars($this->getAttribute('onchange')).'"';
+        }
+        $str .= '<div><textarea id="'.$id.'" name="'.$elname.'[text]" rows="'.$rows.'" cols="'.$cols.'"'.$editorrules.'>';
         $str .= s($text);
         $str .= '</textarea></div>';
 
index 4b02f70..aadc8a0 100644 (file)
@@ -1710,8 +1710,16 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
                             }
                         }
                     }
+                    //for editor element, [text] is appended to the name.
+                    if ($element->getType() == 'editor') {
+                        $elementName .= '[text]';
+                        //Add format to rule as moodleform check which format is supported by browser
+                        //it is not set anywhere... So small hack to make sure we pass it down to quickform
+                        if (is_null($rule['format'])) {
+                            $rule['format'] = $element->getFormat();
+                        }
+                    }
                     // Fix for bug displaying errors for elements in a group
-                    //$test[$elementName][] = $registry->getValidationScript($element, $elementName, $rule);
                     $test[$elementName][0][] = $registry->getValidationScript($element, $elementName, $rule);
                     $test[$elementName][1]=$element;
                     //end of fix
@@ -2394,17 +2402,18 @@ class MoodleQuickForm_Rule_Required extends HTML_QuickForm_Rule {
     /**
      * This function returns Javascript code used to build client-side validation.
      * It checks if an element is not empty.
-     * Note, that QuickForm does not know how to work with editor text field and builds not correct
-     * JS code for validation. If client check is enabled for editor field it will not be validated
-     * on client side no matter what this function returns.
      *
-     * @param     mixed     $options    Not used yet
+     * @param int $format
      * @return array
      */
-    function getValidationScript($options = null) {
+    function getValidationScript($format = null) {
         global $CFG;
         if (!empty($CFG->strictformsrequired)) {
-            return array('', "{jsVar}.replace(/^\s+$/g, '') == ''");
+            if (!empty($format) && $format == FORMAT_HTML) {
+                return array('', "{jsVar}.replace(/(<[^img|hr|canvas]+>)|&nbsp;|\s+/ig, '') == ''");
+            } else {
+                return array('', "{jsVar}.replace(/^\s+$/g, '') == ''");
+            }
         } else {
             return array('', "{jsVar} == ''");
         }
index c9793f1..b78fde9 100644 (file)
@@ -798,7 +798,7 @@ function grade_get_categories_menu($courseid, $includenew=false) {
     foreach ($categories as $category) {
         $cats[$category->id] = $category->get_name();
     }
-    textlib_get_instance()->asort($cats);
+    collatorlib::asort($cats);
 
     return ($result+$cats);
 }
index c2fc0d0..bdf82ff 100644 (file)
@@ -445,6 +445,14 @@ class cm_info extends stdClass  {
      */
     public $showavailability;
 
+    /**
+     * Controls whether the description of the activity displays on the course main page (in
+     * addition to anywhere it might display within the activity itself). 0 = do not show
+     * on main page, 1 = show on main page.
+     * @var int
+     */
+    public $showdescription;
+
     /**
      * Extra HTML that is put in an unhelpful part of the HTML when displaying this module in
      * course page - from cached data in modinfo field
@@ -854,6 +862,7 @@ class cm_info extends stdClass  {
         $this->iconcomponent    = isset($mod->iconcomponent) ? $mod->iconcomponent : '';
         $this->customdata       = isset($mod->customdata) ? $mod->customdata : '';
         $this->context          = get_context_instance(CONTEXT_MODULE, $mod->cm);
+        $this->showdescription  = isset($mod->showdescription) ? $mod->showdescription : 0;
         $this->state = self::STATE_BASIC;
 
         // This special case handles old label data. Labels used to use the 'name' field for
index 51abbdc..bfb8855 100644 (file)
@@ -383,6 +383,9 @@ define('FEATURE_RATE', 'rate');
 /** True if module supports backup/restore of moodle2 format */
 define('FEATURE_BACKUP_MOODLE2', 'backup_moodle2');
 
+/** True if module can show description on course main page */
+define('FEATURE_SHOW_DESCRIPTION', 'showdescription');
+
 /** Unspecified module archetype */
 define('MOD_ARCHETYPE_OTHER', 0);
 /** Resource-like type module */
@@ -6143,7 +6146,7 @@ class core_string_manager implements string_manager {
         }
 
         $countries = $this->load_component_strings('core_countries', $lang);
-        textlib_get_instance()->asort($countries);
+        collatorlib::asort($countries);
         if (!$returnall and !empty($CFG->allcountrycodes)) {
             $enabled = explode(',', $CFG->allcountrycodes);
             $return = array();
@@ -6315,12 +6318,12 @@ class core_string_manager implements string_manager {
 
             if (!empty($CFG->langcache) and !empty($this->menucache)) {
                 // cache the list so that it can be used next time
-                textlib_get_instance()->asort($languages);
+                collatorlib::asort($languages);
                 file_put_contents($this->menucache, json_encode($languages));
             }
         }
 
-        textlib_get_instance()->asort($languages);
+        collatorlib::asort($languages);
 
         return $languages;
     }
@@ -6739,7 +6742,6 @@ function get_list_of_charsets() {
 /**
  * Returns a list of valid and compatible themes
  *
- * @global object
  * @return array
  */
 function get_list_of_themes() {
@@ -6757,7 +6759,8 @@ function get_list_of_themes() {
         $theme = theme_config::load($themename);
         $themes[$themename] = $theme;
     }
-    asort($themes);
+
+    collatorlib::asort_objects_by_method($themes, 'get_theme_name');
 
     return $themes;
 }
index 8bb6d76..9a4a123 100644 (file)
@@ -259,6 +259,78 @@ class user_picture implements renderable {
 
         return $return;
     }
+
+    /**
+     * Works out the URL for the users picture.
+     *
+     * This method is recommended as it avoids costly redirects of user pictures
+     * if requests are made for non-existent files etc.
+     *
+     * @param renderer_base $renderer
+     * @return moodle_url
+     */
+    public function get_url(moodle_page $page, renderer_base $renderer = null) {
+        global $CFG;
+
+        if (is_null($renderer)) {
+            $renderer = $page->get_renderer('core');
+        }
+
+        if (!empty($CFG->forcelogin) and !isloggedin()) {
+            // protect images if login required and not logged in;
+            // do not use require_login() because it is expensive and not suitable here anyway
+            return $renderer->pix_url('u/f1');
+        }
+
+        // Sort out the filename and size. Size is only required for the gravatar
+        // implementation presently.
+        if (empty($this->size)) {
+            $filename = 'f2';
+            $size = 35;
+        } else if ($this->size === true or $this->size == 1) {
+            $filename = 'f1';
+            $size = 100;
+        } else if ($this->size >= 50) {
+            $filename = 'f1';
+            $size = (int)$this->size;
+        } else {
+            $filename = 'f2';
+            $size = (int)$this->size;
+        }
+
+        if ($this->user->picture == 1) {
+            // The user has uploaded their own profile pic. In this case we will
+            // check that a profile pic file does actually exist
+            $fs = get_file_storage();
+            $context = get_context_instance(CONTEXT_USER, $this->user->id);
+            if (!$fs->file_exists($context->id, 'user', 'icon', 0, '/', $filename.'/.png')) {
+                if (!$fs->file_exists($context->id, 'user', 'icon', 0, '/', $filename.'/.jpg')) {
+                    return $renderer->pix_url('u/'.$filename);
+                }
+            }
+            $path = '/';
+            if (clean_param($page->theme->name, PARAM_THEME) == $page->theme->name) {
+                // We append the theme name to the file path if we have it so that
+                // in the circumstance that the profile picture is not available
+                // when the user actually requests it they still get the profile
+                // picture for the correct theme.
+                $path .= $page->theme->name.'/';
+            }
+            return moodle_url::make_pluginfile_url($context->id, 'user', 'icon', NULL, $path, $filename);
+
+        } else if ($this->user->picture == 2) {
+            // This is just VERY basic support for gravatar to give the actual
+            // implementor a headstart in what to do.
+            if ($size < 1 || $size > 500) {
+                $size = 35;
+            }
+            $md5 = md5(strtolower(trim($this->user->email)));
+            $default = urlencode($this->pix_url('u/'.$filename)->out(false));
+            return "http://www.gravatar.com/avatar/{$md5}?s={$size}&d={$default}";
+        }
+
+        return $renderer->pix_url('u/'.$filename);
+    }
 }
 
 /**
index 304b744..8712d5a 100644 (file)
@@ -1179,6 +1179,15 @@ class theme_config {
         }
         return $regions;
     }
+
+    /**
+     * Returns the human readable name of the theme
+     *
+     * @return string
+     */
+    public function get_theme_name() {
+        return get_string('pluginname', 'theme_'.$this->name);
+    }
 }
 
 
index da23b21..d685cd2 100644 (file)
@@ -1801,33 +1801,21 @@ class core_renderer extends renderer_base {
         }
 
         if (empty($userpicture->size)) {
-            $file = 'f2';
             $size = 35;
         } else if ($userpicture->size === true or $userpicture->size == 1) {
-            $file = 'f1';
             $size = 100;
-        } else if ($userpicture->size >= 50) {
-            $file = 'f1';
-            $size = $userpicture->size;
         } else {
-            $file = 'f2';
             $size = $userpicture->size;
         }
 
         $class = $userpicture->class;
 
-        if ($user->picture == 1) {
-            $usercontext = get_context_instance(CONTEXT_USER, $user->id);
-            $src = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', NULL, '/', $file);
-
-        } else if ($user->picture == 2) {
-            //TODO: gravatar user icon support
-
-        } else { // Print default user pictures (use theme version if available)
+        if ($user->picture != 1 && $user->picture != 2) {
             $class .= ' defaultuserpic';
-            $src = $this->pix_url('u/' . $file);
         }
 
+        $src = $userpicture->get_url($this->page, $this);
+
         $attributes = array('src'=>$src, 'alt'=>$alt, 'title'=>$alt, 'class'=>$class, 'width'=>$size, 'height'=>$size);
 
         // get the image html output fisrt
index 446fa95..f0a4436 100644 (file)
@@ -1228,7 +1228,7 @@ function get_import_export_formats($type) {
         }
     }
 
-    textlib_get_instance()->asort($fileformatnames);
+    collatorlib::asort($fileformatnames);
     return $fileformatnames;
 }
 
index e093db3..09f1458 100644 (file)
@@ -272,28 +272,6 @@ class textlib_test extends UnitTestCase {
         $this->assertIdentical(textlib::strtotitle($str), "Žluťoučký Koníček");
     }
 
-    public function test_asort() {
-        global $SESSION;
-        $SESSION->lang = 'en'; // make sure we test en language to get consistent results, hopefully all systems have this locale
-
-        $arr = array('b'=>'ab', 1=>'aa', 0=>'cc');
-        textlib::asort($arr);
-        $this->assertIdentical(array_keys($arr), array(1, 'b', 0));
-        $this->assertIdentical(array_values($arr), array('aa', 'ab', 'cc'));
-
-        if (extension_loaded('intl')) {
-            $error = 'Collation aware sorting not supported';
-        } else {
-            $error = 'Collation aware sorting not supported, PHP extension "intl" is not available.';
-        }
-
-        $arr = array('a'=>'áb', 'b'=>'ab', 1=>'aa', 0=>'cc');
-        textlib::asort($arr);
-        $this->assertIdentical(array_keys($arr), array(1, 'b', 'a', 0), $error);
-
-        unset($SESSION->lang);
-    }
-
     public function test_deprecated_textlib_get_instance() {
         $textlib = textlib_get_instance();
         $this->assertIdentical($textlib->substr('abc', 1, 1), 'b');
@@ -307,3 +285,129 @@ class textlib_test extends UnitTestCase {
         $this->assertIdentical($textlib->strtotitle('abc ABC'), 'Abc Abc');
     }
 }
+
+/**
+ * Unit tests for our utf-8 aware collator.
+ *
+ * Used for sorting.
+ *
+ * @package    core
+ * @subpackage lib
+ * @copyright  2011 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class collatorlib_test extends UnitTestCase {
+
+    protected $initiallang = null;
+    protected $error = null;
+
+    public function setUp() {
+        global $SESSION;
+        if (isset($SESSION->lang)) {
+            $this->initiallang = $SESSION->lang;
+        }
+        $SESSION->lang = 'en'; // make sure we test en language to get consistent results, hopefully all systems have this locale
+        if (extension_loaded('intl')) {
+            $this->error = 'Collation aware sorting not supported';
+        } else {
+            $this->error = 'Collation aware sorting not supported, PHP extension "intl" is not available.';
+        }
+        parent::setUp();
+    }
+    public function tearDown() {
+        global $SESSION;
+        parent::tearDown();
+        if ($this->initiallang !== null) {
+            $SESSION->lang = $this->initiallang;
+            $this->initiallang = null;
+        } else {
+            unset($SESSION->lang);
+        }
+    }
+    function test_asort() {
+        $arr = array('b' => 'ab', 1 => 'aa', 0 => 'cc');
+        collatorlib::asort($arr);
+        $this->assertIdentical(array_keys($arr), array(1, 'b', 0));
+        $this->assertIdentical(array_values($arr), array('aa', 'ab', 'cc'));
+
+        $arr = array('a' => 'áb', 'b' => 'ab', 1 => 'aa', 0=>'cc');
+        collatorlib::asort($arr);
+        $this->assertIdentical(array_keys($arr), array(1, 'b', 'a', 0), $this->error);
+        $this->assertIdentical(array_values($arr), array('aa', 'ab', 'áb', 'cc'), $this->error);
+    }
+    function test_asort_objects_by_method() {
+        $objects = array(
+            'b' => new string_test_class('ab'),
+            1 => new string_test_class('aa'),
+            0 => new string_test_class('cc')
+        );
+        collatorlib::asort_objects_by_method($objects, 'get_protected_name');
+        $this->assertIdentical(array_keys($objects), array(1, 'b', 0));
+        $this->assertIdentical($this->get_ordered_names($objects, 'get_protected_name'), array('aa', 'ab', 'cc'));
+
+        $objects = array(
+            'a' => new string_test_class('áb'),
+            'b' => new string_test_class('ab'),
+            1 => new string_test_class('aa'),
+            0 => new string_test_class('cc')
+        );
+        collatorlib::asort_objects_by_method($objects, 'get_private_name');
+        $this->assertIdentical(array_keys($objects), array(1, 'b', 'a', 0), $this->error);
+        $this->assertIdentical($this->get_ordered_names($objects, 'get_private_name'), array('aa', 'ab', 'áb', 'cc'), $this->error);
+    }
+    function test_asort_objects_by_property() {
+        $objects = array(
+            'b' => new string_test_class('ab'),
+            1 => new string_test_class('aa'),
+            0 => new string_test_class('cc')
+        );
+        collatorlib::asort_objects_by_property($objects, 'publicname');
+        $this->assertIdentical(array_keys($objects), array(1, 'b', 0));
+        $this->assertIdentical($this->get_ordered_names($objects, 'publicname'), array('aa', 'ab', 'cc'));
+
+        $objects = array(
+            'a' => new string_test_class('áb'),
+            'b' => new string_test_class('ab'),
+            1 => new string_test_class('aa'),
+            0 => new string_test_class('cc')
+        );
+        collatorlib::asort_objects_by_property($objects, 'publicname');
+        $this->assertIdentical(array_keys($objects), array(1, 'b', 'a', 0), $this->error);
+        $this->assertIdentical($this->get_ordered_names($objects, 'publicname'), array('aa', 'ab', 'áb', 'cc'), $this->error);
+    }
+    protected function get_ordered_names($objects, $methodproperty = 'get_protected_name') {
+        $return = array();
+        foreach ($objects as $object) {
+            if ($methodproperty == 'publicname') {
+                $return[] = $object->publicname;
+            } else {
+                $return[] = $object->$methodproperty();
+            }
+        }
+        return $return;
+    }
+}
+/**
+ * Simple class used to work with the unit test.
+ *
+ * @package    core
+ * @subpackage lib
+ * @copyright  2011 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class string_test_class extends stdClass {
+    public $publicname;
+    protected $protectedname;
+    private $privatename;
+    public function __construct($name) {
+        $this->publicname = $name;
+        $this->protectedname = $name;
+        $this->privatename = $name;
+    }
+    public function get_protected_name() {
+        return $this->protectedname;
+    }
+    public function get_private_name() {
+        return $this->publicname;
+    }
+}
\ No newline at end of file
index 4dff46b..a488b8c 100644 (file)
@@ -550,18 +550,250 @@ class textlib {
     /**
      * Locale aware sorting, the key associations are kept, values are sorted alphabetically.
      *
-     * Note: this function is using current moodle locale.
+     * @param array $arr array to be sorted (reference)
+     * @param int $sortflag One of Collator::SORT_REGULAR, Collator::SORT_NUMERIC, Collator::SORT_STRING
+     * @return void modifies parameter
+     */
+    public static function asort(array &$arr, $sortflag = null) {
+        debugging('textlib::asort has been superseeded by collatorlib::asort please upgrade your code to use that', DEBUG_DEVELOPER);
+        collatorlib::asort($arr, $sortflag);
+    }
+}
+
+/**
+ * A collator class with static methods that can be used for sorting.
+ *
+ * @package    core
+ * @subpackage lib
+ * @copyright 2011 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class collatorlib {
+
+    /** @var Collator|false|null **/
+    protected static $collator = null;
+
+    /** @var string|null The locale that was used in instantiating the current collator **/
+    protected static $locale = null;
+
+    /**
+     * Ensures that a collator is available and created
+     *
+     * @return bool Returns true if collation is available and ready
+     */
+    protected static function ensure_collator_available() {
+        global $CFG;
+
+        $locale = get_string('locale', 'langconfig');
+        if (is_null(self::$collator) || $locale != self::$locale) {
+            self::$collator = false;
+            self::$locale = $locale;
+            if (class_exists('Collator', false)) {
+                $collator = new Collator($locale);
+                if (!empty($collator) && $collator instanceof Collator) {
+                    // Check for non fatal error messages. This has to be done immediately
+                    // after instantiation as any further calls to collation will cause
+                    // it to reset to 0 again (or another error code if one occurred)
+                    $errorcode = $collator->getErrorCode();
+                    $errormessage = $collator->getErrorMessage();
+                    // Check for an error code, 0 means no error occurred
+                    if ($errorcode !== 0) {
+                        // Get the actual locale being used, e.g. en, he, zh
+                        $localeinuse = $collator->getLocale(Locale::ACTUAL_LOCALE);
+                        // Check for the common fallback warning error codes. If this occurred
+                        // there is normally little to worry about:
+                        // - U_USING_DEFAULT_WARNING (127)  - default fallback locale used (pt => UCA)
+                        // - U_USING_FALLBACK_WARNING (128) - fallback locale used (de_CH => de)
+                        // (UCA: Unicode Collation Algorithm http://unicode.org/reports/tr10/)
+                        if ($errorcode === -127 || $errorcode === -128) {
+                            // Check if the locale in use is UCA default one ('root') or
+                            // if it is anything like the locale we asked for
+                            if ($localeinuse !== 'root' && strpos($locale, $localeinuse) !== 0) {
+                                // The locale we asked for is completely different to the locale
+                                // we have received, let the user know via debugging
+                                debugging('Invalid locale: "' . $locale . '", with warning (not fatal) "' . $errormessage .
+                                    '", falling back to "' . $collator->getLocale(Locale::VALID_LOCALE) . '"');
+                            } else {
+                                // Nothing to do here, this is expected!
+                                // The Moodle locale setting isn't what the collator expected but
+                                // it is smart enough to match the first characters of our locale
+                                // to find the correct locale or to use UCA collation
+                            }
+                        } else {
+                            // We've recieved some other sort of non fatal warning - let the
+                            // user know about it via debugging.
+                            debugging('Problem with locale: "' . $locale . '", with message "' . $errormessage .
+                                '", falling back to "' . $collator->getLocale(Locale::VALID_LOCALE) . '"');
+                        }
+                    }
+                    // Store the collator object now that we can be sure it is in a workable condition
+                    self::$collator = $collator;
+                } else {
+                    // Fatal error while trying to instantiate the collator... something went wrong
+                    debugging('Error instantiating collator for locale: "' . $locale . '", with error [' .
+                        intl_get_error_code() . '] ' . intl_get_error_message($collator));
+                }
+            }
+        }
+        return (self::$collator instanceof Collator);
+    }
+
+    /**
+     * Locale aware sorting, the key associations are kept, values are sorted alphabetically.
      *
-     * @param array $arr array to be sorted
-     * @return void, modifies parameter
+     * @param array $arr array to be sorted (reference)
+     * @param int $sortflag One of Collator::SORT_REGULAR, Collator::SORT_NUMERIC, Collator::SORT_STRING
+     * @return void modifies parameter
      */
-    public static function asort(array &$arr) {
-        if (function_exists('collator_asort')) {
-            if ($coll = collator_create(get_string('locale', 'langconfig'))) {
-                collator_asort($coll, $arr);
-                return;
+    public static function asort(array &$arr, $sortflag = null) {
+        if (self::ensure_collator_available()) {
+            if (!isset($sortflag)) {
+                $sortflag = Collator::SORT_REGULAR;
             }
+            self::$collator->asort($arr, $sortflag);
+            return;
         }
         asort($arr, SORT_LOCALE_STRING);
     }
+
+    /**
+     * Locale aware comparison of two strings.
+     *
+     * Returns:
+     *   1 if str1 is greater than str2
+     *   0 if str1 is equal to str2
+     *  -1 if str1 is less than str2
+     *
+     * @return int
+     */
+    public static function compare($str1, $str2) {
+        if (self::ensure_collator_available()) {
+            return self::$collator->compare($str1, $str2);
+        }
+        return strcmp($str1, $str2);
+    }
+
+    /**
+     * Locale aware sort of objects by a property in common to all objects
+     *
+     * @param array $objects An array of objects to sort (handled by reference)
+     * @param string $property The property to use for comparison
+     * @return bool True on success
+     */
+    public static function asort_objects_by_property(array &$objects, $property) {
+        $comparison = new collatorlib_property_comparison($property);
+        return uasort($objects, array($comparison, 'compare'));
+    }
+
+    /**
+     * Locale aware sort of objects by a method in common to all objects
+     *
+     * @param array $objects An array of objects to sort (handled by reference)
+     * @param string $method The method to call to generate a value for comparison
+     * @return bool True on success
+     */
+    public static function asort_objects_by_method(array &$objects, $method) {
+        $comparison = new collatorlib_method_comparison($method);
+        return uasort($objects, array($comparison, 'compare'));
+    }
+}
+
+/**
+ * Abstract class to aid the sorting of objects with respect to proper language
+ * comparison using collator
+ *
+ * @package    core
+ * @subpackage lib
+ * @copyright 2011 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class collatorlib_comparison {
+    /**
+     * This function will perform the actual comparison of values
+     * It must be overridden by the deriving class.
+     *
+     * Returns:
+     *   1 if str1 is greater than str2
+     *   0 if str1 is equal to str2
+     *  -1 if str1 is less than str2
+     *
+     * @param mixed $a The first something to compare
+     * @param mixed $b The second something to compare
+     * @return int
+     */
+    public abstract function compare($a, $b);
+}
+
+/**
+ * A comparison helper for comparing properties of two objects
+ *
+ * @package    core
+ * @subpackage lib
+ * @copyright 2011 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class collatorlib_property_comparison extends collatorlib_comparison {
+
+    /** @var string The property to sort by **/
+    protected $property;
+
+    /**
+     * @param string $property
+     */
+    public function __construct($property) {
+        $this->property = $property;
+    }
+
+    /**
+     * Returns:
+     *   1 if str1 is greater than str2
+     *   0 if str1 is equal to str2
+     *  -1 if str1 is less than str2
+     *
+     * @param mixed $obja The first object to compare
+     * @param mixed $objb The second object to compare
+     * @return int
+     */
+    public function compare($obja, $objb) {
+        $resulta = $obja->{$this->property};
+        $resultb = $objb->{$this->property};
+        return collatorlib::compare($resulta, $resultb);
+    }
+}
+
+/**
+ * A comparison helper for comparing the result of a method on two objects
+ *
+ * @package    core
+ * @subpackage lib
+ * @copyright 2011 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class collatorlib_method_comparison extends collatorlib_comparison {
+
+    /** @var string The method to use for comparison **/
+    protected $method;
+
+    /**
+     * @param string $method The method to call against each object
+     */
+    public function __construct($method) {
+        $this->method = $method;
+    }
+
+    /**
+     * Returns:
+     *   1 if str1 is greater than str2
+     *   0 if str1 is equal to str2
+     *  -1 if str1 is less than str2
+     *
+     * @param mixed $obja The first object to compare
+     * @param mixed $objb The second object to compare
+     * @return int
+     */
+    public function compare($obja, $objb) {
+        $resulta = $obja->{$this->method}();
+        $resultb = $objb->{$this->method}();
+        return collatorlib::compare($resulta, $resultb);
+    }
 }
index 889ebc6..2f18113 100644 (file)
@@ -2070,11 +2070,10 @@ class assignment_base {
      * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
      *
      * @param $coursemodule object The coursemodule object (record).
-     * @return object An object on information that the courses will know about (most noticeably, an icon).
-     *
+     * @return cached_cm_info Object used to customise appearance on course page
      */
     function get_coursemodule_info($coursemodule) {
-        return false;
+        return null;
     }
 
     /**
@@ -3341,13 +3340,13 @@ function assignment_get_all_submissions($assignment, $sort="", $dir="DESC") {
  * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
  *
  * @param $coursemodule object The coursemodule object (record).
- * @return object An object on information that the courses will know about (most noticeably, an icon).
- *
+ * @return cached_cm_info An object on information that the courses will know about (most noticeably, an icon).
  */
 function assignment_get_coursemodule_info($coursemodule) {
     global $CFG, $DB;
 
-    if (! $assignment = $DB->get_record('assignment', array('id'=>$coursemodule->instance), 'id, assignmenttype, name')) {
+    if (! $assignment = $DB->get_record('assignment', array('id'=>$coursemodule->instance),
+            'id, assignmenttype, name, intro, introformat')) {
         return false;
     }
 
@@ -3357,14 +3356,15 @@ function assignment_get_coursemodule_info($coursemodule) {
         require_once($libfile);
         $assignmentclass = "assignment_$assignment->assignmenttype";
         $ass = new $assignmentclass('staticonly');
-        if ($result = $ass->get_coursemodule_info($coursemodule)) {
-            return $result;
-        } else {
-            $info = new stdClass();
-            $info->name = $assignment->name;
-            return $info;
+        if (!($result = $ass->get_coursemodule_info($coursemodule))) {
+            $result = new cached_cm_info();
+            $result->name = $assignment->name;
         }
-
+        if ($coursemodule->showdescription) {
+            // Convert intro to html. Do not filter cached version, filters run at display time.
+            $info->content = format_module_intro('assignment', $assignment, $coursemodule->id, false);
+        }
+        return $result;
     } else {
         debugging('Incorrect assignment type: '.$assignment->assignmenttype);
         return false;
@@ -3669,6 +3669,7 @@ function assignment_supports($feature) {
         case FEATURE_GRADE_OUTCOMES:          return true;
         case FEATURE_GRADE_HAS_GRADE:         return true;
         case FEATURE_BACKUP_MOODLE2:          return true;
+        case FEATURE_SHOW_DESCRIPTION:        return true;
 
         default: return null;
     }
diff --git a/mod/chat/gui_ajax/theme/compact/input.png b/mod/chat/gui_ajax/theme/compact/input.png
new file mode 100644 (file)
index 0000000..2d7997c
Binary files /dev/null and b/mod/chat/gui_ajax/theme/compact/input.png differ
index a462b6e..fbab4dc 100644 (file)
@@ -1238,6 +1238,7 @@ function chat_supports($feature) {
         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
         case FEATURE_GRADE_HAS_GRADE:         return false;
         case FEATURE_GRADE_OUTCOMES:          return true;
+        case FEATURE_SHOW_DESCRIPTION:        return true;
 
         default: return null;
     }
index a03db26..6c247c2 100644 (file)
@@ -795,6 +795,7 @@ function choice_supports($feature) {
         case FEATURE_GRADE_HAS_GRADE:         return false;
         case FEATURE_GRADE_OUTCOMES:          return false;
         case FEATURE_BACKUP_MOODLE2:          return true;
+        case FEATURE_SHOW_DESCRIPTION:        return true;
 
         default: return null;
     }
index 78c864f..3794952 100644 (file)
@@ -2683,6 +2683,7 @@ function data_supports($feature) {
         case FEATURE_GRADE_OUTCOMES:          return true;
         case FEATURE_RATE:                    return true;
         case FEATURE_BACKUP_MOODLE2:          return true;
+        case FEATURE_SHOW_DESCRIPTION:        return true;
 
         default: return null;
     }
index bf046a3..c403112 100644 (file)
@@ -61,6 +61,7 @@ function feedback_supports($feature) {
         case FEATURE_GRADE_HAS_GRADE:         return false;
         case FEATURE_GRADE_OUTCOMES:          return false;
         case FEATURE_BACKUP_MOODLE2:          return true;
+        case FEATURE_SHOW_DESCRIPTION:        return true;
 
         default: return null;
     }
index 491e5ed..aaee017 100644 (file)
@@ -42,6 +42,7 @@ function folder_supports($feature) {
         case FEATURE_GRADE_HAS_GRADE:         return false;
         case FEATURE_GRADE_OUTCOMES:          return false;
         case FEATURE_BACKUP_MOODLE2:          return true;
+        case FEATURE_SHOW_DESCRIPTION:        return true;
 
         default: return null;
     }
index 8d0e7ec..da89d0a 100644 (file)
@@ -292,6 +292,7 @@ function forum_supports($feature) {
         case FEATURE_GRADE_OUTCOMES:          return true;
         case FEATURE_RATE:                    return true;
         case FEATURE_BACKUP_MOODLE2:          return true;
+        case FEATURE_SHOW_DESCRIPTION:        return true;
 
         default: return null;
     }
index 57e191e..89d992d 100644 (file)
@@ -2691,6 +2691,7 @@ function glossary_supports($feature) {
         case FEATURE_GRADE_OUTCOMES:          return true;
         case FEATURE_RATE:                    return true;
         case FEATURE_BACKUP_MOODLE2:          return true;
+        case FEATURE_SHOW_DESCRIPTION:        return true;
 
         default: return null;
     }
index af804cf..d7ae547 100644 (file)
@@ -42,6 +42,7 @@ function imscp_supports($feature) {
         case FEATURE_GRADE_HAS_GRADE:         return false;
         case FEATURE_GRADE_OUTCOMES:          return false;
         case FEATURE_BACKUP_MOODLE2:          return true;
+        case FEATURE_SHOW_DESCRIPTION:        return true;
 
         default: return null;
     }
index aa39ad8..6936d4d 100644 (file)
@@ -91,7 +91,7 @@ function imscp_parse_structure($imscp, $context) {
         return null;
     }
 
-    imscp_parse_manifestfile($manifestfile->get_content());
+    return imscp_parse_manifestfile($manifestfile->get_content());
 }
 
 /**
index 1d670c9..9a867e1 100644 (file)
@@ -560,7 +560,7 @@ if ($action === 'delete') {
         $table->data[] = array($fontstart.get_string("question", "lesson").": <br />".$fontend.$fontstart2.$page->contents.$fontend2, " ");
         $table->data[] = array($fontstart.get_string("answer", "lesson").":".$fontend, ' ');
         // apply the font to each answer
-        if (!empty($page->answerdata) && isset($page->answerdata->response)) {
+        if (!empty($page->answerdata)) {
             foreach ($page->answerdata->answers as $answer){
                 $modified = array();
                 foreach ($answer as $single) {
@@ -569,12 +569,12 @@ if ($action === 'delete') {
                 }
                 $table->data[] = $modified;
             }
-            if ($page->answerdata->response != NULL) {
+            if (isset($page->answerdata->response)) {
                 $table->data[] = array($fontstart.get_string("response", "lesson").": <br />".$fontend.$fontstart2.format_text($page->answerdata->response,$page->answerdata->responseformat,$formattextdefoptions).$fontend2, " ");
             }
             $table->data[] = array($page->answerdata->score, " ");
         } else {
-            $table->data[] = array(0, " ");
+            $table->data[] = array(get_string('didnotanswerquestion', 'lesson'), " ");
         }
         echo html_writer::table($table);
     }
index 2fcd026..bfa5115 100644 (file)
@@ -40,6 +40,7 @@ function page_supports($feature) {
         case FEATURE_GRADE_HAS_GRADE:         return false;
         case FEATURE_GRADE_OUTCOMES:          return false;
         case FEATURE_BACKUP_MOODLE2:          return true;
+        case FEATURE_SHOW_DESCRIPTION:        return true;
 
         default: return null;
     }
@@ -247,20 +248,26 @@ function page_get_participants($pageid) {
  *
  * See {@link get_array_of_activities()} in course/lib.php
  *
- * @param object $coursemodule
- * @return object info
+ * @param cm_info $coursemodule
+ * @return cached_cm_info Info to customise main page display
  */
 function page_get_coursemodule_info($coursemodule) {
     global $CFG, $DB;
     require_once("$CFG->libdir/resourcelib.php");
 
-    if (!$page = $DB->get_record('page', array('id'=>$coursemodule->instance), 'id, name, display, displayoptions')) {
+    if (!$page = $DB->get_record('page', array('id'=>$coursemodule->instance),
+            'id, name, display, displayoptions, intro, introformat')) {
         return NULL;
     }
 
-    $info = new stdClass();
+    $info = new cached_cm_info();
     $info->name = $page->name;
 
+    if ($coursemodule->showdescription) {
+        // Convert intro to html. Do not filter cached version, filters run at display time.
+        $info->content = format_module_intro('page', $page, $coursemodule->id, false);
+    }
+
     if ($page->display != RESOURCELIB_DISPLAY_POPUP) {
         return $info;
     }
@@ -270,7 +277,7 @@ function page_get_coursemodule_info($coursemodule) {
     $width  = empty($options['popupwidth'])  ? 620 : $options['popupwidth'];
     $height = empty($options['popupheight']) ? 450 : $options['popupheight'];
     $wh = "width=$width,height=$height,toolbar=no,location=no,menubar=no,copyhistory=no,status=no,directories=no,scrollbars=yes,resizable=yes";
-    $info->extra = "onclick=\"window.open('$fullurl', '', '$wh'); return false;\"";
+    $info->onclick = "window.open('$fullurl', '', '$wh'); return false;";
 
     return $info;
 }
index a81aa5e..a31ea98 100644 (file)
@@ -1494,6 +1494,7 @@ function quiz_supports($feature) {
         case FEATURE_GRADE_HAS_GRADE:         return true;
         case FEATURE_GRADE_OUTCOMES:          return true;
         case FEATURE_BACKUP_MOODLE2:          return true;
+        case FEATURE_SHOW_DESCRIPTION:        return true;
 
         default: return null;
     }
index 03c3b72..e357e46 100644 (file)
@@ -40,6 +40,7 @@ function resource_supports($feature) {
         case FEATURE_GRADE_HAS_GRADE:         return false;
         case FEATURE_GRADE_OUTCOMES:          return false;
         case FEATURE_BACKUP_MOODLE2:          return true;
+        case FEATURE_SHOW_DESCRIPTION:        return true;
 
         default: return null;
     }
@@ -226,8 +227,8 @@ function resource_get_participants($resourceid) {
  *
  * See {@link get_array_of_activities()} in course/lib.php
  *
- * @param object $coursemodule
- * @return object info
+ * @param cm_info $coursemodule
+ * @return cached_cm_info info
  */
 function resource_get_coursemodule_info($coursemodule) {
     global $CFG, $DB;
@@ -237,12 +238,17 @@ function resource_get_coursemodule_info($coursemodule) {
 
     $context = get_context_instance(CONTEXT_MODULE, $coursemodule->id);
 
-    if (!$resource = $DB->get_record('resource', array('id'=>$coursemodule->instance), 'id, name, display, displayoptions, tobemigrated, revision')) {
+    if (!$resource = $DB->get_record('resource', array('id'=>$coursemodule->instance),
+            'id, name, display, displayoptions, tobemigrated, revision, intro, introformat')) {
         return NULL;
     }
 
-    $info = new stdClass();
+    $info = new cached_cm_info();
     $info->name = $resource->name;
+    if ($coursemodule->showdescription) {
+        // Convert intro to html. Do not filter cached version, filters run at display time.
+        $info->content = format_module_intro('resource', $resource, $coursemodule->id, false);
+    }
 
     if ($resource->tobemigrated) {
         $info->icon ='i/cross_red_big';
@@ -264,15 +270,15 @@ function resource_get_coursemodule_info($coursemodule) {
         $width  = empty($options['popupwidth'])  ? 620 : $options['popupwidth'];
         $height = empty($options['popupheight']) ? 450 : $options['popupheight'];
         $wh = "width=$width,height=$height,toolbar=no,location=no,menubar=no,copyhistory=no,status=no,directories=no,scrollbars=yes,resizable=yes";
-        $info->extra = "onclick=\"window.open('$fullurl', '', '$wh'); return false;\"";
+        $info->onclick = "window.open('$fullurl', '', '$wh'); return false;";
 
     } else if ($display == RESOURCELIB_DISPLAY_NEW) {
         $fullurl = "$CFG->wwwroot/mod/resource/view.php?id=$coursemodule->id&amp;redirect=1";
-        $info->extra = "onclick=\"window.open('$fullurl'); return false;\"";
+        $info->onclick = "window.open('$fullurl'); return false;";
 
     } else if ($display == RESOURCELIB_DISPLAY_OPEN) {
         $fullurl = "$CFG->wwwroot/mod/resource/view.php?id=$coursemodule->id&amp;redirect=1";
-        $info->extra = "onclick=\"window.location.href ='$fullurl';return false;\"";
+        $info->onclick = "window.location.href ='$fullurl';return false;";
 
     } else if ($display == RESOURCELIB_DISPLAY_DOWNLOAD) {
         if (empty($mainfile)) {
@@ -293,7 +299,7 @@ function resource_get_coursemodule_info($coursemodule) {
         if ($completion->is_enabled($coursemodule) == COMPLETION_TRACKING_AUTOMATIC) {
             $fullurl = "$CFG->wwwroot/mod/resource/view.php?id=$coursemodule->id&amp;redirect=1";
         }
-        $info->extra = "onclick=\"window.open('$fullurl'); return false;\"";
+        $info->onclick = "window.open('$fullurl'); return false;";
     }
 
     return $info;
index 8e3dfb2..f61820e 100644 (file)
@@ -940,6 +940,7 @@ function scorm_supports($feature) {
         case FEATURE_GRADE_HAS_GRADE:         return true;
         case FEATURE_GRADE_OUTCOMES:          return true;
         case FEATURE_BACKUP_MOODLE2:          return true;
+        case FEATURE_SHOW_DESCRIPTION:        return true;
 
         default: return null;
     }
@@ -1123,4 +1124,4 @@ function scorm_version_check($scormversion, $version='') {
         }
     }
     return false;
-}
\ No newline at end of file
+}
index bed1fa1..15e5483 100644 (file)
@@ -806,6 +806,7 @@ function survey_supports($feature) {
         case FEATURE_GRADE_HAS_GRADE:         return false;
         case FEATURE_GRADE_OUTCOMES:          return false;
         case FEATURE_BACKUP_MOODLE2:          return true;
+        case FEATURE_SHOW_DESCRIPTION:        return true;
 
         default: return null;
     }
index f948872..ffd77b2 100644 (file)
@@ -57,4 +57,5 @@ optional - no changes needed in older code:
 
 required changes in code:
 * fix missing parameter types in optional_param() and required_param()
-* use new optional_param_array(), required_param_array() or clean_param_array() when dealing with array parameters
\ No newline at end of file
+* use new optional_param_array(), required_param_array() or clean_param_array() when dealing with array parameters
+* textlib->asort() replaced by specialized collatorlib::asort()
index e354511..ac96658 100644 (file)
@@ -42,6 +42,7 @@ function url_supports($feature) {
         case FEATURE_GRADE_HAS_GRADE:         return false;
         case FEATURE_GRADE_OUTCOMES:          return false;
         case FEATURE_BACKUP_MOODLE2:          return true;
+        case FEATURE_SHOW_DESCRIPTION:        return true;
 
         default: return null;
     }
@@ -260,11 +261,12 @@ function url_get_coursemodule_info($coursemodule) {
     global $CFG, $DB;
     require_once("$CFG->dirroot/mod/url/locallib.php");
 
-    if (!$url = $DB->get_record('url', array('id'=>$coursemodule->instance), 'id, name, display, displayoptions, externalurl, parameters')) {
+    if (!$url = $DB->get_record('url', array('id'=>$coursemodule->instance),
+            'id, name, display, displayoptions, externalurl, parameters, intro, introformat')) {
         return NULL;
     }
 
-    $info = new stdClass();
+    $info = new cached_cm_info();
     $info->name = $url->name;
 
     //note: there should be a way to differentiate links from normal resources
@@ -278,15 +280,20 @@ function url_get_coursemodule_info($coursemodule) {
         $width  = empty($options['popupwidth'])  ? 620 : $options['popupwidth'];
         $height = empty($options['popupheight']) ? 450 : $options['popupheight'];
         $wh = "width=$width,height=$height,toolbar=no,location=no,menubar=no,copyhistory=no,status=no,directories=no,scrollbars=yes,resizable=yes";
-        $info->extra = "onclick=\"window.open('$fullurl', '', '$wh'); return false;\"";
+        $info->onclick = "window.open('$fullurl', '', '$wh'); return false;";
 
     } else if ($display == RESOURCELIB_DISPLAY_NEW) {
         $fullurl = "$CFG->wwwroot/mod/url/view.php?id=$coursemodule->id&amp;redirect=1";
-        $info->extra = "onclick=\"window.open('$fullurl'); return false;\"";
+        $info->onclick = "window.open('$fullurl'); return false;";
 
     } else if ($display == RESOURCELIB_DISPLAY_OPEN) {
         $fullurl = "$CFG->wwwroot/mod/url/view.php?id=$coursemodule->id&amp;redirect=1";
-        $info->extra = "onclick=\"window.location.href ='$fullurl';return false;\"";
+        $info->onclick = "window.location.href ='$fullurl';return false;";
+    }
+
+    if ($coursemodule->showdescription) {
+        // Convert intro to html. Do not filter cached version, filters run at display time.
+        $info->content = format_module_intro('url', $url, $coursemodule->id, false);
     }
 
     return $info;
diff --git a/mod/wiki/admin.php b/mod/wiki/admin.php
new file mode 100644 (file)
index 0000000..1b8ea47
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Delete wiki pages or versions
+ *
+ * This will show options for deleting wiki pages or purging page versions
+ * If user have wiki:managewiki ability then only this page will show delete
+ * options
+ *
+ * @package mod-wiki-2.0
+ * @copyright 2011 Rajesh Taneja
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once('../../config.php');
+require_once($CFG->dirroot . '/mod/wiki/lib.php');
+require_once($CFG->dirroot . '/mod/wiki/locallib.php');
+require_once($CFG->dirroot . '/mod/wiki/pagelib.php');
+
+$pageid = required_param('pageid', PARAM_INT); // Page ID
+$delete = optional_param('delete', 0, PARAM_INT); // ID of the page to be deleted.
+$option = optional_param('option', 1, PARAM_INT); // Option ID
+$listall = optional_param('listall', 0, PARAM_INT); // list all pages
+$toversion = optional_param('toversion', 0, PARAM_INT); // max version to be deleted
+$fromversion = optional_param('fromversion', 0, PARAM_INT); // min version to be deleted
+
+if (!$page = wiki_get_page($pageid)) {
+    print_error('incorrectpageid', 'wiki');
+}
+if (!$subwiki = wiki_get_subwiki($page->subwikiid)) {
+    print_error('incorrectsubwikiid', 'wiki');
+}
+if (!$cm = get_coursemodule_from_instance("wiki", $subwiki->wikiid)) {
+    print_error('invalidcoursemodule');
+}
+$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
+if (!$wiki = wiki_get_wiki($subwiki->wikiid)) {
+    print_error('incorrectwikiid', 'wiki');
+}
+
+require_login($course->id, true, $cm);
+
+
+$context = get_context_instance(CONTEXT_MODULE, $cm->id);
+require_capability('mod/wiki:managewiki', $context);
+add_to_log($course->id, "wiki", "admin", "admin.php?id=$cm->id", "$wiki->id");
+
+//Delete page if a page ID to delete was supplied
+if (!empty($delete) && confirm_sesskey()) {
+    wiki_delete_pages($context, $delete, $page->subwikiid);
+    //when current wiki page is deleted, then redirect user to create that page, as
+    //current pageid is invalid after deletion.
+    if ($pageid == $delete) {
+        $params = array('swid' => $page->subwikiid, 'title' => $page->title);
+        $url = new moodle_url('/mod/wiki/create.php', $params);
+        redirect($url);
+    }
+}
+
+//delete version if toversion and fromversion are set.
+if (!empty($toversion) && !empty($fromversion) && confirm_sesskey()) {
+    //make sure all versions should not be deleted...
+    $versioncount = wiki_count_wiki_page_versions($pageid);
+    $versioncount -= 1; //ignore version 0
+    $totalversionstodelete = $toversion - $fromversion;
+    $totalversionstodelete += 1; //added 1 as toversion should be included
+
+    if (($totalversionstodelete >= $versioncount) || ($versioncount <= 1)) {
+        print_error('incorrectdeleteversions', 'wiki');
+    } else {
+        $versions = array();
+        for ($i = $fromversion; $i <= $toversion; $i++) {
+            //Add all version to deletion list which exist
+            if (wiki_get_wiki_page_version($pageid, $i)) {
+                array_push($versions, $i);
+            }
+        }
+        $purgeversions[$pageid] = $versions;
+        wiki_delete_page_versions($purgeversions);
+    }
+}
+
+//show actual page
+$wikipage = new page_wiki_admin($wiki, $subwiki, $cm);
+
+$wikipage->set_page($page);
+$wikipage->print_header();
+$wikipage->set_view($option, empty($listall)?true:false);
+$wikipage->print_content();
+
+$wikipage->print_footer();
\ No newline at end of file
index 8f93ace..e2ce468 100644 (file)
@@ -10,6 +10,8 @@
  * @package wiki
  */
 $string['addcomment'] = 'Add comment';
+$string['admin'] = 'Administration';
+$string['adminmenu'] = 'Admin menu';
 $string['attachmentattach'] = 'Add as attachment';
 $string['attachmentimage'] = 'Add as image';
 $string['attachmentlink'] = 'Add as link';
@@ -43,6 +45,7 @@ $string['deletecomment'] = 'Deleting comment';
 $string['deleteupload'] = 'Delete';
 $string['deletedbegins'] = 'Deleted begins';
 $string['deletedends'] = 'Deleted ends';
+$string['deleteversions'] = 'Delete page versions';
 $string['addedbegins'] = 'added begins';
 $string['addedends'] = 'added ends';
 $string['diff'] = 'Diff';
@@ -82,6 +85,7 @@ $string['formatnwiki_link'] = 'mod/wiki/nwiki';
 $string['history'] = 'History';
 $string['history_help'] = 'The history lists links to previous versions of the page.';
 $string['html'] = 'HTML';
+$string['incorrectdeleteversions'] = "Page versions provided for deletion are incorrect.";
 $string['insertcomment'] = 'Insert comment';
 $string['insertimage'] = 'Insert an image...';
 $string['insertimage_help'] = 'This drop-down list will insert an image to the wiki editor. If you need to add more images to the wiki, please use "Files" tab.';
@@ -93,6 +97,8 @@ $string['javascriptdisabledlocks'] = 'Javascript is disabled on your browser and
 $string['lockingajaxtimeout'] = 'Edit page locking refresh time';
 $string['lockingtimeout'] = 'Locking timeout';
 $string['links'] = 'Links';
+$string['listall'] = 'List all';
+$string['listorphan'] = 'List orphan';
 $string['map'] = 'Map';
 $string['mapmenu'] = 'Map menu';
 $string['migrationfinished'] = 'Migration finished successfully';
@@ -160,6 +166,7 @@ $string['reparsetimeout'] = 'Reparsing default timeout';
 $string['repeatedsection'] = 'Wiki error: Section name cannot be repeated \'{$a}\'';
 $string['restore'] = 'Restore';
 $string['removeallwikitags'] = 'Remove all wiki tags';
+$string['removepages'] = 'Remove pages';
 $string['restoreconfirm'] = 'Are you sure you want to restore version #{$a}?';
 $string['restoreerror'] = 'Version #{$a} could not be restored';
 $string['restorethis'] = 'Restore this version';
index 1428819..72a266b 100644 (file)
@@ -254,6 +254,8 @@ function wiki_supports($feature) {
         return false;
     case FEATURE_BACKUP_MOODLE2:
         return true;
+    case FEATURE_SHOW_DESCRIPTION:
+        return true;
 
     default:
         return null;
@@ -557,6 +559,11 @@ function wiki_extend_navigation(navigation_node $navref, $course, $module, $cm)
             $link = new moodle_url('/mod/wiki/files.php', array('pageid' => $pageid));
             $node = $navref->add(get_string('files', 'wiki'), $link, navigation_node::TYPE_SETTING);
         }
+
+        if (has_capability('mod/wiki:managewiki', $context)) {
+            $link = new moodle_url('/mod/wiki/admin.php', array('pageid' => $pageid));
+            $node = $navref->add(get_string('admin', 'wiki'), $link, navigation_node::TYPE_SETTING);
+        }
     }
 }
 /**
index 6bd8768..0bd0240 100644 (file)
@@ -1018,6 +1018,136 @@ function wiki_delete_old_locks() {
     $DB->delete_records_select('wiki_locks', "lockedat < ?", array(time() - 3600));
 }
 
+/**
+ * Deletes wiki_links. It can be sepecific link or links attached in subwiki
+ *
+ * @global mixed $DB database object
+ * @param int $linkid id of the link to be deleted
+ * @param int $topageid links to the specific page
+ * @param int $frompageid links from specific page
+ * @param int $subwikiid links to subwiki
+ */
+function wiki_delete_links($linkid = null, $topageid = null, $frompageid = null, $subwikiid = null) {
+    global $DB;
+    $params = array();
+
+    // if link id is givien then don't check for anything else
+    if (!empty($linkid)) {
+        $params['id'] = $linkid;
+    } else {
+        if (!empty($topageid)) {
+            $params['topageid'] = $topageid;
+        }
+        if (!empty($frompageid)) {
+            $params['frompageid'] = $frompageid;
+        }
+        if (!empty($subwikiid)) {
+            $params['subwikiid'] = $subwikiid;
+        }
+    }
+
+    //Delete links if any params are passed, else nothing to delete.
+    if (!empty($params)) {
+        $DB->delete_records('wiki_links', $params);
+    }
+}
+
+/**
+ * Delete wiki synonyms related to subwikiid or page
+ *
+ * @param int $subwikiid id of sunbwiki
+ * @param int $pageid id of page
+ */
+function wiki_delete_synonym($subwikiid, $pageid = null) {
+    global $DB;
+
+    $params = array('subwikiid' => $subwikiid);
+    if (!is_null($pageid)) {
+        $params['pageid'] = $pageid;
+    }
+    $DB->delete_records('wiki_synonyms', $params, IGNORE_MISSING);
+}
+
+/**
+ * Delete pages and all related data
+ *
+ * @param mixed $context context in which page needs to be deleted.
+ * @param mixed $pageids id's of pages to be deleted
+ * @param int $subwikiid id of the subwiki for which all pages should be deleted
+ */
+function wiki_delete_pages($context, $pageids = null, $subwikiid = null) {
+    global $DB;
+
+    if (!empty($pageids) && is_int($pageids)) {
+       $pageids = array($pageids);
+    } else if (!empty($subwikiid)) {
+        $pageids = wiki_get_page_list($subwikiid);
+    }
+
+    //If there is no pageid then return as we can't delete anything.
+    if (empty($pageids)) {
+        return;
+    }
+
+    /// Delete page and all it's relevent data
+    foreach ($pageids as $pageid) {
+        if (is_object($pageid)) {
+            $pageid = $pageid->id;
+        }
+
+        //Delete page comments
+        $comments = wiki_get_comments($context->id, $pageid);
+        foreach ($comments as $commentid => $commentvalue) {
+            wiki_delete_comment($commentid, $context, $pageid);
+        }
+
+        //Delete page tags
+        $tags = tag_get_tags_array('wiki_pages', $pageid);
+        foreach ($tags as $tagid => $tagvalue) {
+            tag_delete_instance('wiki_pages', $pageid, $tagid);
+        }
+
+        //Delete Synonym
+        wiki_delete_synonym($subwikiid, $pageid);
+
+        //Delete all page versions
+        wiki_delete_page_versions(array($pageid=>array(0)));
+
+        //Delete all page locks
+        wiki_delete_locks($pageid);
+
+        //Delete all page links
+        wiki_delete_links(null, $pageid);
+
+        //Delete page
+        $params = array('id' => $pageid);
+        $DB->delete_records('wiki_pages', $params);
+    }
+}
+
+/**
+ * Delete specificed versions of a page or versions created by users
+ * if version is 0 then it will remove all versions of the page
+ *
+ * @param array $deleteversions delete versions for a page
+ */
+function wiki_delete_page_versions($deleteversions) {
+    global $DB;
+
+    /// delete page-versions
+    foreach ($deleteversions as $id => $versions) {
+        foreach ($versions as $version) {
+            $params = array('pageid' => $id);
+            //If version = 0, then remove all versions of this page, else remove
+            //specified version
+            if ($version != 0) {
+                $params['version'] = $version;
+            }
+            $DB->delete_records('wiki_versions', $params, IGNORE_MISSING);
+        }
+    }
+}
+
 function wiki_get_comment($commentid){
     global $DB;
     return $DB->get_record('comments', array('id' => $commentid));
index 963f3b5..8937952 100644 (file)
@@ -77,6 +77,42 @@ M.mod_wiki.history = function(Y, args) {
     }
 }
 
+M.mod_wiki.deleteversion = function(Y, args) {
+    var fromversion = false;
+    var toversion = false;
+    var radio  = document.getElementsByName('fromversion');
+    var radio2 = document.getElementsByName('toversion');
+    var length = radio.length;
+    //version to should be more then version from
+    for (var i = 0; i < radio.length; i++) {
+        //if from-version is selected then disable all to-version options after that.
+        if (fromversion) {
+            radio2[i].disabled = true;
+        } else {
+            radio2[i].disabled = false;
+        }
+        //check when to-version option is selected
+        if (radio2[i].checked) {
+            toversion = true;
+        }
+        //make sure to-version should be >= from-version
+        if (radio[i].checked) {
+            fromversion = true;
+            if (!toversion) {
+                radio2[i].checked = true;
+            }
+        }
+    }
+    //avoid selecting first and last version
+    if (radio[0].checked && radio2[length-1].checked) {
+        radio2[length - 2].checked = true;
+    } else if(radio[length - 1].checked && radio2[0].checked) {
+        radio2[1].checked = true;
+        radio2[0].disabled = true;
+        toversion = true;
+    }
+}
+
 M.mod_wiki.init_tree = function(Y, expand_all, htmlid) {
     Y.use('yui2-treeview', function(Y) {
         var tree = new YAHOO.widget.TreeView(htmlid);
index a81f685..0c7c8b9 100644 (file)
@@ -76,7 +76,8 @@ abstract class page_wiki {
      * @var array The tabs set used in wiki module
      */
     protected $tabs = array('view' => 'view', 'edit' => 'edit', 'comments' => 'comments',
-                            'history' => 'history', 'map' => 'map', 'files' => 'files');
+                            'history' => 'history', 'map' => 'map', 'files' => 'files',
+                            'admin' => 'admin');
     /**
      * @var array tabs options
      */
@@ -2269,3 +2270,338 @@ class page_wiki_overridelocks extends page_wiki_edit {
     }
 
 }
+
+/**
+ * This class will let user to delete wiki pages and page versions
+ *
+ */
+class page_wiki_admin extends page_wiki {
+
+    public $view, $action;
+    public $listorphan = false;
+
+    /**
+     * Constructor
+     *
+     * @global object $PAGE
+     * @param mixed $wiki instance of wiki
+     * @param mixed $subwiki instance of subwiki
+     * @param stdClass $cm course module
+     */
+    function __construct($wiki, $subwiki, $cm) {
+        global $PAGE;
+        parent::__construct($wiki, $subwiki, $cm);
+        $PAGE->requires->js_init_call('M.mod_wiki.deleteversion', null, true);
+    }
+
+    /**
+     * Prints header for wiki page
+     */
+    function print_header() {
+        parent::print_header();
+        $this->print_pagetitle();
+    }
+
+    /**
+     * This function will display administration view to users with managewiki capability
+     */
+    function print_content() {
+        //make sure anyone trying to access this page has managewiki capabilities
+        require_capability('mod/wiki:managewiki', $this->modcontext, NULL, true, 'noviewpagepermission', 'wiki');
+
+        //update wiki cache if timedout
+        $page = $this->page;
+        if ($page->timerendered + WIKI_REFRESH_CACHE_TIME < time()) {
+            $fresh = wiki_refresh_cachedcontent($page);
+            $page = $fresh['page'];
+        }
+
+        //dispaly admin menu
+        echo $this->wikioutput->menu_admin($this->page->id, $this->view);
+
+        //Display appropriate admin view
+        switch ($this->view) {
+            case 1: //delete page view
+                $this->print_delete_content($this->listorphan);
+                break;
+            case 2: //delete version view
+                $this->print_delete_version();
+                break;
+            default: //default is delete view
+                $this->print_delete_content($this->listorphan);
+                break;
+        }
+    }
+
+    /**
+     * Sets admin view option
+     *
+     * @param int $view page view id
+     * @param bool $listorphan is only valid for view 1.
+     */
+    public function set_view($view, $listorphan = true) {
+        $this->view = $view;
+        $this->listorphan = $listorphan;
+    }
+
+    /**
+     * Sets page url
+     *
+     * @global object $PAGE
+     * @global object $CFG
+     */
+    function set_url() {
+        global $PAGE, $CFG;
+        $PAGE->set_url($CFG->wwwroot . '/mod/wiki/admin.php', array('pageid' => $this->page->id));
+    }
+
+    /**
+     * sets navigation bar for the page
+     *
+     * @global object $PAGE
+     */
+    protected function create_navbar() {
+        global $PAGE;
+
+        parent::create_navbar();
+        $PAGE->navbar->add(get_string('admin', 'wiki'));
+    }
+
+    /**
+     * Show wiki page delete options
+     *
+     * @param bool $showorphan
+     */
+    protected function print_delete_content($showorphan = true) {
+        $contents = array();
+        $table = new html_table();
+        $table->head = array('','Page name');
+        $table->attributes['class'] = 'generaltable mdl-align';
+        $swid = $this->subwiki->id;
+        if ($showorphan) {
+            if ($orphanedpages = wiki_get_orphaned_pages($swid)) {
+                $this->add_page_delete_options($orphanedpages, $swid, $table);
+            } else {
+                $table->data[] = array('', get_string('noorphanedpages', 'wiki'));
+            }
+        } else {
+            if ($pages = wiki_get_page_list($swid)) {
+                $this->add_page_delete_options($pages, $swid, $table);
+            } else {
+                $table->data[] = array('', get_string('nopages', 'wiki'));
+            }
+        }
+
+        ///Print the form
+        echo html_writer::start_tag('form', array(
+                                                'action' => new moodle_url('/mod/wiki/admin.php'),
+                                                'method' => 'post'));
+        echo html_writer::tag('div', html_writer::empty_tag('input', array(
+                                                                         'type'  => 'hidden',
+                                                                         'name'  => 'pageid',
+                                                                         'value' => $this->page->id)));
+
+        echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'option', 'value' => $this->view));
+        echo html_writer::table($table);
+        echo html_writer::start_tag('div', array('class' => 'mdl-align'));
+        if (!$showorphan) {
+            echo html_writer::empty_tag('input', array(
+                                                     'type'    => 'submit',
+                                                     'class'   => 'wiki_form-button',
+                                                     'value'   => get_string('listorphan', 'wiki'),
+                                                     'sesskey' => sesskey()));
+        } else {
+            echo html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'listall', 'value'=>'1'));
+            echo html_writer::empty_tag('input', array(
+                                                     'type'    => 'submit',
+                                                     'class'   => 'wiki_form-button',
+                                                     'value'   => get_string('listall', 'wiki'),
+                                                     'sesskey' => sesskey()));
+        }
+        echo html_writer::end_tag('div');
+        echo html_writer::end_tag('form');
+    }
+
+    /**
+     * helper function for print_delete_content. This will add data to the table.
+     *
+     * @global object $OUTPUT
+     * @param array $pages objects of wiki pages in subwiki
+     * @param int $swid id of subwiki
+     * @param object $table reference to the table in which data needs to be added
+     */
+    protected function add_page_delete_options($pages, $swid, &$table) {
+        global $OUTPUT;
+        foreach ($pages as $page) {
+            $link = wiki_parser_link($page->title, array('swid' => $swid));
+            $class = ($link['new']) ? 'class="wiki_newentry"' : '';
+            $pagelink = '<a href="' . $link['url'] . '"' . $class . '>' . format_string($link['content']) . '</a>';
+            $urledit = new moodle_url('/mod/wiki/edit.php', array('pageid' => $page->id, 'sesskey' => sesskey()));
+            $urldelete = new moodle_url('/mod/wiki/admin.php', array(
+                                                                   'pageid'  => $this->page->id,
+                                                                   'delete'  => $page->id,
+                                                                   'option'  => $this->view,
+                                                                   'listall' => !$this->listorphan?'1': '',
+                                                                   'sesskey' => sesskey()));
+
+            $editlinks = $OUTPUT->action_icon($urledit, new pix_icon('t/edit', get_string('edit')));
+            $editlinks .= $OUTPUT->action_icon($urldelete, new pix_icon('t/delete', get_string('delete')));
+            $table->data[] = array($editlinks, $pagelink);
+        }
+    }
+
+    /**
+     * Prints lists of versions which can be deleted
+     *
+     * @global object $OUTPUT
+     */
+    private function print_delete_version() {
+        global $OUTPUT;
+        $pageid = $this->page->id;
+
+        // versioncount is the latest version
+        $versioncount = wiki_count_wiki_page_versions($pageid) - 1;
+        $versions = wiki_get_wiki_page_versions($pageid, 0, $versioncount);
+
+        // We don't want version 0 to be displayed
+        // version 0 is blank page
+        if (end($versions)->version == 0) {
+            array_pop($versions);
+        }
+
+        $contents = array();
+        $version0page = wiki_get_wiki_page_version($this->page->id, 0);
+        $creator = wiki_get_user_info($version0page->userid);
+        $a = new stdClass();
+        $a->date = userdate($this->page->timecreated, get_string('strftimedaydatetime', 'langconfig'));
+        $a->username = $creator->username;
+        echo $OUTPUT->heading(get_string('createddate', 'wiki', $a), 4, 'wiki_headingtime');
+        if ($versioncount > 0) {
+            /// If there is only one version, we don't need radios nor forms
+            if (count($versions) == 1) {
+                $row = array_shift($versions);
+                $username = wiki_get_user_info($row->userid);
+                $picture = $OUTPUT->user_picture($username);
+                $date = userdate($row->timecreated, get_string('strftimedate', 'langconfig'));
+                $time = userdate($row->timecreated, get_string('strftimetime', 'langconfig'));
+                $versionid = wiki_get_version($row->id);
+                $versionlink = new moodle_url('/mod/wiki/viewversion.php', array('pageid' => $pageid, 'versionid' => $versionid->id));
+                $userlink = new moodle_url('/user/view.php', array('id' => $username->id));
+                $picturelink = $picture . html_writer::link($userlink->out(false), fullname($username));
+                $historydate = $OUTPUT->container($date, 'wiki_histdate');
+                $contents[] = array('', html_writer::link($versionlink->out(false), $row->version), $picturelink, $time, $historydate);
+
+                //Show current version
+                $table = new html_table();
+                $table->head = array('', get_string('version'), get_string('user'), get_string('modified'), '');
+                $table->data = $contents;
+                $table->attributes['class'] = 'mdl-align';
+
+                echo html_writer::table($table);
+            } else {
+                $lastdate = '';
+                $rowclass = array();
+
+                foreach ($versions as $version) {
+                    $user = wiki_get_user_info($version->userid);
+                    $picture = $OUTPUT->user_picture($user, array('popup' => true));
+                    $date = userdate($version->timecreated, get_string('strftimedate'));
+                    if ($date == $lastdate) {
+                        $date = '';
+                        $rowclass[] = '';
+                    } else {
+                        $lastdate = $date;
+                        $rowclass[] = 'wiki_histnewdate';
+                    }
+
+                    $time = userdate($version->timecreated, get_string('strftimetime', 'langconfig'));
+                    $versionid = wiki_get_version($version->id);
+                    if ($versionid) {
+                        $url = new moodle_url('/mod/wiki/viewversion.php', array('pageid' => $pageid, 'versionid' => $versionid->id));
+                        $viewlink = html_writer::link($url->out(false), $version->version);
+                    } else {
+                        $viewlink = $version->version;
+                    }
+
+                    $userlink = new moodle_url('/user/view.php', array('id' => $version->userid));
+                    $picturelink = $picture . html_writer::link($userlink->out(false), fullname($user));
+                    $historydate = $OUTPUT->container($date, 'wiki_histdate');
+                    $radiofromelement = $this->choose_from_radio(array($version->version  => null), 'fromversion', 'M.mod_wiki.deleteversion()', $versioncount, true);
+                    $radiotoelement = $this->choose_from_radio(array($version->version  => null), 'toversion', 'M.mod_wiki.deleteversion()', $versioncount, true);
+                    $contents[] = array( $radiofromelement . $radiotoelement, $viewlink, $picturelink, $time, $historydate);
+                }
+
+                $table = new html_table();
+                $table->head = array(get_string('deleteversions', 'wiki'), get_string('version'), get_string('user'), get_string('modified'), '');
+                $table->data = $contents;
+                $table->attributes['class'] = 'generaltable mdl-align';
+                $table->rowclasses = $rowclass;
+
+                ///Print the form
+                echo html_writer::start_tag('form', array('action'=>new moodle_url('/mod/wiki/admin.php'), 'method' => 'post'));
+                echo html_writer::tag('div', html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'pageid', 'value' => $pageid)));
+                echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'option', 'value' => $this->view));
+                echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' =>  sesskey()));
+                echo html_writer::table($table);
+                echo html_writer::start_tag('div', array('class' => 'mdl-align'));
+                echo html_writer::empty_tag('input', array('type' => 'submit', 'class' => 'wiki_form-button', 'value' => get_string('deleteversions', 'wiki')));
+                echo html_writer::end_tag('div');
+                echo html_writer::end_tag('form');
+            }
+        } else {
+            print_string('nohistory', 'wiki');
+        }
+    }
+
+    /**
+     * Given an array of values, creates a group of radio buttons to be part of a form
+     * helper function for print_delete_version
+     *
+     * @param array  $options  An array of value-label pairs for the radio group (values as keys).
+     * @param string $name     Name of the radiogroup (unique in the form).
+     * @param string $onclick  Function to be executed when the radios are clicked.
+     * @param string $checked  The value that is already checked.
+     * @param bool   $return   If true, return the HTML as a string, otherwise print it.
+     *
+     * @return mixed If $return is false, returns nothing, otherwise returns a string of HTML.
+     */
+    private function choose_from_radio($options, $name, $onclick = '', $checked = '', $return = false) {
+
+        static $idcounter = 0;
+
+        if (!$name) {
+            $name = 'unnamed';
+        }
+
+        $output = '<span class="radiogroup ' . $name . "\">\n";
+
+        if (!empty($options)) {
+            $currentradio = 0;
+            foreach ($options as $value => $label) {
+                $htmlid = 'auto-rb' . sprintf('%04d', ++$idcounter);
+                $output .= ' <span class="radioelement ' . $name . ' rb' . $currentradio . "\">";
+                $output .= '<input name="' . $name . '" id="' . $htmlid . '" type="radio" value="' . $value . '"';
+                if ($value == $checked) {
+                    $output .= ' checked="checked"';
+                }
+                if ($onclick) {
+                    $output .= ' onclick="' . $onclick . '"';
+                }
+                if ($label === '') {
+                    $output .= ' /> <label for="' . $htmlid . '">' . $value . '</label></span>' . "\n";
+                } else {
+                    $output .= ' /> <label for="' . $htmlid . '">' . $label . '</label></span>' . "\n";
+                }
+                $currentradio = ($currentradio + 1) % 2;
+            }
+        }
+
+        $output .= '</span>' . "\n";
+
+        if ($return) {
+            return $output;
+        } else {
+            echo $output;
+        }
+    }
+}
index 8853b76..31594b2 100644 (file)
@@ -278,6 +278,9 @@ class mod_wiki_renderer extends plugin_renderer_base {
             if (($tab == 'view' || $tab == 'map' || $tab == 'history') && !has_capability('mod/wiki:viewpage', $context)) {
                 continue;
             }
+            if ($tab == 'admin' && !has_capability('mod/wiki:managewiki', $context)) {
+                continue;
+            }
             $link = $baseurl . $tab . '.php?pageid=' . $pageid;
             if ($linked == $tab) {
                 $tabs[] = new tabobject($tab, $link, get_string($tab, 'wiki'), '', true);
@@ -494,6 +497,21 @@ class mod_wiki_renderer extends plugin_renderer_base {
         return $html;
     }
 
+    function menu_admin($pageid, $currentselect) {
+        $options = array('removepages', 'deleteversions');
+        $items = array();
+        foreach ($options as $opt) {
+            $items[] = get_string($opt, 'wiki');
+        }
+        $selectoptions = array();
+        foreach ($items as $key => $item) {
+            $selectoptions[$key + 1] = $item;
+        }
+        $select = new single_select(new moodle_url('/mod/wiki/admin.php', array('pageid' => $pageid)), 'option', $selectoptions, $currentselect);
+        $select->label = get_string('adminmenu', 'wiki') . ': ';
+        return $this->output->container($this->output->render($select), 'midpad');
+    }
+
     /**
      * Internal function - creates htmls structure suitable for YUI tree.
      */
index 051da2f..bcb0d2d 100644 (file)
@@ -50,6 +50,7 @@ function workshop_supports($feature) {
         case FEATURE_BACKUP_MOODLE2:    return true;
         case FEATURE_COMPLETION_TRACKS_VIEWS:
             return true;
+        case FEATURE_SHOW_DESCRIPTION:  return true;
         default:                        return null;
     }
 }
index e9e83db..635273f 100644 (file)
@@ -279,24 +279,32 @@ if ($component === 'blog') {
 // ========================================================================================================================
 } else if ($component === 'user') {
     if ($filearea === 'icon' and $context->contextlevel == CONTEXT_USER) {
-        // XXX: pix_url will initialize $PAGE, so we have to set up context here
-        // this temp hack should be fixed by better solution
-        $PAGE->set_context(get_system_context());
-        if (!empty($CFG->forcelogin) and !isloggedin()) {
+        $redirect = false;
+        if (count($args) == 1) {
+            $themename = theme_config::DEFAULT_THEME;
+            $filename = array_shift($args);
+        } else {
+            $themename = array_shift($args);
+            $filename = array_shift($args);
+        }
+        if ((!empty($CFG->forcelogin) and !isloggedin())) {
             // protect images if login required and not logged in;
             // do not use require_login() because it is expensive and not suitable here anyway
-            redirect($OUTPUT->pix_url('u/f1'));
+            $redirect = true;
         }
-        $filename = array_pop($args);
-        if ($filename !== 'f1' and $filename !== 'f2') {
-            redirect($OUTPUT->pix_url('u/f1'));
+        if (!$redirect and ($filename !== 'f1' and $filename !== 'f2')) {
+            $filename = 'f1';
+            $redirect = true;
         }
-        if (!$file = $fs->get_file($context->id, 'user', 'icon', 0, '/', $filename.'/.png')) {
+        if (!$redirect && !$file = $fs->get_file($context->id, 'user', 'icon', 0, '/', $filename.'/.png')) {
             if (!$file = $fs->get_file($context->id, 'user', 'icon', 0, '/', $filename.'/.jpg')) {
-                redirect($OUTPUT->pix_url('u/'.$filename));
+                $redirect = true;
             }
         }
-
+        if ($redirect) {
+            $theme = theme_config::load($themename);
+            redirect($theme->pix_url('u/'.$filename, 'moodle'));
+        }
         send_stored_file($file, 60*60*24); // enable long caching, there are many images on each page
 
     } else if ($filearea === 'private' and $context->contextlevel == CONTEXT_USER) {
index 6565315..5ccc2c1 100644 (file)
@@ -182,7 +182,7 @@ abstract class question_bank {
         }
 
         ksort($sortorder);
-        textlib_get_instance()->asort($otherqtypes);
+        collatorlib::asort($otherqtypes);
 
         $sortedqtypes = array();
         foreach ($sortorder as $name) {
index 8c771c0..a8c5d8f 100644 (file)
@@ -43,6 +43,7 @@
     -webkit-border-radius:6px 6px 0px 0px; /* Safari, Chrome */
     border-radius:6px 6px 0px 0px;         /* CSS3 */
 }
+
 #page-content {
     /* arrotonadamento angolo in basso a sx */
     /* -moz-border-radius:0px 0px 0px 6px;    /* Firefox */
     background-color:[[setting:lblockcolumnbgc]]; /* colore della colonna di sinistra */
     float:none;
 }
+
 #page-content #region-main-box {
     left:[[setting:blockcolumnwidth]];
     background-color:#FFF;
 }
+
 #page-content #region-post-box {
     margin-left:[[setting:minusdoubleblockcolumnwidth]]; /*-2*[[setting:blockcolumnwidth]]*/
     /* arrotonadamento angolo in basso a dx */
     /* border-radius:0px 0px 6px 0px;         /* CSS3 */
     background-color:[[setting:rblockcolumnbgc]]; /* colore della colonna di destra */
 }
+
 #page-content #region-main {
     margin-left:[[setting:doubleblockcolumnwidth]];/*2*[[setting:blockcolumnwidth]]*/
     background-color:white; /* questo serve a togliere la curvatura dagli angoli della zona centrale */
 }
+
 #page-content #region-pre {
     left:[[setting:blockcolumnwidth]]; /*400-[[setting:blockcolumnwidth]]*/
     width:[[setting:blockcolumnwidth]];
     background-color:[[setting:lblockcolumnbgc]]; /* colore della colonna di sx */
 }
+
 #page-content #region-post {
     width:[[setting:blockcolumnwidth]];
     /* arrotonadamento angolo in basso a dx */
     /* border-radius:0px 0px 6px 0px;         /* CSS3 */
     background-color:[[setting:rblockcolumnbgc]]; /* colore del fondo della parte occupata dai contenuti della colonna centrale e della colonna dx */
 }
+
 #page-content .region-content {
     padding:0.6em 8px 0.1em 8px; /* definisco lo spazio sopra e sotto ai blocchi */
 }
 
+.pagelayout-report #page-content .region-content {
+    overflow: auto;
+}
+
 /** Only side pre **/
     .side-pre-only #page-content #region-post-box {margin-left:-[[setting:blockcolumnwidth]];}
     .side-pre-only #page-content #region-main {margin-left:[[setting:blockcolumnwidth]];}
index e7ea1c7..af8e537 100644 (file)
@@ -224,7 +224,7 @@ function useredit_shared_definition(&$mform, $editoroptions = null) {
         $themes = get_list_of_themes();
         foreach ($themes as $key=>$theme) {
             if (empty($theme->hidefromselector)) {
-                $choices[$key] = $theme->name;
+                $choices[$key] = get_string('pluginname', 'theme_'.$theme->name);
             }
         }
         $mform->addElement('select', 'theme', get_string('preferredtheme'), $choices);
index 525979b..89bf616 100644 (file)
@@ -353,17 +353,14 @@ abstract class user_selector_base {
      */
     protected function load_selected_users() {
         // See if we got anything.
-        if (isset($_REQUEST[$this->name]) && is_array($_REQUEST[$this->name])) {
+        if ($this->multiselect) {
             $userids = optional_param_array($this->name, array(), PARAM_INTEGER);
         } else {
-            $userids = optional_param($this->name, 0, PARAM_INTEGER);
-        }
-
-        if (empty($userids)) {
-            return array();
-        }
-        if (!$this->multiselect) {
-            $userids = array($userids);
+            $userid = optional_param($this->name, 0, PARAM_INTEGER);
+            if (empty($userid)) {
+                return array();
+            }
+            $userids = array($userid);
         }
 
         // If we did, use the find_users method to validate the ids.
index b87b853..55f7a21 100644 (file)
@@ -31,10 +31,10 @@ defined('MOODLE_INTERNAL') || die();
 
 
 
-$version  = 2011083100.00;              // YYYYMMDD      = weekly release date of this DEV branch
+$version  = 2011090700.00;              // YYYYMMDD      = weekly release date of this DEV branch
                                         //         RR    = release increments - 00 in DEV branches
                                         //           .XX = incremental changes
 
-$release  = '2.2dev (Build: 20110831)'; // Human-friendly version name
+$release  = '2.2dev (Build: 20110907)'; // Human-friendly version name
 
 $maturity = MATURITY_ALPHA;             // this version's maturity level