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
 * Custom lang importer.
19
 *
20
 * @package    tool_customlang
21
 * @copyright  2020 Ferran Recio <ferran@moodle.com>
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
namespace tool_customlang\local;
26
 
27
use tool_customlang\local\mlang\phpparser;
28
use tool_customlang\local\mlang\logstatus;
29
use tool_customlang\local\mlang\langstring;
30
use core\output\notification;
31
use stored_file;
32
use coding_exception;
33
use moodle_exception;
34
use core_component;
35
use stdClass;
36
 
37
/**
38
 * Class containing tha custom lang importer
39
 *
40
 * @package    tool_customlang
41
 * @copyright  2020 Ferran Recio <ferran@moodle.com>
42
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43
 */
44
class importer {
45
 
46
    /** @var int imports will only create new customizations */
47
    public const IMPORTNEW = 1;
48
    /** @var int imports will only update the current customizations */
49
    public const IMPORTUPDATE = 2;
50
    /** @var int imports all strings */
51
    public const IMPORTALL = 3;
52
 
53
    /**
54
     * @var string the language name
55
     */
56
    protected $lng;
57
 
58
    /**
59
     * @var int the importation mode (new, update, all)
60
     */
61
    protected $importmode;
62
 
63
    /**
64
     * @var string request folder path
65
     */
66
    private $folder;
67
 
68
    /**
69
     * @var array import log messages
70
     */
71
    private $log;
72
 
73
    /**
74
     * Constructor for the importer class.
75
     *
76
     * @param string $lng the current language to import.
77
     * @param int $importmode the import method (IMPORTALL, IMPORTNEW, IMPORTUPDATE).
78
     */
79
    public function __construct(string $lng, int $importmode = self::IMPORTALL) {
80
        $this->lng = $lng;
81
        $this->importmode = $importmode;
82
        $this->log = [];
83
    }
84
 
85
    /**
86
     * Returns the last parse log.
87
     *
88
     * @return logstatus[] mlang logstatus with the messages
89
     */
90
    public function get_log(): array {
91
        return $this->log;
92
    }
93
 
94
    /**
95
     * Import customlang files.
96
     *
97
     * @param stored_file[] $files array of files to import
98
     */
99
    public function import(array $files): void {
100
        // Create a temporal folder to store the files.
101
        $this->folder = make_request_directory(false);
102
 
103
        $langfiles = $this->deploy_files($files);
104
 
105
        $this->process_files($langfiles);
106
    }
107
 
108
    /**
109
     * Deploy all files into a request folder.
110
     *
111
     * @param stored_file[] $files array of files to deploy
112
     * @return string[] of file paths
113
     */
114
    private function deploy_files(array $files): array {
115
        $result = [];
116
        // Desploy all files.
117
        foreach ($files as $file) {
118
            if ($file->get_mimetype() == 'application/zip') {
119
                $result = array_merge($result, $this->unzip_file($file));
120
            } else {
121
                $path = $this->folder.'/'.$file->get_filename();
122
                $file->copy_content_to($path);
123
                $result = array_merge($result, [$path]);
124
            }
125
        }
126
        return $result;
127
    }
128
 
129
    /**
130
     * Unzip a file into the request folder.
131
     *
132
     * @param stored_file $file the zip file to unzip
133
     * @return string[] of zip content paths
134
     */
135
    private function unzip_file(stored_file $file): array {
136
        $fp = get_file_packer('application/zip');
137
        $zipcontents = $fp->extract_to_pathname($file, $this->folder);
138
        if (!$zipcontents) {
139
            throw new moodle_exception("Error Unzipping file", 1);
140
        }
141
        $result = [];
142
        foreach ($zipcontents as $contentname => $success) {
143
            if ($success) {
144
                $result[] = $this->folder.'/'.$contentname;
145
            }
146
        }
147
        return $result;
148
    }
149
 
150
    /**
151
     * Import strings from a list of langfiles.
152
     *
153
     * @param string[] $langfiles an array with file paths
154
     */
155
    private function process_files(array $langfiles): void {
156
        $parser = phpparser::get_instance();
157
        foreach ($langfiles as $filepath) {
158
            $component = $this->component_from_filepath($filepath);
159
            if ($component) {
160
                $strings = $parser->parse(file_get_contents($filepath));
161
                $this->import_strings($strings, $component);
162
            }
163
        }
164
    }
165
 
166
    /**
167
     * Try to get the component from a filepath.
168
     *
169
     * @param string $filepath the filepath
170
     * @return stdCalss|null the DB record of that component
171
     */
172
    private function component_from_filepath(string $filepath) {
173
        global $DB;
174
 
175
        // Get component from filename.
176
        $pathparts = pathinfo($filepath);
177
        if (empty($pathparts['filename'])) {
178
            throw new coding_exception("Cannot get filename from $filepath", 1);
179
        }
180
        $filename = $pathparts['filename'];
181
 
182
        $normalized = core_component::normalize_component($filename);
183
        if (count($normalized) == 1 || empty($normalized[1])) {
184
            $componentname = $normalized[0];
185
        } else {
186
            $componentname = implode('_', $normalized);
187
        }
188
 
189
        $result = $DB->get_record('tool_customlang_components', ['name' => $componentname]);
190
 
191
        if (!$result) {
192
            $this->log[] = new logstatus('notice_missingcomponent', notification::NOTIFY_ERROR, null, $componentname);
193
            return null;
194
        }
195
        return $result;
196
    }
197
 
198
    /**
199
     * Import an array of strings into the customlang tables.
200
     *
201
     * @param langstring[] $strings the langstring to set
202
     * @param stdClass $component the target component
203
     */
204
    private function import_strings(array $strings, stdClass $component): void {
205
        global $DB;
206
 
207
        foreach ($strings as $newstring) {
208
            // Check current DB entry.
209
            $customlang = $DB->get_record('tool_customlang', [
210
                'componentid' => $component->id,
211
                'stringid' => $newstring->id,
212
                'lang' => $this->lng,
213
            ]);
214
            if (!$customlang) {
215
                $customlang = null;
216
            }
217
 
218
            if ($this->can_save_string($customlang, $newstring, $component)) {
219
                $customlang->local = $newstring->text;
220
                $customlang->timecustomized = $newstring->timemodified;
221
                $customlang->outdated = 0;
222
                $customlang->modified = 1;
223
                $DB->update_record('tool_customlang', $customlang);
224
            }
225
        }
226
    }
227
 
228
    /**
229
     * Determine if a specific string can be saved based on the current importmode.
230
     *
231
     * @param stdClass $customlang customlang original record
232
     * @param langstring $newstring the new strign to store
233
     * @param stdClass $component the component target
234
     * @return bool if the string can be stored
235
     */
236
    private function can_save_string(?stdClass $customlang, langstring $newstring, stdClass $component): bool {
237
        $result = false;
238
        $message = 'notice_success';
239
        if (empty($customlang)) {
240
            $message = 'notice_inexitentstring';
241
            $this->log[] = new logstatus($message, notification::NOTIFY_ERROR, null, $component->name, $newstring);
242
            return $result;
243
        }
244
 
245
        switch ($this->importmode) {
246
            case self::IMPORTNEW:
247
                $result = empty($customlang->local);
248
                $warningmessage = 'notice_ignoreupdate';
249
                break;
250
            case self::IMPORTUPDATE:
251
                $result = !empty($customlang->local);
252
                $warningmessage = 'notice_ignorenew';
253
                break;
254
            case self::IMPORTALL:
255
                $result = true;
256
                break;
257
        }
258
        if ($result) {
259
            $errorlevel = notification::NOTIFY_SUCCESS;
260
        } else {
261
            $errorlevel = notification::NOTIFY_ERROR;
262
            $message = $warningmessage;
263
        }
264
        $this->log[] = new logstatus($message, $errorlevel, null, $component->name, $newstring);
265
 
266
        return $result;
267
    }
268
}