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
 * Private imscp module utility functions
19
 *
20
 * @package mod_imscp
21
 * @copyright  2009 Petr Skoda  {@link http://skodak.org}
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
defined('MOODLE_INTERNAL') || die();
26
 
27
require_once("$CFG->dirroot/mod/imscp/lib.php");
28
require_once("$CFG->libdir/filelib.php");
29
require_once("$CFG->libdir/resourcelib.php");
30
 
31
/**
32
 * Print IMSCP content to page.
33
 *
34
 * @param stdClass $imscp module instance.
35
 * @param stdClass $cm course module.
36
 * @param stdClass $course record.
37
 */
38
function imscp_print_content($imscp, $cm, $course) {
39
    global $PAGE, $CFG;
40
 
41
    $items = array_filter((array) unserialize_array($imscp->structure));
42
 
43
    echo '<div id="imscp_layout">';
44
    echo '<div id="imscp_toc">';
45
    echo '<div id="imscp_tree"><ul>';
46
    foreach ($items as $item) {
47
        echo imscp_htmllize_item($item, $imscp, $cm);
48
    }
49
    echo '</ul></div>';
50
    echo '<div id="imscp_nav" style="display:none">';
51
    echo '<button id="nav_skipprev">&lt;&lt;</button><button id="nav_prev">&lt;</button><button id="nav_up">^</button>';
52
    echo '<button id="nav_next">&gt;</button><button id="nav_skipnext">&gt;&gt;</button>';
53
    echo '</div>';
54
    echo '</div>';
55
    echo '</div>';
56
 
57
    $PAGE->requires->js_init_call('M.mod_imscp.init');
58
}
59
 
60
/**
61
 * Internal function - creates htmls structure suitable for YUI tree.
62
 */
63
function imscp_htmllize_item($item, $imscp, $cm) {
64
    global $CFG;
65
 
66
    if ($item['href']) {
67
        if (preg_match('|^https?://|', $item['href'])) {
68
            $url = $item['href'];
69
        } else {
70
            $context = context_module::instance($cm->id);
71
            $urlbase = "$CFG->wwwroot/pluginfile.php";
72
            $path = '/'.$context->id.'/mod_imscp/content/'.$imscp->revision.'/'.$item['href'];
73
            $url = file_encode_url($urlbase, $path, false);
74
        }
75
        $result = "<li><a href=\"$url\">".$item['title'].'</a>';
76
    } else {
77
        $result = '<li>'.$item['title'];
78
    }
79
    if ($item['subitems']) {
80
        $result .= '<ul>';
81
        foreach ($item['subitems'] as $subitem) {
82
            $result .= imscp_htmllize_item($subitem, $imscp, $cm);
83
        }
84
        $result .= '</ul>';
85
    }
86
    $result .= '</li>';
87
 
88
    return $result;
89
}
90
 
91
/**
92
 * Parse an IMS content package's manifest file to determine its structure
93
 * @param object $imscp
94
 * @param object $context
95
 * @return array
96
 */
97
function imscp_parse_structure($imscp, $context) {
98
    $fs = get_file_storage();
99
 
100
    if (!$manifestfile = $fs->get_file($context->id, 'mod_imscp', 'content', $imscp->revision, '/', 'imsmanifest.xml')) {
101
        return null;
102
    }
103
 
104
    return imscp_parse_manifestfile($manifestfile->get_content(), $imscp, $context);
105
}
106
 
107
/**
108
 * Parse the contents of a IMS package's manifest file.
109
 * @param string $manifestfilecontents the contents of the manifest file
110
 * @return array
111
 */
112
function imscp_parse_manifestfile($manifestfilecontents, $imscp, $context) {
113
    $doc = new DOMDocument();
114
    if (!$doc->loadXML($manifestfilecontents, LIBXML_NONET)) {
115
        return null;
116
    }
117
 
118
    // We put this fake URL as base in order to detect path changes caused by xml:base attributes.
119
    $doc->documentURI = 'http://grrr/';
120
 
121
    $xmlorganizations = $doc->getElementsByTagName('organizations');
122
    if (empty($xmlorganizations->length)) {
123
        return null;
124
    }
125
    $default = null;
126
    if ($xmlorganizations->item(0)->attributes->getNamedItem('default')) {
127
        $default = $xmlorganizations->item(0)->attributes->getNamedItem('default')->nodeValue;
128
    }
129
    $xmlorganization = $doc->getElementsByTagName('organization');
130
    if (empty($xmlorganization->length)) {
131
        return null;
132
    }
133
    $organization = null;
134
    foreach ($xmlorganization as $org) {
135
        if (is_null($organization)) {
136
            // Use first if default nor found.
137
            $organization = $org;
138
        }
139
        if (!$org->attributes->getNamedItem('identifier')) {
140
            continue;
141
        }
142
        if ($default === $org->attributes->getNamedItem('identifier')->nodeValue) {
143
            // Found default - use it.
144
            $organization = $org;
145
            break;
146
        }
147
    }
148
 
149
    // Load all resources.
150
    $resources = array();
151
 
152
    $xmlresources = $doc->getElementsByTagName('resource');
153
    foreach ($xmlresources as $res) {
154
        if (!$identifier = $res->attributes->getNamedItem('identifier')) {
155
            continue;
156
        }
157
        $identifier = $identifier->nodeValue;
158
        if ($xmlbase = $res->baseURI) {
159
            // Undo the fake URL, we are interested in relative links only.
160
            $xmlbase = str_replace('http://grrr/', '/', $xmlbase);
161
            $xmlbase = rtrim($xmlbase, '/').'/';
162
        } else {
163
            $xmlbase = '';
164
        }
165
        if (!$href = $res->attributes->getNamedItem('href')) {
166
            // If href not found look for <file href="help.htm"/>.
167
            $fileresources = $res->getElementsByTagName('file');
168
            foreach ($fileresources as $file) {
169
                $href = $file->getAttribute('href');
170
            }
171
            if (pathinfo($href, PATHINFO_EXTENSION) == 'xml') {
172
                $href = imscp_recursive_href($href, $imscp, $context);
173
            }
174
            if (empty($href)) {
175
                continue;
176
            }
177
        } else {
178
            $href = $href->nodeValue;
179
        }
180
        if (strpos($href, 'http://') !== 0) {
181
            $href = $xmlbase.$href;
182
        }
183
        // Item href cleanup - Some packages are poorly done and use \ in urls.
184
        $href = ltrim(strtr($href, "\\", '/'), '/');
185
        $resources[$identifier] = $href;
186
    }
187
 
188
    $items = array();
189
    foreach ($organization->childNodes as $child) {
190
        if ($child->nodeName === 'item') {
191
            if (!$item = imscp_recursive_item($child, 0, $resources)) {
192
                continue;
193
            }
194
            $items[] = $item;
195
        }
196
    }
197
 
198
    return $items;
199
}
200
 
201
function imscp_recursive_href($manifestfilename, $imscp, $context) {
202
    $fs = get_file_storage();
203
 
204
    $dirname = dirname($manifestfilename);
205
    $filename = basename($manifestfilename);
206
 
207
    if ($dirname !== '/') {
208
        $dirname = "/$dirname/";
209
    }
210
 
211
    if (!$manifestfile = $fs->get_file($context->id, 'mod_imscp', 'content', $imscp->revision, $dirname, $filename)) {
212
        return null;
213
    }
214
 
215
    $doc = new DOMDocument();
216
    if (!$doc->loadXML($manifestfile->get_content(), LIBXML_NONET)) {
217
        return null;
218
    }
219
 
220
    $xmlresources = $doc->getElementsByTagName('resource');
221
    foreach ($xmlresources as $res) {
222
        if (!$href = $res->attributes->getNamedItem('href')) {
223
            $fileresources = $res->getElementsByTagName('file');
224
            foreach ($fileresources as $file) {
225
                $href = $file->getAttribute('href');
226
                if (pathinfo($href, PATHINFO_EXTENSION) == 'xml') {
227
                    $href = imscp_recursive_href($href, $imscp, $context);
228
                }
229
 
230
                if (pathinfo($href, PATHINFO_EXTENSION) == 'htm' || pathinfo($href, PATHINFO_EXTENSION) == 'html') {
231
                    return $href;
232
                }
233
            }
234
        }
235
    }
236
 
237
    return $manifestfilename;
238
}
239
 
240
function imscp_recursive_item($xmlitem, $level, $resources) {
241
    $identifierref = '';
242
    if ($identifierref = $xmlitem->attributes->getNamedItem('identifierref')) {
243
        $identifierref = $identifierref->nodeValue;
244
    }
245
 
246
    $title = '?';
247
    $subitems = array();
248
 
249
    foreach ($xmlitem->childNodes as $child) {
250
        if ($child->nodeName === 'title') {
251
            $title = $child->textContent;
252
 
253
        } else if ($child->nodeName === 'item') {
254
            if ($subitem = imscp_recursive_item($child, $level + 1, $resources)) {
255
                $subitems[] = $subitem;
256
            }
257
        }
258
    }
259
 
260
    return array('href'     => isset($resources[$identifierref]) ? $resources[$identifierref] : '',
261
                 'title'    => $title,
262
                 'level'    => $level,
263
                 'subitems' => $subitems,
264
                );
265
}
266
 
267
/**
268
 * Wrapper for function libxml_disable_entity_loader() deprecated in PHP 8
269
 *
270
 * Method was deprecated in PHP 8 and it shows deprecation message. However it is still
271
 * required in the previous versions on PHP. While Moodle supports both PHP 7 and 8 we need to keep it.
272
 * @see https://php.watch/versions/8.0/libxml_disable_entity_loader-deprecation
273
 *
274
 * @param bool $value
275
 * @return bool
276
 *
277
 * @deprecated since Moodle 4.3
278
 */
279
function imscp_libxml_disable_entity_loader(bool $value): bool {
280
    debugging(__FUNCTION__ . '() is deprecated, please do not use it any more', DEBUG_DEVELOPER);
281
    return true;
282
}
283
 
284
/**
285
 * File browsing support class
286
 *
287
 * @copyright  2009 Petr Skoda  {@link http://skodak.org}
288
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
289
 */
290
class imscp_file_info extends file_info {
291
    protected $course;
292
    protected $cm;
293
    protected $areas;
294
    protected $filearea;
295
 
296
    public function __construct($browser, $course, $cm, $context, $areas, $filearea) {
297
        parent::__construct($browser, $context);
298
        $this->course   = $course;
299
        $this->cm       = $cm;
300
        $this->areas    = $areas;
301
        $this->filearea = $filearea;
302
    }
303
 
304
    /**
305
     * Returns list of standard virtual file/directory identification.
306
     * The difference from stored_file parameters is that null values
307
     * are allowed in all fields
308
     * @return array with keys contextid, filearea, itemid, filepath and filename
309
     */
310
    public function get_params() {
311
        return array('contextid' => $this->context->id,
312
                     'component' => 'mod_imscp',
313
                     'filearea'  => $this->filearea,
314
                     'itemid'    => null,
315
                     'filepath'  => null,
316
                     'filename'  => null);
317
    }
318
 
319
    /**
320
     * Returns localised visible name.
321
     * @return string
322
     */
323
    public function get_visible_name() {
324
        return $this->areas[$this->filearea];
325
    }
326
 
327
    /**
328
     * Can I add new files or directories?
329
     * @return bool
330
     */
331
    public function is_writable() {
332
        return false;
333
    }
334
 
335
    /**
336
     * Is directory?
337
     * @return bool
338
     */
339
    public function is_directory() {
340
        return true;
341
    }
342
 
343
    /**
344
     * Returns list of children.
345
     * @return array of file_info instances
346
     */
347
    public function get_children() {
348
        return $this->get_filtered_children('*', false, true);
349
    }
350
 
351
    /**
352
     * Help function to return files matching extensions or their count
353
     *
354
     * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
355
     * @param bool|int $countonly if false returns the children, if an int returns just the
356
     *    count of children but stops counting when $countonly number of children is reached
357
     * @param bool $returnemptyfolders if true returns items that don't have matching files inside
358
     * @return array|int array of file_info instances or the count
359
     */
360
    private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
361
        global $DB;
362
        $params = array('contextid' => $this->context->id,
363
            'component' => 'mod_imscp',
364
            'filearea' => $this->filearea);
365
        $sql = 'SELECT DISTINCT itemid
366
                    FROM {files}
367
                    WHERE contextid = :contextid
368
                    AND component = :component
369
                    AND filearea = :filearea';
370
        if (!$returnemptyfolders) {
371
            $sql .= ' AND filename <> :emptyfilename';
372
            $params['emptyfilename'] = '.';
373
        }
374
        list($sql2, $params2) = $this->build_search_files_sql($extensions);
375
        $sql .= ' '.$sql2;
376
        $params = array_merge($params, $params2);
377
        if ($countonly !== false) {
378
            $sql .= ' ORDER BY itemid';
379
        }
380
 
381
        $rs = $DB->get_recordset_sql($sql, $params);
382
        $children = array();
383
        foreach ($rs as $record) {
384
            if ($child = $this->browser->get_file_info($this->context, 'mod_imscp', $this->filearea, $record->itemid)) {
385
                $children[] = $child;
386
                if ($countonly !== false && count($children) >= $countonly) {
387
                    break;
388
                }
389
            }
390
        }
391
        $rs->close();
392
        if ($countonly !== false) {
393
            return count($children);
394
        }
395
        return $children;
396
    }
397
 
398
    /**
399
     * Returns list of children which are either files matching the specified extensions
400
     * or folders that contain at least one such file.
401
     *
402
     * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
403
     * @return array of file_info instances
404
     */
405
    public function get_non_empty_children($extensions = '*') {
406
        return $this->get_filtered_children($extensions, false);
407
    }
408
 
409
    /**
410
     * Returns the number of children which are either files matching the specified extensions
411
     * or folders containing at least one such file.
412
     *
413
     * @param string|array $extensions, for example '*' or array('.gif','.jpg')
414
     * @param int $limit stop counting after at least $limit non-empty children are found
415
     * @return int
416
     */
417
    public function count_non_empty_children($extensions = '*', $limit = 1) {
418
        return $this->get_filtered_children($extensions, $limit);
419
    }
420
 
421
    /**
422
     * Returns parent file_info instance
423
     * @return file_info or null for root
424
     */
425
    public function get_parent() {
426
        return $this->browser->get_file_info($this->context);
427
    }
428
}