MDL-28599 textlib Separated collator to collatorlib with static methods, and added...
authorSam Hemelryk <sam@moodle.com>
Wed, 10 Aug 2011 04:56:04 +0000 (12:56 +0800)
committerSam Hemelryk <sam@moodle.com>
Tue, 6 Sep 2011 03:39:00 +0000 (15:39 +1200)
19 files changed:
admin/blocks.php
admin/localplugins.php
admin/registration/forms.php
blocks/activity_modules/block_activity_modules.php
blocks/community/forms.php
course/edit_form.php
course/editcategory_form.php
course/lib.php
course/publish/forms.php
lib/blocklib.php
lib/filterlib.php
lib/gradelib.php
lib/moodlelib.php
lib/outputlib.php
lib/questionlib.php
lib/simpletest/testtextlib.php
lib/textlib.class.php
question/engine/bank.php
user/editlib.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 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 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..8cace61 100644 (file)
@@ -1206,7 +1206,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 +1231,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);
         }
     }
 }
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 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 0b77f6d..125f43a 100644 (file)
@@ -519,7 +519,7 @@ function filter_get_all_installed() {
             }
         }
     }
-    textlib_get_instance()->asort($filternames);
+    collatorlib::asort($filternames);
     return $filternames;
 }
 
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 51abbdc..e7dee69 100644 (file)
@@ -6143,7 +6143,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 +6315,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 +6739,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 +6756,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 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 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..25eb53b 100644 (file)
@@ -550,18 +550,243 @@ 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 futher calls to collation will cause
+                    // it to reset to 0 again (or another error code if one occured)
+                    $errorcode = $collator->getErrorCode();
+                    // Check for an error code, 0 means no error occured
+                    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 wardning error code. If this occured
+                        // there is normally little to worry about. (U_USING_FALLBACK_WARNING)
+                        if ($errorcode === -128) {
+                            // Check if the local in use is anything like the locale we asked for
+                            if (strpos($locale, $localeinuse) !== 0) {
+                                // The locale we asked for is completely different to the locale
+                                // we have recieved, let the user know via debugging
+                                debugging('Invalid locale, falling back to the system default locale "'.$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
+                                // debugging('Invalid locale, falling back to closest match "'.$localeinuse.'" which may not be the exact locale');
+                            }
+                        } else {
+                            // We've recieved some other sort of non fatal warning - let the
+                            // user know about it via debugging.
+                            debugging('Locale collator generated warnings (not fatal) "'.$collator->getErrorMessage().'" 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... who know what went wrong.
+                    debugging('Error instantiating collator: ['.collator_get_error_code($collator).']'.collator_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);
+    }
+}
\ No newline at end of file
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 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);