diff --git a/lang/en_utf8/imscp.php b/lang/en_utf8/imscp.php
new file mode 100644
index 00000000000..5d71ccbdd44
--- /dev/null
+++ b/lang/en_utf8/imscp.php
@@ -0,0 +1,13 @@
+.
+
+/**
+ * Capability definitions for the imscp module.
+ *
+ * @package mod-imscp
+ * @copyright 2009 Petr Skoda (http://skodak.org)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$mod_imscp_capabilities = array(
+
+/* TODO: review public portfolio API first!
+ 'mod/imscp:portfolioexport' => array(
+
+ 'captype' => 'read',
+ 'contextlevel' => CONTEXT_MODULE,
+ 'legacy' => array(
+ 'teacher' => CAP_ALLOW,
+ 'editingteacher' => CAP_ALLOW,
+ )
+ ),*/
+
+);
+
diff --git a/mod/imscp/db/install.php b/mod/imscp/db/install.php
new file mode 100644
index 00000000000..90f1e81a2b6
--- /dev/null
+++ b/mod/imscp/db/install.php
@@ -0,0 +1,51 @@
+.
+
+/**
+ * Post installation and migration code.
+ *
+ * This file replaces:
+ * - STATEMENTS section in db/install.xml
+ * - lib.php/modulename_install() post installation hook
+ * - partially defaults.php
+ *
+ * @package mod-imscp
+ * @copyright 2009 Petr Skoda (http://skodak.org)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+function xmldb_imscp_install() {
+ global $CFG;
+
+ // Install logging support
+ update_log_display_entry('imscp', 'view', 'imscp', 'name');
+ update_log_display_entry('imscp', 'view all', 'imscp', 'name');
+ update_log_display_entry('imscp', 'update', 'imscp', 'name');
+ update_log_display_entry('imscp', 'add', 'imscp', 'name');
+
+ // Upgrade from old resource module type if needed
+ require_once("$CFG->dirroot/mod/imscp/db/upgradelib.php");
+ imscp_20_migrate();
+}
+
+function xmldb_imscp_install_recovery() {
+ global $CFG;
+
+ // Upgrade from old resource module type if needed
+ require_once("$CFG->dirroot/mod/imscp/db/upgradelib.php");
+ imscp_20_migrate();
+}
diff --git a/mod/imscp/db/install.xml b/mod/imscp/db/install.xml
new file mode 100644
index 00000000000..0ca98320a87
--- /dev/null
+++ b/mod/imscp/db/install.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mod/imscp/db/upgrade.php b/mod/imscp/db/upgrade.php
new file mode 100644
index 00000000000..e542ca46075
--- /dev/null
+++ b/mod/imscp/db/upgrade.php
@@ -0,0 +1,34 @@
+.
+
+/**
+ * IMS CP module upgrade code
+ *
+ * @package mod-imscp
+ * @copyright 2009 Petr Skoda (http://skodak.org)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+function xmldb_imscp_upgrade($oldversion) {
+ global $CFG, $DB;
+
+ $dbman = $DB->get_manager();
+ $result = true;
+
+
+ return $result;
+}
diff --git a/mod/imscp/db/upgradelib.php b/mod/imscp/db/upgradelib.php
new file mode 100644
index 00000000000..1319c41d36f
--- /dev/null
+++ b/mod/imscp/db/upgradelib.php
@@ -0,0 +1,147 @@
+.
+
+/**
+ * IMS CP module upgrade related helper functions
+ *
+ * @package mod-imscp
+ * @copyright 2009 Petr Skoda (http://skodak.org)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Migrate imscp module data from 1.9 resource_old table to new imscp table
+ * @return void
+ */
+function imscp_20_migrate() {
+ global $CFG, $DB;
+ require_once("$CFG->libdir/filelib.php");
+ require_once("$CFG->dirroot/course/lib.php");
+ require_once("$CFG->dirroot/mod/imscp/locallib.php");
+
+ if (!file_exists("$CFG->dirroot/mod/resource/db/upgradelib.php")) {
+ // bad luck, somebody deleted resource module
+ return;
+ }
+
+ require_once("$CFG->dirroot/mod/resource/db/upgradelib.php");
+
+ // create resource_old table and copy resource table there if needed
+ if (!resource_20_prepare_migration()) {
+ // no modules or fresh install
+ return;
+ }
+
+ if (!$candidates = $DB->get_recordset('resource_old', array('type'=>'ims', 'migrated'=>0))) {
+ return;
+ }
+
+ $fs = get_file_storage();
+
+ foreach ($candidates as $candidate) {
+ upgrade_set_timeout(60);
+
+ $imscp = new object();
+ $imscp->course = $candidate->course;
+ $imscp->name = $candidate->name;
+ $imscp->intro = $candidate->intro;
+ $imscp->introformat = $candidate->introformat;
+ $imscp->revision = 1;
+ $imscp->keepold = 1;
+ $imscp->timemodified = time();
+
+ if (!$imscp = resource_migrate_to_module('imscp', $candidate, $imscp)) {
+ continue;
+ }
+
+ $context = get_context_instance(CONTEXT_MODULE, $candidate->cmid);
+ $root = "$CFG->dataroot/$candidate->course/$CFG->moddata/resource/$candidate->oldid";
+
+ // migrate package backup file
+ if ($candidate->reference) {
+ $package = basename($candidate->reference);
+ $fullpath = $root.'/'.$package;
+ if (file_exists($fullpath)) {
+ $file_record = array('contextid' => $context->id,
+ 'filearea' => 'imscp_backup',
+ 'itemid' => 1,
+ 'filepath' => '/',
+ 'filename' => $package);
+ $fs->create_file_from_pathname($file_record, $fullpath);
+ }
+ }
+
+ // migrate extracted package data
+ $files = imsc_migrate_get_old_files($root, '');
+ $file_record = array('contextid'=>$context->id, 'filearea'=>'imscp_content', 'itemid'=>1);
+ $error = false;
+ foreach ($files as $relname=>$fullpath) {
+ $parts = explode('/', $relname);
+ $file_record['filename'] = array_pop($parts);
+ $parts[] = ''; // keep trailing slash
+ $file_record['filepath'] = implode('/', $parts);
+
+ try {
+ $fs->create_file_from_pathname($file_record, $fullpath);
+ } catch (Exception $e) {
+ //continue on error, we can not recover anyway
+ $error = true;
+ notify("IMSCP: failed migrating file: $relname");
+ }
+ }
+ unset($files);
+
+ // parse manifest
+ $structure = imscp_parse_structure($imscp, $context);
+ $imscp->structure = is_array($structure) ? serialize($structure) : null;
+ $DB->update_record('imscp', $imscp);
+
+ // remove old moddata dir only if no error and manifest ok
+ if (!$error and is_array($structure)) {
+ fulldelete($root);
+ }
+ }
+
+ $candidates->close();
+
+ // clear all course modinfo caches
+ rebuild_course_cache(0, true);
+}
+
+/**
+ * Private function returning all extracted IMS content package file
+ */
+function imsc_migrate_get_old_files($path, $relative) {
+ $result = array();
+ $items = new DirectoryIterator($path);
+ foreach ($items as $item) {
+ if ($item->isDot() or $item->isLink()) {
+ // symbolik links could create infinite loops or cause unintended file migration, sorry
+ continue;
+ }
+ $pathname = $item->getPathname();
+ $relname = $relative.'/'.$item->getFilename();
+ if ($item->isFile()) {
+ $result[$relname] = $pathname;
+ } else if ($item->isDir()) {
+ $result = array_merge($result, imsc_migrate_get_old_files($pathname, $relname));
+ }
+ unset($item);
+ }
+ unset($items);
+ return $result;
+}
\ No newline at end of file
diff --git a/mod/imscp/functions.js b/mod/imscp/functions.js
new file mode 100644
index 00000000000..2439a5ec67a
--- /dev/null
+++ b/mod/imscp/functions.js
@@ -0,0 +1,279 @@
+// 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 .
+
+/**
+ * Javascript helper function for IMS Content Package module including
+ * dummy SCORM API.
+ *
+ * @package mod-imscp
+ * @copyright 2009 Petr Skoda (http://skodak.org)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/** Dummy SCORM API adapter */
+var API = new function () {
+ this.LMSCommit = function (parameter) {return "true";};
+ this.LMSFinish = function (parameter) {return "true";};
+ this.LMSGetDiagnostic = function (errorCode) {return "n/a";};
+ this.LMSGetErrorString = function (errorCode) {return "n/a";};
+ this.LMSGetLastError = function () {return "0";};
+ this.LMSGetValue = function (element) {return "";};
+ this.LMSInitialize = function (parameter) {return "true";};
+ this.LMSSetValue = function (element, value) {return "true";};
+};
+
+var imscp_layout_widget;
+var imscp_current_node;
+var imscp_buttons = [];
+var imscp_bloody_labelclick = false;
+var imscp_panel;
+
+function imscp_init() {
+ YAHOO.util.Event.onDOMReady(function () {
+ imscp_setup();
+ });
+}
+
+function imscp_setup() {
+ // layout
+ YAHOO.widget.LayoutUnit.prototype.STR_COLLAPSE = mstr.moodle.hide;
+ YAHOO.widget.LayoutUnit.prototype.STR_EXPAND = mstr.moodle.show;
+
+ imscp_layout_widget = new YAHOO.widget.Layout('imscp_layout', {
+ minWidth: 600,
+ minHeight: 400,
+ units: [
+ { position: 'left', body: 'imscp_toc', header: mstr.imscp.toc, width: 250, resize: true, gutter: '2px 5px 5px 2px', collapse: true, minWidth:150},
+ { position: 'center', body: '
', gutter: '2px 5px 5px 2px', scroll: true}
+ ]
+ });
+ imscp_layout_widget.render();
+ var left = imscp_layout_widget.getUnitByPosition('left');
+ left.on('collapse', function() {
+ imscp_resize_frame();
+ });
+ left.on('expand', function() {
+ imscp_resize_frame();
+ });
+
+ // ugly resizing hack that works around problems with resizing of iframes and objects
+ left._resize.on('startResize', function() {
+ var obj = YAHOO.util.Dom.get('imscp_object');
+ obj.style.display = 'none';
+ });
+ left._resize.on('endResize', function() {
+ var obj = YAHOO.util.Dom.get('imscp_object');
+ obj.style.display = 'block';
+ imscp_resize_frame();
+ });
+
+ // TOC tree
+ var tree = new YAHOO.widget.TreeView('imscp_tree');
+ tree.singleNodeHighlight = true;
+ tree.subscribe('labelClick', function(node) {
+ imscp_activate_item(node);
+ if (node.children.length) {
+ imscp_bloody_labelclick = true;
+ }
+ });
+ tree.subscribe('collapse', function(node) {
+ if (imscp_bloody_labelclick) {
+ imscp_bloody_labelclick = false;
+ return false;
+ }
+ });
+ tree.subscribe('expand', function(node) {
+ if (imscp_bloody_labelclick) {
+ imscp_bloody_labelclick = false;
+ return false;
+ }
+ });
+ tree.expandAll();
+ tree.render();
+
+ // navigation
+ imscp_panel = new YAHOO.widget.Panel('imscp_navpanel', { visible:true, draggable:true, close:false,
+ context: ['page', 'bl', 'bl', ["windowScroll", "textResize", "windowResize"]], constraintoviewport:true} );
+ imscp_panel.setHeader(mstr.imscp.navigation);
+ //TODO: make some better&accessible buttons
+ imscp_panel.setBody('');
+ imscp_panel.render();
+ imscp_buttons[0] = new YAHOO.widget.Button('nav_skipprev');
+ imscp_buttons[1] = new YAHOO.widget.Button('nav_prev');
+ imscp_buttons[2] = new YAHOO.widget.Button('nav_up');
+ imscp_buttons[3] = new YAHOO.widget.Button('nav_next');
+ imscp_buttons[4] = new YAHOO.widget.Button('nav_skipnext');
+ imscp_buttons[0].on('click', function(ev) {
+ imscp_activate_item(imscp_skipprev(imscp_current_node));
+ });
+ imscp_buttons[1].on('click', function(ev) {
+ imscp_activate_item(imscp_prev(imscp_current_node));
+ });
+ imscp_buttons[2].on('click', function(ev) {
+ imscp_activate_item(imscp_up(imscp_current_node));
+ });
+ imscp_buttons[3].on('click', function(ev) {
+ imscp_activate_item(imscp_next(imscp_current_node));
+ });
+ imscp_buttons[4].on('click', function(ev) {
+ imscp_activate_item(imscp_skipnext(imscp_current_node));
+ });
+ imscp_panel.render();
+
+ // finally activate the first item
+ imscp_activate_item(tree.getRoot().children[0]);
+
+ // resizing
+ imscp_resize_layout(false);
+
+ // fix layout if window resized
+ window.onresize = function() {
+ imscp_resize_layout(true);
+ };
+}
+
+
+function imscp_activate_item(node) {
+ if (!node) {
+ return;
+ }
+ imscp_current_node = node;
+ imscp_current_node.highlight();
+
+ var content = new YAHOO.util.Element('imscp_content');
+ try {
+ // first try IE way - it can not set name attribute later
+ // and also it has some restrictions on DOM access from object tag
+ var obj = document.createElement('