. namespace core; use core\exception\coding_exception; use stdClass; /** * 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 * aren'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 requires strings that may * not be printed (after all classes get rendered 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 core * @copyright 2011 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class lang_string { /** @var string The strings component. Default '' */ protected ?string $component = ''; /** @var array|stdClass Any arguments required for the string. Default null */ protected mixed $a = null; /** @var string The processed string (once processed) */ protected ?string $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 bool $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|null $component The strings component * @param mixed $a Any arguments the string requires * @param string|null $lang The language to use when processing the string. * @throws coding_exception */ public function __construct( /** @var string The strings identifier */ protected readonly string $identifier, ?string $component = '', mixed $a = null, /** @var string The language to use when processing the string*/ protected readonly ?string $lang = null, ) { if (empty($component)) { $component = 'moodle'; } $this->component = $component; // 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 ($a !== null) { if (is_scalar($a)) { $this->a = $a; } else if ($a instanceof lang_string) { $this->a = $a->out(); } else if (is_object($a) || is_array($a)) { $a = (array)$a; $this->a = []; foreach ($a as $key => $value) { // Make sure conversion errors don't get displayed (results in ''). if (is_array($value)) { $this->a[$key] = ''; } else if (is_object($value)) { if (method_exists($value, '__toString')) { $this->a[$key] = $value->__toString(); } else { $this->a[$key] = ''; } } else { $this->a[$key] = (string)$value; } } } } if (debugging(false, DEBUG_DEVELOPER)) { 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 check your string definition'); } if (!empty($this->component) && clean_param($this->component, PARAM_COMPONENT) == '') { throw new coding_exception('Invalid string compontent. Please check your string definition'); } if (!get_string_manager()->string_exists($this->identifier, $this->component)) { debugging('String does not exist. Please check your string definition for '.$this->identifier.'/'.$this->component, DEBUG_DEVELOPER); } } } /** * 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 * compatibility. * * @return string * @throws coding_exception */ protected function get_string(): string { global $CFG; // Check if we need to process the string. if ($this->string === null) { // Check the quality of the identifier. if ($CFG->debugdeveloper && 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 check your string definition', DEBUG_DEVELOPER); } // 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): string { if ($lang !== null && $lang != $this->lang && ($this->lang == null && $lang != current_language())) { if ($this->forcedstring) { debugging('lang_string objects that have been used 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 * * @param array $array * @return self */ public static function __set_state(array $array): self { $tmp = new lang_string($array['identifier'], $array['component'], $array['a'], $array['lang']); $tmp->string = $array['string']; $tmp->forcedstring = $array['forcedstring']; return $tmp; } /** * 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 array */ public function __sleep() { $this->get_string(); $this->forcedstring = true; return ['forcedstring', 'string', 'lang']; } /** * Returns the identifier. * * @return string */ public function get_identifier(): string { return $this->identifier; } /** * Returns the component. * * @return string */ public function get_component(): string { return $this->component; } } // Alias this class to the old name. // This file will be autoloaded by the legacyclasses autoload system. // In future all uses of this class will be corrected and the legacy references will be removed. class_alias(lang_string::class, \lang_string::class);