Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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