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