Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Definition of classes used by language customization admin tool 19 * 20 * @package tool 21 * @subpackage customlang 22 * @copyright 2010 David Mudrak <david@moodle.com> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 /** 29 * Provides various utilities to be used by the plugin 30 * 31 * All the public methods here are static ones, this class can not be instantiated 32 */ 33 class tool_customlang_utils { 34 35 /** 36 * Rough number of strings that are being processed during a full checkout. 37 * This is used to estimate the progress of the checkout. 38 */ 39 const ROUGH_NUMBER_OF_STRINGS = 30000; 40 41 /** @var array cache of {@link self::list_components()} results */ 42 private static $components = null; 43 44 /** 45 * This class can not be instantiated 46 */ 47 private function __construct() { 48 } 49 50 /** 51 * Returns a list of all components installed on the server 52 * 53 * @return array (string)legacyname => (string)frankenstylename 54 */ 55 public static function list_components() { 56 57 if (self::$components === null) { 58 $list['moodle'] = 'core'; 59 60 $coresubsystems = core_component::get_core_subsystems(); 61 ksort($coresubsystems); // Should be but just in case. 62 foreach ($coresubsystems as $name => $location) { 63 $list[$name] = 'core_' . $name; 64 } 65 66 $plugintypes = core_component::get_plugin_types(); 67 foreach ($plugintypes as $type => $location) { 68 $pluginlist = core_component::get_plugin_list($type); 69 foreach ($pluginlist as $name => $ununsed) { 70 if ($type == 'mod') { 71 // Plugin names are now automatically validated. 72 $list[$name] = $type . '_' . $name; 73 } else { 74 $list[$type . '_' . $name] = $type . '_' . $name; 75 } 76 } 77 } 78 self::$components = $list; 79 } 80 return self::$components; 81 } 82 83 /** 84 * Updates the translator database with the strings from files 85 * 86 * This should be executed each time before going to the translation page 87 * 88 * @param string $lang language code to checkout 89 * @param progress_bar $progressbar optionally, the given progress bar can be updated 90 */ 91 public static function checkout($lang, progress_bar $progressbar = null) { 92 global $DB; 93 94 // For behat executions we are going to load only a few components in the 95 // language customisation structures. Using the whole "en" langpack is 96 // too much slow (leads to Selenium 30s timeouts, especially on slow 97 // environments) and we don't really need the whole thing for tests. So, 98 // apart from escaping from the timeouts, we are also saving some good minutes 99 // in tests. See MDL-70014 and linked issues for more info. 100 $behatneeded = ['core', 'core_langconfig', 'tool_customlang']; 101 102 // make sure that all components are registered 103 $current = $DB->get_records('tool_customlang_components', null, 'name', 'name,version,id'); 104 foreach (self::list_components() as $component) { 105 // Filter out unwanted components when running behat. 106 if (defined('BEHAT_SITE_RUNNING') && !in_array($component, $behatneeded)) { 107 continue; 108 } 109 110 if (empty($current[$component])) { 111 $record = new stdclass(); 112 $record->name = $component; 113 if (!$version = get_component_version($component)) { 114 $record->version = null; 115 } else { 116 $record->version = $version; 117 } 118 $DB->insert_record('tool_customlang_components', $record); 119 } else if ($version = get_component_version($component)) { 120 if (is_null($current[$component]->version) or ($version > $current[$component]->version)) { 121 $DB->set_field('tool_customlang_components', 'version', $version, array('id' => $current[$component]->id)); 122 } 123 } 124 } 125 unset($current); 126 127 // initialize the progress counter - stores the number of processed strings 128 $done = 0; 129 $strinprogress = get_string('checkoutinprogress', 'tool_customlang'); 130 131 // reload components and fetch their strings 132 $stringman = get_string_manager(); 133 $components = $DB->get_records('tool_customlang_components'); 134 foreach ($components as $component) { 135 $sql = "SELECT stringid, id, lang, componentid, original, master, local, timemodified, timecustomized, outdated, modified 136 FROM {tool_customlang} s 137 WHERE lang = ? AND componentid = ? 138 ORDER BY stringid"; 139 $current = $DB->get_records_sql($sql, array($lang, $component->id)); 140 $english = $stringman->load_component_strings($component->name, 'en', true, true); 141 if ($lang == 'en') { 142 $master =& $english; 143 } else { 144 $master = $stringman->load_component_strings($component->name, $lang, true, true); 145 } 146 $local = $stringman->load_component_strings($component->name, $lang, true, false); 147 148 foreach ($english as $stringid => $stringoriginal) { 149 $stringmaster = isset($master[$stringid]) ? $master[$stringid] : null; 150 $stringlocal = isset($local[$stringid]) ? $local[$stringid] : null; 151 $now = time(); 152 153 if (!is_null($progressbar)) { 154 $done++; 155 $donepercent = floor(min($done, self::ROUGH_NUMBER_OF_STRINGS) / self::ROUGH_NUMBER_OF_STRINGS * 100); 156 $progressbar->update_full($donepercent, $strinprogress); 157 } 158 159 if (isset($current[$stringid])) { 160 $needsupdate = false; 161 $currentoriginal = $current[$stringid]->original; 162 $currentmaster = $current[$stringid]->master; 163 $currentlocal = $current[$stringid]->local; 164 165 if ($currentoriginal !== $stringoriginal or $currentmaster !== $stringmaster) { 166 $needsupdate = true; 167 $current[$stringid]->original = $stringoriginal; 168 $current[$stringid]->master = $stringmaster; 169 $current[$stringid]->timemodified = $now; 170 $current[$stringid]->outdated = 1; 171 } 172 173 if ($stringmaster !== $stringlocal) { 174 $needsupdate = true; 175 $current[$stringid]->local = $stringlocal; 176 $current[$stringid]->timecustomized = $now; 177 } else if (isset($currentlocal) && $stringlocal !== $currentlocal) { 178 // If local string has been removed, we need to remove also the old local value from DB. 179 $needsupdate = true; 180 $current[$stringid]->local = null; 181 $current[$stringid]->timecustomized = $now; 182 } 183 184 if ($needsupdate) { 185 $DB->update_record('tool_customlang', $current[$stringid]); 186 continue; 187 } 188 189 } else { 190 $record = new stdclass(); 191 $record->lang = $lang; 192 $record->componentid = $component->id; 193 $record->stringid = $stringid; 194 $record->original = $stringoriginal; 195 $record->master = $stringmaster; 196 $record->timemodified = $now; 197 $record->outdated = 0; 198 if ($stringmaster !== $stringlocal) { 199 $record->local = $stringlocal; 200 $record->timecustomized = $now; 201 } else { 202 $record->local = null; 203 $record->timecustomized = null; 204 } 205 206 $DB->insert_record('tool_customlang', $record); 207 } 208 } 209 } 210 211 if (!is_null($progressbar)) { 212 $progressbar->update_full(100, get_string('checkoutdone', 'tool_customlang')); 213 } 214 } 215 216 /** 217 * Exports the translator database into disk files 218 * 219 * @param mixed $lang language code 220 */ 221 public static function checkin($lang) { 222 global $DB, $USER, $CFG; 223 require_once($CFG->libdir.'/filelib.php'); 224 225 if ($lang !== clean_param($lang, PARAM_LANG)) { 226 return false; 227 } 228 229 list($insql, $inparams) = $DB->get_in_or_equal(self::list_components()); 230 231 // Get all customized strings from updated valid components. 232 $sql = "SELECT s.*, c.name AS component 233 FROM {tool_customlang} s 234 JOIN {tool_customlang_components} c ON s.componentid = c.id 235 WHERE s.lang = ? 236 AND (s.local IS NOT NULL OR s.modified = 1) 237 AND c.name $insql 238 ORDER BY componentid, stringid"; 239 array_unshift($inparams, $lang); 240 $strings = $DB->get_records_sql($sql, $inparams); 241 242 $files = array(); 243 foreach ($strings as $string) { 244 if (!is_null($string->local)) { 245 $files[$string->component][$string->stringid] = $string->local; 246 } 247 } 248 249 fulldelete(self::get_localpack_location($lang)); 250 foreach ($files as $component => $strings) { 251 self::dump_strings($lang, $component, $strings); 252 } 253 254 $DB->set_field_select('tool_customlang', 'modified', 0, 'lang = ?', array($lang)); 255 $sm = get_string_manager(); 256 $sm->reset_caches(); 257 } 258 259 /** 260 * Returns full path to the directory where local packs are dumped into 261 * 262 * @param string $lang language code 263 * @return string full path 264 */ 265 public static function get_localpack_location($lang) { 266 global $CFG; 267 268 return $CFG->langlocalroot.'/'.$lang.'_local'; 269 } 270 271 /** 272 * Writes strings into a local language pack file 273 * 274 * @param string $component the name of the component 275 * @param array $strings 276 * @return void 277 */ 278 protected static function dump_strings($lang, $component, $strings) { 279 global $CFG; 280 281 if ($lang !== clean_param($lang, PARAM_LANG)) { 282 throw new moodle_exception('Unable to dump local strings for non-installed language pack .'.s($lang)); 283 } 284 if ($component !== clean_param($component, PARAM_COMPONENT)) { 285 throw new coding_exception('Incorrect component name'); 286 } 287 if (!$filename = self::get_component_filename($component)) { 288 throw new moodle_exception('Unable to find the filename for the component '.s($component)); 289 } 290 if ($filename !== clean_param($filename, PARAM_FILE)) { 291 throw new coding_exception('Incorrect file name '.s($filename)); 292 } 293 list($package, $subpackage) = core_component::normalize_component($component); 294 $packageinfo = " * @package $package"; 295 if (!is_null($subpackage)) { 296 $packageinfo .= "\n * @subpackage $subpackage"; 297 } 298 $filepath = self::get_localpack_location($lang); 299 $filepath = $filepath.'/'.$filename; 300 if (!is_dir(dirname($filepath))) { 301 check_dir_exists(dirname($filepath)); 302 } 303 304 if (!$f = fopen($filepath, 'w')) { 305 throw new moodle_exception('Unable to write '.s($filepath)); 306 } 307 fwrite($f, <<<EOF 308 <?php 309 310 // This file is part of Moodle - http://moodle.org/ 311 // 312 // Moodle is free software: you can redistribute it and/or modify 313 // it under the terms of the GNU General Public License as published by 314 // the Free Software Foundation, either version 3 of the License, or 315 // (at your option) any later version. 316 // 317 // Moodle is distributed in the hope that it will be useful, 318 // but WITHOUT ANY WARRANTY; without even the implied warranty of 319 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 320 // GNU General Public License for more details. 321 // 322 // You should have received a copy of the GNU General Public License 323 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 324 325 /** 326 * Local language pack from $CFG->wwwroot 327 * 328 $packageinfo 329 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 330 */ 331 332 defined('MOODLE_INTERNAL') || die(); 333 334 335 EOF 336 ); 337 338 foreach ($strings as $stringid => $text) { 339 if ($stringid !== clean_param($stringid, PARAM_STRINGID)) { 340 debugging('Invalid string identifier '.s($stringid)); 341 continue; 342 } 343 fwrite($f, '$string[\'' . $stringid . '\'] = '); 344 fwrite($f, var_export($text, true)); 345 fwrite($f, ";\n"); 346 } 347 fclose($f); 348 @chmod($filepath, $CFG->filepermissions); 349 } 350 351 /** 352 * Returns the name of the file where the component's local strings should be exported into 353 * 354 * @param string $component normalized name of the component, eg 'core' or 'mod_workshop' 355 * @return string|boolean filename eg 'moodle.php' or 'workshop.php', false if not found 356 */ 357 protected static function get_component_filename($component) { 358 359 $return = false; 360 foreach (self::list_components() as $legacy => $normalized) { 361 if ($component === $normalized) { 362 $return = $legacy.'.php'; 363 break; 364 } 365 } 366 return $return; 367 } 368 369 /** 370 * Returns the number of modified strings checked out in the translator 371 * 372 * @param string $lang language code 373 * @return int 374 */ 375 public static function get_count_of_modified($lang) { 376 global $DB; 377 378 return $DB->count_records('tool_customlang', array('lang'=>$lang, 'modified'=>1)); 379 } 380 381 /** 382 * Saves filter data into a persistant storage such as user session 383 * 384 * @see self::load_filter() 385 * @param stdclass $data filter values 386 * @param stdclass $persistant storage object 387 */ 388 public static function save_filter(stdclass $data, stdclass $persistant) { 389 if (!isset($persistant->tool_customlang_filter)) { 390 $persistant->tool_customlang_filter = array(); 391 } 392 foreach ($data as $key => $value) { 393 if ($key !== 'submit') { 394 $persistant->tool_customlang_filter[$key] = serialize($value); 395 } 396 } 397 } 398 399 /** 400 * Loads the previously saved filter settings from a persistent storage 401 * 402 * @see self::save_filter() 403 * @param stdclass $persistant storage object 404 * @return stdclass filter data 405 */ 406 public static function load_filter(stdclass $persistant) { 407 $data = new stdclass(); 408 if (isset($persistant->tool_customlang_filter)) { 409 foreach ($persistant->tool_customlang_filter as $key => $value) { 410 $data->{$key} = unserialize($value); 411 } 412 } 413 return $data; 414 } 415 } 416 417 /** 418 * Represents the action menu of the tool 419 */ 420 class tool_customlang_menu implements renderable { 421 422 /** @var menu items */ 423 protected $items = array(); 424 425 public function __construct(array $items = array()) { 426 global $CFG; 427 428 foreach ($items as $itemkey => $item) { 429 $this->add_item($itemkey, $item['title'], $item['url'], empty($item['method']) ? 'post' : $item['method']); 430 } 431 } 432 433 /** 434 * Returns the menu items 435 * 436 * @return array (string)key => (object)[->(string)title ->(moodle_url)url ->(string)method] 437 */ 438 public function get_items() { 439 return $this->items; 440 } 441 442 /** 443 * Adds item into the menu 444 * 445 * @param string $key item identifier 446 * @param string $title localized action title 447 * @param moodle_url $url action handler 448 * @param string $method form method 449 */ 450 public function add_item($key, $title, moodle_url $url, $method) { 451 if (isset($this->items[$key])) { 452 throw new coding_exception('Menu item already exists'); 453 } 454 if (empty($title) or empty($key)) { 455 throw new coding_exception('Empty title or item key not allowed'); 456 } 457 $item = new stdclass(); 458 $item->title = $title; 459 $item->url = $url; 460 $item->method = $method; 461 $this->items[$key] = $item; 462 } 463 } 464 465 /** 466 * Represents the translation tool 467 */ 468 class tool_customlang_translator implements renderable { 469 470 /** @const int number of rows per page */ 471 const PERPAGE = 100; 472 473 /** @var int total number of the rows int the table */ 474 public $numofrows = 0; 475 476 /** @var moodle_url */ 477 public $handler; 478 479 /** @var string language code */ 480 public $lang; 481 482 /** @var int page to display, starting with page 0 */ 483 public $currentpage = 0; 484 485 /** @var array of stdclass strings to display */ 486 public $strings = array(); 487 488 /** @var stdclass */ 489 protected $filter; 490 491 public function __construct(moodle_url $handler, $lang, $filter, $currentpage = 0) { 492 global $DB; 493 494 $this->handler = $handler; 495 $this->lang = $lang; 496 $this->filter = $filter; 497 $this->currentpage = $currentpage; 498 499 if (empty($filter) or empty($filter->component)) { 500 // nothing to do 501 $this->currentpage = 1; 502 return; 503 } 504 505 list($insql, $inparams) = $DB->get_in_or_equal($filter->component, SQL_PARAMS_NAMED); 506 507 $csql = "SELECT COUNT(*)"; 508 $fsql = "SELECT s.*, c.name AS component"; 509 $sql = " FROM {tool_customlang_components} c 510 JOIN {tool_customlang} s ON s.componentid = c.id 511 WHERE s.lang = :lang 512 AND c.name $insql"; 513 514 $params = array_merge(array('lang' => $lang), $inparams); 515 516 if (!empty($filter->customized)) { 517 $sql .= " AND s.local IS NOT NULL"; 518 } 519 520 if (!empty($filter->modified)) { 521 $sql .= " AND s.modified = 1"; 522 } 523 524 if (!empty($filter->stringid)) { 525 $sql .= " AND s.stringid = :stringid"; 526 $params['stringid'] = $filter->stringid; 527 } 528 529 if (!empty($filter->substring)) { 530 $sql .= " AND (".$DB->sql_like('s.original', ':substringoriginal', false)." OR 531 ".$DB->sql_like('s.master', ':substringmaster', false)." OR 532 ".$DB->sql_like('s.local', ':substringlocal', false).")"; 533 $params['substringoriginal'] = '%'.$filter->substring.'%'; 534 $params['substringmaster'] = '%'.$filter->substring.'%'; 535 $params['substringlocal'] = '%'.$filter->substring.'%'; 536 } 537 538 if (!empty($filter->helps)) { 539 $sql .= " AND ".$DB->sql_like('s.stringid', ':help', false); //ILIKE 540 $params['help'] = '%\_help'; 541 } else { 542 $sql .= " AND ".$DB->sql_like('s.stringid', ':link', false, true, true); //NOT ILIKE 543 $params['link'] = '%\_link'; 544 } 545 546 $osql = " ORDER BY c.name, s.stringid"; 547 548 $this->numofrows = $DB->count_records_sql($csql.$sql, $params); 549 $this->strings = $DB->get_records_sql($fsql.$sql.$osql, $params, ($this->currentpage) * self::PERPAGE, self::PERPAGE); 550 } 551 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body