MDL-15777 - better handling of integration with file api in portfolio mahara plugin
[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_name() {
24         return get_string('pluginname', 'portfolio_mahara');
25     }
27     public static function get_allowed_config() {
28         return array('mnethostid');
29     }
31     public static function supported_formats() {
32         return array(PORTFOLIO_FORMAT_FILE);
33     }
35     public function expected_time($callertime) {
36         if ($this->sendtype == PORTFOLIO_MAHARA_QUEUE) {
37             return PORTFOLIO_TIME_FORCEQUEUE;
38         }
39         return $callertime;
40     }
42     public static function has_admin_config() {
43         return true;
44     }
46     public function admin_config_form(&$mform) {
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     }
56     public function instance_sanity_check() {
57         // make sure the host record exists since we don't have referential integrity
58         if (!is_enabled_auth('mnet')) {
59             return PORTFOLIO_MAHARA_ERR_NOMNETAUTH;
60         }
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     }
75     public static function plugin_sanity_check() {
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         }
81         if (!is_enabled_auth('mnet')) {
82             $errorcode = PORTFOLIO_MAHARA_ERR_NOMNETAUTH;
83         }
84         if (!self::get_mnet_hosts()) {
85             $errorcode =  PORTFOLIO_MAHARA_ERR_NOHOSTS;
86         }
87         return $errorcode;
88     }
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     }
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();
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     }
154     private function ensure_environment() {
155         global $MNET;
156         if (empty($MNET)) {
157             $MNET = new mnet_environment();
158             $MNET->init();
159         } // no idea why this happens :(
160     }
162     public function send_package() {
163         global $CFG;
164         $this->ensure_environment();
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());
172         $client->add_param(array(
173             'filesmanifest' => $this->filesmanifest,
174             'zipfilesha1'   => $this->get('file')->get_contenthash()
175         ));
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     }
194     public function get_continue_url() {
195         $this->ensure_mnethost();
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;
203     }
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     }
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     }
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     }
262     private function ensure_mnethost() {
263         if (!empty($this->hostrecord) && !empty($this->mnethost)) {
264             return;
265         }
266         global $DB;
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         }
270         $this->mnethost = new mnet_peer();
271         $this->mnethost->set_wwwroot($this->hostrecord->wwwroot);
272     }
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');
280         return array($pf);
281     }
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 {
293             if (!$transferid = $DB->get_field('portfolio_mahara_queue', 'transferid', array('token' => $token))) {
294                 exit(mnet_server_fault(8009, 'could not find token'));
295             }
296             $exporter = portfolio_exporter::rewaken_object($transferid);
297         } catch (portfolio_exception $e) {
298             exit(mnet_server_fault(8010, 'invalid transfer id'));
299         }
300         if ($exporter->get('instance')->get_config('mnethostid') != $MNET_REMOTE_CLIENT->id) {
301             exit(mnet_server_fault(8011, "remote host didn't match saved host"));
302         }
303         global $CFG;
304         try {
305             $i = $exporter->get('instance');
306             $f = $i->get('file');
307             if (empty($f) || !($f instanceof stored_file)) {
308                 exit(mnet_server_fault(8012, 'could not find file in transfer object - weird error'));
309             }
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             }
315             $contents = base64_encode($c);
316         } catch (Exception $e) {
317             exit(mnet_server_fault(8013, 'could not get file to send'));
318         }
319         $exporter->process_stage_cleanup(true);
320         return $contents;
321     }
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     }
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     }
342 ?>