Commit | Line | Data |
---|---|---|
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 | |
29 | define('PORTFOLIO_MAHARA_ERR_NETWORKING_OFF', 'err_networkingoff'); | |
30 | define('PORTFOLIO_MAHARA_ERR_NOHOSTS', 'err_nomnethosts'); | |
aae8cfdc | 31 | define('PORTFOLIO_MAHARA_ERR_INVALIDHOST', 'err_invalidhost'); |
7254f56e | 32 | define('PORTFOLIO_MAHARA_ERR_NOMNETAUTH', 'err_nomnetauth'); |
254f2d05 | 33 | |
24ba58ee PL |
34 | require_once($CFG->libdir . '/portfoliolib.php'); |
35 | require_once($CFG->libdir . '/portfolio/plugin.php'); | |
36 | require_once($CFG->libdir . '/portfolio/exporter.php'); | |
254f2d05 | 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) | |
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 |