1 |
efrain |
1 |
<?php
|
|
|
2 |
|
|
|
3 |
// This file is part of Moodle - http://moodle.org/
|
|
|
4 |
//
|
|
|
5 |
// Moodle is free software: you can redistribute it and/or modify
|
|
|
6 |
// it under the terms of the GNU General Public License as published by
|
|
|
7 |
// the Free Software Foundation, either version 3 of the License, or
|
|
|
8 |
// (at your option) any later version.
|
|
|
9 |
//
|
|
|
10 |
// Moodle is distributed in the hope that it will be useful,
|
|
|
11 |
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
12 |
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
13 |
// GNU General Public License for more details.
|
|
|
14 |
//
|
|
|
15 |
// You should have received a copy of the GNU General Public License
|
|
|
16 |
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
17 |
|
|
|
18 |
/**
|
|
|
19 |
* @package moodlecore
|
|
|
20 |
* @subpackage backup-helper
|
|
|
21 |
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
|
|
|
22 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
23 |
*/
|
|
|
24 |
|
|
|
25 |
/**
|
|
|
26 |
* Base abstract class for all the helper classes providing various operations
|
|
|
27 |
*
|
|
|
28 |
* TODO: Finish phpdocs
|
|
|
29 |
*/
|
|
|
30 |
abstract class backup_helper {
|
|
|
31 |
|
|
|
32 |
/**
|
|
|
33 |
* Given one backupid, create all the needed dirs to have one backup temp dir available
|
|
|
34 |
*/
|
|
|
35 |
public static function check_and_create_backup_dir($backupid) {
|
|
|
36 |
$backupiddir = make_backup_temp_directory($backupid, false);
|
|
|
37 |
if (empty($backupiddir)) {
|
|
|
38 |
throw new backup_helper_exception('cannot_create_backup_temp_dir');
|
|
|
39 |
}
|
|
|
40 |
}
|
|
|
41 |
|
|
|
42 |
/**
|
|
|
43 |
* Given one backupid, ensure its temp dir is completely empty
|
|
|
44 |
*
|
|
|
45 |
* If supplied, progress object should be ready to receive indeterminate
|
|
|
46 |
* progress reports.
|
|
|
47 |
*
|
|
|
48 |
* @param string $backupid Backup id
|
|
|
49 |
* @param \core\progress\base $progress Optional progress reporting object
|
|
|
50 |
*/
|
|
|
51 |
public static function clear_backup_dir($backupid, \core\progress\base $progress = null) {
|
|
|
52 |
$backupiddir = make_backup_temp_directory($backupid, false);
|
|
|
53 |
if (!self::delete_dir_contents($backupiddir, '', $progress)) {
|
|
|
54 |
throw new backup_helper_exception('cannot_empty_backup_temp_dir');
|
|
|
55 |
}
|
|
|
56 |
return true;
|
|
|
57 |
}
|
|
|
58 |
|
|
|
59 |
/**
|
|
|
60 |
* Given one backupid, delete completely its temp dir
|
|
|
61 |
*
|
|
|
62 |
* If supplied, progress object should be ready to receive indeterminate
|
|
|
63 |
* progress reports.
|
|
|
64 |
*
|
|
|
65 |
* @param string $backupid Backup id
|
|
|
66 |
* @param \core\progress\base $progress Optional progress reporting object
|
|
|
67 |
*/
|
|
|
68 |
public static function delete_backup_dir($backupid, \core\progress\base $progress = null) {
|
|
|
69 |
$backupiddir = make_backup_temp_directory($backupid, false);
|
|
|
70 |
self::clear_backup_dir($backupid, $progress);
|
|
|
71 |
return rmdir($backupiddir);
|
|
|
72 |
}
|
|
|
73 |
|
|
|
74 |
/**
|
|
|
75 |
* Given one fullpath to directory, delete its contents recursively
|
|
|
76 |
* Copied originally from somewhere in the net.
|
|
|
77 |
* TODO: Modernise this
|
|
|
78 |
*
|
|
|
79 |
* If supplied, progress object should be ready to receive indeterminate
|
|
|
80 |
* progress reports.
|
|
|
81 |
*
|
|
|
82 |
* @param string $dir Directory to delete
|
|
|
83 |
* @param string $excludedir Exclude this directory
|
|
|
84 |
* @param \core\progress\base $progress Optional progress reporting object
|
|
|
85 |
*/
|
|
|
86 |
public static function delete_dir_contents($dir, $excludeddir='', \core\progress\base $progress = null) {
|
|
|
87 |
global $CFG;
|
|
|
88 |
|
|
|
89 |
if ($progress) {
|
|
|
90 |
$progress->progress();
|
|
|
91 |
}
|
|
|
92 |
|
|
|
93 |
if (!is_dir($dir)) {
|
|
|
94 |
// if we've been given a directory that doesn't exist yet, return true.
|
|
|
95 |
// this happens when we're trying to clear out a course that has only just
|
|
|
96 |
// been created.
|
|
|
97 |
return true;
|
|
|
98 |
}
|
|
|
99 |
$slash = "/";
|
|
|
100 |
|
|
|
101 |
// Create arrays to store files and directories
|
|
|
102 |
$dir_files = array();
|
|
|
103 |
$dir_subdirs = array();
|
|
|
104 |
|
|
|
105 |
// Make sure we can delete it
|
|
|
106 |
chmod($dir, $CFG->directorypermissions);
|
|
|
107 |
|
|
|
108 |
if ((($handle = opendir($dir))) == false) {
|
|
|
109 |
// The directory could not be opened
|
|
|
110 |
return false;
|
|
|
111 |
}
|
|
|
112 |
|
|
|
113 |
// Loop through all directory entries, and construct two temporary arrays containing files and sub directories
|
|
|
114 |
while (false !== ($entry = readdir($handle))) {
|
|
|
115 |
if (is_dir($dir. $slash .$entry) && $entry != ".." && $entry != "." && $entry != $excludeddir) {
|
|
|
116 |
$dir_subdirs[] = $dir. $slash .$entry;
|
|
|
117 |
|
|
|
118 |
} else if ($entry != ".." && $entry != "." && $entry != $excludeddir) {
|
|
|
119 |
$dir_files[] = $dir. $slash .$entry;
|
|
|
120 |
}
|
|
|
121 |
}
|
|
|
122 |
|
|
|
123 |
// Delete all files in the curent directory return false and halt if a file cannot be removed
|
|
|
124 |
for ($i=0; $i<count($dir_files); $i++) {
|
|
|
125 |
chmod($dir_files[$i], $CFG->directorypermissions);
|
|
|
126 |
if (((unlink($dir_files[$i]))) == false) {
|
|
|
127 |
return false;
|
|
|
128 |
}
|
|
|
129 |
}
|
|
|
130 |
|
|
|
131 |
// Empty sub directories and then remove the directory
|
|
|
132 |
for ($i=0; $i<count($dir_subdirs); $i++) {
|
|
|
133 |
chmod($dir_subdirs[$i], $CFG->directorypermissions);
|
|
|
134 |
if (self::delete_dir_contents($dir_subdirs[$i], '', $progress) == false) {
|
|
|
135 |
return false;
|
|
|
136 |
} else {
|
|
|
137 |
if (remove_dir($dir_subdirs[$i]) == false) {
|
|
|
138 |
return false;
|
|
|
139 |
}
|
|
|
140 |
}
|
|
|
141 |
}
|
|
|
142 |
|
|
|
143 |
// Close directory
|
|
|
144 |
closedir($handle);
|
|
|
145 |
|
|
|
146 |
// Success, every thing is gone return true
|
|
|
147 |
return true;
|
|
|
148 |
}
|
|
|
149 |
|
|
|
150 |
/**
|
|
|
151 |
* Delete all the temp dirs older than the time specified.
|
|
|
152 |
*
|
|
|
153 |
* If supplied, progress object should be ready to receive indeterminate
|
|
|
154 |
* progress reports.
|
|
|
155 |
*
|
|
|
156 |
* @param int $deletebefore Delete files and directories older than this time
|
|
|
157 |
* @param \core\progress\base $progress Optional progress reporting object
|
|
|
158 |
*/
|
|
|
159 |
public static function delete_old_backup_dirs($deletebefore, \core\progress\base $progress = null) {
|
|
|
160 |
$status = true;
|
|
|
161 |
// Get files and directories in the backup temp dir.
|
|
|
162 |
$backuptempdir = make_backup_temp_directory('');
|
|
|
163 |
$items = new DirectoryIterator($backuptempdir);
|
|
|
164 |
foreach ($items as $item) {
|
|
|
165 |
if ($item->isDot()) {
|
|
|
166 |
continue;
|
|
|
167 |
}
|
|
|
168 |
if ($item->getMTime() < $deletebefore) {
|
|
|
169 |
if ($item->isDir()) {
|
|
|
170 |
// The item is a directory for some backup.
|
|
|
171 |
if (!self::delete_backup_dir($item->getFilename(), $progress)) {
|
|
|
172 |
// Something went wrong. Finish the list of items and then throw an exception.
|
|
|
173 |
$status = false;
|
|
|
174 |
}
|
|
|
175 |
} else if ($item->isFile()) {
|
|
|
176 |
unlink($item->getPathname());
|
|
|
177 |
}
|
|
|
178 |
}
|
|
|
179 |
}
|
|
|
180 |
if (!$status) {
|
|
|
181 |
throw new backup_helper_exception('problem_deleting_old_backup_temp_dirs');
|
|
|
182 |
}
|
|
|
183 |
}
|
|
|
184 |
|
|
|
185 |
/**
|
|
|
186 |
* This function will be invoked by any log() method in backup/restore, acting
|
|
|
187 |
* as a simple forwarder to the standard loggers but also, if the $display
|
|
|
188 |
* parameter is true, supporting translation via get_string() and sending to
|
|
|
189 |
* standard output.
|
|
|
190 |
*/
|
|
|
191 |
public static function log($message, $level, $a, $depth, $display, $logger) {
|
|
|
192 |
// Send to standard loggers
|
|
|
193 |
$logmessage = $message;
|
|
|
194 |
$options = empty($depth) ? array() : array('depth' => $depth);
|
|
|
195 |
if (!empty($a)) {
|
|
|
196 |
$logmessage = $logmessage . ' ' . implode(', ', (array)$a);
|
|
|
197 |
}
|
|
|
198 |
$logger->process($logmessage, $level, $options);
|
|
|
199 |
|
|
|
200 |
// If $display specified, send translated string to output_controller
|
|
|
201 |
if ($display) {
|
|
|
202 |
output_controller::get_instance()->output($message, 'backup', $a, $depth);
|
|
|
203 |
}
|
|
|
204 |
}
|
|
|
205 |
|
|
|
206 |
/**
|
|
|
207 |
* Given one backupid and the (FS) final generated file, perform its final storage
|
|
|
208 |
* into Moodle file storage. For stored files it returns the complete file_info object
|
|
|
209 |
*
|
|
|
210 |
* Note: the $filepath is deleted if the backup file is created successfully
|
|
|
211 |
*
|
|
|
212 |
* If you specify the progress monitor, this will start a new progress section
|
|
|
213 |
* to track progress in processing (in case this task takes a long time).
|
|
|
214 |
*
|
|
|
215 |
* @param int $backupid
|
|
|
216 |
* @param string $filepath zip file containing the backup
|
|
|
217 |
* @param \core\progress\base $progress Optional progress monitor
|
|
|
218 |
* @return stored_file if created, null otherwise
|
|
|
219 |
*
|
|
|
220 |
* @throws moodle_exception in case of any problems
|
|
|
221 |
*/
|
|
|
222 |
public static function store_backup_file($backupid, $filepath, \core\progress\base $progress = null) {
|
|
|
223 |
global $CFG;
|
|
|
224 |
|
|
|
225 |
// First of all, get some information from the backup_controller to help us decide
|
|
|
226 |
list($dinfo, $cinfo, $sinfo) = backup_controller_dbops::get_moodle_backup_information(
|
|
|
227 |
$backupid, $progress);
|
|
|
228 |
|
|
|
229 |
// Extract useful information to decide
|
|
|
230 |
$hasusers = (bool)$sinfo['users']->value; // Backup has users
|
|
|
231 |
$isannon = (bool)$sinfo['anonymize']->value; // Backup is anonymised
|
|
|
232 |
$filename = $sinfo['filename']->value; // Backup filename
|
|
|
233 |
$backupmode= $dinfo[0]->mode; // Backup mode backup::MODE_GENERAL/IMPORT/HUB
|
|
|
234 |
$backuptype= $dinfo[0]->type; // Backup type backup::TYPE_1ACTIVITY/SECTION/COURSE
|
|
|
235 |
$userid = $dinfo[0]->userid; // User->id executing the backup
|
|
|
236 |
$id = $dinfo[0]->id; // Id of activity/section/course (depends of type)
|
|
|
237 |
$courseid = $dinfo[0]->courseid; // Id of the course
|
|
|
238 |
$format = $dinfo[0]->format; // Type of backup file
|
|
|
239 |
|
|
|
240 |
// Quick hack. If for any reason, filename is blank, fix it here.
|
|
|
241 |
// TODO: This hack will be out once MDL-22142 - P26 gets fixed
|
|
|
242 |
if (empty($filename)) {
|
|
|
243 |
$filename = backup_plan_dbops::get_default_backup_filename('moodle2', $backuptype, $id, $hasusers, $isannon);
|
|
|
244 |
}
|
|
|
245 |
|
|
|
246 |
// Backups of type IMPORT aren't stored ever
|
|
|
247 |
if ($backupmode == backup::MODE_IMPORT) {
|
|
|
248 |
return null;
|
|
|
249 |
}
|
|
|
250 |
|
|
|
251 |
if (!is_readable($filepath)) {
|
|
|
252 |
// we have a problem if zip file does not exist
|
|
|
253 |
throw new coding_exception('backup_helper::store_backup_file() expects valid $filepath parameter');
|
|
|
254 |
|
|
|
255 |
}
|
|
|
256 |
|
|
|
257 |
// Calculate file storage options of id being backup
|
|
|
258 |
$ctxid = 0;
|
|
|
259 |
$filearea = '';
|
|
|
260 |
$component = '';
|
|
|
261 |
$itemid = 0;
|
|
|
262 |
switch ($backuptype) {
|
|
|
263 |
case backup::TYPE_1ACTIVITY:
|
|
|
264 |
$ctxid = context_module::instance($id)->id;
|
|
|
265 |
$component = 'backup';
|
|
|
266 |
$filearea = 'activity';
|
|
|
267 |
$itemid = 0;
|
|
|
268 |
break;
|
|
|
269 |
case backup::TYPE_1SECTION:
|
|
|
270 |
$ctxid = context_course::instance($courseid)->id;
|
|
|
271 |
$component = 'backup';
|
|
|
272 |
$filearea = 'section';
|
|
|
273 |
$itemid = $id;
|
|
|
274 |
break;
|
|
|
275 |
case backup::TYPE_1COURSE:
|
|
|
276 |
$ctxid = context_course::instance($courseid)->id;
|
|
|
277 |
$component = 'backup';
|
|
|
278 |
$filearea = 'course';
|
|
|
279 |
$itemid = 0;
|
|
|
280 |
break;
|
|
|
281 |
}
|
|
|
282 |
|
|
|
283 |
if ($backupmode == backup::MODE_AUTOMATED) {
|
|
|
284 |
// Automated backups have there own special area!
|
|
|
285 |
$filearea = 'automated';
|
|
|
286 |
|
|
|
287 |
// If we're keeping the backup only in a chosen path, just move it there now
|
|
|
288 |
// this saves copying from filepool to here later and filling trashdir.
|
|
|
289 |
$config = get_config('backup');
|
|
|
290 |
$dir = $config->backup_auto_destination;
|
|
|
291 |
if ($config->backup_auto_storage == 1 and $dir and is_dir($dir) and is_writable($dir)) {
|
|
|
292 |
$filedest = $dir.'/'
|
|
|
293 |
.backup_plan_dbops::get_default_backup_filename(
|
|
|
294 |
$format,
|
|
|
295 |
$backuptype,
|
|
|
296 |
$courseid,
|
|
|
297 |
$hasusers,
|
|
|
298 |
$isannon,
|
|
|
299 |
!$config->backup_shortname,
|
|
|
300 |
(bool)$config->backup_auto_files);
|
|
|
301 |
// first try to move the file, if it is not possible copy and delete instead
|
|
|
302 |
if (@rename($filepath, $filedest)) {
|
|
|
303 |
return null;
|
|
|
304 |
}
|
|
|
305 |
umask($CFG->umaskpermissions);
|
|
|
306 |
if (copy($filepath, $filedest)) {
|
|
|
307 |
@chmod($filedest, $CFG->filepermissions); // may fail because the permissions may not make sense outside of dataroot
|
|
|
308 |
unlink($filepath);
|
|
|
309 |
return null;
|
|
|
310 |
} else {
|
|
|
311 |
$bc = backup_controller::load_controller($backupid);
|
|
|
312 |
$bc->log('Attempt to copy backup file to the specified directory using filesystem failed - ',
|
|
|
313 |
backup::LOG_WARNING, $dir);
|
|
|
314 |
$bc->destroy();
|
|
|
315 |
}
|
|
|
316 |
// bad luck, try to deal with the file the old way - keep backup in file area if we can not copy to ext system
|
|
|
317 |
}
|
|
|
318 |
}
|
|
|
319 |
|
|
|
320 |
// Backups of type HUB (by definition never have user info)
|
|
|
321 |
// are sent to user's "user_tohub" file area. The upload process
|
|
|
322 |
// will be responsible for cleaning that filearea once finished
|
|
|
323 |
if ($backupmode == backup::MODE_HUB) {
|
|
|
324 |
$ctxid = context_user::instance($userid)->id;
|
|
|
325 |
$component = 'user';
|
|
|
326 |
$filearea = 'tohub';
|
|
|
327 |
$itemid = 0;
|
|
|
328 |
}
|
|
|
329 |
|
|
|
330 |
// Backups without user info or with the anonymise functionality
|
|
|
331 |
// enabled are sent to user's "user_backup"
|
|
|
332 |
// file area. Maintenance of such area is responsibility of
|
|
|
333 |
// the user via corresponding file manager frontend
|
|
|
334 |
if (($backupmode == backup::MODE_GENERAL || $backupmode == backup::MODE_ASYNC) && (!$hasusers || $isannon)) {
|
|
|
335 |
$ctxid = context_user::instance($userid)->id;
|
|
|
336 |
$component = 'user';
|
|
|
337 |
$filearea = 'backup';
|
|
|
338 |
$itemid = 0;
|
|
|
339 |
}
|
|
|
340 |
|
|
|
341 |
// Let's send the file to file storage, everything already defined
|
|
|
342 |
$fs = get_file_storage();
|
|
|
343 |
$fr = array(
|
|
|
344 |
'contextid' => $ctxid,
|
|
|
345 |
'component' => $component,
|
|
|
346 |
'filearea' => $filearea,
|
|
|
347 |
'itemid' => $itemid,
|
|
|
348 |
'filepath' => '/',
|
|
|
349 |
'filename' => $filename,
|
|
|
350 |
'userid' => $userid,
|
|
|
351 |
'timecreated' => time(),
|
|
|
352 |
'timemodified'=> time());
|
|
|
353 |
// If file already exists, delete if before
|
|
|
354 |
// creating it again. This is BC behaviour - copy()
|
|
|
355 |
// overwrites by default
|
|
|
356 |
if ($fs->file_exists($fr['contextid'], $fr['component'], $fr['filearea'], $fr['itemid'], $fr['filepath'], $fr['filename'])) {
|
|
|
357 |
$pathnamehash = $fs->get_pathname_hash($fr['contextid'], $fr['component'], $fr['filearea'], $fr['itemid'], $fr['filepath'], $fr['filename']);
|
|
|
358 |
$sf = $fs->get_file_by_hash($pathnamehash);
|
|
|
359 |
$sf->delete();
|
|
|
360 |
}
|
|
|
361 |
$file = $fs->create_file_from_pathname($fr, $filepath);
|
|
|
362 |
unlink($filepath);
|
|
|
363 |
return $file;
|
|
|
364 |
}
|
|
|
365 |
|
|
|
366 |
/**
|
|
|
367 |
* This function simply marks one param to be considered as straight sql
|
|
|
368 |
* param, so it won't be searched in the structure tree nor converted at
|
|
|
369 |
* all. Useful for better integration of definition of sources in structure
|
|
|
370 |
* and DB stuff
|
|
|
371 |
*/
|
|
|
372 |
public static function is_sqlparam($value) {
|
|
|
373 |
return array('sqlparam' => $value);
|
|
|
374 |
}
|
|
|
375 |
|
|
|
376 |
/**
|
|
|
377 |
* This function returns one array of itemnames that are being handled by
|
|
|
378 |
* inforef.xml files. Used both by backup and restore
|
|
|
379 |
*/
|
|
|
380 |
public static function get_inforef_itemnames() {
|
|
|
381 |
return array('user', 'grouping', 'group', 'role', 'file', 'scale', 'outcome', 'grade_item', 'question_category');
|
|
|
382 |
}
|
|
|
383 |
|
|
|
384 |
/**
|
|
|
385 |
* Print the course reuse dropdown.
|
|
|
386 |
*
|
|
|
387 |
* @param string $current The current course reuse option where the header is modified
|
|
|
388 |
*/
|
|
|
389 |
public static function print_coursereuse_selector(string $current): void {
|
|
|
390 |
global $OUTPUT, $PAGE;
|
|
|
391 |
|
|
|
392 |
if ($coursereusenode = $PAGE->settingsnav->find('coursereuse', \navigation_node::TYPE_CONTAINER)) {
|
|
|
393 |
|
|
|
394 |
$menuarray = \core\navigation\views\secondary::create_menu_element([$coursereusenode]);
|
|
|
395 |
if (empty($menuarray)) {
|
|
|
396 |
return;
|
|
|
397 |
}
|
|
|
398 |
|
|
|
399 |
$coursereuse = get_string('coursereuse');
|
|
|
400 |
$activeurl = '';
|
|
|
401 |
if (isset($menuarray[0])) {
|
|
|
402 |
// Remove the "Course reuse" entry.
|
|
|
403 |
$result = array_search($coursereuse, $menuarray[0][$coursereuse]);
|
|
|
404 |
unset($menuarray[0][$coursereuse][$result]);
|
|
|
405 |
|
|
|
406 |
// Find the active node.
|
|
|
407 |
foreach ($menuarray[0] as $key => $value) {
|
|
|
408 |
$check = array_search($current, $value);
|
|
|
409 |
if ($check !== false) {
|
|
|
410 |
$activeurl = $check;
|
|
|
411 |
}
|
|
|
412 |
}
|
|
|
413 |
} else {
|
|
|
414 |
$result = array_search($coursereuse, $menuarray);
|
|
|
415 |
unset($menuarray[$result]);
|
|
|
416 |
|
|
|
417 |
$check = array_search(get_string($current), $menuarray);
|
|
|
418 |
if ($check !== false) {
|
|
|
419 |
$activeurl = $check;
|
|
|
420 |
}
|
|
|
421 |
|
|
|
422 |
}
|
|
|
423 |
|
|
|
424 |
$selectmenu = new \core\output\select_menu('coursereusetype', $menuarray, $activeurl);
|
|
|
425 |
$selectmenu->set_label(get_string('coursereusenavigationmenu'), ['class' => 'sr-only']);
|
|
|
426 |
$options = \html_writer::tag(
|
|
|
427 |
'div',
|
|
|
428 |
$OUTPUT->render_from_template('core/tertiary_navigation_selector', $selectmenu->export_for_template($OUTPUT)),
|
|
|
429 |
['class' => 'row pb-3']
|
|
|
430 |
);
|
|
|
431 |
echo \html_writer::tag(
|
|
|
432 |
'div',
|
|
|
433 |
$options,
|
|
|
434 |
['class' => 'container-fluid tertiary-navigation full-width-bottom-border', 'id' => 'tertiary-navigation']);
|
|
|
435 |
} else {
|
11 |
efrain |
436 |
echo $OUTPUT->heading(get_string($current), 2, 'mb-3');
|
1 |
efrain |
437 |
}
|
|
|
438 |
}
|
|
|
439 |
}
|
|
|
440 |
|
|
|
441 |
/*
|
|
|
442 |
* Exception class used by all the @helper stuff
|
|
|
443 |
*/
|
|
|
444 |
class backup_helper_exception extends backup_exception {
|
|
|
445 |
|
|
|
446 |
public function __construct($errorcode, $a=NULL, $debuginfo=null) {
|
|
|
447 |
parent::__construct($errorcode, $a, $debuginfo);
|
|
|
448 |
}
|
|
|
449 |
}
|