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