• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ewallah / moodle-tool_translate / 6586665820

20 Oct 2023 10:41AM UTC coverage: 92.18% (-5.1%) from 97.317%
6586665820

push

github

rdebleu
optional params

37 of 37 new or added lines in 3 files covered. (100.0%)

389 of 422 relevant lines covered (92.18%)

8.4 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.82
/classes/engine.php
1
<?php
2
// This file is part of the tool_translate plugin for 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
 * Base class for translate engines.
19
 *
20
 * All translate engines must extend this class.
21
 *
22
 * @package   tool_translate
23
 * @copyright 2023 iplusacademy
24
 * @author    Renaat Debleu <info@eWallah.net>
25
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26
 */
27
namespace tool_translate;
28

29
use context_course;
30
use context_module;
31
use html_writer;
32
use moodle_exception;
33
use stdClass;
34

35
/**
36
 * Base class for translate engines.
37
 *
38
 * @package   tool_translate
39
 * @copyright 2023 iplusacademy
40
 * @author    Renaat Debleu <info@eWallah.net>
41
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42
 */
43
abstract class engine {
44

45
    /** @var stdClass course */
46
    protected $course;
47

48
    /** @var bool counting */
49
    public $counting = true;
50

51
    /** @var string targetlang */
52
    public $targetlang;
53

54
    /** @var string sourcelang */
55
    public $sourcelang;
56

57
    /**
58
     * Constructor
59
     *
60
     * @param course $course
61
     */
62
    public function __construct($course) {
63
        $this->course = $course;
36✔
64
    }
65

66
    /**
67
     * The name of this engine.
68
     *
69
     * @return string
70
     */
71
    public function get_name(): string {
72
        $classname = str_ireplace('\engine', '', get_class($this));
9✔
73
        return get_string('pluginname', $classname);
9✔
74
    }
75

76
    /**
77
     * Is the translate engine fully configured and ready to use.
78
     *
79
     * @return bool if the engine is ready for use
80
     */
81
    abstract public function is_configured(): bool;
82

83
    /**
84
     * Rough calculation of price.
85
     *
86
     * @param int $letters count of letters
87
     * @return string
88
     */
89
    abstract public function get_price(int $letters): string;
90

91
    /**
92
     * Supported languages.
93
     *
94
     * @return string[] Array of suported source/target languages
95
     */
96
    public function supported_langs(): array {
97
        throw new moodle_exception('supported_langs not configured for this engine');
9✔
98
    }
99

100
    /**
101
     * Is language supported.
102
     *
103
     * @param string $lang
104
     * @return bool
105
     */
106
    public function lang_supported($lang): bool {
107
        $values = array_values($this->supported_langs());
18✔
108
        if (!in_array($lang, $values, true)) {
18✔
109
            throw new moodle_exception('language not supported');
9✔
110
        }
111
        return true;
9✔
112
    }
113

114
    /**
115
     * Translate text.
116
     *
117
     * @param string $source The source language
118
     * @param string $target The target language
119
     * @param string $txt The text that has to be translated
120
     * @return string|null Translated text or nothing
121
     */
122
    abstract public function translatetext(string $source, string $target, string $txt): ?string;
123

124
    /**
125
     * Translate other
126
     *
127
     * @return string
128
     */
129
    public function translate_other(): string {
130
        global $CFG, $DB;
131
        require_once($CFG->libdir . '/badgeslib.php');
9✔
132
        $id = $this->course->id;
9✔
133
        $context = context_course::instance($id);
9✔
134
        $s = $this->add_records('enrol', 'courseid', $id);
9✔
135
        $s .= $this->add_records('course', 'id', $id);
9✔
136
        $s .= $this->add_records('customfield_data', 'contextid', $context->id);
9✔
137
        // Badges.
138
        $s .= $this->add_records('badge', 'courseid', $id, ['description', 'message', 'messagesubject']);
9✔
139
        $badges = $DB->get_records('badge', ['type' => BADGE_TYPE_COURSE, 'courseid' => $id]);
9✔
140
        foreach ($badges as $badge) {
9✔
141
            $s .= $this->add_records('badge_criteria', 'badgeid', $badge->id);
9✔
142
            $s .= $this->add_records('badge_endorsement', 'badgeid', $badge->id, ['claimcomment']);
9✔
143
        }
144
        $s .= $this->add_records('certfificate', 'course', $id, ['customtext']);
9✔
145
        $s .= $this->add_records('grade_categories', 'courseid', $id, ['fullname']);
9✔
146
        $s .= $this->add_records('grade_items', 'courseid', $id, ['itemname']);
9✔
147
        $s .= $this->add_records('grade_outcomes', 'courseid', $id, ['shortname', 'fullname']);
9✔
148
        $s .= $this->add_records('groupings', 'courseid', $id);
9✔
149
        $s .= $this->add_records('groups', 'courseid', $id);
9✔
150
        $s .= $this->add_records('role_names', 'contextid', $context->id);
9✔
151
        $s .= $this->add_records('scales', 'courseid', $id, ['scale']);
9✔
152
        // Notes.
153
        $s .= $this->add_records('post', 'courseid', $id, ['content']);
9✔
154
        if ($this->counting) {
9✔
155
            return $s;
9✔
156
        }
157
        $event = event\course_translated::create(['context' => $context]);
9✔
158
        $event->trigger();
9✔
159
        rebuild_course_cache($id);
9✔
160
        return "$s <br/>Course with id $id translated all extra elements.";
9✔
161
    }
162

163
    /**
164
     * Translate section
165
     *
166
     * @param int $sectionid
167
     * @return string
168
     */
169
    public function translate_section($sectionid): string {
170
        $s = $this->add_records('course_sections', 'id', $sectionid);
9✔
171
        if ($this->counting) {
9✔
172
            return $s;
9✔
173
        }
174
        $context = context_course::instance($this->course->id);
9✔
175
        $event = event\module_translated::create(['context' => $context, 'other' => ['sectionid' => $sectionid]]);
9✔
176
        $event->trigger();
9✔
177
        rebuild_course_cache($this->course->id);
9✔
178
        $courseformat = course_get_format($this->course)->get_format();
9✔
179
        return $s . get_string('sectionname', 'format_' . $courseformat) . " with id $sectionid translated";
9✔
180
    }
181

182
    /**
183
     * Translate module
184
     *
185
     * @param int $moduleid
186
     * @return string
187
     */
188
    public function translate_module($moduleid): string {
189
        global $CFG, $DB;
190
        require_once($CFG->libdir . '/questionlib.php');
9✔
191
        $modinfo = get_fast_modinfo($this->course->id, -1);
9✔
192
        $cm = $modinfo->cms[$moduleid];
9✔
193
        $context = context_module::instance($cm->id);
9✔
194
        $s = $this->translate_record($cm->modname, $cm->instance);
9✔
195
        switch ($cm->modname) {
9✔
196
            case 'book':
9✔
197
                $s .= $this->add_records('book_chapters', 'bookid', $cm->instance);
9✔
198
                break;
9✔
199
            case 'checklist':
9✔
200
                $s .= $this->add_records('checklist_item', 'checklist', $cm->instance, ['displaytext']);
×
201
                break;
×
202
            case 'choice':
9✔
203
                $s .= $this->add_records('choice_options', 'choiceid', $cm->instance, ['text']);
9✔
204
                break;
9✔
205
            case 'feedback':
9✔
206
                $s .= $this->add_records('feedback_item', 'feedback', $cm->instance, ['label', 'presentation']);
9✔
207
                break;
9✔
208
            case 'forum':
9✔
209
                $s .= $this->add_records('forum_discussions', 'forum', $cm->instance);
9✔
210
                break;
9✔
211
            case 'glossary':
9✔
212
                $s .= $this->add_records('glossary_categories', 'glossaryid', $cm->instance);
9✔
213
                $s .= $this->add_records('glossary_entries', 'glossaryid', $cm->instance, ['concept']);
9✔
214
                break;
9✔
215
            case 'lesson':
9✔
216
                $s .= $this->add_records('lesson_pages', 'lessonid', $cm->instance);
9✔
217
                $s .= $this->add_records('lesson_answers', 'lessonid', $cm->instance);
9✔
218
            case 'quiz':
9✔
219
                $s .= $this->add_records('quiz_sections', 'quizid', $cm->instance, ['heading']);
9✔
220
                $s .= $this->add_records('quiz_feedback', 'quizid', $cm->instance);
9✔
221
                $slots = $CFG->version < 2022020300 ? $DB->get_records('quiz_slots', ['quizid' => $cm->instance]) :
9✔
222
                     \mod_quiz\question\bank\qbank_helper::get_question_structure($cm->instance, $context);
9✔
223
                foreach ($slots as $slot) {
9✔
224
                    $sid = $slot->questionid;
9✔
225
                    if (!is_integer($sid)) {
9✔
226
                        continue;
9✔
227
                    }
228
                    $s .= $this->add_records('question', 'id', $sid);
×
229
                    $s .= $this->add_records('question_answers', 'question', $sid);
×
230
                    $s .= $this->add_records('question_hints', 'questionid', $sid);
×
231
                    $s .= $this->add_records('question_order', 'question', $sid);
×
232
                    $s .= $this->add_records('question_order_sub', 'question', $sid);
×
233
                    $q = \question_bank::load_question($sid);
×
234
                    $qt = get_class($q->qtype);
×
235
                    // Brute force collect feedback.
236
                    $s .= $this->add_records($qt, 'questionid', $sid);
×
237
                    $s .= $this->add_records($qt . '_options' , 'questionid', $sid);
×
238
                    $s .= $this->add_records($qt . '_answers' , 'questionid', $sid);
×
239
                    $s .= $this->add_records($qt . '_subquestions' , 'questionid', $sid);
×
240
                    $qt = str_ireplace('qtype', 'question', $qt);
×
241
                    $s .= $this->add_records($qt, 'questionid', $sid);
×
242
                    $s .= $this->add_records($qt, 'question', $sid);
×
243
                    $s .= $this->add_records($qt . '_options' , 'questionid', $sid);
×
244
                    $s .= $this->add_records($qt . '_answers' , 'questionid', $sid);
×
245
                    $s .= $this->add_records($qt . '_subquestions' , 'questionid', $sid);
×
246
                }
247
                break;
9✔
248
        }
249
        if ($this->counting) {
9✔
250
            return $s;
9✔
251
        }
252
        $event = event\module_translated::create(['context' => $context]);
9✔
253
        $event->trigger();
9✔
254
        rebuild_course_cache($this->course->id);
9✔
255
        $cm = $modinfo->cms[$moduleid];
9✔
256
        $url = html_writer::link($cm->url, $cm->get_formatted_name());
9✔
257
        return "$s<br/>Module with id $moduleid translated<br/>$url<br/>";
9✔
258
    }
259

260
    /**
261
     * Add record
262
     *
263
     * @param string $tablename
264
     * @param string $fieldname
265
     * @param int $id
266
     * @param array $extra
267
     * @return string
268
     */
269
    private function add_records($tablename, $fieldname, $id, $extra = []) {
270
        global $DB;
271
        $s = '';
9✔
272
        $dbman = $DB->get_manager();
9✔
273
        if ($dbman->table_exists($tablename)) {
9✔
274
            if ($dbman->field_exists($tablename, $fieldname)) {
9✔
275
                $items = $DB->get_records($tablename, [$fieldname => $id]);
9✔
276
                foreach ($items as $item) {
9✔
277
                    $s .= $this->translate_record($tablename, $item->id, $extra);
9✔
278
                }
279
            }
280
        }
281
        return $s;
9✔
282
    }
283

284
    /**
285
     * Translate record
286
     *
287
     * @param string $table
288
     * @param int $id
289
     * @param array $fields
290
     * @return string
291
     */
292
    private function translate_record($table, $id, $fields = []) {
293
        global $DB;
294
        if (!$this->counting && (is_null($this->targetlang) || is_null($this->sourcelang))) {
9✔
295
            throw new moodle_exception('Language not specified');
9✔
296
        }
297
        $s = [];
9✔
298
        if ($record = $DB->get_record($table, ['id' => $id])) {
9✔
299
            $dbman = $DB->get_manager();
9✔
300
            $ref = new \ReflectionObject($record);
9✔
301
            $updatetime = false;
9✔
302
            $properties = $ref->getProperties();
9✔
303
            $skipped = ['displayformat', 'approvaldisplayformat'];
9✔
304
            $handled = ['name', 'answertext', 'title'];
9✔
305
            foreach ($properties as $prop) {
9✔
306
                if (in_array($prop->name, $skipped, true)) {
9✔
307
                    continue;
9✔
308
                }
309
                if (in_array($prop->name, $handled, true)) {
9✔
310
                    $fields[] = $prop->name;
9✔
311
                }
312
                $x = stripos($prop->name, 'format');
9✔
313
                if ( $x > 1) {
9✔
314
                    $fields[] = substr($prop->name, 0, $x);
9✔
315
                }
316
                if ($prop->name === 'timemodified') {
9✔
317
                    $updatetime = true;
9✔
318
                }
319
            }
320
            foreach ($fields as $field) {
9✔
321
                if ($dbman->field_exists($table, $field)) {
9✔
322
                    $task = $record->{$field};
9✔
323
                    if (strlen($task ?? '') > 0) {
9✔
324
                        $result = $task;
9✔
325
                        if (!$this->counting) {
9✔
326
                            // TODO: What if max lenght > result.
327
                            $result = $this->translatetext($this->sourcelang, $this->targetlang, $task);
9✔
328
                            if (!is_null($result) && $task != $result) {
9✔
329
                                $DB->set_field($table, $field, $result, ['id' => $id]);
9✔
330
                                if ($updatetime) {
9✔
331
                                    $DB->set_field($table, 'timemodified', time(), ['id' => $id]);
9✔
332
                                }
333
                            }
334
                        }
335
                        $s[] = $result;
9✔
336
                    }
337
                }
338
            }
339
        }
340
        return implode('<br />', $s);
9✔
341
    }
342

343

344
    /**
345
     * Translate a plugin
346
     *
347
     * @param string $component
348
     * @param string $fromlanguage
349
     * @param string $tolanguage
350
     * @return string
351
     */
352
    public function translate_plugin($component, $fromlanguage, $tolanguage) {
353
        global $CFG;
354
        require_once($CFG->dirroot . '/admin/tool/customlang/locallib.php');
9✔
355
        $done = [];
9✔
356
        $components = \tool_customlang_utils::list_components();
9✔
357
        if (!array_key_exists($component, $components)) {
9✔
358
             throw new moodle_exception('Plugin not found');
9✔
359
        }
360
        $sm = get_string_manager();
9✔
361
        $entries = $sm->load_component_strings($component, $fromlanguage);
9✔
362
        foreach ($entries as $key => $value) {
9✔
363
            $s = $this->translatetext($fromlanguage, $tolanguage, $value);
9✔
364
            if ($s != $value) {
9✔
365
                $done[$key] = $s;
9✔
366
            }
367
        }
368
        return self::dump_strings($tolanguage, $component, $done);
9✔
369
    }
370

371
    /**
372
     * Writes strings into a local language pack file
373
     *
374
     * @param string $lang the language
375
     * @param string $component the name of the component
376
     * @param array $strings
377
     * @return string
378
     */
379
    protected static function dump_strings($lang, $component, $strings) {
380
        $admin = fullname(get_admin());
9✔
381
        $year = date("Y");
9✔
382
        $str = "<?php
9✔
383

384
// This file is part of Moodle - http://moodle.org/
385
//
386
// Moodle is free software: you can redistribute it and/or modify
387
// it under the terms of the GNU General Public License as published by
388
// the Free Software Foundation, either version 3 of the License, or
389
// (at your option) any later version.
390
//
391
// Moodle is distributed in the hope that it will be useful,
392
// but WITHOUT ANY WARRANTY; without even the implied warranty of
393
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
394
// GNU General Public License for more details.
395
//
396
// You should have received a copy of the GNU General Public License
397
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
398

399
/**
400
 * Automatic translated strings ($lang) for $component
9✔
401
 *
402
 * @package   $component
9✔
403
 * @copyright $year $admin
9✔
404
 * @author    tool_translate
405
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
406
 */
407

408
";
9✔
409

410
        foreach ($strings as $stringid => $text) {
9✔
411
            $str .= '$string[\'' . $stringid . '\'] =  ' . var_export($text, true) . ';
9✔
412
';
9✔
413
        }
414
        return $str;
9✔
415
    }
416

417
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc