source: moodle/trunk/fuentes/admin/tool/customlang/locallib.php

Last change on this file was 136, checked in by mabarracus, 4 years ago

Ported code to xenial

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