MDL-15362 - mahara portfolio plugin.
authormjollnir_ <mjollnir_>
Sat, 30 Aug 2008 11:39:43 +0000 (11:39 +0000)
committermjollnir_ <mjollnir_>
Sat, 30 Aug 2008 11:39:43 +0000 (11:39 +0000)
currently the code on the mahara side is in the mahoodle-phase2 branch. it'll be merged into head shortly and should be in 1.1 which is going to alpha next week

lang/en_utf8/portfolio_mahara.php [new file with mode: 0644]
portfolio/type/mahara/db/install.xml [new file with mode: 0644]
portfolio/type/mahara/db/upgrade.php [new file with mode: 0644]
portfolio/type/mahara/lib.php [new file with mode: 0644]
portfolio/type/mahara/preconfig.php [new file with mode: 0644]
portfolio/type/mahara/version.php [new file with mode: 0644]

diff --git a/lang/en_utf8/portfolio_mahara.php b/lang/en_utf8/portfolio_mahara.php
new file mode 100644 (file)
index 0000000..68650c8
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+
+$string['err_nomnethosts'] = 'This plugin relies on Moodle Networking peers with SSO IDP published, and portfolio and SSO SP subscribed.  Any instances of this plugin has been set to not visible until this is fixed - you will need to set them to visible again manually. They cannot be used before this happens.';
+$string['err_networkingoff'] = 'Moodle Networking is off entirely. Please enable it before trying to configure this pugin.  Any instances of this plugin have been set to not visible until this is fixed - you will need to set them to visible again manully.  They cannot be used until this happens';
+$string['failedtojump'] = 'Failed to start communication with remote server';
+$string['failedtoping'] = 'Failed to start communication with remote server: $a';
+$string['mnethost'] = 'Moodle Networking Host';
+$string['senddisallowed'] = 'You cannot transfer files to Mahara at this time';
+$string['url'] = 'URL';
+$string['pf_name'] = 'Portfolio services';
+$string['pf_description'] = 'Allow users to push Moodle content to this host<br />'.
+                                 'Subscribe to this service to allow authenticated users in your site to push content to $a<br />' .
+                                 '<ul><li><em>Dependency</em>: You must also <strong>publish</strong> the SSO (Identify Provider) service to $a.</li>' .
+                                 '<li><em>Dependency</em>: You must also <strong>subscribe</strong> to the SSO (Service Provider) service on $a</li></ul><br />';
+?>
diff --git a/portfolio/type/mahara/db/install.xml b/portfolio/type/mahara/db/install.xml
new file mode 100644 (file)
index 0000000..7d2c840
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<XMLDB PATH="portfolio/type/mahara/db" VERSION="20080827" COMMENT="XMLDB file for Moodle portfolio/type/mahara"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="../../../../lib/xmldb/xmldb.xsd"
+>
+  <TABLES>
+    <TABLE NAME="portfolio_mahara_queue" COMMENT="maps mahara tokens to transfer ids">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="transferid"/>
+        <FIELD NAME="transferid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="fk to portfolio_tempdata.id" PREVIOUS="id" NEXT="token"/>
+        <FIELD NAME="token" TYPE="char" LENGTH="50" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="the token mahara sent us to use for this transfer." PREVIOUS="transferid"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" NEXT="transferfk"/>
+        <KEY NAME="transferfk" TYPE="foreign" FIELDS="transferid" REFTABLE="portfolio_tempdata" REFFIELDS="id" COMMENT="fk to portfolio_tempdata" PREVIOUS="primary"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="tokenidx" UNIQUE="false" FIELDS="token" COMMENT="index for token field (used for lookups)"/>
+      </INDEXES>
+    </TABLE>
+  </TABLES>
+</XMLDB>
diff --git a/portfolio/type/mahara/db/upgrade.php b/portfolio/type/mahara/db/upgrade.php
new file mode 100644 (file)
index 0000000..36cfb6c
--- /dev/null
@@ -0,0 +1,32 @@
+<?php  //$Id$
+
+// This file keeps track of upgrades to
+// the portfolio/mahara plugin
+//
+// Sometimes, changes between versions involve
+// alterations to database structures and other
+// major things that may break installations.
+//
+// The upgrade function in this file will attempt
+// to perform all the necessary actions to upgrade
+// your older installtion to the current version.
+//
+// If there's something it cannot do itself, it
+// will tell you what you need to do.
+//
+// The commands in here will all be database-neutral,
+// using the methods of database_manager class
+//
+// Please do not forget to use upgrade_set_timeout()
+// before any action that may take longer time to finish.
+
+function xmldb_portfolio_mahara_upgrade($oldversion) {
+
+    global $CFG, $DB;
+
+    $dbman = $DB->get_manager(); // loads ddl manager and xmldb classes
+    $result = true;
+
+    return $result;
+
+}
diff --git a/portfolio/type/mahara/lib.php b/portfolio/type/mahara/lib.php
new file mode 100644 (file)
index 0000000..61e5b56
--- /dev/null
@@ -0,0 +1,306 @@
+<?php
+
+define('PORTFOLIO_MAHARA_ERR_NETWORKING_OFF', 'err_networkingoff');
+define('PORTFOLIO_MAHARA_ERR_NOHOSTS', 'err_nomnethosts');
+
+require_once($CFG->dirroot . '/lib/portfoliolib.php');
+require_once($CFG->dirroot . '/mnet/lib.php');
+
+define('PORTFOLIO_MAHARA_QUEUE', PORTFOLIO_TIME_HIGH);
+define('PORTFOLIO_MAHARA_IMMEDIATE', PORTFOLIO_TIME_MODERATE);
+
+class portfolio_plugin_mahara extends portfolio_plugin_pull_base {
+
+    private $hosts; // used in the admin config form
+    private $mnethost; // privately set during export from the admin config value (mnethostid)
+    private $hostrecord; // the host record that corresponds to the peer
+    private $token; // during-transfer token
+    private $sendtype; // whatever mahara has said it can handle (immediate or queued)
+    private $filesmanifest; // manifest of files to send to mahara (set during prepare_package and sent later)
+
+    public static function get_allowed_config() {
+        return array('mnethostid');
+    }
+
+    public static function supported_formats() {
+        return array(PORTFOLIO_FORMAT_FILE);
+    }
+
+    public function expected_time($callertime) {
+        if ($this->sendtype == PORTFOLIO_MAHARA_QUEUE) {
+            return PORTFOLIO_TIME_FORCEQUEUE;
+        }
+        return $callertime;
+    }
+
+    public static function has_admin_config() {
+        return true;
+    }
+
+    public function admin_config_form(&$mform) {
+        if ($errorcode = self::plugin_sanity_check()) {
+            return $errorcode; // processing stops when we return a string.
+        }
+        if (!empty($this) && $errorcode = $this->instance_sanity_check()) {
+            return $errorcode;
+        }
+        $strrequired = get_string('required');
+        $hosts = self::get_mnet_hosts(); // this is called by sanity check but it's ok because it's cached
+        foreach ($hosts as $host) {
+            $hosts[$host->id] = $host->name;
+        }
+        $mform->addElement('select', 'mnethostid', get_string('mnethost', 'portfolio_mahara'), $hosts);
+        $mform->addRule('mnethostid', $strrequired, 'required', null, 'client');
+    }
+
+
+    public static function plugin_sanity_check() {
+        /* @todo more here like
+            - check for services in the plugins that are configured
+        */
+        global $CFG, $DB;
+        $errorcode = 0;
+        if (!isset($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode != 'strict') {
+            $errorcode =  PORTFOLIO_MAHARA_ERR_NETWORKING_OFF;
+        }
+        if (!self::get_mnet_hosts()) {
+            $errorcode =  PORTFOLIO_MAHARA_ERR_NOHOSTS;
+        }
+        if (!empty($errorcode)) { // disable the plugins // @todo
+            $DB->set_field('portfolio_instance', 'visible', 0, array('plugin' => 'mahara'));
+        }
+        return $errorcode;
+    }
+
+    private static function get_mnet_hosts() {
+        global $DB, $CFG;
+        static $hosts;
+        if (isset($this) && is_object($this) && isset($this->hosts)) {
+            return $this->hosts;
+        } else if (!isset($this) && isset($hosts)) {
+            return $hosts;
+        }
+        $hosts = $DB->get_records_sql('  SELECT
+                                    h.id,
+                                    h.wwwroot,
+                                    h.ip_address,
+                                    h.name,
+                                    h.public_key,
+                                    h.public_key_expires,
+                                    h.transport,
+                                    h.portno,
+                                    h.last_connect_time,
+                                    h.last_log_id,
+                                    h.applicationid,
+                                    a.name as app_name,
+                                    a.display_name as app_display_name,
+                                    a.xmlrpc_server_url
+                                FROM {mnet_host} h
+                                    JOIN {mnet_application} a ON h.applicationid=a.id
+                                    JOIN {mnet_host2service} hs1 ON hs1.hostid = h.id
+                                    JOIN {mnet_service} s1 ON hs1.serviceid = s1.id
+                                    JOIN {mnet_host2service} hs2 ON hs2.hostid = h.id
+                                    JOIN {mnet_service} s2 ON hs2.serviceid = s2.id
+                                    JOIN {mnet_host2service} hs3 ON hs3.hostid = h.id
+                                    JOIN {mnet_service} s3 ON hs3.serviceid = s3.id
+                                WHERE
+                                    h.id <> ? AND
+                                    h.deleted = 0 AND
+                                    a.name = ? AND
+                                    s1.name = ? AND hs1.publish = ? AND
+                                    s2.name = ? AND hs2.subscribe = ? AND
+                                    s3.name = ? AND hs3.subscribe = ?',
+                        array($CFG->mnet_localhost_id, 'mahara', 'sso_idp', 1, 'sso_sp', 1, 'pf', 1));;
+        if (empty($hosts)) { $hosts = array(); }
+        if (isset($this) && is_object($this)) {
+            $this->hosts = $hosts;
+        }
+        return $hosts;
+    }
+
+    public function prepare_package() {
+        $files = $this->exporter->get_tempfiles();
+        foreach ($files as $f) {
+            $this->filesmanifest[$f->get_contenthash()] = array(
+                'filename' => $f->get_filename(),
+                'sha1'     => $f->get_contenthash(),
+            );
+        }
+        $zipper = new zip_packer();
+
+        $filename = 'portfolio-export.zip';
+        if ($newfile = $zipper->archive_to_storage($files, SYSCONTEXTID, 'portfolio_exporter', $this->exporter->get('id'), '/final/', $filename, $this->user->id)) {
+            $this->set('file', $newfile);
+            return true;
+        }
+        return false;
+    }
+
+    public function send_package() {
+        global $CFG;
+        global $MNET;
+        if (empty($MNET)) {
+            $MNET = new mnet_environment();
+            $MNET->init();
+        } // no idea why this happens :(
+        // send the 'content_ready' request to mahara
+        require_once($CFG->dirroot . '/mnet/xmlrpc/client.php');
+        $client = new mnet_xmlrpc_client();
+        $client->set_method('portfolio/mahara/lib.php/send_content_ready');
+        $client->add_param($this->token);
+        $client->add_param($this->get('user')->username);
+        $client->add_param($this->resolve_format());
+        $client->add_param($this->filesmanifest);
+        $client->add_param($this->get_export_config('wait'));
+        $this->ensure_mnethost();
+        if (!$client->send($this->mnethost)) {
+            foreach ($client->error as $errormessage) {
+                list($code, $message) = array_map('trim',explode(':', $errormessage, 2));
+                $message .= "ERROR $code:<br/>$errormessage<br/>";
+            }
+            throw new portfolio_export_exception($this->get('exporter'), 'failedtoping', 'portfolio_mahara', '', $message);
+        }
+        // we should get back...  an ok and a status
+        // either we've been waiting a while and mahara has fetched the file or has queued it.
+        $response = (object)$client->response;
+        if (!$response->status) {
+            throw new portfolio_export_exception($this->get('exporter'), 'failedtoping', 'portfolio_mahara');
+        }
+        return true;
+    }
+
+    public function get_continue_url() {
+        $this->ensure_mnethost();
+        return $this->hostrecord->wwwroot . '/artefact/file/'; // @todo penny this might change later when we change formats.
+    }
+
+    public function steal_control($stage) {
+        if ($stage != PORTFOLIO_STAGE_CONFIG) {
+            return false;
+        }
+        global $CFG;
+        return $CFG->wwwroot . '/portfolio/type/mahara/preconfig.php?id=' . $this->exporter->get('id');
+    }
+
+    public function verify_file_request_params($params) {
+        return false;
+        // the data comes from an xmlrpc request,
+        // not a request to file.php
+    }
+
+    /**
+    * sends the 'content_intent' ping to mahara
+    * if all goes well, this will set the 'token' and 'sendtype' member variables.
+    */
+    public function send_intent() {
+        global $CFG, $DB;
+        require_once($CFG->dirroot . '/mnet/xmlrpc/client.php');
+        $client = new mnet_xmlrpc_client();
+        $client->set_method('portfolio/mahara/lib.php/send_content_intent');
+        $client->add_param($this->get('user')->username);
+        $this->ensure_mnethost();
+        if (!$client->send($this->mnethost)) {
+            foreach ($client->error as $errormessage) {
+                list($code, $message) = array_map('trim',explode(':', $errormessage, 2));
+                $message .= "ERROR $code:<br/>$errormessage<br/>";
+            }
+            throw new portfolio_export_exception($this->get('exporter'), 'failedtoping', 'portfolio_mahara', '', $message);
+        }
+        // we should get back... the send type and a shared token
+        $response = (object)$client->response;
+        if (empty($response->sendtype) || empty($response->token)) {
+            throw new portfolio_export_exception($this->get('exporter'), 'senddisallowed', 'portfolio_mahara');
+        }
+        switch ($response->sendtype) {
+            case 'immediate':
+                $this->sendtype = PORTFOLIO_MAHARA_IMMEDIATE;
+                break;
+            case 'queue':
+                $this->sendtype = PORTFOLIO_MAHARA_QUEUE;
+                break;
+            case 'none':
+            default:
+                throw new portfolio_export_exception($this->get('exporter'), 'senddisallowed', 'portfolio_mahara');
+        }
+        $this->token = $response->token;
+        $this->get('exporter')->save();
+        // put the entry in the mahara queue table now too
+        $q = new stdClass;
+        $q->token = $this->token;
+        $q->transferid = $this->get('exporter')->get('id');
+        $DB->insert_record('portfolio_mahara_queue', $q);
+    }
+
+    private function ensure_mnethost() {
+        if (!empty($this->hostrecord) && !empty($this->mnethost)) {
+            return;
+        }
+        global $DB;
+        $this->hostrecord = $DB->get_record('mnet_host', array('id' => $this->get_config('mnethostid')));
+        $this->mnethost = new mnet_peer();
+        $this->mnethost->set_wwwroot($this->hostrecord->wwwroot);
+    }
+
+    public static function mnet_publishes() {
+        $pf= array();
+        $pf['name']        = 'pf'; // Name & Description go in lang file
+        $pf['apiversion']  = 1;
+        $pf['methods']     = array('send_content_intent', 'send_content_ready', 'fetch_file');
+
+        return array($pf);
+    }
+
+    /**
+    * xmlrpc (mnet) function to get the file.
+    * reads in the file and returns it base_64 encoded
+    * so that it can be enrypted by mnet.
+    *
+    * @param string $token the token recieved previously during send_content_intent
+    */
+    public static function fetch_file($token) {
+        global $DB, $MNET_REMOTE_CLIENT;;
+        try {
+            $transferid = $DB->get_field('portfolio_mahara_queue', 'transferid', array('token' => $token));
+            $exporter = portfolio_exporter::rewaken_object($transferid);
+        } catch (portfolio_exception $e) {
+            return false; // @todo penny figure out what mnet wants in the error case
+        }
+        if ($exporter->get('instance')->get_config('mnethostid') != $MNET_REMOTE_CLIENT->id) {
+            return false; // @todo penny complain loudly here.. some other  host is trying to talk to us
+        }
+        global $CFG;
+        $contents = base64_encode($exporter->get('instance')->get('file')->get_content());
+        $exporter->process_stage_cleanup(true);
+        return $contents;
+    }
+
+    public function cleanup() {
+        global $DB;
+        $DB->delete_records('portfolio_mahara_queue', array('transferid' => $this->get('exporter')->get('id'), 'token' => $this->token));
+    }
+
+
+    private function resolve_format() {
+        $thisformat = $this->get_export_config('format');
+        $allformats = portfolio_supported_formats();
+        $thisobj = new $allformats[$thisformat];
+        foreach ($this->supported_formats() as $f) {
+            $class = $allformats[$f];
+            if ($thisobj instanceof $class) {
+                return $f;
+            }
+        }
+    }
+
+/*
+    public function __wakeup() {
+        global $CFG;
+        if (empty($CFG)) {
+            return; // too early
+        }
+        require_once($CFG->dirroot . '/mnet/lib.php');
+    }
+*/
+}
+
+?>
diff --git a/portfolio/type/mahara/preconfig.php b/portfolio/type/mahara/preconfig.php
new file mode 100644 (file)
index 0000000..0f1d38d
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+require_once(dirname(dirname(dirname(dirname(__FILE__)))). '/config.php');
+require_once($CFG->libdir . '/portfoliolib.php');
+require_once($CFG->dirroot . '/mnet/lib.php');
+
+if (!$landed = optional_param('landed', false, PARAM_BOOL)) {
+    $id = required_param('id', PARAM_INT);
+    $exporter = portfolio_exporter::rewaken_object($id);
+    $exporter->verify_rewaken();
+
+    $mnetauth = get_auth_plugin('mnet');
+    if (!$url = $mnetauth->start_jump_session($exporter->get('instance')->get_config('mnethostid'), '/portfolio/type/mahara/preconfig.php?landed=1', true)) {
+        throw new porfolio_exception('failedtojump', 'portfolio_mahara');
+    }
+    redirect($url);
+} else {
+    // now we have the sso session set up, start sending intent stuff and then redirect back to portfolio/add.php when we're done
+    $exporter = portfolio_exporter::rewaken_object($SESSION->portfolioexport);
+    $exporter->verify_rewaken();
+
+    $exporter->get('instance')->send_intent();
+    redirect($CFG->wwwroot . '/portfolio/add.php?postcontrol=1&id=' . $exporter->get('id'));
+}
+?>
diff --git a/portfolio/type/mahara/version.php b/portfolio/type/mahara/version.php
new file mode 100644 (file)
index 0000000..1a00826
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+
+$plugin->version  = 2008072500;
+$plugin->requires = 2008072500;
+$plugin->cron     = 0;
+
+?>