Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
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-plan
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
 * Abstract class defining the needed stuff to restore one xml file
27
 *
28
 * TODO: Finish phpdocs
29
 */
30
abstract class restore_structure_step extends restore_step {
31
 
32
    protected $filename; // Name of the file to be parsed
33
    protected $contentprocessor; // xml parser processor being used
34
                                 // (need it here, apart from parser
35
                                 // thanks to serialized data to process -
36
                                 // say thanks to blocks!)
37
    protected $pathelements;  // Array of pathelements to process
38
    protected $elementsoldid; // Array to store last oldid used on each element
39
    protected $elementsnewid; // Array to store last newid used on each element
40
 
41
    protected $pathlock;      // Path currently locking processing of children
42
 
43
    const SKIP_ALL_CHILDREN = -991399; // To instruct the dispatcher about to ignore
44
                                       // all children below path processor returning it
45
 
46
    /**
47
     * Constructor - instantiates one object of this class
48
     */
49
    public function __construct($name, $filename, $task = null) {
50
        if (!is_null($task) && !($task instanceof restore_task)) {
51
            throw new restore_step_exception('wrong_restore_task_specified');
52
        }
53
        $this->filename = $filename;
54
        $this->contentprocessor = null;
55
        $this->pathelements = array();
56
        $this->elementsoldid = array();
57
        $this->elementsnewid = array();
58
        $this->pathlock = null;
59
        parent::__construct($name, $task);
60
    }
61
 
62
    final public function execute() {
63
 
64
        if (!$this->execute_condition()) { // Check any condition to execute this
65
            return;
66
        }
67
 
68
        $fullpath = $this->task->get_taskbasepath();
69
 
70
        // We MUST have one fullpath here, else, error
71
        if (empty($fullpath)) {
72
            throw new restore_step_exception('restore_structure_step_undefined_fullpath');
73
        }
74
 
75
        // Append the filename to the fullpath
76
        $fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
77
 
78
        // And it MUST exist
79
        if (!file_exists($fullpath)) { // Shouldn't happen ever, but...
80
            throw new restore_step_exception('missing_moodle_backup_xml_file', $fullpath);
81
        }
82
 
83
        // Get restore_path elements array adapting and preparing it for processing
84
        $structure = $this->define_structure();
85
        if (!is_array($structure)) {
86
            throw new restore_step_exception('restore_step_structure_not_array', $this->get_name());
87
        }
88
        $this->prepare_pathelements($structure);
89
 
90
        // Create parser and processor
91
        $xmlparser = new progressive_parser();
92
        $xmlparser->set_file($fullpath);
93
        $xmlprocessor = new restore_structure_parser_processor($this->task->get_courseid(), $this);
94
        $this->contentprocessor = $xmlprocessor; // Save the reference to the contentprocessor
95
                                                 // as far as we are going to need it out
96
                                                 // from parser (blame serialized data!)
97
        $xmlparser->set_processor($xmlprocessor);
98
 
99
        // Add pathelements to processor
100
        foreach ($this->pathelements as $element) {
101
            $xmlprocessor->add_path($element->get_path(), $element->is_grouped());
102
        }
103
 
104
        // Set up progress tracking.
105
        $progress = $this->get_task()->get_progress();
106
        $progress->start_progress($this->get_name(), \core\progress\base::INDETERMINATE);
107
        $xmlparser->set_progress($progress);
108
 
109
        // And process it, dispatch to target methods in step will start automatically
110
        $xmlparser->process();
111
 
112
        // Have finished, launch the after_execute method of all the processing objects
113
        $this->launch_after_execute_methods();
114
        $progress->end_progress();
115
    }
116
 
117
    /**
118
     * Receive one chunk of information form the xml parser processor and
119
     * dispatch it, following the naming rules
120
     */
121
    final public function process($data) {
122
        if (!array_key_exists($data['path'], $this->pathelements)) { // Incorrect path, must not happen
123
            throw new restore_step_exception('restore_structure_step_missing_path', $data['path']);
124
        }
125
        $element = $this->pathelements[$data['path']];
126
        $object = $element->get_processing_object();
127
        $method = $element->get_processing_method();
128
        $rdata = null;
129
        if (empty($object)) { // No processing object defined
130
            throw new restore_step_exception('restore_structure_step_missing_pobject', $object);
131
        }
132
        // Release the lock if we aren't anymore within children of it
133
        if (!is_null($this->pathlock) and strpos($data['path'], $this->pathlock) === false) {
134
            $this->pathlock = null;
135
        }
136
        if (is_null($this->pathlock)) { // Only dispatch if there isn't any lock
137
            $rdata = $object->$method($data['tags']); // Dispatch to proper object/method
138
        }
139
 
140
        // If the dispatched method returns SKIP_ALL_CHILDREN, we grab current path in order to
141
        // lock dispatching to any children
142
        if ($rdata === self::SKIP_ALL_CHILDREN) {
143
            // Check we haven't any previous lock
144
            if (!is_null($this->pathlock)) {
145
                throw new restore_step_exception('restore_structure_step_already_skipping', $data['path']);
146
            }
147
            // Set the lock
148
            $this->pathlock = $data['path'] . '/'; // Lock everything below current path
149
 
150
        // Continue with normal processing of return values
151
        } else if ($rdata !== null) { // If the method has returned any info, set element data to it
152
            $element->set_data($rdata);
153
        } else {               // Else, put the original parsed data
154
            $element->set_data($data);
155
        }
156
    }
157
 
158
    /**
159
     * To send ids pairs to backup_ids_table and to store them into paths
160
     *
161
     * This method will send the given itemname and old/new ids to the
162
     * backup_ids_temp table, and, at the same time, will save the new id
163
     * into the corresponding restore_path_element for easier access
164
     * by children. Also will inject the known old context id for the task
165
     * in case it's going to be used for restoring files later
166
     */
167
    public function set_mapping($itemname, $oldid, $newid, $restorefiles = false, $filesctxid = null, $parentid = null) {
168
        if ($restorefiles && $parentid) {
169
            throw new restore_step_exception('set_mapping_cannot_specify_both_restorefiles_and_parentitemid');
170
        }
171
        // If we haven't specified one context for the files, use the task one
172
        if (is_null($filesctxid)) {
173
            $parentitemid = $restorefiles ? $this->task->get_old_contextid() : null;
174
        } else { // Use the specified one
175
            $parentitemid = $restorefiles ? $filesctxid : null;
176
        }
177
        // We have passed one explicit parentid, apply it
178
        $parentitemid = !is_null($parentid) ? $parentid : $parentitemid;
179
 
180
        // Let's call the low level one
181
        restore_dbops::set_backup_ids_record($this->get_restoreid(), $itemname, $oldid, $newid, $parentitemid);
182
        // Now, if the itemname matches any pathelement->name, store the latest $newid
183
        if (array_key_exists($itemname, $this->elementsoldid)) { // If present in  $this->elementsoldid, is valid, put both ids
184
            $this->elementsoldid[$itemname] = $oldid;
185
            $this->elementsnewid[$itemname] = $newid;
186
        }
187
    }
188
 
189
    /**
190
     * Returns the latest (parent) old id mapped by one pathelement
191
     */
192
    public function get_old_parentid($itemname) {
193
        return array_key_exists($itemname, $this->elementsoldid) ? $this->elementsoldid[$itemname] : null;
194
    }
195
 
196
    /**
197
     * Returns the latest (parent) new id mapped by one pathelement
198
     */
199
    public function get_new_parentid($itemname) {
200
        return array_key_exists($itemname, $this->elementsnewid) ? $this->elementsnewid[$itemname] : null;
201
    }
202
 
203
    /**
204
     * Return the new id of a mapping for the given itemname
205
     *
206
     * @param string $itemname the type of item
207
     * @param int $oldid the item ID from the backup
208
     * @param mixed $ifnotfound what to return if $oldid wasnt found. Defaults to false
209
     */
210
    public function get_mappingid($itemname, $oldid, $ifnotfound = false) {
211
        $mapping = $this->get_mapping($itemname, $oldid);
212
        return $mapping ? $mapping->newitemid : $ifnotfound;
213
    }
214
 
215
    /**
216
     * Return the complete mapping from the given itemname, itemid
217
     */
218
    public function get_mapping($itemname, $oldid) {
219
        return restore_dbops::get_backup_ids_record($this->get_restoreid(), $itemname, $oldid);
220
    }
221
 
222
    /**
223
     * Add all the existing file, given their component and filearea and one backup_ids itemname to match with
224
     */
225
    public function add_related_files($component, $filearea, $mappingitemname, $filesctxid = null, $olditemid = null) {
226
        // If the current progress object is set up and ready to receive
227
        // indeterminate progress, then use it, otherwise don't. (This check is
228
        // just in case this function is ever called from somewhere not within
229
        // the execute() method here, which does set up progress like this.)
230
        $progress = $this->get_task()->get_progress();
231
        if (!$progress->is_in_progress_section() ||
232
                $progress->get_current_max() !== \core\progress\base::INDETERMINATE) {
233
            $progress = null;
234
        }
235
 
236
        $filesctxid = is_null($filesctxid) ? $this->task->get_old_contextid() : $filesctxid;
237
        $results = restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), $component,
238
                $filearea, $filesctxid, $this->task->get_userid(), $mappingitemname, $olditemid, null, false,
239
                $progress);
240
        $resultstoadd = array();
241
        foreach ($results as $result) {
242
            $this->log($result->message, $result->level);
243
            $resultstoadd[$result->code] = true;
244
        }
245
        $this->task->add_result($resultstoadd);
246
    }
247
 
248
    /**
249
     * As far as restore structure steps are implementing restore_plugin stuff, they need to
250
     * have the parent task available for wrapping purposes (get course/context....)
251
     * @return restore_task|null
252
     */
253
    public function get_task() {
254
        return $this->task;
255
    }
256
 
257
// Protected API starts here
258
 
259
    /**
260
     * Add plugin structure to any element in the structure restore tree
261
     *
262
     * @param string $plugintype type of plugin as defined by core_component::get_plugin_types()
263
     * @param restore_path_element $element element in the structure restore tree that
264
     *                                       we are going to add plugin information to
265
     */
266
    protected function add_plugin_structure($plugintype, $element) {
267
 
268
        global $CFG;
269
 
270
        // Check the requested plugintype is a valid one
271
        if (!array_key_exists($plugintype, core_component::get_plugin_types($plugintype))) {
272
             throw new restore_step_exception('incorrect_plugin_type', $plugintype);
273
        }
274
 
275
        // Get all the restore path elements, looking across all the plugin dirs
276
        $pluginsdirs = core_component::get_plugin_list($plugintype);
277
        foreach ($pluginsdirs as $name => $pluginsdir) {
278
            // We need to add also backup plugin classes on restore, they may contain
279
            // some stuff used both in backup & restore
280
            $backupclassname = 'backup_' . $plugintype . '_' . $name . '_plugin';
281
            $backupfile = $pluginsdir . '/backup/moodle2/' . $backupclassname . '.class.php';
282
            if (file_exists($backupfile)) {
283
                require_once($backupfile);
284
            }
285
            // Now add restore plugin classes and prepare stuff
286
            $restoreclassname = 'restore_' . $plugintype . '_' . $name . '_plugin';
287
            $restorefile = $pluginsdir . '/backup/moodle2/' . $restoreclassname . '.class.php';
288
            if (file_exists($restorefile)) {
289
                require_once($restorefile);
290
                $restoreplugin = new $restoreclassname($plugintype, $name, $this);
291
                // Add plugin paths to the step
292
                $this->prepare_pathelements($restoreplugin->define_plugin_structure($element));
293
            }
294
        }
295
    }
296
 
297
    /**
298
     * Add subplugin structure for a given plugin to any element in the structure restore tree
299
     *
300
     * This method allows the injection of subplugins (of a specific plugin) parsing and proccessing
301
     * to any element in the restore structure.
302
     *
303
     * NOTE: Initially subplugins were only available for activities (mod), so only the
304
     * {@link restore_activity_structure_step} class had support for them, always
305
     * looking for /mod/modulenanme subplugins. This new method is a generalization of the
306
     * existing one for activities, supporting all subplugins injecting information everywhere.
307
     *
308
     * @param string $subplugintype type of subplugin as defined in plugin's db/subplugins.json.
309
     * @param restore_path_element $element element in the structure restore tree that
310
     *                              we are going to add subplugin information to.
311
     * @param string $plugintype type of the plugin.
312
     * @param string $pluginname name of the plugin.
313
     * @return void
314
     */
315
    protected function add_subplugin_structure($subplugintype, $element, $plugintype = null, $pluginname = null) {
316
        global $CFG;
317
        // This global declaration is required, because where we do require_once($backupfile);
318
        // That file may in turn try to do require_once($CFG->dirroot ...).
319
        // That worked in the past, we should keep it working.
320
 
321
        // Verify if this is a BC call for an activity restore. See NOTE above for this special case.
322
        if ($plugintype === null and $pluginname === null) {
323
            $plugintype = 'mod';
324
            $pluginname = $this->task->get_modulename();
325
            // TODO: Once all the calls have been changed to add both not null plugintype and pluginname, add a debugging here.
326
        }
327
 
328
        // Check the requested plugintype is a valid one.
329
        if (!array_key_exists($plugintype, core_component::get_plugin_types())) {
330
            throw new restore_step_exception('incorrect_plugin_type', $plugintype);
331
        }
332
 
333
        // Check the requested pluginname, for the specified plugintype, is a valid one.
334
        if (!array_key_exists($pluginname, core_component::get_plugin_list($plugintype))) {
335
            throw new restore_step_exception('incorrect_plugin_name', array($plugintype, $pluginname));
336
        }
337
 
338
        // Check the requested subplugintype is a valid one.
339
        $subplugins = core_component::get_subplugins("{$plugintype}_{$pluginname}");
340
        if (null === $subplugins) {
341
            throw new restore_step_exception('plugin_missing_subplugins_configuration', array($plugintype, $pluginname));
342
        }
343
        if (!array_key_exists($subplugintype, $subplugins)) {
344
             throw new restore_step_exception('incorrect_subplugin_type', $subplugintype);
345
        }
346
 
347
        // Every subplugin optionally can have a common/parent subplugin
348
        // class for shared stuff.
349
        $parentclass = 'restore_' . $plugintype . '_' . $pluginname . '_' . $subplugintype . '_subplugin';
350
        $parentfile = core_component::get_component_directory($plugintype . '_' . $pluginname) .
351
            '/backup/moodle2/' . $parentclass . '.class.php';
352
        if (file_exists($parentfile)) {
353
            require_once($parentfile);
354
        }
355
 
356
        // Get all the restore path elements, looking across all the subplugin dirs.
357
        $subpluginsdirs = core_component::get_plugin_list($subplugintype);
358
        foreach ($subpluginsdirs as $name => $subpluginsdir) {
359
            $classname = 'restore_' . $subplugintype . '_' . $name . '_subplugin';
360
            $restorefile = $subpluginsdir . '/backup/moodle2/' . $classname . '.class.php';
361
            if (file_exists($restorefile)) {
362
                require_once($restorefile);
363
                $restoresubplugin = new $classname($subplugintype, $name, $this);
364
                // Add subplugin paths to the step.
365
                $this->prepare_pathelements($restoresubplugin->define_subplugin_structure($element));
366
            }
367
        }
368
    }
369
 
370
    /**
371
     * Launch all the after_execute methods present in all the processing objects
372
     *
373
     * This method will launch all the after_execute methods that can be defined
374
     * both in restore_plugin and restore_structure_step classes
375
     *
376
     * For restore_plugin classes the name of the method to be executed will be
377
     * "after_execute_" + connection point (as far as can be multiple connection
378
     * points in the same class)
379
     *
380
     * For restore_structure_step classes is will be, simply, "after_execute". Note
381
     * that this is executed *after* the plugin ones
382
     */
383
    protected function launch_after_execute_methods() {
384
        $alreadylaunched = array(); // To avoid multiple executions
385
        foreach ($this->pathelements as $key => $pathelement) {
386
            // Get the processing object
387
            $pobject = $pathelement->get_processing_object();
388
            // Skip null processors (child of grouped ones for sure)
389
            if (is_null($pobject)) {
390
                continue;
391
            }
392
            // Skip restore structure step processors (this)
393
            if ($pobject instanceof restore_structure_step) {
394
                continue;
395
            }
396
            // Skip already launched processing objects
397
            if (in_array($pobject, $alreadylaunched, true)) {
398
                continue;
399
            }
400
            // Add processing object to array of launched ones
401
            $alreadylaunched[] = $pobject;
402
            // If the processing object has support for
403
            // launching after_execute methods, use it
404
            if (method_exists($pobject, 'launch_after_execute_methods')) {
405
                $pobject->launch_after_execute_methods();
406
            }
407
        }
408
        // Finally execute own (restore_structure_step) after_execute method
409
        $this->after_execute();
410
 
411
    }
412
 
413
    /**
414
     * Launch all the after_restore methods present in all the processing objects
415
     *
416
     * This method will launch all the after_restore methods that can be defined
417
     * both in restore_plugin class
418
     *
419
     * For restore_plugin classes the name of the method to be executed will be
420
     * "after_restore_" + connection point (as far as can be multiple connection
421
     * points in the same class)
422
     */
423
    public function launch_after_restore_methods() {
424
        $alreadylaunched = array(); // To avoid multiple executions
425
        foreach ($this->pathelements as $pathelement) {
426
            // Get the processing object
427
            $pobject = $pathelement->get_processing_object();
428
            // Skip null processors (child of grouped ones for sure)
429
            if (is_null($pobject)) {
430
                continue;
431
            }
432
            // Skip restore structure step processors (this)
433
            if ($pobject instanceof restore_structure_step) {
434
                continue;
435
            }
436
            // Skip already launched processing objects
437
            if (in_array($pobject, $alreadylaunched, true)) {
438
                continue;
439
            }
440
            // Add processing object to array of launched ones
441
            $alreadylaunched[] = $pobject;
442
            // If the processing object has support for
443
            // launching after_restore methods, use it
444
            if (method_exists($pobject, 'launch_after_restore_methods')) {
445
                $pobject->launch_after_restore_methods();
446
            }
447
        }
448
        // Finally execute own (restore_structure_step) after_restore method
449
        $this->after_restore();
450
    }
451
 
452
    /**
453
     * This method will be executed after the whole structure step have been processed
454
     *
455
     * After execution method for code needed to be executed after the whole structure
456
     * has been processed. Useful for cleaning tasks, files process and others. Simply
457
     * overwrite in in your steps if needed
458
     */
459
    protected function after_execute() {
460
        // do nothing by default
461
    }
462
 
463
    /**
464
     * This method will be executed after the rest of the restore has been processed.
465
     *
466
     * Use if you need to update IDs based on things which are restored after this
467
     * step has completed.
468
     */
469
    protected function after_restore() {
470
        // do nothing by default
471
    }
472
 
473
    /**
474
     * Prepare the pathelements for processing, looking for duplicates, applying
475
     * processing objects and other adjustments
476
     */
477
    protected function prepare_pathelements($elementsarr) {
478
 
479
        // First iteration, push them to new array, indexed by name
480
        // detecting duplicates in names or paths
481
        $names = array();
482
        $paths = array();
483
        foreach($elementsarr as $element) {
484
            if (!$element instanceof restore_path_element) {
485
                throw new restore_step_exception('restore_path_element_wrong_class', get_class($element));
486
            }
487
            if (array_key_exists($element->get_name(), $names)) {
488
                throw new restore_step_exception('restore_path_element_name_alreadyexists', $element->get_name());
489
            }
490
            if (array_key_exists($element->get_path(), $paths)) {
491
                throw new restore_step_exception('restore_path_element_path_alreadyexists', $element->get_path());
492
            }
493
            $names[$element->get_name()] = true;
494
            $paths[$element->get_path()] = $element;
495
        }
496
        // Now, for each element not having one processing object, if
497
        // not child of grouped element, assign $this (the step itself) as processing element
498
        // Note method must exist or we'll get one @restore_path_element_exception
499
        foreach ($paths as $pelement) {
500
            if ($pelement->get_processing_object() === null && !$this->grouped_parent_exists($pelement, $paths)) {
501
                $pelement->set_processing_object($this);
502
            }
503
            // Populate $elementsoldid and $elementsoldid based on available pathelements
504
            $this->elementsoldid[$pelement->get_name()] = null;
505
            $this->elementsnewid[$pelement->get_name()] = null;
506
        }
507
        // Done, add them to pathelements (dupes by key - path - are discarded)
508
        $this->pathelements = array_merge($this->pathelements, $paths);
509
    }
510
 
511
    /**
512
     * Given one pathelement, return true if grouped parent was found
513
     *
514
     * @param restore_path_element $pelement the element we are interested in.
515
     * @param restore_path_element[] $elements the elements that exist.
516
     * @return bool true if this element is inside a grouped parent.
517
     */
518
    public function grouped_parent_exists($pelement, $elements) {
519
        foreach ($elements as $element) {
520
            if ($pelement->get_path() == $element->get_path()) {
521
                continue; // Don't compare against itself.
522
            }
523
            // If element is grouped and parent of pelement, return true.
524
            if ($element->is_grouped() and strpos($pelement->get_path() .  '/', $element->get_path()) === 0) {
525
                return true;
526
            }
527
        }
528
        return false; // No grouped parent found.
529
    }
530
 
531
    /**
532
     * To conditionally decide if one step will be executed or no
533
     *
534
     * For steps needing to be executed conditionally, based in dynamic
535
     * conditions (at execution time vs at declaration time) you must
536
     * override this function. It will return true if the step must be
537
     * executed and false if not
538
     */
539
    protected function execute_condition() {
540
        return true;
541
    }
542
 
543
    /**
544
     * Function that will return the structure to be processed by this restore_step.
545
     * Must return one array of @restore_path_element elements
546
     */
547
    abstract protected function define_structure();
548
}