on-demand release 4.5dev+
[moodle.git] / portfolio / mahara / lib.php
blob09ae1eb89f04f29ad1404b98ee3d45157e212bfb
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 /**
19 * This file contains the class definition for the mahara portfolio plugin
21 * @since Moodle 2.0
22 * @package moodlecore
23 * @subpackage portfolio
24 * @copyright 2009 Penny Leach
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29 define('PORTFOLIO_MAHARA_ERR_NETWORKING_OFF', 'err_networkingoff');
30 define('PORTFOLIO_MAHARA_ERR_NOHOSTS', 'err_nomnethosts');
31 define('PORTFOLIO_MAHARA_ERR_INVALIDHOST', 'err_invalidhost');
32 define('PORTFOLIO_MAHARA_ERR_NOMNETAUTH', 'err_nomnetauth');
34 require_once($CFG->libdir . '/portfoliolib.php');
35 require_once($CFG->libdir . '/portfolio/plugin.php');
36 require_once($CFG->libdir . '/portfolio/exporter.php');
37 require_once($CFG->dirroot . '/mnet/lib.php');
39 define('PORTFOLIO_MAHARA_QUEUE', PORTFOLIO_TIME_HIGH);
40 define('PORTFOLIO_MAHARA_IMMEDIATE', PORTFOLIO_TIME_MODERATE);
42 class portfolio_plugin_mahara extends portfolio_plugin_pull_base {
44 private $hosts; // used in the admin config form
45 private $mnethost; // privately set during export from the admin config value (mnethostid)
46 private $hostrecord; // the host record that corresponds to the peer
47 private $token; // during-transfer token
48 private $sendtype; // whatever mahara has said it can handle (immediate or queued)
49 private $filesmanifest; // manifest of files to send to mahara (set during prepare_package and sent later)
50 private $totalsize; // total size of all included files added together
51 private $continueurl; // if we've been sent back a specific url to continue to (eg folder id)
53 /** @var mnet_environment the equivalent of old $MNET global. */
54 public $mnet;
56 protected function init() {
57 $this->mnet = get_mnet_environment();
60 public function __wakeup() {
61 $this->mnet = get_mnet_environment();
64 public static function get_name() {
65 return get_string('pluginname', 'portfolio_mahara');
68 public static function get_allowed_config() {
69 return array('mnethostid', 'enableleap2a');
72 public function supported_formats() {
73 if ($this->get_config('enableleap2a')) {
74 return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_LEAP2A);
76 return array(PORTFOLIO_FORMAT_FILE);
79 public function expected_time($callertime) {
80 if ($this->sendtype == PORTFOLIO_MAHARA_QUEUE) {
81 return PORTFOLIO_TIME_FORCEQUEUE;
83 return $callertime;
86 public static function has_admin_config() {
87 return true;
90 public static function admin_config_form(&$mform) {
91 $strrequired = get_string('required');
92 $hosts = self::get_mnet_hosts(); // this is called by sanity check but it's ok because it's cached
93 foreach ($hosts as $host) {
94 $hosts[$host->id] = $host->name;
96 $mform->addElement('select', 'mnethostid', get_string('mnethost', 'portfolio_mahara'), $hosts);
97 $mform->addRule('mnethostid', $strrequired, 'required', null, 'client');
98 $mform->setType('mnethostid', PARAM_INT);
99 $mform->addElement('selectyesno', 'enableleap2a', get_string('enableleap2a', 'portfolio_mahara'));
100 $mform->setType('enableleap2a', PARAM_BOOL);
103 public function instance_sanity_check() {
104 // make sure the host record exists since we don't have referential integrity
105 if (!is_enabled_auth('mnet')) {
106 return PORTFOLIO_MAHARA_ERR_NOMNETAUTH;
108 try {
109 $this->ensure_mnethost();
111 catch (portfolio_exception $e) {
112 return PORTFOLIO_MAHARA_ERR_INVALIDHOST;
114 // make sure we have the right services
115 $hosts = $this->get_mnet_hosts();
116 if (!array_key_exists($this->get_config('mnethostid'), $hosts)) {
117 return PORTFOLIO_MAHARA_ERR_INVALIDHOST;
119 return 0;
122 public static function plugin_sanity_check() {
123 global $CFG, $DB;
124 $errorcode = 0;
125 if (!isset($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode != 'strict') {
126 $errorcode = PORTFOLIO_MAHARA_ERR_NETWORKING_OFF;
128 if (!is_enabled_auth('mnet')) {
129 $errorcode = PORTFOLIO_MAHARA_ERR_NOMNETAUTH;
131 if (!self::get_mnet_hosts()) {
132 $errorcode = PORTFOLIO_MAHARA_ERR_NOHOSTS;
134 return $errorcode;
137 private static function get_mnet_hosts() {
138 global $DB, $CFG;
139 static $hosts;
140 if (isset($hosts)) {
141 return $hosts;
143 $hosts = $DB->get_records_sql(' SELECT
144 h.id,
145 h.wwwroot,
146 h.ip_address,
147 h.name,
148 h.public_key,
149 h.public_key_expires,
150 h.transport,
151 h.portno,
152 h.last_connect_time,
153 h.last_log_id,
154 h.applicationid,
155 a.name as app_name,
156 a.display_name as app_display_name,
157 a.xmlrpc_server_url
158 FROM {mnet_host} h
159 JOIN {mnet_application} a ON h.applicationid=a.id
160 JOIN {mnet_host2service} hs1 ON hs1.hostid = h.id
161 JOIN {mnet_service} s1 ON hs1.serviceid = s1.id
162 JOIN {mnet_host2service} hs2 ON hs2.hostid = h.id
163 JOIN {mnet_service} s2 ON hs2.serviceid = s2.id
164 JOIN {mnet_host2service} hs3 ON hs3.hostid = h.id
165 JOIN {mnet_service} s3 ON hs3.serviceid = s3.id
166 WHERE
167 h.id <> ? AND
168 h.deleted = 0 AND
169 a.name = ? AND
170 s1.name = ? AND hs1.publish = ? AND
171 s2.name = ? AND hs2.subscribe = ? AND
172 s3.name = ? AND hs3.subscribe = ? AND
173 s3.name = ? AND hs3.publish = ?',
174 array($CFG->mnet_localhost_id, 'mahara', 'sso_idp', 1, 'sso_sp', 1, 'pf', 1, 'pf', 1));
175 return $hosts;
178 public function prepare_package() {
179 $files = $this->exporter->get_tempfiles();
180 $this->totalsize = 0;
181 foreach ($files as $f) {
182 $this->filesmanifest[$f->get_contenthash()] = array(
183 'filename' => $f->get_filename(),
184 'sha1' => $f->get_contenthash(),
185 'size' => $f->get_filesize(),
187 $this->totalsize += $f->get_filesize();
190 $this->set('file', $this->exporter->zip_tempfiles()); // this will throw a file_exception which the exporter catches separately.
193 public function send_package() {
194 global $CFG;
195 // send the 'content_ready' request to mahara
196 require_once($CFG->dirroot . '/mnet/xmlrpc/client.php');
197 $client = new mnet_xmlrpc_client();
198 $client->set_method('portfolio/mahara/lib.php/send_content_ready');
199 $client->add_param($this->token);
200 $client->add_param($this->get('user')->username);
201 $client->add_param($this->resolve_format());
202 $client->add_param(array(
203 'filesmanifest' => $this->filesmanifest,
204 'zipfilesha1' => $this->get('file')->get_contenthash(),
205 'zipfilesize' => $this->get('file')->get_filesize(),
206 'totalsize' => $this->totalsize,
208 $client->add_param($this->get_export_config('wait'));
209 $this->ensure_mnethost();
210 if (!$client->send($this->mnethost)) {
211 foreach ($client->error as $errormessage) {
212 list($code, $message) = array_map('trim',explode(':', $errormessage, 2));
213 $message .= "ERROR $code:<br/>$errormessage<br/>";
215 throw new portfolio_export_exception($this->get('exporter'), 'failedtoping', 'portfolio_mahara', '', $message);
217 // we should get back... an ok and a status
218 // either we've been waiting a while and mahara has fetched the file or has queued it.
219 $response = (object)$client->response;
220 if (!$response->status) {
221 throw new portfolio_export_exception($this->get('exporter'), 'failedtoping', 'portfolio_mahara');
223 if ($response->type =='queued') {
224 $this->exporter->set_forcequeue();
226 if (isset($response->querystring)) {
227 $this->continueurl = $response->querystring;
229 // if we're not queuing the logging might have already happened
230 $this->exporter->update_log_url($this->get_static_continue_url());
233 public function get_static_continue_url() {
234 $remoteurl = '';
235 if ($this->resolve_format() == 'file') {
236 $remoteurl = '/artefact/file/'; // we hopefully get the files that were imported highlighted
238 if (isset($this->continueurl)) {
239 $remoteurl .= $this->continueurl;
241 return $remoteurl;
244 public function resolve_static_continue_url($remoteurl) {
245 global $CFG;
246 $this->ensure_mnethost();
247 $u = new moodle_url('/auth/mnet/jump.php', array('hostid' => $this->get_config('mnethostid'), 'wantsurl' => $remoteurl));
248 return $u->out();
251 public function get_interactive_continue_url() {
252 return $this->resolve_static_continue_url($this->get_static_continue_url());
255 public function steal_control($stage) {
256 if ($stage != PORTFOLIO_STAGE_CONFIG) {
257 return false;
259 global $CFG;
260 return $CFG->wwwroot . '/portfolio/mahara/preconfig.php?id=' . $this->exporter->get('id');
263 public function verify_file_request_params($params) {
264 return false;
265 // the data comes from an xmlrpc request,
266 // not a request to file.php
270 * sends the 'content_intent' ping to mahara
271 * if all goes well, this will set the 'token' and 'sendtype' member variables.
273 public function send_intent() {
274 global $CFG, $DB;
275 require_once($CFG->dirroot . '/mnet/xmlrpc/client.php');
276 $client = new mnet_xmlrpc_client();
277 $client->set_method('portfolio/mahara/lib.php/send_content_intent');
278 $client->add_param($this->get('user')->username);
279 $this->ensure_mnethost();
280 if (!$client->send($this->mnethost)) {
281 foreach ($client->error as $errormessage) {
282 list($code, $message) = array_map('trim',explode(':', $errormessage, 2));
283 $message .= "ERROR $code:<br/>$errormessage<br/>";
285 throw new portfolio_export_exception($this->get('exporter'), 'failedtoping', 'portfolio_mahara', '', $message);
287 // we should get back... the send type and a shared token
288 $response = (object)$client->response;
289 if (empty($response->sendtype) || empty($response->token)) {
290 throw new portfolio_export_exception($this->get('exporter'), 'senddisallowed', 'portfolio_mahara');
292 switch ($response->sendtype) {
293 case 'immediate':
294 $this->sendtype = PORTFOLIO_MAHARA_IMMEDIATE;
295 break;
296 case 'queue':
297 $this->sendtype = PORTFOLIO_MAHARA_QUEUE;
298 break;
299 case 'none':
300 default:
301 throw new portfolio_export_exception($this->get('exporter'), 'senddisallowed', 'portfolio_mahara');
303 $this->token = $response->token;
304 $this->get('exporter')->save();
305 // put the entry in the mahara queue table now too
306 $q = new stdClass;
307 $q->token = $this->token;
308 $q->transferid = $this->get('exporter')->get('id');
309 $DB->insert_record('portfolio_mahara_queue', $q);
312 private function ensure_mnethost() {
313 if (!empty($this->hostrecord) && !empty($this->mnethost)) {
314 return;
316 global $DB;
317 if (!$this->hostrecord = $DB->get_record('mnet_host', array('id' => $this->get_config('mnethostid')))) {
318 throw new portfolio_plugin_exception(PORTFOLIO_MAHARA_ERR_INVALIDHOST, 'portfolio_mahara');
320 $this->mnethost = new mnet_peer();
321 $this->mnethost->set_wwwroot($this->hostrecord->wwwroot);
325 * xmlrpc (mnet) function to get the file.
326 * reads in the file and returns it base_64 encoded
327 * so that it can be enrypted by mnet.
329 * @param string $token the token recieved previously during send_content_intent
331 public static function fetch_file($token) {
332 global $DB;
333 $remoteclient = get_mnet_remote_client();
334 try {
335 if (!$transferid = $DB->get_field('portfolio_mahara_queue', 'transferid', array('token' => $token))) {
336 throw new mnet_server_exception(8009, 'mnet_notoken', 'portfolio_mahara');
338 $exporter = portfolio_exporter::rewaken_object($transferid);
339 } catch (portfolio_exception $e) {
340 throw new mnet_server_exception(8010, 'mnet_noid', 'portfolio_mahara');
342 if ($exporter->get('instance')->get_config('mnethostid') != $remoteclient->id) {
343 throw new mnet_server_exception(8011, 'mnet_wronghost', 'portfolio_mahara');
345 global $CFG;
346 try {
347 $i = $exporter->get('instance');
348 $f = $i->get('file');
349 if (empty($f) || !($f instanceof stored_file)) {
350 throw new mnet_server_exception(8012, 'mnet_nofile', 'portfolio_mahara');
352 try {
353 $c = $f->get_content();
354 } catch (file_exception $e) {
355 throw new mnet_server_exception(8013, 'mnet_nofilecontents', 'portfolio_mahara', $e->getMessage());
357 $contents = base64_encode($c);
358 } catch (Exception $e) {
359 throw new mnet_server_exception(8013, 'mnet_nofile', 'portfolio_mahara');
361 $exporter->log_transfer();
362 $exporter->process_stage_cleanup(true);
363 return $contents;
366 public function cleanup() {
367 global $DB;
368 $DB->delete_records('portfolio_mahara_queue', array('transferid' => $this->get('exporter')->get('id'), 'token' => $this->token));
373 * internal helper function, that converts between the format constant,
374 * which might be too specific (eg 'image') and the class in our *supported* list
375 * which might be higher up the format hierarchy tree (eg 'file')
377 private function resolve_format() {
378 global $CFG;
379 $thisformat = $this->get_export_config('format');
380 $allformats = portfolio_supported_formats();
381 require_once($CFG->libdir . '/portfolio/formats.php');
382 $thisobj = new $allformats[$thisformat];
383 foreach ($this->supported_formats() as $f) {
384 $class = $allformats[$f];
385 if ($thisobj instanceof $class) {
386 return $f;