Merge branch 'w25_MDL-32003_m23_dbcleanup' of git://github.com/skodak/moodle
[moodle.git] / portfolio / mahara / lib.php
CommitLineData
254f2d05 1<?php
de260e0f
PL
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.
13//
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/>.
16
17
18/**
19 * This file contains the class definition for the mahara portfolio plugin
20 *
21 * @since 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
26 */
27
254f2d05 28
29define('PORTFOLIO_MAHARA_ERR_NETWORKING_OFF', 'err_networkingoff');
30define('PORTFOLIO_MAHARA_ERR_NOHOSTS', 'err_nomnethosts');
aae8cfdc 31define('PORTFOLIO_MAHARA_ERR_INVALIDHOST', 'err_invalidhost');
7254f56e 32define('PORTFOLIO_MAHARA_ERR_NOMNETAUTH', 'err_nomnetauth');
254f2d05 33
24ba58ee
PL
34require_once($CFG->libdir . '/portfoliolib.php');
35require_once($CFG->libdir . '/portfolio/plugin.php');
36require_once($CFG->libdir . '/portfolio/exporter.php');
254f2d05 37require_once($CFG->dirroot . '/mnet/lib.php');
38
39define('PORTFOLIO_MAHARA_QUEUE', PORTFOLIO_TIME_HIGH);
40define('PORTFOLIO_MAHARA_IMMEDIATE', PORTFOLIO_TIME_MODERATE);
41
42class portfolio_plugin_mahara extends portfolio_plugin_pull_base {
43
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)
2f6bd2a9 50 private $totalsize; // total size of all included files added together
14cb94eb 51 private $continueurl; // if we've been sent back a specific url to continue to (eg folder id)
254f2d05 52
287efec6
PL
53 protected function init() {
54 $this->mnet = get_mnet_environment();
55 }
56
57 public function __wakeup() {
58 $this->mnet = get_mnet_environment();
59 }
60
0f71f48b 61 public static function get_name() {
62 return get_string('pluginname', 'portfolio_mahara');
63 }
64
254f2d05 65 public static function get_allowed_config() {
c5b149f8 66 return array('mnethostid', 'enableleap2a');
254f2d05 67 }
68
38652d90 69 public function supported_formats() {
c5b149f8
PL
70 if ($this->get_config('enableleap2a')) {
71 return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_LEAP2A);
72 }
73 return array(PORTFOLIO_FORMAT_FILE);
254f2d05 74 }
75
76 public function expected_time($callertime) {
77 if ($this->sendtype == PORTFOLIO_MAHARA_QUEUE) {
78 return PORTFOLIO_TIME_FORCEQUEUE;
79 }
80 return $callertime;
81 }
82
83 public static function has_admin_config() {
84 return true;
85 }
86
c17ec774 87 public static function admin_config_form(&$mform) {
254f2d05 88 $strrequired = get_string('required');
89 $hosts = self::get_mnet_hosts(); // this is called by sanity check but it's ok because it's cached
90 foreach ($hosts as $host) {
91 $hosts[$host->id] = $host->name;
92 }
93 $mform->addElement('select', 'mnethostid', get_string('mnethost', 'portfolio_mahara'), $hosts);
94 $mform->addRule('mnethostid', $strrequired, 'required', null, 'client');
c5b149f8 95 $mform->addElement('selectyesno', 'enableleap2a', get_string('enableleap2a', 'portfolio_mahara'));
254f2d05 96 }
97
aae8cfdc 98 public function instance_sanity_check() {
99 // make sure the host record exists since we don't have referential integrity
b0482154 100 if (!is_enabled_auth('mnet')) {
101 return PORTFOLIO_MAHARA_ERR_NOMNETAUTH;
102 }
aae8cfdc 103 try {
104 $this->ensure_mnethost();
105 }
106 catch (portfolio_exception $e) {
107 return PORTFOLIO_MAHARA_ERR_INVALIDHOST;
108 }
109 // make sure we have the right services
110 $hosts = $this->get_mnet_hosts();
111 if (!array_key_exists($this->get_config('mnethostid'), $hosts)) {
112 return PORTFOLIO_MAHARA_ERR_INVALIDHOST;
113 }
114 return 0;
115 }
254f2d05 116
117 public static function plugin_sanity_check() {
254f2d05 118 global $CFG, $DB;
119 $errorcode = 0;
120 if (!isset($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode != 'strict') {
121 $errorcode = PORTFOLIO_MAHARA_ERR_NETWORKING_OFF;
122 }
7254f56e 123 if (!is_enabled_auth('mnet')) {
124 $errorcode = PORTFOLIO_MAHARA_ERR_NOMNETAUTH;
125 }
254f2d05 126 if (!self::get_mnet_hosts()) {
127 $errorcode = PORTFOLIO_MAHARA_ERR_NOHOSTS;
128 }
254f2d05 129 return $errorcode;
130 }
131
132 private static function get_mnet_hosts() {
133 global $DB, $CFG;
134 static $hosts;
ab23e6e2 135 if (isset($hosts)) {
254f2d05 136 return $hosts;
137 }
138 $hosts = $DB->get_records_sql(' SELECT
139 h.id,
140 h.wwwroot,
141 h.ip_address,
142 h.name,
143 h.public_key,
144 h.public_key_expires,
145 h.transport,
146 h.portno,
147 h.last_connect_time,
148 h.last_log_id,
149 h.applicationid,
150 a.name as app_name,
151 a.display_name as app_display_name,
152 a.xmlrpc_server_url
153 FROM {mnet_host} h
154 JOIN {mnet_application} a ON h.applicationid=a.id
155 JOIN {mnet_host2service} hs1 ON hs1.hostid = h.id
156 JOIN {mnet_service} s1 ON hs1.serviceid = s1.id
157 JOIN {mnet_host2service} hs2 ON hs2.hostid = h.id
158 JOIN {mnet_service} s2 ON hs2.serviceid = s2.id
159 JOIN {mnet_host2service} hs3 ON hs3.hostid = h.id
160 JOIN {mnet_service} s3 ON hs3.serviceid = s3.id
161 WHERE
162 h.id <> ? AND
163 h.deleted = 0 AND
164 a.name = ? AND
165 s1.name = ? AND hs1.publish = ? AND
166 s2.name = ? AND hs2.subscribe = ? AND
8a709ecf
PL
167 s3.name = ? AND hs3.subscribe = ? AND
168 s3.name = ? AND hs3.publish = ?',
169 array($CFG->mnet_localhost_id, 'mahara', 'sso_idp', 1, 'sso_sp', 1, 'pf', 1, 'pf', 1));
254f2d05 170 return $hosts;
171 }
172
173 public function prepare_package() {
174 $files = $this->exporter->get_tempfiles();
2f6bd2a9 175 $this->totalsize = 0;
254f2d05 176 foreach ($files as $f) {
177 $this->filesmanifest[$f->get_contenthash()] = array(
178 'filename' => $f->get_filename(),
179 'sha1' => $f->get_contenthash(),
2f6bd2a9 180 'size' => $f->get_filesize(),
254f2d05 181 );
2f6bd2a9 182 $this->totalsize += $f->get_filesize();
254f2d05 183 }
254f2d05 184
37f03ea0 185 $this->set('file', $this->exporter->zip_tempfiles()); // this will throw a file_exception which the exporter catches separately.
254f2d05 186 }
187
aed2937f 188 public function send_package() {
189 global $CFG;
254f2d05 190 // send the 'content_ready' request to mahara
191 require_once($CFG->dirroot . '/mnet/xmlrpc/client.php');
192 $client = new mnet_xmlrpc_client();
193 $client->set_method('portfolio/mahara/lib.php/send_content_ready');
194 $client->add_param($this->token);
195 $client->add_param($this->get('user')->username);
196 $client->add_param($this->resolve_format());
1c597211 197 $client->add_param(array(
198 'filesmanifest' => $this->filesmanifest,
2f6bd2a9 199 'zipfilesha1' => $this->get('file')->get_contenthash(),
200 'zipfilesize' => $this->get('file')->get_filesize(),
201 'totalsize' => $this->totalsize,
1c597211 202 ));
254f2d05 203 $client->add_param($this->get_export_config('wait'));
204 $this->ensure_mnethost();
205 if (!$client->send($this->mnethost)) {
206 foreach ($client->error as $errormessage) {
207 list($code, $message) = array_map('trim',explode(':', $errormessage, 2));
208 $message .= "ERROR $code:<br/>$errormessage<br/>";
209 }
210 throw new portfolio_export_exception($this->get('exporter'), 'failedtoping', 'portfolio_mahara', '', $message);
211 }
212 // we should get back... an ok and a status
213 // either we've been waiting a while and mahara has fetched the file or has queued it.
214 $response = (object)$client->response;
215 if (!$response->status) {
216 throw new portfolio_export_exception($this->get('exporter'), 'failedtoping', 'portfolio_mahara');
217 }
37f03ea0 218 if ($response->type =='queued') {
f2e933bb 219 $this->exporter->set_forcequeue();
37f03ea0 220 }
14cb94eb 221 if (isset($response->querystring)) {
222 $this->continueurl = $response->querystring;
223 }
5d0dbf13
PL
224 // if we're not queuing the logging might have already happened
225 $this->exporter->update_log_url($this->get_static_continue_url());
254f2d05 226 }
227
5d0dbf13 228 public function get_static_continue_url() {
155ce500
PL
229 $remoteurl = '';
230 if ($this->resolve_format() == 'file') {
231 $remoteurl = '/artefact/file/'; // we hopefully get the files that were imported highlighted
232 }
14cb94eb 233 if (isset($this->continueurl)) {
234 $remoteurl .= $this->continueurl;
235 }
5d0dbf13
PL
236 return $remoteurl;
237 }
238
239 public function resolve_static_continue_url($remoteurl) {
14e23787 240 global $CFG;
5d0dbf13 241 $this->ensure_mnethost();
14e23787
PL
242 $u = new moodle_url('/auth/mnet/jump.php', array('hostid' => $this->get_config('mnethostid'), 'wantsurl' => $remoteurl));
243 return $u->out();
254f2d05 244 }
245
5d0dbf13
PL
246 public function get_interactive_continue_url() {
247 return $this->resolve_static_continue_url($this->get_static_continue_url());
248 }
249
254f2d05 250 public function steal_control($stage) {
251 if ($stage != PORTFOLIO_STAGE_CONFIG) {
252 return false;
253 }
254 global $CFG;
edf1fc35 255 return $CFG->wwwroot . '/portfolio/mahara/preconfig.php?id=' . $this->exporter->get('id');
254f2d05 256 }
257
258 public function verify_file_request_params($params) {
259 return false;
260 // the data comes from an xmlrpc request,
261 // not a request to file.php
262 }
263
264 /**
265 * sends the 'content_intent' ping to mahara
266 * if all goes well, this will set the 'token' and 'sendtype' member variables.
267 */
268 public function send_intent() {
269 global $CFG, $DB;
270 require_once($CFG->dirroot . '/mnet/xmlrpc/client.php');
271 $client = new mnet_xmlrpc_client();
272 $client->set_method('portfolio/mahara/lib.php/send_content_intent');
273 $client->add_param($this->get('user')->username);
274 $this->ensure_mnethost();
275 if (!$client->send($this->mnethost)) {
276 foreach ($client->error as $errormessage) {
277 list($code, $message) = array_map('trim',explode(':', $errormessage, 2));
278 $message .= "ERROR $code:<br/>$errormessage<br/>";
279 }
280 throw new portfolio_export_exception($this->get('exporter'), 'failedtoping', 'portfolio_mahara', '', $message);
281 }
282 // we should get back... the send type and a shared token
283 $response = (object)$client->response;
284 if (empty($response->sendtype) || empty($response->token)) {
285 throw new portfolio_export_exception($this->get('exporter'), 'senddisallowed', 'portfolio_mahara');
286 }
287 switch ($response->sendtype) {
288 case 'immediate':
289 $this->sendtype = PORTFOLIO_MAHARA_IMMEDIATE;
290 break;
291 case 'queue':
292 $this->sendtype = PORTFOLIO_MAHARA_QUEUE;
293 break;
294 case 'none':
295 default:
296 throw new portfolio_export_exception($this->get('exporter'), 'senddisallowed', 'portfolio_mahara');
297 }
298 $this->token = $response->token;
299 $this->get('exporter')->save();
300 // put the entry in the mahara queue table now too
301 $q = new stdClass;
302 $q->token = $this->token;
303 $q->transferid = $this->get('exporter')->get('id');
304 $DB->insert_record('portfolio_mahara_queue', $q);
305 }
306
307 private function ensure_mnethost() {
308 if (!empty($this->hostrecord) && !empty($this->mnethost)) {
309 return;
310 }
311 global $DB;
aae8cfdc 312 if (!$this->hostrecord = $DB->get_record('mnet_host', array('id' => $this->get_config('mnethostid')))) {
313 throw new portfolio_plugin_exception(PORTFOLIO_MAHARA_ERR_INVALIDHOST, 'portfolio_mahara');
314 }
254f2d05 315 $this->mnethost = new mnet_peer();
316 $this->mnethost->set_wwwroot($this->hostrecord->wwwroot);
317 }
318
254f2d05 319 /**
320 * xmlrpc (mnet) function to get the file.
321 * reads in the file and returns it base_64 encoded
322 * so that it can be enrypted by mnet.
323 *
324 * @param string $token the token recieved previously during send_content_intent
325 */
326 public static function fetch_file($token) {
287efec6
PL
327 global $DB;
328 $remoteclient = get_mnet_remote_client();
254f2d05 329 try {
aed2937f 330 if (!$transferid = $DB->get_field('portfolio_mahara_queue', 'transferid', array('token' => $token))) {
d234faf3 331 throw new mnet_server_exception(8009, 'mnet_notoken', 'portfolio_mahara');
aed2937f 332 }
254f2d05 333 $exporter = portfolio_exporter::rewaken_object($transferid);
334 } catch (portfolio_exception $e) {
d234faf3 335 throw new mnet_server_exception(8010, 'mnet_noid', 'portfolio_mahara');
254f2d05 336 }
287efec6 337 if ($exporter->get('instance')->get_config('mnethostid') != $remoteclient->id) {
d234faf3 338 throw new mnet_server_exception(8011, 'mnet_wronghost', 'portfolio_mahara');
254f2d05 339 }
340 global $CFG;
d5dfe1b3 341 try {
342 $i = $exporter->get('instance');
343 $f = $i->get('file');
432ad8bf 344 if (empty($f) || !($f instanceof stored_file)) {
d234faf3 345 throw new mnet_server_exception(8012, 'mnet_nofile', 'portfolio_mahara');
d5dfe1b3 346 }
432ad8bf 347 try {
348 $c = $f->get_content();
349 } catch (file_exception $e) {
d234faf3 350 throw new mnet_server_exception(8013, 'mnet_nofilecontents', 'portfolio_mahara', $e->getMessage());
432ad8bf 351 }
d5dfe1b3 352 $contents = base64_encode($c);
353 } catch (Exception $e) {
d234faf3 354 throw new mnet_server_exception(8013, 'mnet_nofile', 'portfolio_mahara');
d5dfe1b3 355 }
fae69685 356 $exporter->log_transfer();
254f2d05 357 $exporter->process_stage_cleanup(true);
358 return $contents;
359 }
360
361 public function cleanup() {
362 global $DB;
363 $DB->delete_records('portfolio_mahara_queue', array('transferid' => $this->get('exporter')->get('id'), 'token' => $this->token));
364 }
365
366
e862c50a 367 /**
368 * internal helper function, that converts between the format constant,
369 * which might be too specific (eg 'image') and the class in our *supported* list
370 * which might be higher up the format hierarchy tree (eg 'file')
371 */
254f2d05 372 private function resolve_format() {
24ba58ee 373 global $CFG;
254f2d05 374 $thisformat = $this->get_export_config('format');
375 $allformats = portfolio_supported_formats();
24ba58ee 376 require_once($CFG->libdir . '/portfolio/formats.php');
254f2d05 377 $thisobj = new $allformats[$thisformat];
378 foreach ($this->supported_formats() as $f) {
379 $class = $allformats[$f];
380 if ($thisobj instanceof $class) {
381 return $f;
382 }
383 }
384 }
254f2d05 385}
386
4317f92f 387