1 |
efrain |
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.
|
|
|
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 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
|
|
|
26 |
*/
|
|
|
27 |
|
|
|
28 |
|
|
|
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');
|
|
|
33 |
|
|
|
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');
|
|
|
38 |
|
|
|
39 |
define('PORTFOLIO_MAHARA_QUEUE', PORTFOLIO_TIME_HIGH);
|
|
|
40 |
define('PORTFOLIO_MAHARA_IMMEDIATE', PORTFOLIO_TIME_MODERATE);
|
|
|
41 |
|
|
|
42 |
class 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)
|
|
|
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)
|
|
|
52 |
|
|
|
53 |
/** @var mnet_environment the equivalent of old $MNET global. */
|
|
|
54 |
public $mnet;
|
|
|
55 |
|
|
|
56 |
protected function init() {
|
|
|
57 |
$this->mnet = get_mnet_environment();
|
|
|
58 |
}
|
|
|
59 |
|
|
|
60 |
public function __wakeup() {
|
|
|
61 |
$this->mnet = get_mnet_environment();
|
|
|
62 |
}
|
|
|
63 |
|
|
|
64 |
public static function get_name() {
|
|
|
65 |
return get_string('pluginname', 'portfolio_mahara');
|
|
|
66 |
}
|
|
|
67 |
|
|
|
68 |
public static function get_allowed_config() {
|
|
|
69 |
return array('mnethostid', 'enableleap2a');
|
|
|
70 |
}
|
|
|
71 |
|
|
|
72 |
public function supported_formats() {
|
|
|
73 |
if ($this->get_config('enableleap2a')) {
|
|
|
74 |
return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_LEAP2A);
|
|
|
75 |
}
|
|
|
76 |
return array(PORTFOLIO_FORMAT_FILE);
|
|
|
77 |
}
|
|
|
78 |
|
|
|
79 |
public function expected_time($callertime) {
|
|
|
80 |
if ($this->sendtype == PORTFOLIO_MAHARA_QUEUE) {
|
|
|
81 |
return PORTFOLIO_TIME_FORCEQUEUE;
|
|
|
82 |
}
|
|
|
83 |
return $callertime;
|
|
|
84 |
}
|
|
|
85 |
|
|
|
86 |
public static function has_admin_config() {
|
|
|
87 |
return true;
|
|
|
88 |
}
|
|
|
89 |
|
|
|
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;
|
|
|
95 |
}
|
|
|
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);
|
|
|
101 |
}
|
|
|
102 |
|
|
|
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;
|
|
|
107 |
}
|
|
|
108 |
try {
|
|
|
109 |
$this->ensure_mnethost();
|
|
|
110 |
}
|
|
|
111 |
catch (portfolio_exception $e) {
|
|
|
112 |
return PORTFOLIO_MAHARA_ERR_INVALIDHOST;
|
|
|
113 |
}
|
|
|
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;
|
|
|
118 |
}
|
|
|
119 |
return 0;
|
|
|
120 |
}
|
|
|
121 |
|
|
|
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;
|
|
|
127 |
}
|
|
|
128 |
if (!is_enabled_auth('mnet')) {
|
|
|
129 |
$errorcode = PORTFOLIO_MAHARA_ERR_NOMNETAUTH;
|
|
|
130 |
}
|
|
|
131 |
if (!self::get_mnet_hosts()) {
|
|
|
132 |
$errorcode = PORTFOLIO_MAHARA_ERR_NOHOSTS;
|
|
|
133 |
}
|
|
|
134 |
return $errorcode;
|
|
|
135 |
}
|
|
|
136 |
|
|
|
137 |
private static function get_mnet_hosts() {
|
|
|
138 |
global $DB, $CFG;
|
|
|
139 |
static $hosts;
|
|
|
140 |
if (isset($hosts)) {
|
|
|
141 |
return $hosts;
|
|
|
142 |
}
|
|
|
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;
|
|
|
176 |
}
|
|
|
177 |
|
|
|
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(),
|
|
|
186 |
);
|
|
|
187 |
$this->totalsize += $f->get_filesize();
|
|
|
188 |
}
|
|
|
189 |
|
|
|
190 |
$this->set('file', $this->exporter->zip_tempfiles()); // this will throw a file_exception which the exporter catches separately.
|
|
|
191 |
}
|
|
|
192 |
|
|
|
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,
|
|
|
207 |
));
|
|
|
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/>";
|
|
|
214 |
}
|
|
|
215 |
throw new portfolio_export_exception($this->get('exporter'), 'failedtoping', 'portfolio_mahara', '', $message);
|
|
|
216 |
}
|
|
|
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');
|
|
|
222 |
}
|
|
|
223 |
if ($response->type =='queued') {
|
|
|
224 |
$this->exporter->set_forcequeue();
|
|
|
225 |
}
|
|
|
226 |
if (isset($response->querystring)) {
|
|
|
227 |
$this->continueurl = $response->querystring;
|
|
|
228 |
}
|
|
|
229 |
// if we're not queuing the logging might have already happened
|
|
|
230 |
$this->exporter->update_log_url($this->get_static_continue_url());
|
|
|
231 |
}
|
|
|
232 |
|
|
|
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
|
|
|
237 |
}
|
|
|
238 |
if (isset($this->continueurl)) {
|
|
|
239 |
$remoteurl .= $this->continueurl;
|
|
|
240 |
}
|
|
|
241 |
return $remoteurl;
|
|
|
242 |
}
|
|
|
243 |
|
|
|
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();
|
|
|
249 |
}
|
|
|
250 |
|
|
|
251 |
public function get_interactive_continue_url() {
|
|
|
252 |
return $this->resolve_static_continue_url($this->get_static_continue_url());
|
|
|
253 |
}
|
|
|
254 |
|
|
|
255 |
public function steal_control($stage) {
|
|
|
256 |
if ($stage != PORTFOLIO_STAGE_CONFIG) {
|
|
|
257 |
return false;
|
|
|
258 |
}
|
|
|
259 |
global $CFG;
|
|
|
260 |
return $CFG->wwwroot . '/portfolio/mahara/preconfig.php?id=' . $this->exporter->get('id');
|
|
|
261 |
}
|
|
|
262 |
|
|
|
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
|
|
|
267 |
}
|
|
|
268 |
|
|
|
269 |
/**
|
|
|
270 |
* sends the 'content_intent' ping to mahara
|
|
|
271 |
* if all goes well, this will set the 'token' and 'sendtype' member variables.
|
|
|
272 |
*/
|
|
|
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/>";
|
|
|
284 |
}
|
|
|
285 |
throw new portfolio_export_exception($this->get('exporter'), 'failedtoping', 'portfolio_mahara', '', $message);
|
|
|
286 |
}
|
|
|
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');
|
|
|
291 |
}
|
|
|
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');
|
|
|
302 |
}
|
|
|
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);
|
|
|
310 |
}
|
|
|
311 |
|
|
|
312 |
private function ensure_mnethost() {
|
|
|
313 |
if (!empty($this->hostrecord) && !empty($this->mnethost)) {
|
|
|
314 |
return;
|
|
|
315 |
}
|
|
|
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');
|
|
|
319 |
}
|
|
|
320 |
$this->mnethost = new mnet_peer();
|
|
|
321 |
$this->mnethost->set_wwwroot($this->hostrecord->wwwroot);
|
|
|
322 |
}
|
|
|
323 |
|
|
|
324 |
/**
|
|
|
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.
|
|
|
328 |
*
|
|
|
329 |
* @param string $token the token recieved previously during send_content_intent
|
|
|
330 |
*/
|
|
|
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');
|
|
|
337 |
}
|
|
|
338 |
$exporter = portfolio_exporter::rewaken_object($transferid);
|
|
|
339 |
} catch (portfolio_exception $e) {
|
|
|
340 |
throw new mnet_server_exception(8010, 'mnet_noid', 'portfolio_mahara');
|
|
|
341 |
}
|
|
|
342 |
if ($exporter->get('instance')->get_config('mnethostid') != $remoteclient->id) {
|
|
|
343 |
throw new mnet_server_exception(8011, 'mnet_wronghost', 'portfolio_mahara');
|
|
|
344 |
}
|
|
|
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');
|
|
|
351 |
}
|
|
|
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());
|
|
|
356 |
}
|
|
|
357 |
$contents = base64_encode($c);
|
|
|
358 |
} catch (Exception $e) {
|
|
|
359 |
throw new mnet_server_exception(8013, 'mnet_nofile', 'portfolio_mahara');
|
|
|
360 |
}
|
|
|
361 |
$exporter->log_transfer();
|
|
|
362 |
$exporter->process_stage_cleanup(true);
|
|
|
363 |
return $contents;
|
|
|
364 |
}
|
|
|
365 |
|
|
|
366 |
public function cleanup() {
|
|
|
367 |
global $DB;
|
|
|
368 |
$DB->delete_records('portfolio_mahara_queue', array('transferid' => $this->get('exporter')->get('id'), 'token' => $this->token));
|
|
|
369 |
}
|
|
|
370 |
|
|
|
371 |
|
|
|
372 |
/**
|
|
|
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')
|
|
|
376 |
*/
|
|
|
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;
|
|
|
387 |
}
|
|
|
388 |
}
|
|
|
389 |
}
|
|
|
390 |
}
|
|
|
391 |
|
|
|
392 |
|