3 ///////////////////////////////////////////////////////////////////////////
5 // NOTICE OF COPYRIGHT //
7 // Moodle - Modular Object-Oriented Dynamic Learning Environment //
8 // http://moodle.com //
10 // Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com //
11 // (C) 2001-3001 Eloy Lafuente (stronk7) http://contiento.com //
13 // This program is free software; you can redistribute it and/or modify //
14 // it under the terms of the GNU General Public License as published by //
15 // the Free Software Foundation; either version 2 of the License, or //
16 // (at your option) any later version. //
18 // This program is distributed in the hope that it will be useful, //
19 // but WITHOUT ANY WARRANTY; without even the implied warranty of //
20 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
21 // GNU General Public License for more details: //
23 // http://www.gnu.org/copyleft/gpl.html //
25 ///////////////////////////////////////////////////////////////////////////
28 * This page will deploy an IMS Content Package zip file,
29 * building all the structures and auxiliary files to
30 * work inside a Moodle resource.
34 require_once('../../../../config.php');
35 require_once('../../lib.php');
36 require_once('resource.class.php');
37 require_once('../../../../backup/lib.php');
38 require_once('../../../../lib/filelib.php');
39 require_once('../../../../lib/xmlize.php');
41 /// Load request parameters
42 $courseid = required_param ('courseid', PARAM_INT
);
43 $cmid = required_param ('cmid', PARAM_INT
);
44 $file = required_param ('file', PARAM_PATH
);
45 $inpopup = optional_param ('inpopup', 0, PARAM_BOOL
);
47 /// Fetch some records from DB
48 $course = get_record ('course', 'id', $courseid);
49 $cm = get_coursemodule_from_id('resource', $cmid);
50 $resource = get_record ('resource', 'id', $cm->instance
);
52 /// Get some needed strings
53 $strdeploy = get_string('deploy','resource');
55 /// Instantiate a resource_ims object and modify its navigation
56 $resource_obj = new resource_ims ($cmid);
58 /// Print the header of the page
59 $pagetitle = strip_tags($course->shortname
.': '.
60 format_string($resource->name
)).': '.
64 print_header($pagetitle, $course->fullname
);
67 $resource_obj->navlinks
[] = array('name' => $strdeploy, 'link' => '', 'type' => 'action');
68 $navigation = build_navigation($resource_obj->navlinks
, $cm);
69 print_header($pagetitle, $course->fullname
, $navigation,
71 update_module_button($cm->id
, $course->id
, $resource_obj->strresource
));
74 /// Security Constraints (sesskey and isteacheredit)
75 if (!confirm_sesskey()) {
76 print_error('confirmsesskeybad', 'error');
77 } else if (!has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE
, $courseid))) {
78 print_error('onlyeditingteachers', 'error');
82 /// Main process, where everything is deployed
85 /// Set some variables
87 /// Create directories
88 if (!$resourcedir = make_upload_directory($courseid.'/'.$CFG->moddata
.'/resource/'.$resource->id
)) {
89 error (get_string('errorcreatingdirectory', 'error', $CFG->moddata
.'/resource/'.$resource->id
));
93 if (!delete_dir_contents($resourcedir)) {
94 error (get_string('errorcleaningdirectory', 'error', $resourcedir));
98 $origin = $CFG->dataroot
.'/'.$courseid.'/'.$file;
100 if (!is_file($origin)) {
101 error (get_string('filenotfound' , 'error', $file));
103 $mimetype = mimeinfo("type", $file);
104 if ($mimetype != "application/zip") {
105 error (get_string('invalidfiletype', 'error', $file));
107 $resourcefile = $resourcedir.'/'.basename($origin);
108 if (!backup_copy_file($origin, $resourcefile)) {
109 error (get_string('errorcopyingfiles', 'error'));
113 if (!unzip_file($resourcefile, '', false)) {
114 error (get_string('errorunzippingfiles', 'error'));
117 /// Check for imsmanifest
118 if (!file_exists($resourcedir.'/imsmanifest.xml')) {
119 error (get_string('filenotfound', 'error', 'imsmanifest.xml'));
122 /// Load imsmanifest to memory (instead of using a full parser,
123 /// we are going to use xmlize intensively (because files aren't too big)
124 if (!$imsmanifest = ims_file2var ($resourcedir.'/imsmanifest.xml')) {
125 error (get_string ('errorreadingfile', 'error', 'imsmanifest.xml'));
128 /// Check if the first line is a proper one, because I've seen some
129 /// packages with some control characters at the beginning.
130 $inixml = strpos($imsmanifest, '<?xml ');
131 if ($inixml !== false) {
133 //Strip strange chars before "<?xml "
134 $imsmanifest = substr($imsmanifest, $inixml);
137 error (get_string ('invalidxmlfile', 'error', 'imsmanifest.xml'));
140 /// xmlize the variable
141 $data = xmlize($imsmanifest, 0);
143 /// Extract every manifest present in the imsmanifest file.
144 /// Returns a tree structure.
145 if (!$manifests = ims_extract_manifests($data)) {
146 error (get_string('nonmeaningfulcontent', 'error'));
149 /// Process every manifest found in inverse order so every one
150 /// will be able to use its own submanifests. Not perfect because
151 /// teorically this will allow some manifests to use other non-childs
152 /// but this is supposed to be
154 /// Detect if all the manifest share a common xml:base tag
155 $manifest_base = $data['manifest']['@']['xml:base'];
157 /// Parse XML-metadata
158 /// Skip this for now (until a proper METADATA container was created in Moodle).
160 /// Parse XML-content package data
161 /// First we select an organization an load all the items
162 if (!$items = ims_process_organizations($data['manifest']['#']['organizations']['0'])) {
163 error (get_string('nonmeaningfulcontent', 'error'));
166 /// Detect if all the resources share a common xml:base tag
167 $resources_base = $data['manifest']['#']['resources']['0']['@']['xml:base'];
169 /// Now, we load all the resources available (keys are identifiers)
170 if (!$resources = ims_load_resources($data['manifest']['#']['resources']['0']['#']['resource'], $manifest_base, $resources_base)) {
171 error (get_string('nonmeaningfulcontent', 'error'));
173 ///Now we assign to each item, its resource (by identifier)
174 foreach ($items as $key=>$item) {
175 if (!empty($resources[$item->identifierref
])) {
176 $items[$key]->href
= $resources[$item->identifierref
];
178 $items[$key]->href
= '';
182 /// Create the INDEX (moodle_inx.ser - where the order of the pages are stored serialized) file
183 if (!ims_save_serialized_file($resourcedir.'/moodle_inx.ser', $items)) {
184 error (get_string('errorcreatingfile', 'error', 'moodle_inx.ser'));
187 /// Create the HASH file (moodle_hash.ser - where the hash of the ims is stored serialized) file
188 $hash = $resource_obj->calculatefilehash($resourcefile);
189 if (!ims_save_serialized_file($resourcedir.'/moodle_hash.ser', $hash)) {
190 error (get_string('errorcreatingfile', 'error', 'moodle_hash.ser'));
193 /// End button (go to view mode)
195 print_simple_box(get_string('imspackageloaded', 'resource'), 'center');
196 $link = $CFG->wwwroot
.'/mod/resource/view.php';
197 $options['r'] = $resource->id
;
198 $label = get_string('viewims', 'resource');
200 print_single_button($link, $options, $label, $method);
204 /// End of main process, where everything is deployed
207 /// Print the footer of the page
211 /// Common and useful functions used by the body of the script
214 /*** This function will return a tree of manifests (xmlized) as they are
215 * found and extracted from one manifest file. The first manifest in the
216 * will be the main one, while the rest will be submanifests. In the
217 * future (when IMS CP suppors it, external submanifest will be detected
218 * and retrieved here too). See IMS specs for more info.
220 function ims_extract_manifests($data) {
222 $manifest = new stdClass
; //To store found manifests in a tree structure
224 /// If there are some manifests
225 if (!empty($data['manifest'])) {
226 /// Add manifest to results array
227 $manifest->data
= $data['manifest'];
228 /// Look for submanifests
229 $submanifests = ims_extract_submanifests($data['manifest']['#']);
230 /// Add them as child
231 if (!empty($submanifests)) {
232 $manifest->childs
= $submanifests;
235 /// Return tree of manifests found
239 /* This function will search recursively for submanifests returning an array
240 * containing them (xmlized) following a tree structure.
242 function ims_extract_submanifests($data) {
244 $submanifests = array(); //To store found submanifests
246 /// If there are some manifests
247 if (!empty($data['manifest'])) {
249 foreach ($data['manifest'] as $submanifest) {
250 /// Create a new submanifest object
251 $submanifest_object = new stdClass
;
252 $submanifest_object->data
= $submanifest;
253 /// Look for more submanifests recursively
254 $moresubmanifests = ims_extract_submanifests($submanifest['#']);
255 /// Add them to results array
256 if (!empty($moresubmanifests)) {
257 $submanifest_object->childs
= moresubmanifests
;
259 /// Add submanifest object to results array
260 $submanifests[] = $submanifest_object;
263 /// Return array of manifests found
264 return $submanifests;
267 /*** This function will return an ordered and nested array of items
268 * that is a perfect representation of the prefered organization
270 function ims_process_organizations($data) {
274 /// Get the default organization
275 $default_organization = $data['@']['default'];
276 debugging('default_organization: '.$default_organization);
278 /// Iterate (reverse) over organizations until we find the default one
279 if (empty($data['#']['organization'])) { /// Verify <organization> exists
282 $count_organizations = count($data['#']['organization']);
283 debugging('count_organizations: '.$count_organizations);
285 $current_organization = $count_organizations - 1;
286 while ($current_organization >= 0) {
287 /// Load organization and check it
288 $organization = $data['#']['organization'][$current_organization];
289 if ($organization['@']['identifier'] == $default_organization) {
290 $current_organization = -1; //Match, so exit.
292 $current_organization--;
295 /// At this point we MUST have the final organization
296 debugging('final organization: '.$organization['#']['title'][0]['#']);
297 if (empty($organization)) {
298 return false; //Error, no organization found
301 /// Extract items map from organization
302 $items = $organization['#']['item'];
303 if (empty($organization['#']['item'])) { /// Verify <item> exists
306 if (!$itemmap = ims_process_items($items)) {
307 return false; //Error, no items found
312 /*** This function gets the xmlized representation of the items
313 * and returns an array of items, ordered, with level and info
315 function ims_process_items($items, $level = 1, $id = 1, $parent = 0) {
320 /// Iterate over items from start to end
321 $count_items = count($items);
322 debugging('level '.$level.'-count_items: '.$count_items);
325 while ($current_item < $count_items) {
327 $item = $items[$current_item];
328 $obj_item = new stdClass
;
329 $obj_item->title
= $item['#']['title'][0]['#'];
330 $obj_item->identifier
= $item['@']['identifier'];
331 $obj_item->identifierref
= $item['@']['identifierref'];
333 $obj_item->level
= $level;
334 $obj_item->parent
= $parent;
335 /// Only if the item has everything
336 if (!empty($obj_item->title
) &&
337 !empty($obj_item->identifier
)) {
339 $itemmap[$id] = $obj_item;
340 debugging('level '.$level.'-id '.$id.'-parent '.$parent.'-'.$obj_item->title
);
343 /// Check for subitems recursively
344 $subitems = $item['#']['item'];
345 if (count($subitems)) {
347 $subitemmap = ims_process_items($subitems, $level+
1, $id, $obj_item->id
);
348 /// Add at the end and counters if necessary
349 if ($count_subitems = count($subitemmap)) {
350 foreach ($subitemmap as $subitem) {
351 /// Add the subitem to the main items array
352 $itemmap[$subitem->id
] = $subitem;
364 /*** This function will load an array of resources to be used later.
365 * Keys are identifiers
367 function ims_load_resources($data, $manifest_base, $resources_base) {
370 $resources = array();
372 if (empty($data)) { /// Verify <resource> exists
375 $count_resources = count($data);
376 debugging('count_resources: '.$count_resources);
378 $current_resource = 0;
379 while ($current_resource < $count_resources) {
381 $resource = $data[$current_resource];
383 /// Create a new object resource
384 $obj_resource = new stdClass
;
385 $obj_resource->identifier
= $resource['@']['identifier'];
386 $obj_resource->resource_base
= $resource['@']['xml:base'];
387 $obj_resource->href
= $resource['@']['href'];
388 if (empty($obj_resource->href
)) {
389 $obj_resource->href
= $resource['#']['file']['0']['@']['href'];
392 /// Some packages are poorly done and use \ in roots. This makes them
393 /// not display since the URLs are not valid.
394 if (!empty($obj_resource->href
)) {
395 $obj_resource->href
= strtr($obj_resource->href
, "\\", '/');
398 /// Only if the resource has everything
399 if (!empty($obj_resource->identifier
) &&
400 !empty($obj_resource->href
)) {
401 /// Add to resources (identifier as key)
402 /// Depending of $manifest_base, $resources_base and the particular
403 /// $resource_base variable, concatenate them to build the correct href
405 if (!empty($manifest_base)) {
406 $href_base = $manifest_base;
408 if (!empty($resources_base)) {
409 $href_base .= $resources_base;
411 if (!empty($obj_resource->resource_base
)) {
412 $href_base .= $obj_resource->resource_base
;
414 $resources[$obj_resource->identifier
] = $href_base.$obj_resource->href
;