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
 * Mlang PHP based on David Mudrak phpparser for local_amos.
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\mlang;
26
 
27
use coding_exception;
28
use moodle_exception;
29
 
30
/**
31
 * Parser of Moodle strings defined as associative array.
32
 *
33
 * Moodle core just includes this file format directly as normal PHP code. However
34
 * for security reasons, we must not do this for files uploaded by anonymous users.
35
 * This parser reconstructs the associative $string array without actually including
36
 * the file.
37
 */
38
class phpparser {
39
 
40
    /** @var holds the singleton instance of self */
41
    private static $instance = null;
42
 
43
    /**
44
     * Prevents direct creation of object
45
     */
46
    private function __construct() {
47
    }
48
 
49
    /**
50
     * Prevent from cloning the instance
51
     */
52
    public function __clone() {
53
        throw new coding_exception('Cloning os singleton is not allowed');
54
    }
55
 
56
    /**
57
     * Get the singleton instance fo this class
58
     *
59
     * @return phpparser singleton instance of phpparser
60
     */
61
    public static function get_instance(): phpparser {
62
        if (is_null(self::$instance)) {
63
            self::$instance = new phpparser();
64
        }
65
        return self::$instance;
66
    }
67
 
68
    /**
69
     * Parses the given data in Moodle PHP string format
70
     *
71
     * Note: This method is adapted from local_amos as it is highly tested and robust.
72
     * The priority is keeping it similar to the original one to make it easier to mantain.
73
     *
74
     * @param string $data definition of the associative array
75
     * @param int $format the data format on the input, defaults to the one used since 2.0
76
     * @return langstring[] array of langstrings of this file
77
     */
78
    public function parse(string $data, int $format = 2): array {
79
        $result = [];
80
        $strings = $this->extract_strings($data);
81
        foreach ($strings as $id => $text) {
82
            $cleaned = clean_param($id, PARAM_STRINGID);
83
            if ($cleaned !== $id) {
84
                continue;
85
            }
86
            $text = langstring::fix_syntax($text, 2, $format);
87
            $result[] = new langstring($id, $text);
88
        }
89
        return $result;
90
    }
91
 
92
    /**
93
     * Low level parsing method
94
     *
95
     * Note: This method is adapted from local_amos as it is highly tested and robust.
96
     * The priority is keeping it similar to the original one to make it easier to mantain.
97
     *
98
     * @param string $data
99
     * @return string[] the data strings
100
     */
101
    protected function extract_strings(string $data): array {
102
 
103
        $strings = []; // To be returned.
104
 
105
        if (empty($data)) {
106
            return $strings;
107
        }
108
 
109
        // Tokenize data - we expect valid PHP code.
110
        $tokens = token_get_all($data);
111
 
112
        // Get rid of all non-relevant tokens.
113
        $rubbish = [T_WHITESPACE, T_INLINE_HTML, T_COMMENT, T_DOC_COMMENT, T_OPEN_TAG, T_CLOSE_TAG];
114
        foreach ($tokens as $i => $token) {
115
            if (is_array($token)) {
116
                if (in_array($token[0], $rubbish)) {
117
                    unset($tokens[$i]);
118
                }
119
            }
120
        }
121
 
122
        $id = null;
123
        $text = null;
124
        $line = 0;
125
        $expect = 'STRING_VAR'; // The first expected token is '$string'.
126
 
127
        // Iterate over tokens and look for valid $string array assignment patterns.
128
        foreach ($tokens as $token) {
129
            $foundtype = null;
130
            $founddata = null;
131
            if (is_array($token)) {
132
                $foundtype = $token[0];
133
                $founddata = $token[1];
134
                if (!empty($token[2])) {
135
                    $line = $token[2];
136
                }
137
 
138
            } else {
139
                $foundtype = 'char';
140
                $founddata = $token;
141
            }
142
 
143
            if ($expect == 'STRING_VAR') {
144
                if ($foundtype === T_VARIABLE and $founddata === '$string') {
145
                    $expect = 'LEFT_BRACKET';
146
                    continue;
147
                } else {
148
                    // Allow other code at the global level.
149
                    continue;
150
                }
151
            }
152
 
153
            if ($expect == 'LEFT_BRACKET') {
154
                if ($foundtype === 'char' and $founddata === '[') {
155
                    $expect = 'STRING_ID';
156
                    continue;
157
                } else {
158
                    throw new moodle_exception('Parsing error. Expected character [ at line '.$line);
159
                }
160
            }
161
 
162
            if ($expect == 'STRING_ID') {
163
                if ($foundtype === T_CONSTANT_ENCAPSED_STRING) {
164
                    $id = $this->decapsulate($founddata);
165
                    $expect = 'RIGHT_BRACKET';
166
                    continue;
167
                } else {
168
                    throw new moodle_exception('Parsing error. Expected T_CONSTANT_ENCAPSED_STRING array key at line '.$line);
169
                }
170
            }
171
 
172
            if ($expect == 'RIGHT_BRACKET') {
173
                if ($foundtype === 'char' and $founddata === ']') {
174
                    $expect = 'ASSIGNMENT';
175
                    continue;
176
                } else {
177
                    throw new moodle_exception('Parsing error. Expected character ] at line '.$line);
178
                }
179
            }
180
 
181
            if ($expect == 'ASSIGNMENT') {
182
                if ($foundtype === 'char' and $founddata === '=') {
183
                    $expect = 'STRING_TEXT';
184
                    continue;
185
                } else {
186
                    throw new moodle_exception('Parsing error. Expected character = at line '.$line);
187
                }
188
            }
189
 
190
            if ($expect == 'STRING_TEXT') {
191
                if ($foundtype === T_CONSTANT_ENCAPSED_STRING) {
192
                    $text = $this->decapsulate($founddata);
193
                    $expect = 'SEMICOLON';
194
                    continue;
195
                } else {
196
                    throw new moodle_exception(
197
                        'Parsing error. Expected T_CONSTANT_ENCAPSED_STRING array item value at line '.$line
198
                    );
199
                }
200
            }
201
 
202
            if ($expect == 'SEMICOLON') {
203
                if (is_null($id) or is_null($text)) {
204
                    throw new moodle_exception('Parsing error. NULL string id or value at line '.$line);
205
                }
206
                if ($foundtype === 'char' and $founddata === ';') {
207
                    if (!empty($id)) {
208
                        $strings[$id] = $text;
209
                    }
210
                    $id = null;
211
                    $text = null;
212
                    $expect = 'STRING_VAR';
213
                    continue;
214
                } else {
215
                    throw new moodle_exception('Parsing error. Expected character ; at line '.$line);
216
                }
217
            }
218
 
219
        }
220
 
221
        return $strings;
222
    }
223
 
224
    /**
225
     * Given one T_CONSTANT_ENCAPSED_STRING, return its value without quotes
226
     *
227
     * Also processes escaped quotes inside the text.
228
     *
229
     * Note: This method is taken directly from local_amos as it is highly tested and robust.
230
     *
231
     * @param string $text value obtained by token_get_all()
232
     * @return string value without quotes
233
     */
234
    protected function decapsulate(string $text): string {
235
 
236
        if (strlen($text) < 2) {
237
            throw new moodle_exception('Parsing error. Expected T_CONSTANT_ENCAPSED_STRING in decapsulate()');
238
        }
239
 
240
        if (substr($text, 0, 1) == "'" and substr($text, -1) == "'") {
241
            // Single quoted string.
242
            $text = trim($text, "'");
243
            $text = str_replace("\'", "'", $text);
244
            $text = str_replace('\\\\', '\\', $text);
245
            return $text;
246
 
247
        } else if (substr($text, 0, 1) == '"' and substr($text, -1) == '"') {
248
            // Double quoted string.
249
            $text = trim($text, '"');
250
            $text = str_replace('\"', '"', $text);
251
            $text = str_replace('\\\\', '\\', $text);
252
            return $text;
253
 
254
        } else {
255
            throw new moodle_exception(
256
                'Parsing error. Unexpected quotation in T_CONSTANT_ENCAPSED_STRING in decapsulate(): '.$text
257
            );
258
        }
259
    }
260
}