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
 * Provides {@link convert_helper} and {@link convert_helper_exception} classes
20
 *
21
 * @package    core
22
 * @subpackage backup-convert
23
 * @copyright  2011 Mark Nielsen <mark@moodlerooms.com>
24
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25
 */
26
 
27
defined('MOODLE_INTERNAL') || die();
28
 
29
require_once($CFG->dirroot . '/backup/util/includes/convert_includes.php');
30
 
31
/**
32
 * Provides various functionality via its static methods
33
 */
34
abstract class convert_helper {
35
 
36
    /**
37
     * @param string $entropy
38
     * @return string random identifier
39
     */
40
    public static function generate_id($entropy) {
41
        return md5(time() . '-' . $entropy . '-' . random_string(20));
42
    }
43
 
44
    /**
45
     * Returns the list of all available converters and loads their classes
46
     *
47
     * Converter must be installed as a directory in backup/converter/ and its
48
     * method is_available() must return true to get to the list.
49
     *
50
     * @see base_converter::is_available()
51
     * @return array of strings
52
     */
53
    public static function available_converters($restore=true) {
54
        global $CFG;
55
 
56
        $converters = array();
57
 
58
        $plugins    = get_list_of_plugins('backup/converter');
59
        foreach ($plugins as $name) {
60
            $filename = $restore ? 'lib.php' : 'backuplib.php';
61
            $classuf  = $restore ? '_converter' : '_export_converter';
62
            $classfile = "{$CFG->dirroot}/backup/converter/{$name}/{$filename}";
63
            $classname = "{$name}{$classuf}";
64
            $zip_contents      = "{$name}_zip_contents";
65
            $store_backup_file = "{$name}_store_backup_file";
66
            $convert           = "{$name}_backup_convert";
67
 
68
            if (!file_exists($classfile)) {
69
                throw new convert_helper_exception('converter_classfile_not_found', $classfile);
70
            }
71
 
72
            require_once($classfile);
73
 
74
            if (!class_exists($classname)) {
75
                throw new convert_helper_exception('converter_classname_not_found', $classname);
76
            }
77
 
78
            if (call_user_func($classname .'::is_available')) {
79
                if (!$restore) {
80
                    if (!class_exists($zip_contents)) {
81
                        throw new convert_helper_exception('converter_classname_not_found', $zip_contents);
82
                    }
83
                    if (!class_exists($store_backup_file)) {
84
                        throw new convert_helper_exception('converter_classname_not_found', $store_backup_file);
85
                    }
86
                    if (!class_exists($convert)) {
87
                        throw new convert_helper_exception('converter_classname_not_found', $convert);
88
                    }
89
                }
90
 
91
                $converters[] = $name;
92
            }
93
 
94
        }
95
 
96
        return $converters;
97
    }
98
 
99
    public static function export_converter_dependencies($converter, $dependency) {
100
        global $CFG;
101
 
102
        $result = array();
103
        $filename = 'backuplib.php';
104
        $classuf  = '_export_converter';
105
        $classfile = "{$CFG->dirroot}/backup/converter/{$converter}/{$filename}";
106
        $classname = "{$converter}{$classuf}";
107
 
108
        if (!file_exists($classfile)) {
109
            throw new convert_helper_exception('converter_classfile_not_found', $classfile);
110
        }
111
        require_once($classfile);
112
 
113
        if (!class_exists($classname)) {
114
            throw new convert_helper_exception('converter_classname_not_found', $classname);
115
        }
116
 
117
        if (call_user_func($classname .'::is_available')) {
118
            $deps = call_user_func($classname .'::get_deps');
119
            if (array_key_exists($dependency, $deps)) {
120
                $result = $deps[$dependency];
121
            }
122
        }
123
 
124
        return $result;
125
    }
126
 
127
    /**
128
     * Detects if the given folder contains an unpacked moodle2 backup
129
     *
130
     * @param string $tempdir the name of the backup directory
131
     * @return boolean true if moodle2 format detected, false otherwise
132
     */
133
    public static function detect_moodle2_format($tempdir) {
134
        $dirpath = make_backup_temp_directory($tempdir, false);
135
        if (!is_dir($dirpath)) {
136
            throw new convert_helper_exception('tmp_backup_directory_not_found', $dirpath);
137
        }
138
 
139
        $filepath = $dirpath . '/moodle_backup.xml';
140
        if (!file_exists($filepath)) {
141
            return false;
142
        }
143
 
144
        $handle     = fopen($filepath, 'r');
145
        $firstchars = fread($handle, 200);
146
        $status     = fclose($handle);
147
 
148
        // Look for expected XML elements (case-insensitive to account for encoding attribute).
149
        if (stripos($firstchars, '<?xml version="1.0" encoding="UTF-8"?>') !== false &&
150
            strpos($firstchars, '<moodle_backup>') !== false &&
151
            strpos($firstchars, '<information>') !== false) {
152
 
153
                return true;
154
        }
155
 
156
        return false;
157
    }
158
 
159
    /**
160
     * Converts the given directory with the backup into moodle2 format
161
     *
162
     * @param string $tempdir The directory to convert
163
     * @param string $format The current format, if already detected
164
     * @param base_logger|null if the conversion should be logged, use this logger
165
     * @throws convert_helper_exception
166
     * @return bool false if unable to find the conversion path, true otherwise
167
     */
168
    public static function to_moodle2_format($tempdir, $format = null, $logger = null) {
169
 
170
        if (is_null($format)) {
171
            $format = backup_general_helper::detect_backup_format($tempdir);
172
        }
173
 
174
        // get the supported conversion paths from all available converters
175
        $converters   = self::available_converters();
176
        $descriptions = array();
177
        foreach ($converters as $name) {
178
            $classname = "{$name}_converter";
179
            if (!class_exists($classname)) {
180
                throw new convert_helper_exception('class_not_loaded', $classname);
181
            }
182
            if ($logger instanceof base_logger) {
183
                backup_helper::log('available converter', backup::LOG_DEBUG, $classname, 1, false, $logger);
184
            }
185
            $descriptions[$name] = call_user_func($classname .'::description');
186
        }
187
 
188
        // choose the best conversion path for the given format
189
        $path = self::choose_conversion_path($format, $descriptions);
190
 
191
        if (empty($path)) {
192
            if ($logger instanceof base_logger) {
193
                backup_helper::log('unable to find the conversion path', backup::LOG_ERROR, null, 0, false, $logger);
194
            }
195
            return false;
196
        }
197
 
198
        if ($logger instanceof base_logger) {
199
            backup_helper::log('conversion path established', backup::LOG_INFO,
200
                implode(' => ', array_merge($path, array('moodle2'))), 0, false, $logger);
201
        }
202
 
203
        foreach ($path as $name) {
204
            if ($logger instanceof base_logger) {
205
                backup_helper::log('running converter', backup::LOG_INFO, $name, 0, false, $logger);
206
            }
207
            $converter = convert_factory::get_converter($name, $tempdir, $logger);
208
            $converter->convert();
209
        }
210
 
211
        // make sure we ended with moodle2 format
212
        if (!self::detect_moodle2_format($tempdir)) {
213
            throw new convert_helper_exception('conversion_failed');
214
        }
215
 
216
        return true;
217
    }
218
 
219
   /**
220
    * Inserts an inforef into the conversion temp table
221
    */
222
    public static function set_inforef($contextid) {
223
        global $DB;
224
    }
225
 
226
    public static function get_inforef($contextid) {
227
    }
228
 
229
    /// end of public API //////////////////////////////////////////////////////
230
 
231
    /**
232
     * Choose the best conversion path for the given format
233
     *
234
     * Given the source format and the list of available converters and their properties,
235
     * this methods picks the most effective way how to convert the source format into
236
     * the target moodle2 format. The method returns a list of converters that should be
237
     * called, in order.
238
     *
239
     * This implementation uses Dijkstra's algorithm to find the shortest way through
240
     * the oriented graph.
241
     *
242
     * @see http://en.wikipedia.org/wiki/Dijkstra's_algorithm
243
     * @author David Mudrak <david@moodle.com>
244
     * @param string $format the source backup format, one of backup::FORMAT_xxx
245
     * @param array $descriptions list of {@link base_converter::description()} indexed by the converter name
246
     * @return array ordered list of converter names to call (may be empty if not reachable)
247
     */
248
    protected static function choose_conversion_path($format, array $descriptions) {
249
 
250
        // construct an oriented graph of conversion paths. backup formats are nodes
251
        // and the the converters are edges of the graph.
252
        $paths = array();   // [fromnode][tonode] => converter
253
        foreach ($descriptions as $converter => $description) {
254
            $from   = $description['from'];
255
            $to     = $description['to'];
256
            $cost   = $description['cost'];
257
 
258
            if (is_null($from) or $from === backup::FORMAT_UNKNOWN or
259
                is_null($to) or $to === backup::FORMAT_UNKNOWN or
260
                is_null($cost) or $cost <= 0) {
261
                    throw new convert_helper_exception('invalid_converter_description', $converter);
262
            }
263
 
264
            if (!isset($paths[$from][$to])) {
265
                $paths[$from][$to] = $converter;
266
            } else {
267
                // if there are two converters available for the same conversion
268
                // path, choose the one with the lowest cost. if there are more
269
                // available converters with the same cost, the chosen one is
270
                // undefined (depends on the order of processing)
271
                if ($descriptions[$paths[$from][$to]]['cost'] > $cost) {
272
                    $paths[$from][$to] = $converter;
273
                }
274
            }
275
        }
276
 
277
        if (empty($paths)) {
278
            // no conversion paths available
279
            return array();
280
        }
281
 
282
        // now use Dijkstra's algorithm and find the shortest conversion path
283
 
284
        $dist = array(); // list of nodes and their distances from the source format
285
        $prev = array(); // list of previous nodes in optimal path from the source format
286
        foreach ($paths as $fromnode => $tonodes) {
287
            $dist[$fromnode] = null; // infinitive distance, can't be reached
288
            $prev[$fromnode] = null; // unknown
289
            foreach ($tonodes as $tonode => $converter) {
290
                $dist[$tonode] = null; // infinitive distance, can't be reached
291
                $prev[$tonode] = null; // unknown
292
            }
293
        }
294
 
295
        if (!array_key_exists($format, $dist)) {
296
            return array();
297
        } else {
298
            $dist[$format] = 0;
299
        }
300
 
301
        $queue = array_flip(array_keys($dist));
302
        while (!empty($queue)) {
303
            // find the node with the smallest distance from the source in the queue
304
            // in the first iteration, this will find the original format node itself
305
            $closest = null;
306
            foreach ($queue as $node => $undefined) {
307
                if (is_null($dist[$node])) {
308
                    continue;
309
                }
310
                if (is_null($closest) or ($dist[$node] < $dist[$closest])) {
311
                    $closest = $node;
312
                }
313
            }
314
 
315
            if (is_null($closest) or is_null($dist[$closest])) {
316
                // all remaining nodes are inaccessible from source
317
                break;
318
            }
319
 
320
            if ($closest === backup::FORMAT_MOODLE) {
321
                // bingo we can break now
322
                break;
323
            }
324
 
325
            unset($queue[$closest]);
326
 
327
            // visit all neighbors and update distances to them eventually
328
 
329
            if (!isset($paths[$closest])) {
330
                continue;
331
            }
332
            $neighbors = array_keys($paths[$closest]);
333
            // keep just neighbors that are in the queue yet
334
            foreach ($neighbors as $ix => $neighbor) {
335
                if (!array_key_exists($neighbor, $queue)) {
336
                    unset($neighbors[$ix]);
337
                }
338
            }
339
 
340
            foreach ($neighbors as $neighbor) {
341
                // the alternative distance to the neighbor if we went thru the
342
                // current $closest node
343
                $alt = $dist[$closest] + $descriptions[$paths[$closest][$neighbor]]['cost'];
344
 
345
                if (is_null($dist[$neighbor]) or $alt < $dist[$neighbor]) {
346
                    // we found a shorter way to the $neighbor, remember it
347
                    $dist[$neighbor] = $alt;
348
                    $prev[$neighbor] = $closest;
349
                }
350
            }
351
        }
352
 
353
        if (is_null($dist[backup::FORMAT_MOODLE])) {
354
            // unable to find a conversion path, the target format not reachable
355
            return array();
356
        }
357
 
358
        // reconstruct the optimal path from the source format to the target one
359
        $conversionpath = array();
360
        $target         = backup::FORMAT_MOODLE;
361
        while (isset($prev[$target])) {
362
            array_unshift($conversionpath, $paths[$prev[$target]][$target]);
363
            $target = $prev[$target];
364
        }
365
 
366
        return $conversionpath;
367
    }
368
}
369
 
370
/**
371
 * General convert_helper related exception
372
 *
373
 * @author David Mudrak <david@moodle.com>
374
 */
375
class convert_helper_exception extends moodle_exception {
376
 
377
    /**
378
     * Constructor
379
     *
380
     * @param string $errorcode key for the corresponding error string
381
     * @param object $a extra words and phrases that might be required in the error string
382
     * @param string $debuginfo optional debugging information
383
     */
384
    public function __construct($errorcode, $a = null, $debuginfo = null) {
385
        parent::__construct($errorcode, '', '', $a, $debuginfo);
386
    }
387
}