From c7e6fb6c8d5fd55a32e47b9c9ef7892ad3486acb Mon Sep 17 00:00:00 2001
From: Dan Marsden <dan@danmarsden.com>
Date: Fri, 5 Jul 2013 16:38:10 +1200
Subject: [PATCH] MDL-35380 SCORM: improve check for imsmanifest file and
 consolidate into a single function.

---
 mod/scorm/lang/en/scorm.php              |   5 +-
 mod/scorm/lib.php                        |  64 ++++++++++++-------
 mod/scorm/mod_form.php                   |  28 +--------
 mod/scorm/tests/packages/badscorm.zip    | Bin 0 -> 279 bytes
 mod/scorm/tests/packages/invalid.zip     | Bin 0 -> 114 bytes
 mod/scorm/tests/packages/validaicc.zip   | Bin 0 -> 147 bytes
 mod/scorm/tests/packages/validscorm.zip  | Bin 0 -> 167 bytes
 mod/scorm/tests/validatepackage_test.php |  77 +++++++++++++++++++++++
 8 files changed, 121 insertions(+), 53 deletions(-)
 create mode 100644 mod/scorm/tests/packages/badscorm.zip
 create mode 100644 mod/scorm/tests/packages/invalid.zip
 create mode 100644 mod/scorm/tests/packages/validaicc.zip
 create mode 100644 mod/scorm/tests/packages/validscorm.zip
 create mode 100644 mod/scorm/tests/validatepackage_test.php

diff --git a/mod/scorm/lang/en/scorm.php b/mod/scorm/lang/en/scorm.php
index dafdd2649df..9d370e5161f 100644
--- a/mod/scorm/lang/en/scorm.php
+++ b/mod/scorm/lang/en/scorm.php
@@ -59,7 +59,8 @@ $string['autocontinue_help'] = 'If enabled, subsequent learning objects are laun
 $string['autocontinuedesc'] = 'If enabled, subsequent learning objects are launched automatically, otherwise the Continue button must be used.';
 $string['averageattempt'] = 'Average attempts';
 $string['badmanifest'] = 'Some manifest errors: see errors log';
-$string['badpackage'] = 'The specified package/manifest is not valid. Check it and try again.';
+$string['badimsmanifestlocation'] = 'An imsmanifest.xml file was found but it was not in the root of your zip file, please re-package your SCORM';
+$string['badarchive'] = 'You must provide a valid zip file';
 $string['browse'] = 'Preview';
 $string['browsed'] = 'Browsed';
 $string['browsemode'] = 'Preview mode';
@@ -222,7 +223,7 @@ $string['noattemptsmade'] = 'Number of attempts you have made';
 $string['no_attributes'] = 'Tag {$a->tag} must have attributes';
 $string['no_children'] = 'Tag {$a->tag} must have children';
 $string['nolimit'] = 'Unlimited attempts';
-$string['nomanifest'] = 'Manifest not found';
+$string['nomanifest'] = 'Incorrect file package - missing imsmanifest.xml or AICC structure';
 $string['noprerequisites'] = 'Sorry but you don\'t have the required prerequisites to access this activity.';
 $string['noreports'] = 'No report to display';
 $string['normal'] = 'Normal';
diff --git a/mod/scorm/lib.php b/mod/scorm/lib.php
index 94e81038bbe..02da6c9ee2b 100644
--- a/mod/scorm/lib.php
+++ b/mod/scorm/lib.php
@@ -1271,32 +1271,10 @@ function scorm_dndupload_handle($uploadinfo) {
     $file = reset($files);
 
     // Validate the file, make sure it's a valid SCORM package!
-    $packer = get_file_packer('application/zip');
-    $filelist = $file->list_files($packer);
-
-    if (!is_array($filelist)) {
+    $errors = scorm_validate_package($file);
+    if (!empty($errors)) {
         return false;
-    } else {
-        $manifestpresent = false;
-        $aiccfound = false;
-
-        foreach ($filelist as $info) {
-            if ($info->pathname == 'imsmanifest.xml') {
-                $manifestpresent = true;
-                break;
-            }
-
-            if (preg_match('/\.cst$/', $info->pathname)) {
-                $aiccfound = true;
-                break;
-            }
-        }
-
-        if (!$manifestpresent && !$aiccfound) {
-            return false;
-        }
     }
-
     // Create a default scorm object to pass to scorm_add_instance()!
     $scorm = get_config('scorm');
     $scorm->course = $uploadinfo->course->id;
@@ -1343,3 +1321,41 @@ function scorm_set_completion($scorm, $userid, $completionstate = COMPLETION_COM
         $completion->update_state($cm, $completionstate, $userid);
     }
 }
+
+/**
+ * Check that a Zip file contains a valid SCORM package
+ *
+ * @param $file stored_file a Zip file.
+ * @return array empty if no issue is found. Array of error message otherwise
+ */
+function scorm_validate_package($file) {
+    $packer = get_file_packer('application/zip');
+    $errors = array();
+    $filelist = $file->list_files($packer);
+
+    if (!is_array($filelist)) {
+        $errors['packagefile'] = get_string('badarchive', 'scorm');
+    } else {
+        $aiccfound = false;
+        $badmanifestpresent = false;
+        foreach ($filelist as $info) {
+            if ($info->pathname == 'imsmanifest.xml') {
+                return array();
+            } else if (strpos($info->pathname, 'imsmanifest.xml') !== false) {
+                // This package has an imsmanifest file inside a folder of the package.
+                $badmanifestpresent = true;
+            }
+            if (preg_match('/\.cst$/', $info->pathname)) {
+                return array();
+            }
+        }
+        if (!$aiccfound) {
+            if ($badmanifestpresent) {
+                $errors['packagefile'] = get_string('badimsmanifestlocation', 'scorm');
+            } else {
+                $errors['packagefile'] = get_string('nomanifest', 'scorm');
+            }
+        }
+    }
+    return $errors;
+}
\ No newline at end of file
diff --git a/mod/scorm/mod_form.php b/mod/scorm/mod_form.php
index 8263a6d90a4..c69ef6ffb88 100644
--- a/mod/scorm/mod_form.php
+++ b/mod/scorm/mod_form.php
@@ -342,33 +342,7 @@ class mod_scorm_mod_form extends moodleform_mod {
                     return $errors;
                 }
                 $file = reset($files);
-                $filename = $CFG->tempdir.'/scormimport/scrom_'.time();
-                make_temp_directory('scormimport');
-                $file->copy_content_to($filename);
-
-                $packer = get_file_packer('application/zip');
-
-                $filelist = $packer->list_files($filename);
-                if (!is_array($filelist)) {
-                    $errors['packagefile'] = 'Incorrect file package - not an archive'; //TODO: localise
-                } else {
-                    $manifestpresent = false;
-                    $aiccfound       = false;
-                    foreach ($filelist as $info) {
-                        if ($info->pathname == 'imsmanifest.xml') {
-                            $manifestpresent = true;
-                            break;
-                        }
-                        if (preg_match('/\.cst$/', $info->pathname)) {
-                            $aiccfound = true;
-                            break;
-                        }
-                    }
-                    if (!$manifestpresent and !$aiccfound) {
-                        $errors['packagefile'] = 'Incorrect file package - missing imsmanifest.xml or AICC structure'; //TODO: localise
-                    }
-                }
-                unlink($filename);
+                $errors = array_merge($errors, scorm_validate_package($file));
             }
 
         } else if ($type === SCORM_TYPE_EXTERNAL) {
diff --git a/mod/scorm/tests/packages/badscorm.zip b/mod/scorm/tests/packages/badscorm.zip
new file mode 100644
index 0000000000000000000000000000000000000000..b2c52b60439d1d134365017a0b8f62d01e8fa0c4
GIT binary patch
literal 279
zcmWIWW@Zs#W&nbu^^DFS8U{FljHJYr;^h3IT>Su`GA^*P5TG&_kLx$ofzlu>fvhYu
zw>UR3FEcH*xJ0iaH^;^vNGO!078Pga=h-S5>KQ00q~;~(r)1`(+bV^IxanFb+1qgi
zcr!BDGvjsz&;}q7X!z?0qLJJL(uUhrAjJ#}3JqHtLGFZWLvv$*H!B-R9TO1N0_heI
GhXDX+G&wl{

literal 0
HcmV?d00001

diff --git a/mod/scorm/tests/packages/invalid.zip b/mod/scorm/tests/packages/invalid.zip
new file mode 100644
index 0000000000000000000000000000000000000000..f7fc62c0819e73255d855615da6e0c98ede91aa2
GIT binary patch
literal 114
zcmWIWW@h1HW&i@e4UEnp8U{FkjFQyi61|L)+yHMzCVOVw>L7X)8n!foSO{ILY#=@(
M5SjsLH4ujZ01~tfcmMzZ

literal 0
HcmV?d00001

diff --git a/mod/scorm/tests/packages/validaicc.zip b/mod/scorm/tests/packages/validaicc.zip
new file mode 100644
index 0000000000000000000000000000000000000000..9e2669715da6df7da3fcd004e28f336c397d6942
GIT binary patch
literal 147
zcmWIWW@h1HW&nbx%xsoB{cf4d0@)zU1;n}e`6)T6ddbBlN=Z5S$=OOeO1Y`INvTCj
zyj)5}`S~S40Y?L+0B=SnduH6m08Ih{g@!GSAR1w4fHx}}NQ4mxEr7Huh{FH?;6oe4

literal 0
HcmV?d00001

diff --git a/mod/scorm/tests/packages/validscorm.zip b/mod/scorm/tests/packages/validscorm.zip
new file mode 100644
index 0000000000000000000000000000000000000000..5d00afb071036948acdb2d61dbcceeb37b203eb9
GIT binary patch
literal 167
zcmWIWW@h1HW&nba^^DFg9@lTE1KA+V55$?d#kq-jnQ5uTC3+RPIX3n{LZK|Rs5mn}
z&sND$&p=5bH7_|oB{MJGRw*>ZP1i!n-i|B4n~}+$8MmoGgMmPyVM`;3MzbTpo0SbD
O%m{?GKsp4(VE_OSdn9ZC

literal 0
HcmV?d00001

diff --git a/mod/scorm/tests/validatepackage_test.php b/mod/scorm/tests/validatepackage_test.php
new file mode 100644
index 00000000000..2c24981e7a2
--- /dev/null
+++ b/mod/scorm/tests/validatepackage_test.php
@@ -0,0 +1,77 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for the mod_quiz_display_options class.
+ *
+ * @package    mod_scorm
+ * @category   phpunit
+ * @copyright  2013 Dan Marsden
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/mod/scorm/locallib.php');
+
+
+/**
+ * Unit tests for {@link mod_scorm}.
+ *
+ * @copyright  2013 Dan Marsden
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_scorm_validatepackage_testcase extends basic_testcase {
+    public function test_validate_package() {
+        global $CFG;
+        $filename = "validscorm.zip";
+        $file = new zip_archive();
+        $file->open($CFG->dirroot.'/mod/scorm/tests/packages/'.$filename, file_archive::OPEN);
+        $errors = scorm_validate_package($file);
+        $this->assertEmpty($errors);
+        $file->close();
+
+        $filename = "validaicc.zip";
+        $file = new zip_archive();
+        $file->open($CFG->dirroot.'/mod/scorm/tests/packages/'.$filename, file_archive::OPEN);
+        $errors = scorm_validate_package($file);
+        $this->assertEmpty($errors);
+        $file->close();
+
+        $filename = "invalid.zip";
+        $file = new zip_archive();
+        $file->open($CFG->dirroot.'/mod/scorm/tests/packages/'.$filename, file_archive::OPEN);
+        $errors = scorm_validate_package($file);
+        $this->assertArrayHasKey('packagefile', $errors);
+        if (isset($errors['packagefile'])) {
+            $this->assertEquals(get_string('nomanifest', 'scorm'), $errors['packagefile']);
+        }
+        $file->close();
+
+        $filename = "badscorm.zip";
+        $file = new zip_archive();
+        $file->open($CFG->dirroot.'/mod/scorm/tests/packages/'.$filename, file_archive::OPEN);
+        $errors = scorm_validate_package($file);
+        $this->assertArrayHasKey('packagefile', $errors);
+        if (isset($errors['packagefile'])) {
+            $this->assertEquals(get_string('badimsmanifestlocation', 'scorm'), $errors['packagefile']);
+        }
+        $file->close();
+    }
+}
+