mirror of
https://github.com/moodle/moodle.git
synced 2025-08-04 00:16:46 +02:00

@const is not a valid phpdoc tag and @var should be used to document both classes properties and constants (no matter how weird that may sound, heh). Link to (draft right now) PHP-FIG: https://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc-tags.md#518-var So, with this commit we are just replacing all uses by the correct @var one. Note that the type is entirely optional, in fact I think that there isn't much need of it for constants because it's obvious for both humans and machines which the type is. But, as far as it's also correct to specify it, we haven't modified that detail. The only detail modified are the cases where the constant name was specified in the phpdoc, that's not needed, hence, the names have been removed from there when present (a couple of cases).
553 lines
20 KiB
PHP
553 lines
20 KiB
PHP
<?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/>.
|
|
|
|
/**
|
|
* Definition of classes used by language customization admin tool
|
|
*
|
|
* @package tool
|
|
* @subpackage customlang
|
|
* @copyright 2010 David Mudrak <david@moodle.com>
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
|
|
defined('MOODLE_INTERNAL') || die();
|
|
|
|
/**
|
|
* Provides various utilities to be used by the plugin
|
|
*
|
|
* All the public methods here are static ones, this class can not be instantiated
|
|
*/
|
|
class tool_customlang_utils {
|
|
|
|
/**
|
|
* Rough number of strings that are being processed during a full checkout.
|
|
* This is used to estimate the progress of the checkout.
|
|
*/
|
|
const ROUGH_NUMBER_OF_STRINGS = 32000;
|
|
|
|
/** @var array cache of {@link self::list_components()} results */
|
|
private static $components = null;
|
|
|
|
/**
|
|
* This class can not be instantiated
|
|
*/
|
|
private function __construct() {
|
|
}
|
|
|
|
/**
|
|
* Returns a list of all components installed on the server
|
|
*
|
|
* @return array (string)legacyname => (string)frankenstylename
|
|
*/
|
|
public static function list_components() {
|
|
|
|
if (self::$components === null) {
|
|
$list['moodle'] = 'core';
|
|
|
|
$coresubsystems = core_component::get_core_subsystems();
|
|
ksort($coresubsystems); // Should be but just in case.
|
|
foreach ($coresubsystems as $name => $location) {
|
|
$list[$name] = 'core_' . $name;
|
|
}
|
|
|
|
$plugintypes = core_component::get_plugin_types();
|
|
foreach ($plugintypes as $type => $location) {
|
|
$pluginlist = core_component::get_plugin_list($type);
|
|
foreach ($pluginlist as $name => $ununsed) {
|
|
if ($type == 'mod') {
|
|
// Plugin names are now automatically validated.
|
|
$list[$name] = $type . '_' . $name;
|
|
} else {
|
|
$list[$type . '_' . $name] = $type . '_' . $name;
|
|
}
|
|
}
|
|
}
|
|
self::$components = $list;
|
|
}
|
|
return self::$components;
|
|
}
|
|
|
|
/**
|
|
* Updates the translator database with the strings from files
|
|
*
|
|
* This should be executed each time before going to the translation page
|
|
*
|
|
* @param string $lang language code to checkout
|
|
* @param progress_bar $progressbar optionally, the given progress bar can be updated
|
|
*/
|
|
public static function checkout($lang, progress_bar $progressbar = null) {
|
|
global $DB, $CFG;
|
|
|
|
require_once("{$CFG->libdir}/adminlib.php");
|
|
|
|
// For behat executions we are going to load only a few components in the
|
|
// language customisation structures. Using the whole "en" langpack is
|
|
// too much slow (leads to Selenium 30s timeouts, especially on slow
|
|
// environments) and we don't really need the whole thing for tests. So,
|
|
// apart from escaping from the timeouts, we are also saving some good minutes
|
|
// in tests. See MDL-70014 and linked issues for more info.
|
|
$behatneeded = ['core', 'core_langconfig', 'tool_customlang'];
|
|
|
|
// make sure that all components are registered
|
|
$current = $DB->get_records('tool_customlang_components', null, 'name', 'name,version,id');
|
|
foreach (self::list_components() as $component) {
|
|
// Filter out unwanted components when running behat.
|
|
if (defined('BEHAT_SITE_RUNNING') && !in_array($component, $behatneeded)) {
|
|
continue;
|
|
}
|
|
|
|
if (empty($current[$component])) {
|
|
$record = new stdclass();
|
|
$record->name = $component;
|
|
if (!$version = get_component_version($component)) {
|
|
$record->version = null;
|
|
} else {
|
|
$record->version = $version;
|
|
}
|
|
$DB->insert_record('tool_customlang_components', $record);
|
|
} else if ($version = get_component_version($component)) {
|
|
if (is_null($current[$component]->version) or ($version > $current[$component]->version)) {
|
|
$DB->set_field('tool_customlang_components', 'version', $version, array('id' => $current[$component]->id));
|
|
}
|
|
}
|
|
}
|
|
unset($current);
|
|
|
|
// initialize the progress counter - stores the number of processed strings
|
|
$done = 0;
|
|
$strinprogress = get_string('checkoutinprogress', 'tool_customlang');
|
|
|
|
// reload components and fetch their strings
|
|
$stringman = get_string_manager();
|
|
$components = $DB->get_records('tool_customlang_components');
|
|
foreach ($components as $component) {
|
|
$sql = "SELECT stringid, id, lang, componentid, original, master, local, timemodified, timecustomized, outdated, modified
|
|
FROM {tool_customlang} s
|
|
WHERE lang = ? AND componentid = ?
|
|
ORDER BY stringid";
|
|
$current = $DB->get_records_sql($sql, array($lang, $component->id));
|
|
$english = $stringman->load_component_strings($component->name, 'en', true, true);
|
|
if ($lang == 'en') {
|
|
$master =& $english;
|
|
} else {
|
|
$master = $stringman->load_component_strings($component->name, $lang, true, true);
|
|
}
|
|
$local = $stringman->load_component_strings($component->name, $lang, true, false);
|
|
|
|
foreach ($english as $stringid => $stringoriginal) {
|
|
$stringmaster = isset($master[$stringid]) ? $master[$stringid] : null;
|
|
$stringlocal = isset($local[$stringid]) ? $local[$stringid] : null;
|
|
$now = time();
|
|
|
|
if (!is_null($progressbar)) {
|
|
$done++;
|
|
$donepercent = floor(min($done, self::ROUGH_NUMBER_OF_STRINGS) / self::ROUGH_NUMBER_OF_STRINGS * 100);
|
|
$progressbar->update_full($donepercent, $strinprogress);
|
|
}
|
|
|
|
if (isset($current[$stringid])) {
|
|
$needsupdate = false;
|
|
$currentoriginal = $current[$stringid]->original;
|
|
$currentmaster = $current[$stringid]->master;
|
|
$currentlocal = $current[$stringid]->local;
|
|
|
|
if ($currentoriginal !== $stringoriginal or $currentmaster !== $stringmaster) {
|
|
$needsupdate = true;
|
|
$current[$stringid]->original = $stringoriginal;
|
|
$current[$stringid]->master = $stringmaster;
|
|
$current[$stringid]->timemodified = $now;
|
|
$current[$stringid]->outdated = 1;
|
|
}
|
|
|
|
if ($stringmaster !== $stringlocal) {
|
|
$needsupdate = true;
|
|
$current[$stringid]->local = $stringlocal;
|
|
$current[$stringid]->timecustomized = $now;
|
|
} else if (isset($currentlocal) && $stringlocal !== $currentlocal) {
|
|
// If local string has been removed, we need to remove also the old local value from DB.
|
|
$needsupdate = true;
|
|
$current[$stringid]->local = null;
|
|
$current[$stringid]->timecustomized = $now;
|
|
}
|
|
|
|
if ($needsupdate) {
|
|
$DB->update_record('tool_customlang', $current[$stringid]);
|
|
continue;
|
|
}
|
|
|
|
} else {
|
|
$record = new stdclass();
|
|
$record->lang = $lang;
|
|
$record->componentid = $component->id;
|
|
$record->stringid = $stringid;
|
|
$record->original = $stringoriginal;
|
|
$record->master = $stringmaster;
|
|
$record->timemodified = $now;
|
|
$record->outdated = 0;
|
|
if ($stringmaster !== $stringlocal) {
|
|
$record->local = $stringlocal;
|
|
$record->timecustomized = $now;
|
|
} else {
|
|
$record->local = null;
|
|
$record->timecustomized = null;
|
|
}
|
|
|
|
$DB->insert_record('tool_customlang', $record);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!is_null($progressbar)) {
|
|
$progressbar->update_full(100, get_string('checkoutdone', 'tool_customlang'));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Exports the translator database into disk files
|
|
*
|
|
* @param mixed $lang language code
|
|
*/
|
|
public static function checkin($lang) {
|
|
global $DB, $USER, $CFG;
|
|
require_once($CFG->libdir.'/filelib.php');
|
|
|
|
if ($lang !== clean_param($lang, PARAM_LANG)) {
|
|
return false;
|
|
}
|
|
|
|
list($insql, $inparams) = $DB->get_in_or_equal(self::list_components());
|
|
|
|
// Get all customized strings from updated valid components.
|
|
$sql = "SELECT s.*, c.name AS component
|
|
FROM {tool_customlang} s
|
|
JOIN {tool_customlang_components} c ON s.componentid = c.id
|
|
WHERE s.lang = ?
|
|
AND (s.local IS NOT NULL OR s.modified = 1)
|
|
AND c.name $insql
|
|
ORDER BY componentid, stringid";
|
|
array_unshift($inparams, $lang);
|
|
$strings = $DB->get_records_sql($sql, $inparams);
|
|
|
|
$files = array();
|
|
foreach ($strings as $string) {
|
|
if (!is_null($string->local)) {
|
|
$files[$string->component][$string->stringid] = $string->local;
|
|
}
|
|
}
|
|
|
|
fulldelete(self::get_localpack_location($lang));
|
|
foreach ($files as $component => $strings) {
|
|
self::dump_strings($lang, $component, $strings);
|
|
}
|
|
|
|
$DB->set_field_select('tool_customlang', 'modified', 0, 'lang = ?', array($lang));
|
|
$sm = get_string_manager();
|
|
$sm->reset_caches();
|
|
}
|
|
|
|
/**
|
|
* Returns full path to the directory where local packs are dumped into
|
|
*
|
|
* @param string $lang language code
|
|
* @return string full path
|
|
*/
|
|
public static function get_localpack_location($lang) {
|
|
global $CFG;
|
|
|
|
return $CFG->langlocalroot.'/'.$lang.'_local';
|
|
}
|
|
|
|
/**
|
|
* Writes strings into a local language pack file
|
|
*
|
|
* @param string $component the name of the component
|
|
* @param array $strings
|
|
* @return void
|
|
*/
|
|
protected static function dump_strings($lang, $component, $strings) {
|
|
global $CFG;
|
|
|
|
if ($lang !== clean_param($lang, PARAM_LANG)) {
|
|
throw new moodle_exception('Unable to dump local strings for non-installed language pack .'.s($lang));
|
|
}
|
|
if ($component !== clean_param($component, PARAM_COMPONENT)) {
|
|
throw new coding_exception('Incorrect component name');
|
|
}
|
|
if (!$filename = self::get_component_filename($component)) {
|
|
throw new moodle_exception('Unable to find the filename for the component '.s($component));
|
|
}
|
|
if ($filename !== clean_param($filename, PARAM_FILE)) {
|
|
throw new coding_exception('Incorrect file name '.s($filename));
|
|
}
|
|
list($package, $subpackage) = core_component::normalize_component($component);
|
|
$packageinfo = " * @package $package";
|
|
if (!is_null($subpackage)) {
|
|
$packageinfo .= "\n * @subpackage $subpackage";
|
|
}
|
|
$filepath = self::get_localpack_location($lang);
|
|
$filepath = $filepath.'/'.$filename;
|
|
if (!is_dir(dirname($filepath))) {
|
|
check_dir_exists(dirname($filepath));
|
|
}
|
|
|
|
if (!$f = fopen($filepath, 'w')) {
|
|
throw new moodle_exception('Unable to write '.s($filepath));
|
|
}
|
|
fwrite($f, <<<EOF
|
|
<?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/>.
|
|
|
|
/**
|
|
* Local language pack from $CFG->wwwroot
|
|
*
|
|
$packageinfo
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
|
|
defined('MOODLE_INTERNAL') || die();
|
|
|
|
|
|
EOF
|
|
);
|
|
|
|
foreach ($strings as $stringid => $text) {
|
|
if ($stringid !== clean_param($stringid, PARAM_STRINGID)) {
|
|
debugging('Invalid string identifier '.s($stringid));
|
|
continue;
|
|
}
|
|
fwrite($f, '$string[\'' . $stringid . '\'] = ');
|
|
fwrite($f, var_export($text, true));
|
|
fwrite($f, ";\n");
|
|
}
|
|
fclose($f);
|
|
@chmod($filepath, $CFG->filepermissions);
|
|
}
|
|
|
|
/**
|
|
* Returns the name of the file where the component's local strings should be exported into
|
|
*
|
|
* @param string $component normalized name of the component, eg 'core' or 'mod_workshop'
|
|
* @return string|boolean filename eg 'moodle.php' or 'workshop.php', false if not found
|
|
*/
|
|
protected static function get_component_filename($component) {
|
|
|
|
$return = false;
|
|
foreach (self::list_components() as $legacy => $normalized) {
|
|
if ($component === $normalized) {
|
|
$return = $legacy.'.php';
|
|
break;
|
|
}
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Returns the number of modified strings checked out in the translator
|
|
*
|
|
* @param string $lang language code
|
|
* @return int
|
|
*/
|
|
public static function get_count_of_modified($lang) {
|
|
global $DB;
|
|
|
|
return $DB->count_records('tool_customlang', array('lang'=>$lang, 'modified'=>1));
|
|
}
|
|
|
|
/**
|
|
* Saves filter data into a persistant storage such as user session
|
|
*
|
|
* @see self::load_filter()
|
|
* @param stdclass $data filter values
|
|
* @param stdclass $persistant storage object
|
|
*/
|
|
public static function save_filter(stdclass $data, stdclass $persistant) {
|
|
if (!isset($persistant->tool_customlang_filter)) {
|
|
$persistant->tool_customlang_filter = array();
|
|
}
|
|
foreach ($data as $key => $value) {
|
|
if ($key !== 'submit') {
|
|
$persistant->tool_customlang_filter[$key] = serialize($value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads the previously saved filter settings from a persistent storage
|
|
*
|
|
* @see self::save_filter()
|
|
* @param stdclass $persistant storage object
|
|
* @return stdclass filter data
|
|
*/
|
|
public static function load_filter(stdclass $persistant) {
|
|
$data = new stdclass();
|
|
if (isset($persistant->tool_customlang_filter)) {
|
|
foreach ($persistant->tool_customlang_filter as $key => $value) {
|
|
$data->{$key} = unserialize($value);
|
|
}
|
|
}
|
|
return $data;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Represents the action menu of the tool
|
|
*/
|
|
class tool_customlang_menu implements renderable {
|
|
|
|
/** @var menu items */
|
|
protected $items = array();
|
|
|
|
public function __construct(array $items = array()) {
|
|
global $CFG;
|
|
|
|
foreach ($items as $itemkey => $item) {
|
|
$this->add_item($itemkey, $item['title'], $item['url'], empty($item['method']) ? 'post' : $item['method']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the menu items
|
|
*
|
|
* @return array (string)key => (object)[->(string)title ->(moodle_url)url ->(string)method]
|
|
*/
|
|
public function get_items() {
|
|
return $this->items;
|
|
}
|
|
|
|
/**
|
|
* Adds item into the menu
|
|
*
|
|
* @param string $key item identifier
|
|
* @param string $title localized action title
|
|
* @param moodle_url $url action handler
|
|
* @param string $method form method
|
|
*/
|
|
public function add_item($key, $title, moodle_url $url, $method) {
|
|
if (isset($this->items[$key])) {
|
|
throw new coding_exception('Menu item already exists');
|
|
}
|
|
if (empty($title) or empty($key)) {
|
|
throw new coding_exception('Empty title or item key not allowed');
|
|
}
|
|
$item = new stdclass();
|
|
$item->title = $title;
|
|
$item->url = $url;
|
|
$item->method = $method;
|
|
$this->items[$key] = $item;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Represents the translation tool
|
|
*/
|
|
class tool_customlang_translator implements renderable {
|
|
|
|
/** @var int number of rows per page */
|
|
const PERPAGE = 100;
|
|
|
|
/** @var int total number of the rows int the table */
|
|
public $numofrows = 0;
|
|
|
|
/** @var moodle_url */
|
|
public $handler;
|
|
|
|
/** @var string language code */
|
|
public $lang;
|
|
|
|
/** @var int page to display, starting with page 0 */
|
|
public $currentpage = 0;
|
|
|
|
/** @var array of stdclass strings to display */
|
|
public $strings = array();
|
|
|
|
/** @var stdclass */
|
|
protected $filter;
|
|
|
|
public function __construct(moodle_url $handler, $lang, $filter, $currentpage = 0) {
|
|
global $DB;
|
|
|
|
$this->handler = $handler;
|
|
$this->lang = $lang;
|
|
$this->filter = $filter;
|
|
$this->currentpage = $currentpage;
|
|
|
|
if (empty($filter) or empty($filter->component)) {
|
|
// nothing to do
|
|
$this->currentpage = 1;
|
|
return;
|
|
}
|
|
|
|
list($insql, $inparams) = $DB->get_in_or_equal($filter->component, SQL_PARAMS_NAMED);
|
|
|
|
$csql = "SELECT COUNT(*)";
|
|
$fsql = "SELECT s.*, c.name AS component";
|
|
$sql = " FROM {tool_customlang_components} c
|
|
JOIN {tool_customlang} s ON s.componentid = c.id
|
|
WHERE s.lang = :lang
|
|
AND c.name $insql";
|
|
|
|
$params = array_merge(array('lang' => $lang), $inparams);
|
|
|
|
if (!empty($filter->customized)) {
|
|
$sql .= " AND s.local IS NOT NULL";
|
|
}
|
|
|
|
if (!empty($filter->modified)) {
|
|
$sql .= " AND s.modified = 1";
|
|
}
|
|
|
|
if (!empty($filter->stringid)) {
|
|
$sql .= " AND s.stringid = :stringid";
|
|
$params['stringid'] = $filter->stringid;
|
|
}
|
|
|
|
if (!empty($filter->substring)) {
|
|
$sql .= " AND (".$DB->sql_like('s.original', ':substringoriginal', false)." OR
|
|
".$DB->sql_like('s.master', ':substringmaster', false)." OR
|
|
".$DB->sql_like('s.local', ':substringlocal', false).")";
|
|
$params['substringoriginal'] = '%'.$filter->substring.'%';
|
|
$params['substringmaster'] = '%'.$filter->substring.'%';
|
|
$params['substringlocal'] = '%'.$filter->substring.'%';
|
|
}
|
|
|
|
if (!empty($filter->helps)) {
|
|
$sql .= " AND ".$DB->sql_like('s.stringid', ':help', false); //ILIKE
|
|
$params['help'] = '%\_help';
|
|
} else {
|
|
$sql .= " AND ".$DB->sql_like('s.stringid', ':link', false, true, true); //NOT ILIKE
|
|
$params['link'] = '%\_link';
|
|
}
|
|
|
|
$osql = " ORDER BY c.name, s.stringid";
|
|
|
|
$this->numofrows = $DB->count_records_sql($csql.$sql, $params);
|
|
$this->strings = $DB->get_records_sql($fsql.$sql.$osql, $params, ($this->currentpage) * self::PERPAGE, self::PERPAGE);
|
|
}
|
|
}
|