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