$string = $string[$identifier];
if ($a !== NULL) {
- if (is_object($a) or is_array($a)) {
+ // Process array's and objects (except lang_strings)
+ if (is_array($a) or (is_object($a) && !($a instanceof lang_string))) {
$a = (array)$a;
$search = array();
$replace = array();
// we do not support numeric keys - sorry!
continue;
}
- if (is_object($value) or is_array($value)) {
- // we support just string as value
+ if (is_array($value) or (is_object($value) && !($value instanceof lang_string))) {
+ // we support just string or lang_string as value
continue;
}
$search[] = '{$a->'.$key.'}';
* As a last resort, should the identifier fail to map to a string
* the returned string will be [[ $identifier ]]
*
+ * In Moodle 2.3 there is a new argument to this function $lazyload.
+ * Setting $lazyload to true causes get_string to return a lang_string object
+ * rather than the string itself. The fetching of the string is then put off until
+ * the string object is first used. The object can be used by calling it's out
+ * method or by casting the object to a string, either directly e.g.
+ * (string)$stringobject
+ * or indirectly by using the string within another string or echoing it out e.g.
+ * echo $stringobject
+ * return "<p>{$stringobject}</p>";
+ * It is worth noting that using $lazyload and attempting to use the string as an
+ * array key will cause a fatal error as objects cannot be used as array keys.
+ * But you should never do that anyway!
+ * For more information {@see lang_string}
+ *
* @param string $identifier The key identifier for the localized string
* @param string $component The module where the key identifier is stored,
* usually expressed as the filename in the language pack without the
* If none is specified then moodle.php is used.
* @param string|object|array $a An object, string or number that can be used
* within translation strings
+ * @param bool $lazyload If set to true a string object is returned instead of
+ * the string itself. The string then isn't calculated until it is first used.
* @return string The localized string.
*/
-function get_string($identifier, $component = '', $a = NULL) {
+function get_string($identifier, $component = '', $a = NULL, $lazyload = false) {
global $CFG;
+ // If the lazy load argument has been supplied return a lang_string object
+ // instead.
+ // We need to make sure it is true (and a bool) as you will see below there
+ // used to be a forth argument at one point.
+ if ($lazyload === true) {
+ return new lang_string($identifier, $component, $a);
+ }
+
$identifier = clean_param($identifier, PARAM_STRINGID);
if (empty($identifier)) {
throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please fix your get_string() call and string definition');
}
- if (func_num_args() > 3) {
+ // There is now a forth argument again, this time it is a boolean however so
+ // we can still check for the old extralocations parameter.
+ if (!is_bool($lazyload) && !empty($lazyload)) {
debugging('extralocations parameter in get_string() is not supported any more, please use standard lang locations only.');
}
}
return HOMEPAGE_SITE;
}
+
+/**
+ * The lang_string class
+ *
+ * This special class is used to create an object representation of a string request.
+ * It is special because processing doesn't occur until the object is first used.
+ * The class was created especially to aid performance in areas where strings were
+ * required to be generated but were not necessarily used.
+ * As an example the admin tree when generated uses over 1500 strings, of which
+ * normally only 1/3 are ever actually printed at any time.
+ * The performance advantage is achieved by not actually processing strings that
+ * arn't being used, as such reducing the processing required for the page.
+ *
+ * How to use the lang_string class?
+ * There are two methods of using the lang_string class, first through the
+ * forth argument of the get_string function, and secondly directly.
+ * The following are examples of both.
+ * 1. Through get_string calls e.g.
+ * $string = get_string($identifier, $component, $a, true);
+ * $string = get_string('yes', 'moodle', null, true);
+ * 2. Direct instantiation
+ * $string = new lang_string($identifier, $component, $a, $lang);
+ * $string = new lang_string('yes');
+ *
+ * How do I use a lang_string object?
+ * The lang_string object makes use of a magic __toString method so that you
+ * are able to use the object exactly as you would use a string in most cases.
+ * This means you are able to collect it into a variable and then directly
+ * echo it, or concatenate it into another string, or similar.
+ * The other thing you can do is manually get the string by calling the
+ * lang_strings out method e.g.
+ * $string = new lang_string('yes');
+ * $string->out();
+ * Also worth noting is that the out method can take one argument, $lang which
+ * allows the developer to change the language on the fly.
+ *
+ * When should I use a lang_string object?
+ * The lang_string object is designed to be used in any situation where a
+ * string may not be needed, but needs to be generated.
+ * The admin tree is a good example of where lang_string objects should be
+ * used.
+ * A more practical example would be any class that requries strings that may
+ * not be printed (after all classes get renderer by renderers and who knows
+ * what they will do ;))
+ *
+ * When should I not use a lang_string object?
+ * Don't use lang_strings when you are going to use a string immediately.
+ * There is no need as it will be processed immediately and there will be no
+ * advantage, and in fact perhaps a negative hit as a class has to be
+ * instantiated for a lang_string object, however get_string won't require
+ * that.
+ *
+ * Limitations:
+ * 1. You cannot use a lang_string object as an array offset. Doing so will
+ * result in PHP throwing an error. (You can use it as an object property!)
+ *
+ * @package moodlecore
+ *
+ * @copyright 2011 Sam Hemelryk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class lang_string {
+
+ /** @var string The strings identifier */
+ protected $identifier;
+ /** @var string The strings component. Default '' */
+ protected $component = '';
+ /** @var array|stdClass Any arguments required for the string. Default null */
+ protected $a = null;
+ /** @var string The language to use when processing the string. Default null */
+ protected $lang = null;
+
+ /** @var string The processed string (once processed) */
+ protected $string = null;
+
+ /**
+ * A special boolean. If set to true then the object has been woken up and
+ * cannot be regenerated. If this is set then $this->string MUST be used.
+ * @var bool
+ */
+ protected $forcedstring = false;
+
+ /**
+ * Constructs a lang_string object
+ *
+ * This function should do as little processing as possible to ensure the best
+ * performance for strings that won't be used.
+ *
+ * @param string $identifier The strings identifier
+ * @param string $component The strings component
+ * @param stdClass|array $a Any arguments the string requires
+ * @param string $lang The language to use when processing the string.
+ */
+ public function __construct($identifier, $component = '', $a = null, $lang = null) {
+ $this->identifier = $identifier;
+ $this->component = $component;
+ $this->lang = $lang;
+
+ // We MUST duplicate $a to ensure that it if it changes by reference those
+ // changes are not carried across.
+ // To do this we always ensure $a or its properties/values are strings
+ // and that any properties/values that arn't convertable are forgotten.
+ if (!empty($a)) {
+ if (is_scalar($a)) {
+ $this->a = $a;
+ } else if ($a instanceof lang_string) {
+ $this->a = $a->out();
+ } else if (is_object($a)) {
+ $this->a = new stdClass;
+ foreach (get_object_vars($a) as $key => $value) {
+ // Make sure conversion errors don't get displayed (results in '')
+ $this->a->$key = @(string)$value;
+ }
+ } else if (is_array($a)) {
+ $this->a = array();
+ foreach ($a as $key => $value) {
+ // Make sure conversion errors don't get displayed (results in '')
+ $this->a[$key] = @(string)$value;
+ }
+ }
+ }
+ }
+
+ /**
+ * Processes the string.
+ *
+ * This function actually processes the string, stores it in the string property
+ * and then returns it.
+ * You will notice that this function is VERY similar to the get_string method.
+ * That is because it is pretty much doing the same thing.
+ * However as this function is an upgrade it isn't as tolerant to backwards
+ * compatability.
+ *
+ * @return string
+ */
+ protected function get_string() {
+ global $CFG;
+
+ // Check if we need to process the string
+ if ($this->string === null) {
+ // Check the quality of the identifier.
+ if (clean_param($this->identifier, PARAM_STRINGID) == '') {
+ throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please fix your get_string() call and string definition');
+ }
+
+ // Process the string
+ $this->string = get_string_manager()->get_string($this->identifier, $this->component, $this->a, $this->lang);
+ // Debugging feature lets you display string identifier and component
+ if (isset($CFG->debugstringids) && $CFG->debugstringids && optional_param('strings', 0, PARAM_INT)) {
+ $this->string .= ' {' . $this->identifier . '/' . $this->component . '}';
+ }
+ }
+ // Return the string
+ return $this->string;
+ }
+
+ /**
+ * Returns the string
+ *
+ * @param string $lang The langauge to use when processing the string
+ * @return string
+ */
+ public function out($lang = null) {
+ if ($lang !== null && $lang != $this->lang && ($this->lang == null && $lang != current_language())) {
+ if ($this->forcedstring) {
+ debugging('lang_string objects that have been serialised and unserialised cannot be printed in another language. ('.$this->lang.' used)', DEBUG_DEVELOPER);
+ return $this->get_string();
+ }
+ $translatedstring = new lang_string($this->identifier, $this->component, $this->a, $lang);
+ return $translatedstring->out();
+ }
+ return $this->get_string();
+ }
+
+ /**
+ * Magic __toString method for printing a string
+ *
+ * @return string
+ */
+ public function __toString() {
+ return $this->get_string();
+ }
+
+ /**
+ * Magic __set_state method used for var_export
+ *
+ * @return string
+ */
+ public function __set_state() {
+ return $this->get_string();
+ }
+
+ /**
+ * Prepares the lang_string for sleep and stores only the forcedstring and
+ * string properties... the string cannot be regenerated so we need to ensure
+ * it is generated for this.
+ *
+ * @return string
+ */
+ public function __sleep() {
+ $this->get_string();
+ $this->forcedstring = true;
+ return array('forcedstring', 'string', 'lang');
+ }
+}
\ No newline at end of file
date_default_timezone_set($systemdefaulttimezone);
setlocale(LC_TIME, $oldlocale);
}
-}
+
+ /**
+ * Test get_string and most importantly the implementation of the lang_string
+ * object.
+ */
+ public function test_get_string() {
+ global $COURSE;
+
+ // Make sure we are using English
+ $originallang = $COURSE->lang;
+ $COURSE->lang = 'en';
+
+ $yes = get_string('yes');
+ $yesexpected = 'Yes';
+ $this->assertIsA($yes, 'string');
+ $this->assertEqual($yes, $yesexpected);
+
+ $yes = get_string('yes', 'moodle');
+ $this->assertIsA($yes, 'string');
+ $this->assertEqual($yes, $yesexpected);
+
+ $yes = get_string('yes', 'core');
+ $this->assertIsA($yes, 'string');
+ $this->assertEqual($yes, $yesexpected);
+
+ $yes = get_string('yes', '');
+ $this->assertIsA($yes, 'string');
+ $this->assertEqual($yes, $yesexpected);
+
+ $yes = get_string('yes', null);
+ $this->assertIsA($yes, 'string');
+ $this->assertEqual($yes, $yesexpected);
+
+ $yes = get_string('yes', null, 1);
+ $this->assertIsA($yes, 'string');
+ $this->assertEqual($yes, $yesexpected);
+
+ $days = 1;
+ $numdays = get_string('numdays', 'core', '1');
+ $numdaysexpected = $days.' days';
+ $this->assertIsA($numdays, 'string');
+ $this->assertEqual($numdays, $numdaysexpected);
+
+ $yes = get_string('yes', null, null, true);
+ $this->assertEqual(get_class($yes), 'lang_string');
+ $this->assertIsA($yes, 'lang_string');
+ $this->assertEqual((string)$yes, $yesexpected);
+
+ // Test using a lang_string object as the $a argument for a normal
+ // get_string call (returning string)
+ $test = new lang_string('yes', null, null, true);
+ $testexpected = get_string('numdays', 'core', get_string('yes'));
+ $testresult = get_string('numdays', null, $test);
+ $this->assertIsA($testresult, 'string');
+ $this->assertEqual($testresult, $testexpected);
+
+ // Test using a lang_string object as the $a argument for an object
+ // get_string call (returning lang_string)
+ $test = new lang_string('yes', null, null, true);
+ $testexpected = get_string('numdays', 'core', get_string('yes'));
+ $testresult = get_string('numdays', null, $test, true);
+ $this->assertEqual(get_class($testresult), 'lang_string');
+ $this->assertIsA($testresult, 'lang_string');
+ $this->assertEqual("$testresult", $testexpected);
+
+ // Make sure that object properties that can't be converted don't cause
+ // errors
+ // Level one: This is as deep as current language processing goes
+ $test = new stdClass;
+ $test->one = 'here';
+ $string = get_string('yes', null, $test, true);
+ $this->assertEqual($string, $yesexpected);
+
+ // Make sure that object properties that can't be converted don't cause
+ // errors.
+ // Level two: Language processing doesn't currently reach this deep.
+ // only immediate scalar properties are worked with.
+ $test = new stdClass;
+ $test->one = new stdClass;
+ $test->one->two = 'here';
+ $string = get_string('yes', null, $test, true);
+ $this->assertEqual($string, $yesexpected);
+
+ // Make sure that object properties that can't be converted don't cause
+ // errors.
+ // Level three: It should never ever go this deep, but we're making sure
+ // it doesn't cause any probs anyway.
+ $test = new stdClass;
+ $test->one = new stdClass;
+ $test->one->two = new stdClass;
+ $test->one->two->three = 'here';
+ $string = get_string('yes', null, $test, true);
+ $this->assertEqual($string, $yesexpected);
+
+ // Make sure that object properties that can't be converted don't cause
+ // errors and check lang_string properties.
+ // Level one: This is as deep as current language processing goes
+ $test = new stdClass;
+ $test->one = new lang_string('yes');
+ $string = get_string('yes', null, $test, true);
+ $this->assertEqual($string, $yesexpected);
+
+ // Make sure that object properties that can't be converted don't cause
+ // errors and check lang_string properties.
+ // Level two: Language processing doesn't currently reach this deep.
+ // only immediate scalar properties are worked with.
+ $test = new stdClass;
+ $test->one = new stdClass;
+ $test->one->two = new lang_string('yes');
+ $string = get_string('yes', null, $test, true);
+ $this->assertEqual($string, $yesexpected);
+
+ // Make sure that object properties that can't be converted don't cause
+ // errors and check lang_string properties.
+ // Level three: It should never ever go this deep, but we're making sure
+ // it doesn't cause any probs anyway.
+ $test = new stdClass;
+ $test->one = new stdClass;
+ $test->one->two = new stdClass;
+ $test->one->two->three = new lang_string('yes');
+ $string = get_string('yes', null, $test, true);
+ $this->assertEqual($string, $yesexpected);
+
+ // Make sure that array properties that can't be converted don't cause
+ // errors
+ $test = array();
+ $test['one'] = new stdClass;
+ $test['one']->two = 'here';
+ $string = get_string('yes', null, $test, true);
+ $this->assertEqual($string, $yesexpected);
+
+ // This is one of the limitations to the lang_string class. It can't be
+ // used as a key
+ $this->expectError('Illegal offset type', 'Array offsets now support objects we can consider making lazy loading the default!.');
+ $array = array(get_string('yes', null, null, true) => 'yes');
+
+ // Same thing but as above except using an object... this is allowed :P
+ $string = get_string('yes', null, null, true);
+ $object = new stdClass;
+ $object->$string = 'Yes';
+ $this->assertEqual($string, $yesexpected);
+ $this->assertEqual($object->$string, $yesexpected);
+
+ // Reset the language
+ $COURSE->lang = $originallang;
+ }
+}
\ No newline at end of file