Passed
Push — master ( 39ba65...4e7d2f )
by Julito
10:09
created

scorm::import_manifest()   F

Complexity

Conditions 27
Paths > 20000

Size

Total Lines 221
Code Lines 133

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 27
eloc 133
nc 387096
nop 4
dl 0
loc 221
rs 0
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Framework\Container;
6
use Chamilo\CourseBundle\Entity\CLp;
7
use Symfony\Component\DomCrawler\Crawler;
8
9
/**
10
 * Defines the scorm class, which is meant to contain the scorm items (nuclear elements).
11
 *
12
 * @author    Yannick Warnier <[email protected]>
13
 */
14
class scorm extends learnpath
15
{
16
    public $manifest = [];
17
    public $resources = [];
18
    public $resources_att = [];
19
    public $organizations = [];
20
    public $organizations_att = [];
21
    public $metadata = [];
22
    // Will hold the references to resources for each item ID found.
23
    public $idrefs = [];
24
    // For each resource found, stores the file url/uri.
25
    public $refurls = [];
26
    /*  Path between the scorm/ directory and the imsmanifest.xml e.g.
27
    maritime_nav/maritime_nav. This is the path that will be used in the
28
    lp_path when importing a package. */
29
    public $subdir = '';
30
    public $items = [];
31
    // Keeps the zipfile safe for the object's life so that we can use it if no title avail.
32
    public $zipname = '';
33
    // Keeps an index of the number of uses of the zipname so far.
34
    public $lastzipnameindex = 0;
35
    public $manifest_encoding = 'UTF-8';
36
    public $debug = false;
37
38
    /**
39
     * Class constructor. Based on the parent constructor.
40
     *
41
     * @param    string    Course code
42
     * @param    int    Learnpath ID in DB
43
     * @param    int    User ID
44
     */
45
    public function __construct($course_code = null, $resource_id = null, $user_id = null)
46
    {
47
        parent::__construct($course_code, $resource_id, $user_id);
48
    }
49
50
    /**
51
     * Opens a resource.
52
     *
53
     * @param int $id Database ID of the resource
54
     */
55
    public function open($id)
56
    {
57
        if ($this->debug > 0) {
58
            error_log('New LP - scorm::open() - In scorm::open method', 0);
59
        }
60
    }
61
62
    /**
63
     * Possible SCO status: see CAM doc 2.3.2.5.1: passed, completed, browsed, failed, not attempted, incomplete.
64
     * Prerequisites: see CAM doc 2.3.2.5.1 for pseudo-code.
65
     *
66
     * Parses an imsmanifest.xml file and puts everything into the $manifest array.
67
     *
68
     * @param	string	Path to the imsmanifest.xml file on the system.
69
     * If not defined, uses the base path of the course's scorm dir
70
     *
71
     * @return array Structured array representing the imsmanifest's contents
72
     */
73
    public function parse_manifest($file = '')
74
    {
75
        if ($this->debug > 0) {
76
            error_log('In scorm::parse_manifest('.$file.')', 0);
77
        }
78
        if (empty($file)) {
79
            // Get the path of the imsmanifest file.
80
        }
81
82
        if (is_file($file) && is_readable($file) && ($xml = @file_get_contents($file))) {
83
            // Parsing using PHP5 DOMXML methods.
84
            if ($this->debug > 0) {
85
                error_log('In scorm::parse_manifest() - Parsing using PHP5 method');
86
            }
87
            // $this->manifest_encoding = api_detect_encoding_xml($xml);
88
            // This is the usual way for reading the encoding.
89
            // This method reads the encoding, it tries to be correct even in cases
90
            // of wrong or missing encoding declarations.
91
            $this->manifest_encoding = self::detect_manifest_encoding($xml);
0 ignored issues
show
Bug Best Practice introduced by
The method scorm::detect_manifest_encoding() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

91
            /** @scrutinizer ignore-call */ 
92
            $this->manifest_encoding = self::detect_manifest_encoding($xml);
Loading history...
92
93
            // UTF-8 is supported by DOMDocument class, this is for sure.
94
            $xml = api_utf8_encode_xml($xml, $this->manifest_encoding);
95
96
            $crawler = new Crawler();
97
            $crawler->addXmlContent($xml);
98
99
            $xmlErrors = libxml_get_errors();
100
101
            if (!empty($xmlErrors)) {
102
                if ($this->debug > 0) {
103
                    error_log('New LP - In scorm::parse_manifest() - Exception thrown when loading '.$file.' in DOMDocument');
104
                }
105
                // Throw exception?
106
                return null;
107
            }
108
109
            if ($this->debug > 1) {
110
                error_log('New LP - Called  (encoding:'.$this->manifest_encoding.' - saved: '.$this->manifest_encoding.')', 0);
111
            }
112
113
            $root = $crawler->getNode(0);
114
115
            if ($root->hasAttributes()) {
116
                $attributes = $root->attributes;
117
                if (0 !== $attributes->length) {
118
                    foreach ($attributes as $attrib) {
119
                        // <manifest> element attributes
120
                        $this->manifest[$attrib->name] = $attrib->value;
121
                    }
122
                }
123
            }
124
            $this->manifest['name'] = $root->tagName;
125
            if ($root->hasChildNodes()) {
126
                $children = $root->childNodes;
127
                if (0 !== $children->length) {
128
                    foreach ($children as $child) {
129
                        // <manifest> element children (can be <metadata>, <organizations> or <resources> )
130
                        if (XML_ELEMENT_NODE == $child->nodeType) {
131
                            switch ($child->tagName) {
132
                                case 'metadata':
133
                                    // Parse items from inside the <metadata> element.
134
                                    $this->metadata = new scormMetadata('manifest', $child);
135
                                    break;
136
                                case 'organizations':
137
                                    // Contains the course structure - this element appears 1 and only 1 time in a package imsmanifest.
138
                                    // It contains at least one 'organization' sub-element.
139
                                    $orgs_attribs = $child->attributes;
140
                                    foreach ($orgs_attribs as $orgs_attrib) {
141
                                        // Attributes of the <organizations> element.
142
                                        if (XML_ATTRIBUTE_NODE == $orgs_attrib->nodeType) {
143
                                            $this->manifest['organizations'][$orgs_attrib->name] = $orgs_attrib->value;
144
                                        }
145
                                    }
146
                                    $orgs_nodes = $child->childNodes;
147
                                    $i = 0;
148
                                    $found_an_org = false;
149
                                    foreach ($orgs_nodes as $orgnode) {
150
                                        // <organization> elements - can contain <item>, <metadata> and <title>
151
                                        // Here we are at the 'organization' level. There might be several organization tags but
152
                                        // there is generally only one.
153
                                        // There are generally three children nodes we are looking for inside and organization:
154
                                        // -title
155
                                        // -item (may contain other item tags or may appear several times inside organization)
156
                                        // -metadata (relative to the organization)
157
                                        $found_an_org = false;
158
                                        switch ($orgnode->nodeType) {
159
                                            case XML_TEXT_NODE:
160
                                                // Ignore here.
161
                                                break;
162
                                            case XML_ATTRIBUTE_NODE:
163
                                                // Just in case there would be interesting attributes inside the organization tag.
164
                                                // There shouldn't as this is a node-level, not a data level.
165
                                                //$manifest['organizations'][$i][$orgnode->name] = $orgnode->value;
166
                                                //$found_an_org = true;
167
                                                break;
168
                                            case XML_ELEMENT_NODE:
169
                                                // <item>, <metadata> or <title> (or attributes)
170
                                                $organizations_attributes = $orgnode->attributes;
171
                                                foreach ($organizations_attributes as $orgs_attr) {
172
                                                    $this->organizations_att[$orgs_attr->name] = $orgs_attr->value;
173
                                                }
174
                                                $oOrganization = new scormOrganization(
175
                                                    'manifest',
176
                                                    $orgnode,
177
                                                    $this->manifest_encoding
178
                                                );
179
                                                if ('' != $oOrganization->identifier) {
180
                                                    $name = $oOrganization->get_name();
181
                                                    if (empty($name)) {
182
                                                        // If the org title is empty, use zip file name.
183
                                                        $myname = $this->zipname;
184
                                                        if (0 != $this->lastzipnameindex) {
185
                                                            $myname = $myname + $this->lastzipnameindex;
186
                                                            $this->lastzipnameindex++;
187
                                                        }
188
                                                        $oOrganization->set_name($this->zipname);
189
                                                    }
190
                                                    $this->organizations[$oOrganization->identifier] = $oOrganization;
191
                                                }
192
                                                break;
193
                                        }
194
                                    }
195
                                    break;
196
                                case 'resources':
197
                                    if ($child->hasAttributes()) {
198
                                        $resources_attribs = $child->attributes;
199
                                        foreach ($resources_attribs as $res_attr) {
200
                                            if (XML_ATTRIBUTE_NODE == $res_attr->type) {
201
                                                $this->manifest['resources'][$res_attr->name] = $res_attr->value;
202
                                            }
203
                                        }
204
                                    }
205
                                    if ($child->hasChildNodes()) {
206
                                        $resources_nodes = $child->childNodes;
207
                                        $i = 0;
208
                                        foreach ($resources_nodes as $res_node) {
209
                                            $oResource = new scormResource('manifest', $res_node);
210
                                            if ('' != $oResource->identifier) {
211
                                                $this->resources[$oResource->identifier] = $oResource;
212
                                                $i++;
213
                                            }
214
                                        }
215
                                    }
216
                                    // Contains links to physical resources.
217
                                    break;
218
                                case 'manifest':
219
                                    // Only for sub-manifests.
220
                                    break;
221
                            }
222
                        }
223
                    }
224
                }
225
            }
226
            // End parsing using PHP5 DOMXML methods.
227
        } else {
228
            if ($this->debug > 1) {
229
                error_log('New LP - Could not open/read file '.$file);
230
            }
231
            $this->set_error_msg("File $file could not be read");
232
233
            return null;
234
        }
235
236
        $fixTemplate = api_get_configuration_value('learnpath_fix_xerte_template');
237
        $proxyPath = api_get_configuration_value('learnpath_proxy_url');
238
        if ($fixTemplate && !empty($proxyPath)) {
239
            // Check organisations:
240
            if (isset($this->manifest['organizations'])) {
241
                foreach ($this->manifest['organizations'] as $data) {
242
                    if (false !== strpos(strtolower($data), 'xerte')) {
243
                        // Check if template.xml exists:
244
                        $templatePath = str_replace('imsmanifest.xml', 'template.xml', $file);
245
                        if (file_exists($templatePath) && is_file($templatePath)) {
246
                            $templateContent = file_get_contents($templatePath);
247
                            $find = [
248
                                'href="www.',
249
                                'href="https://',
250
                                'href="http://',
251
                                'url="www.',
252
                                'pdfs/download.php?',
253
                            ];
254
255
                            $replace = [
256
                                'href="http://www.',
257
                                'target = "_blank" href="'.$proxyPath.'?type=link&src=https://',
258
                                'target = "_blank" href="'.$proxyPath.'?type=link&src=http://',
259
                                'url="http://www.',
260
                                'pdfs/download.php&',
261
                            ];
262
                            $templateContent = str_replace($find, $replace, $templateContent);
263
                            file_put_contents($templatePath, $templateContent);
264
                        }
265
266
                        // Fix link generation:
267
                        $linkPath = str_replace('imsmanifest.xml', 'models_html5/links.html', $file);
268
                        if (file_exists($linkPath) && is_file($linkPath)) {
269
                            $linkContent = file_get_contents($linkPath);
270
                            $find = [
271
                                ':this.getAttribute("url")',
272
                            ];
273
                            $replace = [
274
                                ':"'.$proxyPath.'?type=link&src=" + this.getAttribute("url")',
275
                            ];
276
                            $linkContent = str_replace($find, $replace, $linkContent);
277
                            file_put_contents($linkPath, $linkContent);
278
                        }
279
280
                        // Fix iframe generation
281
                        $framePath = str_replace('imsmanifest.xml', 'models_html5/embedDiv.html', $file);
282
283
                        if (file_exists($framePath) && is_file($framePath)) {
284
                            $content = file_get_contents($framePath);
285
                            $find = [
286
                                '$iFrameHolder.html(iFrameTag);',
287
                            ];
288
                            $replace = [
289
                                'iFrameTag = \'<a target ="_blank" href="'.$proxyPath.'?type=link&src=\'+ pageSrc + \'">Open website. <img width="16px" src="'.Display::returnIconPath('link-external.png').'"></a>\'; $iFrameHolder.html(iFrameTag); ',
290
                            ];
291
                            $content = str_replace($find, $replace, $content);
292
                            file_put_contents($framePath, $content);
293
                        }
294
295
                        // Fix new window generation
296
                        $newWindowPath = str_replace('imsmanifest.xml', 'models_html5/newWindow.html', $file);
297
298
                        if (file_exists($newWindowPath) && is_file($newWindowPath)) {
299
                            $content = file_get_contents($newWindowPath);
300
                            $find = [
301
                                'var src = x_currentPageXML',
302
                            ];
303
                            $replace = [
304
                                'var src = "'.$proxyPath.'?type=link&src=" + x_currentPageXML',
305
                            ];
306
                            $content = str_replace($find, $replace, $content);
307
                            file_put_contents($newWindowPath, $content);
308
                        }
309
                    }
310
                }
311
            }
312
        }
313
314
        // TODO: Close the DOM handler.
315
        return $this->manifest;
316
    }
317
318
    /**
319
     * Import the scorm object (as a result from the parse_manifest function) into the database structure.
320
     *
321
     * @param string $courseCode
322
     * @param int    $userMaxScore
323
     * @param int    $sessionId
324
     * @param int    $userId
325
     *
326
     * @return bool Returns -1 on error
327
     */
328
    public function import_manifest(
329
        $courseCode,
330
        $userMaxScore = 1,
331
        $sessionId = 0,
332
        $userId = 0
333
    ) {
334
        if ($this->debug > 0) {
335
            error_log('New LP - Entered import_manifest('.$courseCode.')', 0);
336
        }
337
        $courseInfo = api_get_course_info($courseCode);
338
        $courseId = $courseInfo['real_id'];
339
340
        $userId = (int) $userId;
341
        if (empty($userId)) {
342
            $userId = api_get_user_id();
343
        }
344
        // Get table names.
345
        $new_lp = Database::get_course_table(TABLE_LP_MAIN);
346
        $new_lp_item = Database::get_course_table(TABLE_LP_ITEM);
347
        $userMaxScore = (int) $userMaxScore;
348
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
349
        foreach ($this->organizations as $id => $dummy) {
350
            $oOrganization = &$this->organizations[$id];
351
            // Prepare and execute insert queries:
352
            // -for learnpath
353
            // -for items
354
            // -for views?
355
            $get_max = "SELECT MAX(display_order) FROM $new_lp WHERE c_id = $courseId ";
356
            $res_max = Database::query($get_max);
357
            $dsp = 1;
358
            if (Database::num_rows($res_max) > 0) {
359
                $row = Database::fetch_array($res_max);
360
                $dsp = $row[0] + 1;
361
            }
362
363
            $courseEntity = api_get_course_entity($courseInfo['real_id']);
364
            $myname = api_utf8_decode($oOrganization->get_name());
365
            $lp = new CLp();
366
            $lp
367
                ->setCId($courseId)
368
                ->setLpType(2)
369
                ->setName($myname)
370
                ->setRef($oOrganization->get_ref())
371
                ->setPath($this->subdir)
372
                ->setDefaultEncoding($this->manifest_encoding)
373
                ->setJsLib('scorm_api.php')
374
                ->setDisplayOrder($dsp)
375
                ->setUseMaxScore($userMaxScore)
376
                ->setSessionId($sessionId)
377
                ->setParent($courseEntity)
378
                ->addCourseLink($courseEntity, api_get_session_entity())
379
            ;
380
381
            $repo = Container::getLpRepository();
382
            $repo->create($lp);
383
384
            /*if ($lp_id) {
385
                $sql = "UPDATE $new_lp SET id = iid WHERE iid = $lp_id";
386
                Database::query($sql);
387
388
                $this->lp_id = $lp_id;
389
390
                // Insert into item_property.
391
                api_item_property_update(
392
                    $courseInfo,
393
                    TOOL_LEARNPATH,
394
                    $this->lp_id,
395
                    'LearnpathAdded',
396
                    $userId
397
                );
398
399
                api_item_property_update(
400
                    $courseInfo,
401
                    TOOL_LEARNPATH,
402
                    $this->lp_id,
403
                    'visible',
404
                    $userId
405
                );
406
            }*/
407
408
            // Now insert all elements from inside that learning path.
409
            // Make sure we also get the href and sco/asset from the resources.
410
            $list = $oOrganization->get_flat_items_list();
411
            $parents_stack = [0];
412
            $parent = 0;
413
            $previous = 0;
414
            $level = 0;
415
416
            foreach ($list as $item) {
417
                if ($item['level'] > $level) {
418
                    // Push something into the parents array.
419
                    array_push($parents_stack, $previous);
420
                    $parent = $previous;
421
                } elseif ($item['level'] < $level) {
422
                    $diff = $level - $item['level'];
423
                    // Pop something out of the parents array.
424
                    for ($j = 1; $j <= $diff; $j++) {
425
                        $outdated_parent = array_pop($parents_stack);
426
                    }
427
                    $parent = array_pop($parents_stack); // Just save that value, then add it back.
428
                    array_push($parents_stack, $parent);
429
                }
430
                $path = '';
431
                $type = 'dir';
432
                if (isset($this->resources[$item['identifierref']])) {
433
                    $oRes = &$this->resources[$item['identifierref']];
434
                    $path = @$oRes->get_path();
435
                    if (!empty($path)) {
436
                        $temptype = $oRes->get_scorm_type();
437
                        if (!empty($temptype)) {
438
                            $type = $temptype;
439
                        }
440
                    }
441
                }
442
                $level = $item['level'];
443
                $field_add = '';
444
                $value_add = '';
445
446
                if (!empty($item['masteryscore'])) {
447
                    $field_add .= 'mastery_score, ';
448
                    $value_add .= $item['masteryscore'].',';
449
                }
450
451
                if (!empty($item['maxtimeallowed'])) {
452
                    $field_add .= 'max_time_allowed, ';
453
                    $value_add .= "'".$item['maxtimeallowed']."',";
454
                }
455
                $title = Database::escape_string($item['title']);
456
                $title = api_utf8_decode($title);
457
                $max_score = (int) $item['max_score'];
458
459
                if (0 === $max_score) {
460
                    // If max score is not set The use_max_score parameter
461
                    // is check in order to use 100 (chamilo style) or '' (strict scorm)
462
                    $max_score = 'NULL';
463
                    if ($userMaxScore) {
464
                        $max_score = 100;
465
                    }
466
                } else {
467
                    // Otherwise save the max score.
468
                    $max_score = "'$max_score'";
469
                }
470
471
                $identifier = Database::escape_string($item['identifier']);
472
473
                if (empty($title)) {
474
                    $title = get_lang('Untitled');
475
                }
476
477
                $prereq = Database::escape_string($item['prerequisites']);
478
                $item['datafromlms'] = Database::escape_string($item['datafromlms']);
479
                $item['parameters'] = Database::escape_string($item['parameters']);
480
481
                $sql = "INSERT INTO $new_lp_item (c_id, lp_id,item_type,ref,title, path,min_score,max_score, $field_add parent_item_id,previous_item_id,next_item_id, prerequisite,display_order,launch_data, parameters)
482
                        VALUES ($courseId, $lp_id, '$type', '$identifier', '$title', '$path' , 0, $max_score, $value_add $parent, $previous, 0, '$prereq', ".$item['rel_order'].", '".$item['datafromlms']."', '".$item['parameters']."' )";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $lp_id seems to be never defined.
Loading history...
483
484
                Database::query($sql);
485
                if ($this->debug > 1) {
486
                    error_log('New LP - In import_manifest(), inserting item : '.$sql);
487
                }
488
                $item_id = Database::insert_id();
489
490
                if ($item_id) {
491
                    $sql = "UPDATE $new_lp_item SET id = iid WHERE iid = $item_id";
492
                    Database::query($sql);
493
494
                    // Now update previous item to change next_item_id.
495
                    $upd = "UPDATE $new_lp_item SET next_item_id = $item_id
496
                            WHERE iid = $previous";
497
                    Database::query($upd);
498
                    // Update previous item id.
499
                    $previous = $item_id;
500
                }
501
502
                // Code for indexing, now only index specific fields like terms and the title.
503
                if (!empty($_POST['index_document'])) {
504
                    require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
505
506
                    $di = new ChamiloIndexer();
507
                    isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
508
                    $di->connectDb(null, null, $lang);
509
                    $ic_slide = new IndexableChunk();
510
                    $ic_slide->addValue('title', $title);
511
                    $specific_fields = get_specific_field_list();
512
                    $all_specific_terms = '';
513
                    foreach ($specific_fields as $specific_field) {
514
                        if (isset($_REQUEST[$specific_field['code']])) {
515
                            $sterms = trim($_REQUEST[$specific_field['code']]);
516
                            $all_specific_terms .= ' '.$sterms;
517
                            if (!empty($sterms)) {
518
                                $sterms = explode(',', $sterms);
519
                                foreach ($sterms as $sterm) {
520
                                    $ic_slide->addTerm(trim($sterm), $specific_field['code']);
521
                                }
522
                            }
523
                        }
524
                    }
525
                    $body_to_index = $all_specific_terms.' '.$title;
526
                    $ic_slide->addValue("content", $body_to_index);
527
                    // TODO: Add a comment to say terms separated by commas.
528
                    $courseid = api_get_course_id();
529
                    $ic_slide->addCourseId($courseid);
530
                    $ic_slide->addToolId(TOOL_LEARNPATH);
531
                    // TODO: Unify with other lp types.
532
                    $xapian_data = [
533
                        SE_COURSE_ID => $courseid,
534
                        SE_TOOL_ID => TOOL_LEARNPATH,
535
                        SE_DATA => ['lp_id' => $lp_id, 'lp_item' => $previous, 'document_id' => ''],
536
                        SE_USER => api_get_user_id(),
537
                    ];
538
                    $ic_slide->xapian_data = serialize($xapian_data);
539
                    $di->addChunk($ic_slide);
540
                    // Index and return search engine document id.
541
                    $did = $di->index();
542
                    if ($did) {
543
                        // Save it to db.
544
                        $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
545
                        $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did)
546
                                VALUES (NULL , \'%s\', \'%s\', %s, %s, %s)';
547
                        $sql = sprintf($sql, $tbl_se_ref, $courseCode, TOOL_LEARNPATH, $lp_id, $previous, $did);
548
                        Database::query($sql);
549
                    }
550
                }
551
            }
552
        }
553
    }
554
555
    /**
556
     * Intermediate to import_package only to allow import from local zip files.
557
     *
558
     * @param  string    Path to the zip file, from the sys root
559
     * @param  string    Current path (optional)
560
     *
561
     * @return string Absolute path to the imsmanifest.xml file or empty string on error
562
     */
563
    public function import_local_package($file_path, $currentDir = '')
564
    {
565
        // TODO: Prepare info as given by the $_FILES[''] vector.
566
        $fileInfo = [];
567
        $fileInfo['tmp_name'] = $file_path;
568
        $fileInfo['name'] = basename($file_path);
569
        // Call the normal import_package function.
570
        return $this->import_package($fileInfo, $currentDir);
571
    }
572
573
    /**
574
     * Imports a zip file into the Chamilo structure.
575
     *
576
     * @param string    $zipFileInfo       Zip file info as given by $_FILES['userFile']
577
     * @param string    $currentDir
578
     * @param array     $courseInfo
579
     * @param bool      $updateDirContents
580
     * @param learnpath $lpToCheck
581
     * @param bool      $allowHtaccess
582
     *
583
     * @return string $current_dir Absolute path to the imsmanifest.xml file or empty string on error
584
     */
585
    public function import_package(
586
        $zipFileInfo,
587
        $currentDir = '',
588
        $courseInfo = [],
589
        $updateDirContents = false,
590
        $lpToCheck = null,
591
        $allowHtaccess = false
592
    ) {
593
        if ($this->debug > 0) {
594
            error_log(
595
                'In scorm::import_package('.print_r($zipFileInfo, true).',"'.$currentDir.'") method'
596
            );
597
        }
598
599
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
600
        $maxFilledSpace = DocumentManager::get_course_quota($courseInfo['code']);
601
602
        $zipFilePath = $zipFileInfo['tmp_name'];
603
        $zipFileName = $zipFileInfo['name'];
604
605
        if ($this->debug > 1) {
606
            error_log(
607
                'New LP - import_package() - zip file path = '.$zipFilePath.', zip file name = '.$zipFileName,
608
                0
609
            );
610
        }
611
612
        $courseRelDir = api_get_course_path($courseInfo['code']).'/scorm'; // scorm dir web path starting from /courses
613
        $courseSysDir = api_get_path(SYS_COURSE_PATH).$courseRelDir; // Absolute system path for this course.
0 ignored issues
show
Bug introduced by
The constant SYS_COURSE_PATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
614
        $currentDir = api_replace_dangerous_char(trim($currentDir)); // Current dir we are in, inside scorm/
615
616
        if ($this->debug > 1) {
617
            error_log('New LP - import_package() - current_dir = '.$currentDir, 0);
618
        }
619
620
        // Get name of the zip file without the extension.
621
        $fileInfo = pathinfo($zipFileName);
622
        $filename = $fileInfo['basename'];
623
        $extension = $fileInfo['extension'];
624
        $fileBaseName = str_replace('.'.$extension, '', $filename); // Filename without its extension.
625
        $this->zipname = $fileBaseName; // Save for later in case we don't have a title.
626
        $newDir = api_replace_dangerous_char(trim($fileBaseName));
627
        $this->subdir = $newDir;
628
        if ($this->debug > 1) {
629
            error_log('New LP - Received zip file name: '.$zipFilePath);
630
            error_log("New LP - subdir is first set to : ".$this->subdir);
631
            error_log("New LP - base file name is : ".$fileBaseName);
632
        }
633
634
        $zipFile = new PclZip($zipFilePath);
635
        // Check the zip content (real size and file extension).
636
        $zipContentArray = $zipFile->listContent();
637
        $packageType = '';
638
        $manifestList = [];
639
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
640
        $realFileSize = 0;
641
        foreach ($zipContentArray as $thisContent) {
642
            if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
643
                $file = $thisContent['filename'];
644
                $this->set_error_msg("File $file contains a PHP script");
645
            } elseif (stristr($thisContent['filename'], 'imsmanifest.xml')) {
646
                if ($thisContent['filename'] == basename($thisContent['filename'])) {
647
                } else {
648
                    if ($this->debug > 2) {
649
                        error_log("New LP - subdir is now ".$this->subdir);
650
                    }
651
                }
652
                $packageType = 'scorm';
653
                $manifestList[] = $thisContent['filename'];
654
            }
655
            $realFileSize += $thisContent['size'];
656
        }
657
658
        // Now get the shortest path (basically, the imsmanifest that is the closest to the root).
659
        $shortestPath = $manifestList[0];
660
        $slashCount = substr_count($shortestPath, '/');
661
        foreach ($manifestList as $manifestPath) {
662
            $tmpSlashCount = substr_count($manifestPath, '/');
663
            if ($tmpSlashCount < $slashCount) {
664
                $shortestPath = $manifestPath;
665
                $slashCount = $tmpSlashCount;
666
            }
667
        }
668
669
        $this->subdir .= '/'.dirname($shortestPath); // Do not concatenate because already done above.
670
        $manifest = $shortestPath;
671
        if ($this->debug) {
672
            error_log("New LP - Package type is now: '$packageType'");
673
        }
674
        if ('' == $packageType) {
675
            Display::addFlash(
676
                Display::return_message(get_lang('This is not a valid SCORM ZIP file !'))
677
            );
678
679
            return false;
680
        }
681
682
        if (!enough_size($realFileSize, $courseSysDir, $maxFilledSpace)) {
683
            if ($this->debug > 1) {
684
                error_log('New LP - Not enough space to store package');
685
            }
686
            Display::addFlash(
687
                Display::return_message(
688
                    get_lang(
689
                        'The upload has failed. Either you have exceeded your maximum quota, or there is not enough disk space.'
690
                    )
691
                )
692
            );
693
694
            return false;
695
        }
696
697
        if ($updateDirContents && $lpToCheck) {
698
            $originalPath = str_replace('/.', '', $lpToCheck->path);
699
            if ($originalPath != $newDir) {
700
                Display::addFlash(Display::return_message(get_lang('The file to upload is not valid.')));
701
702
                return false;
703
            }
704
        }
705
706
        // It happens on Linux that $newDir sometimes doesn't start with '/'
707
        if ('/' != $newDir[0]) {
708
            $newDir = '/'.$newDir;
709
        }
710
711
        if ('/' == $newDir[strlen($newDir) - 1]) {
712
            $newDir = substr($newDir, 0, -1);
713
        }
714
715
        /* Uncompressing phase */
716
        /*
717
            We need to process each individual file in the zip archive to
718
            - add it to the database
719
            - parse & change relative html links
720
            - make sure the filenames are secure (filter funny characters or php extensions)
721
        */
722
        if (is_dir($courseSysDir.$newDir) ||
723
            @mkdir(
724
                $courseSysDir.$newDir,
725
                api_get_permissions_for_new_directories()
726
            )
727
        ) {
728
            // PHP method - slower...
729
            if ($this->debug >= 1) {
730
                error_log('New LP - Changing dir to '.$courseSysDir.$newDir);
731
            }
732
733
            chdir($courseSysDir.$newDir);
734
735
            $callBack = 'clean_up_files_in_zip';
736
            if ($allowHtaccess) {
737
                $callBack = 'cleanZipFilesAllowHtaccess';
738
            }
739
740
            if (api_get_configuration_value('skip_scorm_package_clean_up')) {
741
                $callBack = 'cleanZipFilesNoRename';
742
            }
743
744
            $zipFile->extract(
745
                PCLZIP_CB_PRE_EXTRACT,
0 ignored issues
show
Bug introduced by
The constant PCLZIP_CB_PRE_EXTRACT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
746
                $callBack
747
            );
748
749
            if (!empty($newDir)) {
750
                $newDir = $newDir.'/';
751
            }
752
            api_chmod_R($courseSysDir.$newDir, api_get_permissions_for_new_directories());
753
        } else {
754
            return false;
755
        }
756
757
        return $courseSysDir.$newDir.$manifest;
758
    }
759
760
    /**
761
     * Exports the current SCORM object's files as a zip.
762
     * Excerpts taken from learnpath_functions.inc.php::exportpath().
763
     *
764
     * @param int    Learnpath ID (optional, taken from object context if not defined)
765
     *
766
     * @return bool
767
     */
768
    public function export_zip($lp_id = null)
769
    {
770
        if ($this->debug > 0) {
771
            error_log('In scorm::export_zip method('.$lp_id.')');
772
        }
773
        if (empty($lp_id)) {
774
            if (!is_object($this)) {
775
                return false;
776
            } else {
777
                $id = $this->get_id();
778
                if (empty($id)) {
779
                    return false;
780
                } else {
781
                    $lp_id = $this->get_id();
782
                }
783
            }
784
        }
785
        //zip everything that is in the corresponding scorm dir
786
        //write the zip file somewhere (might be too big to return)
787
788
        $_course = api_get_course_info();
789
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
790
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id";
791
        $result = Database::query($sql);
792
        $row = Database::fetch_array($result);
793
        $LPname = $row['path'];
794
        $list = explode('/', $LPname);
795
        $LPnamesafe = $list[0];
796
        $zipfoldername = api_get_path(SYS_COURSE_PATH).$_course['directory'].'/temp/'.$LPnamesafe;
0 ignored issues
show
Bug introduced by
The constant SYS_COURSE_PATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
797
        $scormfoldername = api_get_path(SYS_COURSE_PATH).$_course['directory'].'/scorm/'.$LPnamesafe;
798
        $zipfilename = $zipfoldername.'/'.$LPnamesafe.'.zip';
799
800
        // Get a temporary dir for creating the zip file.
801
        //error_log('New LP - cleaning dir '.$zipfoldername, 0);
802
        my_delete($zipfoldername); // Make sure the temp dir is cleared.
803
        mkdir($zipfoldername, api_get_permissions_for_new_directories());
804
805
        // Create zipfile of given directory.
806
        $zip_folder = new PclZip($zipfilename);
807
        $zip_folder->create($scormfoldername.'/', PCLZIP_OPT_REMOVE_PATH, $scormfoldername.'/');
0 ignored issues
show
Bug introduced by
The constant PCLZIP_OPT_REMOVE_PATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
808
809
        //This file sending implies removing the default mime-type from php.ini
810
        //DocumentManager::file_send_for_download($zipfilename, true, $LPnamesafe.'.zip');
811
        DocumentManager::file_send_for_download($zipfilename, true);
812
813
        // Delete the temporary zip file and directory in fileManage.lib.php
814
        my_delete($zipfilename);
815
        my_delete($zipfoldername);
816
817
        return true;
818
    }
819
820
    /**
821
     * Gets a resource's path if available, otherwise return empty string.
822
     *
823
     * @param	string	Resource ID as used in resource array
824
     *
825
     * @return string The resource's path as declared in imsmanifest.xml
826
     */
827
    public function get_res_path($id)
828
    {
829
        if ($this->debug > 0) {
830
            error_log('In scorm::get_res_path('.$id.') method');
831
        }
832
        $path = '';
833
        if (isset($this->resources[$id])) {
834
            $oRes = &$this->resources[$id];
835
            $path = @$oRes->get_path();
836
        }
837
838
        return $path;
839
    }
840
841
    /**
842
     * Gets a resource's type if available, otherwise return empty string.
843
     *
844
     * @param  string    Resource ID as used in resource array
845
     *
846
     * @return string The resource's type as declared in imsmanifest.xml
847
     */
848
    public function get_res_type($id)
849
    {
850
        if ($this->debug > 0) {
851
            error_log('In scorm::get_res_type('.$id.') method');
852
        }
853
        $type = '';
854
        if (isset($this->resources[$id])) {
855
            $oRes = &$this->resources[$id];
856
            $temptype = $oRes->get_scorm_type();
857
            if (!empty($temptype)) {
858
                $type = $temptype;
859
            }
860
        }
861
862
        return $type;
863
    }
864
865
    /**
866
     * Gets the default organisation's title.
867
     *
868
     * @return string The organization's title
869
     */
870
    public function get_title()
871
    {
872
        if ($this->debug > 0) {
873
            error_log('In scorm::get_title() method');
874
        }
875
        $title = '';
876
        if (isset($this->manifest['organizations']['default'])) {
877
            $title = $this->organizations[$this->manifest['organizations']['default']]->get_name();
878
        } elseif (1 == count($this->organizations)) {
879
            // This will only get one title but so we don't need to know the index.
880
            foreach ($this->organizations as $id => $value) {
881
                $title = $this->organizations[$id]->get_name();
882
                break;
883
            }
884
        }
885
886
        return $title;
887
    }
888
889
    /**
890
     * // TODO @TODO Implement this function to restore items data from an imsmanifest,
891
     * updating the existing table... This will prove very useful in case initial data
892
     * from imsmanifest were not imported well enough.
893
     *
894
     * @param string $courseCode
895
     * @param int	LP ID (in database)
896
     * @param string	Manifest file path (optional if lp_id defined)
897
     *
898
     * @return int New LP ID or false on failure
899
     *             TODO @TODO Implement imsmanifest_path parameter
900
     */
901
    public function reimport_manifest($courseCode, $lp_id = null, $imsmanifest_path = '')
902
    {
903
        if ($this->debug > 0) {
904
            error_log('In scorm::reimport_manifest() method', 0);
905
        }
906
907
        $courseInfo = api_get_course_info($courseCode);
908
        if (empty($courseInfo)) {
909
            $this->error = 'Course code does not exist in database';
910
911
            return false;
912
        }
913
914
        $this->cc = $courseInfo['code'];
915
916
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
917
        $lp_id = intval($lp_id);
918
        $sql = "SELECT * FROM $lp_table WHERE iid = $lp_id";
919
        if ($this->debug > 2) {
920
            error_log('New LP - scorm::reimport_manifest() '.__LINE__.' - Querying lp: '.$sql);
921
        }
922
        $res = Database::query($sql);
923
        if (Database::num_rows($res) > 0) {
924
            $this->lp_id = $lp_id;
925
            $row = Database::fetch_array($res);
926
            $this->type = $row['lp_type'];
927
            $this->name = stripslashes($row['name']);
928
            $this->encoding = $row['default_encoding'];
929
            $this->proximity = $row['content_local'];
930
            $this->maker = $row['content_maker'];
931
            $this->prevent_reinit = $row['prevent_reinit'];
932
            $this->license = $row['content_license'];
933
            $this->scorm_debug = $row['debug'];
934
            $this->js_lib = $row['js_lib'];
935
            $this->path = $row['path'];
936
            if (2 == $this->type) {
937
                if (1 == $row['force_commit']) {
938
                    $this->force_commit = true;
939
                }
940
            }
941
            $this->mode = $row['default_view_mod'];
942
            $this->subdir = $row['path'];
943
        }
944
        // Parse the manifest (it is already in this lp's details).
945
        $manifest_file = api_get_path(SYS_COURSE_PATH).$courseInfo['directory'].'/scorm/'.$this->subdir.'/imsmanifest.xml';
0 ignored issues
show
Bug introduced by
The constant SYS_COURSE_PATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
946
        if ('' == $this->subdir) {
947
            $manifest_file = api_get_path(SYS_COURSE_PATH).$courseInfo['directory'].'/scorm/imsmanifest.xml';
948
        }
949
        echo $manifest_file;
950
        if (is_file($manifest_file) && is_readable($manifest_file)) {
951
            // Re-parse the manifest file.
952
            if ($this->debug > 1) {
953
                error_log('New LP - In scorm::reimport_manifest() - Parsing manifest '.$manifest_file);
954
            }
955
            $manifest = $this->parse_manifest($manifest_file);
956
            // Import new LP in DB (ignore the current one).
957
            if ($this->debug > 1) {
958
                error_log('New LP - In scorm::reimport_manifest() - Importing manifest '.$manifest_file);
959
            }
960
            $this->import_manifest($this->cc);
961
        } else {
962
            if ($this->debug > 0) {
963
                error_log('New LP - In scorm::reimport_manifest() - Could not find manifest file at '.$manifest_file);
964
            }
965
        }
966
967
        return false;
968
    }
969
970
    /**
971
     * Detects the encoding of a given manifest (a xml-text).
972
     * It is possible the encoding of the manifest to be wrongly declared or
973
     * not to be declared at all. The proposed method tries to resolve these problems.
974
     *
975
     * @param string $xml the input xml-text
976
     *
977
     * @return string the detected value of the input xml
978
     */
979
    private function detect_manifest_encoding(&$xml)
980
    {
981
        if (api_is_valid_utf8($xml)) {
982
            return 'UTF-8';
983
        }
984
985
        if (preg_match(_PCRE_XML_ENCODING, $xml, $matches)) {
986
            $declared_encoding = api_refine_encoding_id($matches[1]);
987
        } else {
988
            $declared_encoding = '';
989
        }
990
991
        if (!empty($declared_encoding) && !api_is_utf8($declared_encoding)) {
992
            return $declared_encoding;
993
        }
994
995
        $test_string = '';
996
        if (preg_match_all('/<langstring[^>]*>(.*)<\/langstring>/m', $xml, $matches)) {
997
            $test_string = implode("\n", $matches[1]);
998
            unset($matches);
999
        }
1000
        if (preg_match_all('/<title[^>]*>(.*)<\/title>/m', $xml, $matches)) {
1001
            $test_string .= "\n".implode("\n", $matches[1]);
1002
            unset($matches);
1003
        }
1004
        if (empty($test_string)) {
1005
            $test_string = $xml;
1006
        }
1007
1008
        return api_detect_encoding($test_string);
1009
    }
1010
}
1011