MDL-16289 - actually unpack xmlrpc errors rather than just whinge the payload is...
[moodle.git] / portfolio / type / mahara / lib.php
CommitLineData
254f2d05 1<?php
2
3define('PORTFOLIO_MAHARA_ERR_NETWORKING_OFF', 'err_networkingoff');
4define('PORTFOLIO_MAHARA_ERR_NOHOSTS', 'err_nomnethosts');
aae8cfdc 5define('PORTFOLIO_MAHARA_ERR_INVALIDHOST', 'err_invalidhost');
254f2d05 6
7require_once($CFG->dirroot . '/lib/portfoliolib.php');
8require_once($CFG->dirroot . '/mnet/lib.php');
9
10define('PORTFOLIO_MAHARA_QUEUE', PORTFOLIO_TIME_HIGH);
11define('PORTFOLIO_MAHARA_IMMEDIATE', PORTFOLIO_TIME_MODERATE);
12
13class portfolio_plugin_mahara extends portfolio_plugin_pull_base {
14
15 private $hosts; // used in the admin config form
16 private $mnethost; // privately set during export from the admin config value (mnethostid)
17 private $hostrecord; // the host record that corresponds to the peer
18 private $token; // during-transfer token
19 private $sendtype; // whatever mahara has said it can handle (immediate or queued)
20 private $filesmanifest; // manifest of files to send to mahara (set during prepare_package and sent later)
21
22 public static function get_allowed_config() {
23 return array('mnethostid');
24 }
25
26 public static function supported_formats() {
27 return array(PORTFOLIO_FORMAT_FILE);
28 }
29
30 public function expected_time($callertime) {
31 if ($this->sendtype == PORTFOLIO_MAHARA_QUEUE) {
32 return PORTFOLIO_TIME_FORCEQUEUE;
33 }
34 return $callertime;
35 }
36
37 public static function has_admin_config() {
38 return true;
39 }
40
41 public function admin_config_form(&$mform) {
254f2d05 42 $strrequired = get_string('required');
43 $hosts = self::get_mnet_hosts(); // this is called by sanity check but it's ok because it's cached
44 foreach ($hosts as $host) {
45 $hosts[$host->id] = $host->name;
46 }
47 $mform->addElement('select', 'mnethostid', get_string('mnethost', 'portfolio_mahara'), $hosts);
48 $mform->addRule('mnethostid', $strrequired, 'required', null, 'client');
aae8cfdc 49 if ($errorcode = self::plugin_sanity_check()) {
50 return $errorcode; // processing stops when we return a string.
51 }
52 if (!empty($this) && $errorcode = $this->instance_sanity_check()) {
53 return $errorcode;
54 }
254f2d05 55 }
56
aae8cfdc 57 public function instance_sanity_check() {
58 // make sure the host record exists since we don't have referential integrity
59 try {
60 $this->ensure_mnethost();
61 }
62 catch (portfolio_exception $e) {
63 return PORTFOLIO_MAHARA_ERR_INVALIDHOST;
64 }
65 // make sure we have the right services
66 $hosts = $this->get_mnet_hosts();
67 if (!array_key_exists($this->get_config('mnethostid'), $hosts)) {
68 return PORTFOLIO_MAHARA_ERR_INVALIDHOST;
69 }
70 return 0;
71 }
254f2d05 72
73 public static function plugin_sanity_check() {
254f2d05 74 global $CFG, $DB;
75 $errorcode = 0;
76 if (!isset($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode != 'strict') {
77 $errorcode = PORTFOLIO_MAHARA_ERR_NETWORKING_OFF;
78 }
79 if (!self::get_mnet_hosts()) {
80 $errorcode = PORTFOLIO_MAHARA_ERR_NOHOSTS;
81 }
82 if (!empty($errorcode)) { // disable the plugins // @todo
83 $DB->set_field('portfolio_instance', 'visible', 0, array('plugin' => 'mahara'));
84 }
85 return $errorcode;
86 }
87
88 private static function get_mnet_hosts() {
89 global $DB, $CFG;
90 static $hosts;
91 if (isset($this) && is_object($this) && isset($this->hosts)) {
92 return $this->hosts;
93 } else if (!isset($this) && isset($hosts)) {
94 return $hosts;
95 }
96 $hosts = $DB->get_records_sql(' SELECT
97 h.id,
98 h.wwwroot,
99 h.ip_address,
100 h.name,
101 h.public_key,
102 h.public_key_expires,
103 h.transport,
104 h.portno,
105 h.last_connect_time,
106 h.last_log_id,
107 h.applicationid,
108 a.name as app_name,
109 a.display_name as app_display_name,
110 a.xmlrpc_server_url
111 FROM {mnet_host} h
112 JOIN {mnet_application} a ON h.applicationid=a.id
113 JOIN {mnet_host2service} hs1 ON hs1.hostid = h.id
114 JOIN {mnet_service} s1 ON hs1.serviceid = s1.id
115 JOIN {mnet_host2service} hs2 ON hs2.hostid = h.id
116 JOIN {mnet_service} s2 ON hs2.serviceid = s2.id
117 JOIN {mnet_host2service} hs3 ON hs3.hostid = h.id
118 JOIN {mnet_service} s3 ON hs3.serviceid = s3.id
119 WHERE
120 h.id <> ? AND
121 h.deleted = 0 AND
122 a.name = ? AND
123 s1.name = ? AND hs1.publish = ? AND
124 s2.name = ? AND hs2.subscribe = ? AND
125 s3.name = ? AND hs3.subscribe = ?',
126 array($CFG->mnet_localhost_id, 'mahara', 'sso_idp', 1, 'sso_sp', 1, 'pf', 1));;
127 if (empty($hosts)) { $hosts = array(); }
128 if (isset($this) && is_object($this)) {
129 $this->hosts = $hosts;
130 }
131 return $hosts;
132 }
133
134 public function prepare_package() {
135 $files = $this->exporter->get_tempfiles();
136 foreach ($files as $f) {
137 $this->filesmanifest[$f->get_contenthash()] = array(
138 'filename' => $f->get_filename(),
139 'sha1' => $f->get_contenthash(),
140 );
141 }
142 $zipper = new zip_packer();
143
144 $filename = 'portfolio-export.zip';
145 if ($newfile = $zipper->archive_to_storage($files, SYSCONTEXTID, 'portfolio_exporter', $this->exporter->get('id'), '/final/', $filename, $this->user->id)) {
146 $this->set('file', $newfile);
147 return true;
148 }
149 return false;
150 }
151
aed2937f 152 private function ensure_environment() {
254f2d05 153 global $MNET;
154 if (empty($MNET)) {
155 $MNET = new mnet_environment();
156 $MNET->init();
157 } // no idea why this happens :(
aed2937f 158 }
159
160 public function send_package() {
161 global $CFG;
162 $this->ensure_environment();
254f2d05 163 // send the 'content_ready' request to mahara
164 require_once($CFG->dirroot . '/mnet/xmlrpc/client.php');
165 $client = new mnet_xmlrpc_client();
166 $client->set_method('portfolio/mahara/lib.php/send_content_ready');
167 $client->add_param($this->token);
168 $client->add_param($this->get('user')->username);
169 $client->add_param($this->resolve_format());
1c597211 170 $client->add_param(array(
171 'filesmanifest' => $this->filesmanifest,
172 'zipfilesha1' => $this->get('file')->get_contenthash()
173 ));
254f2d05 174 $client->add_param($this->get_export_config('wait'));
175 $this->ensure_mnethost();
176 if (!$client->send($this->mnethost)) {
177 foreach ($client->error as $errormessage) {
178 list($code, $message) = array_map('trim',explode(':', $errormessage, 2));
179 $message .= "ERROR $code:<br/>$errormessage<br/>";
180 }
181 throw new portfolio_export_exception($this->get('exporter'), 'failedtoping', 'portfolio_mahara', '', $message);
182 }
183 // we should get back... an ok and a status
184 // either we've been waiting a while and mahara has fetched the file or has queued it.
185 $response = (object)$client->response;
186 if (!$response->status) {
187 throw new portfolio_export_exception($this->get('exporter'), 'failedtoping', 'portfolio_mahara');
188 }
189 return true;
190 }
191
192 public function get_continue_url() {
193 $this->ensure_mnethost();
aed2937f 194 $this->ensure_environment();
195 $mnetauth = get_auth_plugin('mnet');
196 $remoteurl = '/artefact/file/';// @todo penny this might change later when we change formats.
197 if (!$url = $mnetauth->start_jump_session($this->get_config('mnethostid'), $remoteurl)) {
198 return false;
199 }
200 return $url;
254f2d05 201 }
202
203 public function steal_control($stage) {
204 if ($stage != PORTFOLIO_STAGE_CONFIG) {
205 return false;
206 }
207 global $CFG;
208 return $CFG->wwwroot . '/portfolio/type/mahara/preconfig.php?id=' . $this->exporter->get('id');
209 }
210
211 public function verify_file_request_params($params) {
212 return false;
213 // the data comes from an xmlrpc request,
214 // not a request to file.php
215 }
216
217 /**
218 * sends the 'content_intent' ping to mahara
219 * if all goes well, this will set the 'token' and 'sendtype' member variables.
220 */
221 public function send_intent() {
222 global $CFG, $DB;
223 require_once($CFG->dirroot . '/mnet/xmlrpc/client.php');
224 $client = new mnet_xmlrpc_client();
225 $client->set_method('portfolio/mahara/lib.php/send_content_intent');
226 $client->add_param($this->get('user')->username);
227 $this->ensure_mnethost();
228 if (!$client->send($this->mnethost)) {
229 foreach ($client->error as $errormessage) {
230 list($code, $message) = array_map('trim',explode(':', $errormessage, 2));
231 $message .= "ERROR $code:<br/>$errormessage<br/>";
232 }
233 throw new portfolio_export_exception($this->get('exporter'), 'failedtoping', 'portfolio_mahara', '', $message);
234 }
235 // we should get back... the send type and a shared token
236 $response = (object)$client->response;
237 if (empty($response->sendtype) || empty($response->token)) {
238 throw new portfolio_export_exception($this->get('exporter'), 'senddisallowed', 'portfolio_mahara');
239 }
240 switch ($response->sendtype) {
241 case 'immediate':
242 $this->sendtype = PORTFOLIO_MAHARA_IMMEDIATE;
243 break;
244 case 'queue':
245 $this->sendtype = PORTFOLIO_MAHARA_QUEUE;
246 break;
247 case 'none':
248 default:
249 throw new portfolio_export_exception($this->get('exporter'), 'senddisallowed', 'portfolio_mahara');
250 }
251 $this->token = $response->token;
252 $this->get('exporter')->save();
253 // put the entry in the mahara queue table now too
254 $q = new stdClass;
255 $q->token = $this->token;
256 $q->transferid = $this->get('exporter')->get('id');
257 $DB->insert_record('portfolio_mahara_queue', $q);
258 }
259
260 private function ensure_mnethost() {
261 if (!empty($this->hostrecord) && !empty($this->mnethost)) {
262 return;
263 }
264 global $DB;
aae8cfdc 265 if (!$this->hostrecord = $DB->get_record('mnet_host', array('id' => $this->get_config('mnethostid')))) {
266 throw new portfolio_plugin_exception(PORTFOLIO_MAHARA_ERR_INVALIDHOST, 'portfolio_mahara');
267 }
254f2d05 268 $this->mnethost = new mnet_peer();
269 $this->mnethost->set_wwwroot($this->hostrecord->wwwroot);
270 }
271
272 public static function mnet_publishes() {
273 $pf= array();
274 $pf['name'] = 'pf'; // Name & Description go in lang file
275 $pf['apiversion'] = 1;
276 $pf['methods'] = array('send_content_intent', 'send_content_ready', 'fetch_file');
277
278 return array($pf);
279 }
280
281 /**
282 * xmlrpc (mnet) function to get the file.
283 * reads in the file and returns it base_64 encoded
284 * so that it can be enrypted by mnet.
285 *
286 * @param string $token the token recieved previously during send_content_intent
287 */
288 public static function fetch_file($token) {
289 global $DB, $MNET_REMOTE_CLIENT;;
290 try {
aed2937f 291 if (!$transferid = $DB->get_field('portfolio_mahara_queue', 'transferid', array('token' => $token))) {
292 exit(mnet_server_fault(8009, 'could not find token'));
293 }
254f2d05 294 $exporter = portfolio_exporter::rewaken_object($transferid);
295 } catch (portfolio_exception $e) {
d5dfe1b3 296 exit(mnet_server_fault(8010, 'invalid transfer id'));
254f2d05 297 }
298 if ($exporter->get('instance')->get_config('mnethostid') != $MNET_REMOTE_CLIENT->id) {
d5dfe1b3 299 exit(mnet_server_fault(8011, "remote host didn't match saved host"));
254f2d05 300 }
301 global $CFG;
d5dfe1b3 302 try {
303 $i = $exporter->get('instance');
304 $f = $i->get('file');
305 if (empty($f)) {
306 exit(mnet_server_fault(8012, 'could not find file in transfer object - weird error'));
307 }
308 $c = $f->get_content();
309 $contents = base64_encode($c);
310 } catch (Exception $e) {
311 exit(mnet_server_fault(8013, 'could not get file to send'));
312 }
254f2d05 313 $exporter->process_stage_cleanup(true);
314 return $contents;
315 }
316
317 public function cleanup() {
318 global $DB;
319 $DB->delete_records('portfolio_mahara_queue', array('transferid' => $this->get('exporter')->get('id'), 'token' => $this->token));
320 }
321
322
323 private function resolve_format() {
324 $thisformat = $this->get_export_config('format');
325 $allformats = portfolio_supported_formats();
326 $thisobj = new $allformats[$thisformat];
327 foreach ($this->supported_formats() as $f) {
328 $class = $allformats[$f];
329 if ($thisobj instanceof $class) {
330 return $f;
331 }
332 }
333 }
254f2d05 334}
335
336?>