portfolio MDL-21079 be more selective about including libraries.
[moodle.git] / portfolio / mahara / lib.php
1 <?php
3 define('PORTFOLIO_MAHARA_ERR_NETWORKING_OFF', 'err_networkingoff');
4 define('PORTFOLIO_MAHARA_ERR_NOHOSTS', 'err_nomnethosts');
5 define('PORTFOLIO_MAHARA_ERR_INVALIDHOST', 'err_invalidhost');
6 define('PORTFOLIO_MAHARA_ERR_NOMNETAUTH', 'err_nomnetauth');
8 require_once($CFG->libdir . '/portfoliolib.php');
9 require_once($CFG->libdir . '/portfolio/plugin.php');
10 require_once($CFG->libdir . '/portfolio/exporter.php');
11 require_once($CFG->dirroot . '/mnet/lib.php');
13 define('PORTFOLIO_MAHARA_QUEUE', PORTFOLIO_TIME_HIGH);
14 define('PORTFOLIO_MAHARA_IMMEDIATE', PORTFOLIO_TIME_MODERATE);
16 class portfolio_plugin_mahara extends portfolio_plugin_pull_base {
18     private $hosts; // used in the admin config form
19     private $mnethost; // privately set during export from the admin config value (mnethostid)
20     private $hostrecord; // the host record that corresponds to the peer
21     private $token; // during-transfer token
22     private $sendtype; // whatever mahara has said it can handle (immediate or queued)
23     private $filesmanifest; // manifest of files to send to mahara (set during prepare_package and sent later)
24     private $totalsize; // total size of all included files added together
25     private $continueurl; // if we've been sent back a specific url to continue to (eg folder id)
27     public static function get_name() {
28         return get_string('pluginname', 'portfolio_mahara');
29     }
31     public static function get_allowed_config() {
32         return array('mnethostid');
33     }
35     public static function supported_formats() {
36         return array(PORTFOLIO_FORMAT_FILE);
37         // TODO remove above line once leap over mnet is tested
38         return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_LEAP2A);
39     }
41     public function expected_time($callertime) {
42         if ($this->sendtype == PORTFOLIO_MAHARA_QUEUE) {
43             return PORTFOLIO_TIME_FORCEQUEUE;
44         }
45         return $callertime;
46     }
48     public static function has_admin_config() {
49         return true;
50     }
52     public function admin_config_form(&$mform) {
53         $strrequired = get_string('required');
54         $hosts = self::get_mnet_hosts(); // this is called by sanity check but it's ok because it's cached
55         foreach ($hosts as $host) {
56             $hosts[$host->id] = $host->name;
57         }
58         $mform->addElement('select', 'mnethostid', get_string('mnethost', 'portfolio_mahara'), $hosts);
59         $mform->addRule('mnethostid', $strrequired, 'required', null, 'client');
60     }
62     public function instance_sanity_check() {
63         // make sure the host record exists since we don't have referential integrity
64         if (!is_enabled_auth('mnet')) {
65             return PORTFOLIO_MAHARA_ERR_NOMNETAUTH;
66         }
67         try {
68             $this->ensure_mnethost();
69         }
70         catch (portfolio_exception $e) {
71             return PORTFOLIO_MAHARA_ERR_INVALIDHOST;
72         }
73         // make sure we have the right services
74         $hosts = $this->get_mnet_hosts();
75         if (!array_key_exists($this->get_config('mnethostid'), $hosts)) {
76             return PORTFOLIO_MAHARA_ERR_INVALIDHOST;
77         }
78         return 0;
79     }
81     public static function plugin_sanity_check() {
82         global $CFG, $DB;
83         $errorcode = 0;
84         if (!isset($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode != 'strict') {
85             $errorcode =  PORTFOLIO_MAHARA_ERR_NETWORKING_OFF;
86         }
87         if (!is_enabled_auth('mnet')) {
88             $errorcode = PORTFOLIO_MAHARA_ERR_NOMNETAUTH;
89         }
90         if (!self::get_mnet_hosts()) {
91             $errorcode =  PORTFOLIO_MAHARA_ERR_NOHOSTS;
92         }
93         return $errorcode;
94     }
96     private static function get_mnet_hosts() {
97         global $DB, $CFG;
98         static $hosts;
99         if (isset($this) && is_object($this) && isset($this->hosts)) {
100             return $this->hosts;
101         } else if (!isset($this) && isset($hosts)) {
102             return $hosts;
103         }
104         $hosts = $DB->get_records_sql('  SELECT
105                                     h.id,
106                                     h.wwwroot,
107                                     h.ip_address,
108                                     h.name,
109                                     h.public_key,
110                                     h.public_key_expires,
111                                     h.transport,
112                                     h.portno,
113                                     h.last_connect_time,
114                                     h.last_log_id,
115                                     h.applicationid,
116                                     a.name as app_name,
117                                     a.display_name as app_display_name,
118                                     a.xmlrpc_server_url
119                                 FROM {mnet_host} h
120                                     JOIN {mnet_application} a ON h.applicationid=a.id
121                                     JOIN {mnet_host2service} hs1 ON hs1.hostid = h.id
122                                     JOIN {mnet_service} s1 ON hs1.serviceid = s1.id
123                                     JOIN {mnet_host2service} hs2 ON hs2.hostid = h.id
124                                     JOIN {mnet_service} s2 ON hs2.serviceid = s2.id
125                                     JOIN {mnet_host2service} hs3 ON hs3.hostid = h.id
126                                     JOIN {mnet_service} s3 ON hs3.serviceid = s3.id
127                                 WHERE
128                                     h.id <> ? AND
129                                     h.deleted = 0 AND
130                                     a.name = ? AND
131                                     s1.name = ? AND hs1.publish = ? AND
132                                     s2.name = ? AND hs2.subscribe = ? AND
133                                     s3.name = ? AND hs3.subscribe = ? AND
134                                     s3.name = ? AND hs3.publish = ?',
135                         array($CFG->mnet_localhost_id, 'mahara', 'sso_idp', 1, 'sso_sp', 1, 'pf', 1, 'pf', 1));
136         if (empty($hosts)) { $hosts = array(); }
137         if (isset($this) && is_object($this)) {
138             $this->hosts = $hosts;
139         }
140         return $hosts;
141     }
143     public function prepare_package() {
144         $files = $this->exporter->get_tempfiles();
145         $this->totalsize = 0;
146         foreach ($files as $f) {
147             $this->filesmanifest[$f->get_contenthash()] = array(
148                 'filename' => $f->get_filename(),
149                 'sha1'     => $f->get_contenthash(),
150                 'size'     => $f->get_filesize(),
151             );
152             $this->totalsize += $f->get_filesize();
153         }
155         $this->set('file', $this->exporter->zip_tempfiles());  // this will throw a file_exception which the exporter catches separately.
156     }
158     private function ensure_environment() {
159         global $MNET;
160         if (empty($MNET)) {
161             $MNET = new mnet_environment();
162             $MNET->init();
163         } // no idea why this happens :(
164     }
166     public function send_package() {
167         global $CFG;
168         $this->ensure_environment();
169         // send the 'content_ready' request to mahara
170         require_once($CFG->dirroot . '/mnet/xmlrpc/client.php');
171         $client = new mnet_xmlrpc_client();
172         $client->set_method('portfolio/mahara/lib.php/send_content_ready');
173         $client->add_param($this->token);
174         $client->add_param($this->get('user')->username);
175         $client->add_param($this->resolve_format());
176         $client->add_param(array(
177             'filesmanifest' => $this->filesmanifest,
178             'zipfilesha1'   => $this->get('file')->get_contenthash(),
179             'zipfilesize'   => $this->get('file')->get_filesize(),
180             'totalsize'     => $this->totalsize,
181         ));
182         $client->add_param($this->get_export_config('wait'));
183         $this->ensure_mnethost();
184         if (!$client->send($this->mnethost)) {
185             foreach ($client->error as $errormessage) {
186                 list($code, $message) = array_map('trim',explode(':', $errormessage, 2));
187                 $message .= "ERROR $code:<br/>$errormessage<br/>";
188             }
189             throw new portfolio_export_exception($this->get('exporter'), 'failedtoping', 'portfolio_mahara', '', $message);
190         }
191         // we should get back...  an ok and a status
192         // either we've been waiting a while and mahara has fetched the file or has queued it.
193         $response = (object)$client->response;
194         if (!$response->status) {
195             throw new portfolio_export_exception($this->get('exporter'), 'failedtoping', 'portfolio_mahara');
196         }
197         if ($response->type =='queued') {
198             $this->exporter->set_forcequeue();
199         }
200         if (isset($response->querystring)) {
201             $this->continueurl = $response->querystring;
202         }
203         // if we're not queuing the logging might have already happened
204         $this->exporter->update_log_url($this->get_static_continue_url());
205     }
207     public function get_static_continue_url() {
208         $remoteurl = '/artefact/file/';// @todo penny this might change later when we change formats.
209         if (isset($this->continueurl)) {
210             $remoteurl .= $this->continueurl;
211         }
212         return $remoteurl;
213     }
215     public function resolve_static_continue_url($remoteurl) {
216         static $sessions = array();
217         // if this is called mutliple times for the same host, stuff breaks
218         // so we have to keep track and just replace the wantsurl bit
219         // in case things go to different plugins or whatever
220         if (array_key_exists($this->get_config('mnethostid'), $sessions)) {
221             return preg_replace('/wantsurl=[^&]*&/', 'wantsurl=' . urlencode($remoteurl) . '&', $sessions[$this->get_config('mnethostid')]);
222         }
223         $this->ensure_mnethost();
224         $this->ensure_environment();
225         $mnetauth = get_auth_plugin('mnet');
226         if (!$url = $mnetauth->start_jump_session($this->get_config('mnethostid'), $remoteurl)) {
227             return false;
228         }
229         $sessions[$this->get_config('mnethostid')] = $url;
230         return $url;
231     }
233     public function get_interactive_continue_url() {
234         return $this->resolve_static_continue_url($this->get_static_continue_url());
235     }
237     public function steal_control($stage) {
238         if ($stage != PORTFOLIO_STAGE_CONFIG) {
239             return false;
240         }
241         global $CFG;
242         return $CFG->wwwroot . '/portfolio/mahara/preconfig.php?id=' . $this->exporter->get('id');
243     }
245     public function verify_file_request_params($params) {
246         return false;
247         // the data comes from an xmlrpc request,
248         // not a request to file.php
249     }
251     /**
252     * sends the 'content_intent' ping to mahara
253     * if all goes well, this will set the 'token' and 'sendtype' member variables.
254     */
255     public function send_intent() {
256         global $CFG, $DB;
257         require_once($CFG->dirroot . '/mnet/xmlrpc/client.php');
258         $client = new mnet_xmlrpc_client();
259         $client->set_method('portfolio/mahara/lib.php/send_content_intent');
260         $client->add_param($this->get('user')->username);
261         $this->ensure_mnethost();
262         if (!$client->send($this->mnethost)) {
263             foreach ($client->error as $errormessage) {
264                 list($code, $message) = array_map('trim',explode(':', $errormessage, 2));
265                 $message .= "ERROR $code:<br/>$errormessage<br/>";
266             }
267             throw new portfolio_export_exception($this->get('exporter'), 'failedtoping', 'portfolio_mahara', '', $message);
268         }
269         // we should get back... the send type and a shared token
270         $response = (object)$client->response;
271         if (empty($response->sendtype) || empty($response->token)) {
272             throw new portfolio_export_exception($this->get('exporter'), 'senddisallowed', 'portfolio_mahara');
273         }
274         switch ($response->sendtype) {
275             case 'immediate':
276                 $this->sendtype = PORTFOLIO_MAHARA_IMMEDIATE;
277                 break;
278             case 'queue':
279                 $this->sendtype = PORTFOLIO_MAHARA_QUEUE;
280                 break;
281             case 'none':
282             default:
283                 throw new portfolio_export_exception($this->get('exporter'), 'senddisallowed', 'portfolio_mahara');
284         }
285         $this->token = $response->token;
286         $this->get('exporter')->save();
287         // put the entry in the mahara queue table now too
288         $q = new stdClass;
289         $q->token = $this->token;
290         $q->transferid = $this->get('exporter')->get('id');
291         $DB->insert_record('portfolio_mahara_queue', $q);
292     }
294     private function ensure_mnethost() {
295         if (!empty($this->hostrecord) && !empty($this->mnethost)) {
296             return;
297         }
298         global $DB;
299         if (!$this->hostrecord = $DB->get_record('mnet_host', array('id' => $this->get_config('mnethostid')))) {
300             throw new portfolio_plugin_exception(PORTFOLIO_MAHARA_ERR_INVALIDHOST, 'portfolio_mahara');
301         }
302         $this->mnethost = new mnet_peer();
303         $this->mnethost->set_wwwroot($this->hostrecord->wwwroot);
304     }
306     public static function mnet_publishes() {
307         $pf= array();
308         $pf['name']        = 'pf'; // Name & Description go in lang file
309         $pf['apiversion']  = 1;
310         $pf['methods']     = array('send_content_intent', 'send_content_ready', 'fetch_file');
312         return array($pf);
313     }
315     /**
316     * xmlrpc (mnet) function to get the file.
317     * reads in the file and returns it base_64 encoded
318     * so that it can be enrypted by mnet.
319     *
320     * @param string $token the token recieved previously during send_content_intent
321     */
322     public static function fetch_file($token) {
323         global $DB, $MNET_REMOTE_CLIENT;;
324         try {
325             if (!$transferid = $DB->get_field('portfolio_mahara_queue', 'transferid', array('token' => $token))) {
326                 exit(mnet_server_fault(8009, get_string('mnet_notoken', 'portfolio_mahara')));
327             }
328             $exporter = portfolio_exporter::rewaken_object($transferid);
329         } catch (portfolio_exception $e) {
330             exit(mnet_server_fault(8010, get_string('mnet_noid', 'portfolio_mahara')));
331         }
332         if ($exporter->get('instance')->get_config('mnethostid') != $MNET_REMOTE_CLIENT->id) {
333             exit(mnet_server_fault(8011, get_string('mnet_wronghost', 'portfolio_mahara')));
334         }
335         global $CFG;
336         try {
337             $i = $exporter->get('instance');
338             $f = $i->get('file');
339             if (empty($f) || !($f instanceof stored_file)) {
340                 exit(mnet_server_fault(8012, get_string('mnet_nofile', 'portfolio_mahara')));
341             }
342             try {
343                 $c = $f->get_content();
344             } catch (file_exception $e) {
345                 exit(mnet_server_fault(8013, get_string('mnet_nofilecontents', 'portfolio_mahara', $e->getMessage())));
346             }
347             $contents = base64_encode($c);
348         } catch (Exception $e) {
349             exit(mnet_server_fault(8013, get_string('mnet_nofile', 'portfolio_mahara')));
350         }
351         $exporter->log_transfer();
352         $exporter->process_stage_cleanup(true);
353         return $contents;
354     }
356     public function cleanup() {
357         global $DB;
358         $DB->delete_records('portfolio_mahara_queue', array('transferid' => $this->get('exporter')->get('id'), 'token' => $this->token));
359     }
362     /**
363      * internal helper function, that converts between the format constant,
364      * which might be too specific (eg 'image') and the class in our *supported* list
365      * which might be higher up the format hierarchy tree (eg 'file')
366      */
367     private function resolve_format() {
368         global $CFG;
369         $thisformat = $this->get_export_config('format');
370         $allformats = portfolio_supported_formats();
371         require_once($CFG->libdir . '/portfolio/formats.php');
372         $thisobj = new $allformats[$thisformat];
373         foreach ($this->supported_formats() as $f) {
374             $class = $allformats[$f];
375             if ($thisobj instanceof $class) {
376                 return $f;
377             }
378         }
379     }