MDL-33552 - portfolio api E_STRICT fixes
[moodle.git] / lib / portfolio / exporter.php
CommitLineData
87fcac8d 1<?php
93dd2725
RW
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
87fcac8d 17/**
87fcac8d 18 * This file contains the class definition for the exporter object.
93dd2725
RW
19 *
20 * @package core_portfolio
21 * @copyright 2008 Penny Leach <penny@catalyst.net.nz>
22 * Martin Dougiamas <http://dougiamas.com>
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
87fcac8d 24 */
25
0972665f
PS
26defined('MOODLE_INTERNAL') || die();
27
87fcac8d 28/**
93dd2725
RW
29 * The class that handles the various stages of the actual export
30 * and the communication between the caller and the portfolio plugin.
18cdcdbf
SH
31 *
32 * This is stored in the database between page requests in serialized base64 encoded form
93dd2725 33 * also contains helper methods for the plugin and caller to use (at the end of the file)
18cdcdbf
SH
34 * @see get_base_filearea - where to write files to
35 * @see write_new_file - write some content to a file in the export filearea
36 * @see copy_existing_file - copy an existing file into the export filearea
37 * @see get_tempfiles - return list of all files in the export filearea
93dd2725
RW
38 *
39 * @package core_portfolio
40 * @category portfolio
41 * @copyright 2008 Penny Leach <penny@catalyst.net.nz>
42 * Martin Dougiamas <http://dougiamas.com>
43 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
44 */
87fcac8d 45class portfolio_exporter {
46
93dd2725 47 /** @var portfolio_caller_base the caller object used during the export */
87fcac8d 48 private $caller;
49
93dd2725 50 /** @var portfolio_plugin_base the portfolio plugin instanced used during the export */
87fcac8d 51 private $instance;
52
93dd2725 53 /** @var bool if there has been no config form displayed to the user */
36b1cfd6 54 private $noexportconfig;
87fcac8d 55
87fcac8d 56 /**
93dd2725
RW
57 * @var stdClass the user currently exporting content always $USER,
58 * but more conveniently placed here
59 */
87fcac8d 60 private $user;
61
93dd2725
RW
62 /**
63 * @var string the file to include that contains the class defintion of
64 * the portfolio instance plugin used to re-waken the object after sleep
65 */
87fcac8d 66 public $instancefile;
67
68 /**
93dd2725
RW
69 * @var string the file to include that contains the class definition of
70 * the caller object used to re-waken the object after sleep
71 */
87fcac8d 72 public $callerfile;
73
93dd2725 74 /** @var int the current stage of the export */
87fcac8d 75 private $stage;
76
93dd2725 77 /** @var bool whether something (usually the portfolio plugin) has forced queuing */
87fcac8d 78 private $forcequeue;
79
80 /**
93dd2725
RW
81 * @var int id of this export matches record in portfolio_tempdata table
82 * and used for itemid for file storage.
83 */
87fcac8d 84 private $id;
85
93dd2725 86 /** @var array of stages that have had the portfolio plugin already steal control from them */
87fcac8d 87 private $alreadystolen;
88
2ddd044a 89 /**
93dd2725
RW
90 * @var stored_file files that the exporter has written to this temp area keep track of
91 * this in case of duplicates within one export see MDL-16390
92 */
2ddd044a 93 private $newfilehashes;
94
6be1dcae 95 /**
93dd2725
RW
96 * @var string selected exportformat this is also set in
97 * export_config in the portfolio and caller classes
98 */
6be1dcae 99 private $format;
100
93dd2725 101 /** @var bool queued - this is set after the event is triggered */
c95a6095
PL
102 private $queued = false;
103
93dd2725 104 /** @var int expiry time - set the first time the object is saved out */
c95a6095
PL
105 private $expirytime;
106
5d0dbf13 107 /**
93dd2725
RW
108 * @var bool deleted - this is set during the cleanup routine so
109 * that subsequent save() calls can detect it
5d0dbf13
PL
110 */
111 private $deleted = false;
112
87fcac8d 113 /**
93dd2725
RW
114 * Construct a new exporter for use
115 *
116 * @param portfolio_plugin_base $instance portfolio instance (passed by reference)
117 * @param portfolio_caller_base $caller portfolio caller (passed by reference)
118 * @param string $callerfile path to callerfile (relative to dataroot)
119 */
f1d2641d 120 public function __construct(&$instance, &$caller, $callerfile) {
87fcac8d 121 $this->instance =& $instance;
122 $this->caller =& $caller;
123 if ($instance) {
edf1fc35 124 $this->instancefile = 'portfolio/' . $instance->get('plugin') . '/lib.php';
87fcac8d 125 $this->instance->set('exporter', $this);
126 }
127 $this->callerfile = $callerfile;
128 $this->stage = PORTFOLIO_STAGE_CONFIG;
87fcac8d 129 $this->caller->set('exporter', $this);
130 $this->alreadystolen = array();
2ddd044a 131 $this->newfilehashes = array();
87fcac8d 132 }
133
93dd2725
RW
134 /**
135 * Generic getter for properties belonging to this instance
136 * <b>outside</b> the subclasses like name, visible etc.
137 *
138 * @param string $field property's name
139 * @return portfolio_format|mixed
140 */
87fcac8d 141 public function get($field) {
6be1dcae 142 if ($field == 'format') {
143 return portfolio_format_object($this->format);
59dd457e
PL
144 } else if ($field == 'formatclass') {
145 return $this->format;
6be1dcae 146 }
87fcac8d 147 if (property_exists($this, $field)) {
148 return $this->{$field};
149 }
150 $a = (object)array('property' => $field, 'class' => get_class($this));
151 throw new portfolio_export_exception($this, 'invalidproperty', 'portfolio', null, $a);
152 }
153
154 /**
93dd2725
RW
155 * Generic setter for properties belonging to this instance
156 * <b>outside</b> the subclass like name, visible, etc.
157 *
158 * @param string $field property's name
159 * @param mixed $value property's value
160 * @return bool
161 * @throws portfolio_export_exception
162 */
87fcac8d 163 public function set($field, &$value) {
164 if (property_exists($this, $field)) {
165 $this->{$field} =& $value;
166 if ($field == 'instance') {
edf1fc35 167 $this->instancefile = 'portfolio/' . $this->instance->get('plugin') . '/lib.php';
87fcac8d 168 $this->instance->set('exporter', $this);
169 }
170 $this->dirty = true;
171 return true;
172 }
173 $a = (object)array('property' => $field, 'class' => get_class($this));
174 throw new portfolio_export_exception($this, 'invalidproperty', 'portfolio', null, $a);
175
176 }
177
f2e933bb 178 /**
93dd2725
RW
179 * Sets this export to force queued.
180 * Sometimes plugins need to set this randomly
181 * if an external system changes its mind
182 * about what's supported
183 */
f2e933bb 184 public function set_forcequeue() {
185 $this->forcequeue = true;
186 }
187
87fcac8d 188 /**
93dd2725
RW
189 * Process the given stage calling whatever functions are necessary
190 *
191 * @param int $stage (see PORTFOLIO_STAGE_* constants)
192 * @param bool $alreadystolen used to avoid letting plugins steal control twice.
193 * @return bool whether or not to process the next stage. this is important as the function is called recursively.
194 */
87fcac8d 195 public function process_stage($stage, $alreadystolen=false) {
196 $this->set('stage', $stage);
197 if ($alreadystolen) {
198 $this->alreadystolen[$stage] = true;
199 } else {
200 if (!array_key_exists($stage, $this->alreadystolen)) {
201 $this->alreadystolen[$stage] = false;
202 }
203 }
87fcac8d 204 if (!$this->alreadystolen[$stage] && $url = $this->instance->steal_control($stage)) {
2e0c7925 205 $this->save();
86fef776 206 redirect($url); // does not return
2e0c7925 207 } else {
208 $this->save();
87fcac8d 209 }
210
211 $waiting = $this->instance->get_export_config('wait');
212 if ($stage > PORTFOLIO_STAGE_QUEUEORWAIT && empty($waiting)) {
213 $stage = PORTFOLIO_STAGE_FINISHED;
214 }
215 $functionmap = array(
216 PORTFOLIO_STAGE_CONFIG => 'config',
217 PORTFOLIO_STAGE_CONFIRM => 'confirm',
218 PORTFOLIO_STAGE_QUEUEORWAIT => 'queueorwait',
219 PORTFOLIO_STAGE_PACKAGE => 'package',
220 PORTFOLIO_STAGE_CLEANUP => 'cleanup',
221 PORTFOLIO_STAGE_SEND => 'send',
222 PORTFOLIO_STAGE_FINISHED => 'finished'
223 );
224
225 $function = 'process_stage_' . $functionmap[$stage];
226 try {
227 if ($this->$function()) {
228 // if we get through here it means control was returned
229 // as opposed to wanting to stop processing
230 // eg to wait for user input.
231 $this->save();
232 $stage++;
233 return $this->process_stage($stage);
234 } else {
235 $this->save();
236 return false;
237 }
238 } catch (portfolio_caller_exception $e) {
239 portfolio_export_rethrow_exception($this, $e);
240 } catch (portfolio_plugin_exception $e) {
241 portfolio_export_rethrow_exception($this, $e);
242 } catch (portfolio_export_exception $e) {
243 throw $e;
244 } catch (Exception $e) {
245 debugging(get_string('thirdpartyexception', 'portfolio', get_class($e)));
a67dc274 246 debugging($e);
87fcac8d 247 portfolio_export_rethrow_exception($this, $e);
248 }
249 }
250
251 /**
93dd2725
RW
252 * Helper function to return the portfolio instance
253 *
254 * @return portfolio_plugin_base subclass
255 */
87fcac8d 256 public function instance() {
257 return $this->instance;
258 }
259
260 /**
93dd2725
RW
261 * Helper function to return the caller object
262 *
263 * @return portfolio_caller_base subclass
264 */
87fcac8d 265 public function caller() {
266 return $this->caller;
267 }
268
269 /**
93dd2725
RW
270 * Processes the 'config' stage of the export
271 *
272 * @return bool whether or not to process the next stage. this is important as the control function is called recursively.
273 */
87fcac8d 274 public function process_stage_config() {
24ba58ee 275 global $OUTPUT, $CFG;
87fcac8d 276 $pluginobj = $callerobj = null;
277 if ($this->instance->has_export_config()) {
278 $pluginobj = $this->instance;
279 }
280 if ($this->caller->has_export_config()) {
281 $callerobj = $this->caller;
282 }
59dd457e 283 $formats = portfolio_supported_formats_intersect($this->caller->supported_formats(), $this->instance->supported_formats());
87fcac8d 284 $expectedtime = $this->instance->expected_time($this->caller->expected_time());
285 if (count($formats) == 0) {
286 // something went wrong, we should not have gotten this far.
40d6bc48 287 throw new portfolio_export_exception($this, 'nocommonformats', 'portfolio', null, array('location' => get_class($this->caller), 'formats' => implode(',', $formats)));
87fcac8d 288 }
289 // even if neither plugin or caller wants any config, we have to let the user choose their format, and decide to wait.
290 if ($pluginobj || $callerobj || count($formats) > 1 || ($expectedtime != PORTFOLIO_TIME_LOW && $expectedtime != PORTFOLIO_TIME_FORCEQUEUE)) {
291 $customdata = array(
292 'instance' => $this->instance,
c95a6095 293 'id' => $this->id,
87fcac8d 294 'plugin' => $pluginobj,
295 'caller' => $callerobj,
296 'userid' => $this->user->id,
297 'formats' => $formats,
298 'expectedtime' => $expectedtime,
299 );
24ba58ee 300 require_once($CFG->libdir . '/portfolio/forms.php');
87fcac8d 301 $mform = new portfolio_export_form('', $customdata);
302 if ($mform->is_cancelled()){
303 $this->cancel_request();
304 } else if ($fromform = $mform->get_data()){
305 if (!confirm_sesskey()) {
306 throw new portfolio_export_exception($this, 'confirmsesskeybad');
307 }
308 $pluginbits = array();
309 $callerbits = array();
310 foreach ($fromform as $key => $value) {
311 if (strpos($key, 'plugin_') === 0) {
312 $pluginbits[substr($key, 7)] = $value;
313 } else if (strpos($key, 'caller_') === 0) {
314 $callerbits[substr($key, 7)] = $value;
315 }
316 }
317 $callerbits['format'] = $pluginbits['format'] = $fromform->format;
318 $pluginbits['wait'] = $fromform->wait;
319 if ($expectedtime == PORTFOLIO_TIME_LOW) {
320 $pluginbits['wait'] = 1;
321 $pluginbits['hidewait'] = 1;
322 } else if ($expectedtime == PORTFOLIO_TIME_FORCEQUEUE) {
323 $pluginbits['wait'] = 0;
324 $pluginbits['hidewait'] = 1;
325 $this->forcequeue = true;
326 }
327 $callerbits['hideformat'] = $pluginbits['hideformat'] = (count($formats) == 1);
328 $this->caller->set_export_config($callerbits);
329 $this->instance->set_export_config($pluginbits);
6be1dcae 330 $this->set('format', $fromform->format);
87fcac8d 331 return true;
332 } else {
11fbe3fc 333 $this->print_header(get_string('configexport', 'portfolio'));
aa9a6867 334 echo $OUTPUT->box_start();
87fcac8d 335 $mform->display();
aa9a6867 336 echo $OUTPUT->box_end();
7e0d6675 337 echo $OUTPUT->footer();
87fcac8d 338 return false;;
339 }
340 } else {
341 $this->noexportconfig = true;
342 $format = array_shift($formats);
343 $config = array(
344 'hidewait' => 1,
345 'wait' => (($expectedtime == PORTFOLIO_TIME_LOW) ? 1 : 0),
346 'format' => $format,
347 'hideformat' => 1
348 );
6be1dcae 349 $this->set('format', $format);
87fcac8d 350 $this->instance->set_export_config($config);
351 $this->caller->set_export_config(array('format' => $format, 'hideformat' => 1));
352 if ($expectedtime == PORTFOLIO_TIME_FORCEQUEUE) {
353 $this->forcequeue = true;
354 }
355 return true;
356 // do not break - fall through to confirm
357 }
358 }
359
360 /**
93dd2725
RW
361 * Processes the 'confirm' stage of the export
362 *
363 * @return bool whether or not to process the next stage. this is important as the control function is called recursively.
364 */
87fcac8d 365 public function process_stage_confirm() {
3c159385 366 global $CFG, $DB, $OUTPUT;
87fcac8d 367
368 $previous = $DB->get_records(
369 'portfolio_log',
370 array(
371 'userid' => $this->user->id,
372 'portfolio' => $this->instance->get('id'),
373 'caller_sha1' => $this->caller->get_sha1(),
374 )
375 );
376 if (isset($this->noexportconfig) && empty($previous)) {
377 return true;
378 }
379 $strconfirm = get_string('confirmexport', 'portfolio');
c95a6095
PL
380 $baseurl = $CFG->wwwroot . '/portfolio/add.php?sesskey=' . sesskey() . '&id=' . $this->get('id');
381 $yesurl = $baseurl . '&stage=' . PORTFOLIO_STAGE_QUEUEORWAIT;
382 $nourl = $baseurl . '&cancel=1';
11fbe3fc 383 $this->print_header(get_string('confirmexport', 'portfolio'));
aa9a6867 384 echo $OUTPUT->box_start();
bf0fc3c0 385 echo $OUTPUT->heading(get_string('confirmsummary', 'portfolio'), 3);
87fcac8d 386 $mainsummary = array();
387 if (!$this->instance->get_export_config('hideformat')) {
388 $mainsummary[get_string('selectedformat', 'portfolio')] = get_string('format_' . $this->instance->get_export_config('format'), 'portfolio');
389 }
390 if (!$this->instance->get_export_config('hidewait')) {
391 $mainsummary[get_string('selectedwait', 'portfolio')] = get_string(($this->instance->get_export_config('wait') ? 'yes' : 'no'));
392 }
393 if ($previous) {
394 $previousstr = '';
395 foreach ($previous as $row) {
396 $previousstr .= userdate($row->time);
397 if ($row->caller_class != get_class($this->caller)) {
398 require_once($CFG->dirroot . '/' . $row->caller_file);
399 $previousstr .= ' (' . call_user_func(array($row->caller_class, 'display_name')) . ')';
400 }
401 $previousstr .= '<br />';
402 }
403 $mainsummary[get_string('exportedpreviously', 'portfolio')] = $previousstr;
404 }
405 if (!$csummary = $this->caller->get_export_summary()) {
406 $csummary = array();
407 }
408 if (!$isummary = $this->instance->get_export_summary()) {
409 $isummary = array();
410 }
411 $mainsummary = array_merge($mainsummary, $csummary, $isummary);
642816a6 412 $table = new html_table();
bf0fc3c0 413 $table->attributes['class'] = 'generaltable exportsummary';
87fcac8d 414 $table->data = array();
415 foreach ($mainsummary as $string => $value) {
416 $table->data[] = array($string, $value);
417 }
16be8974 418 echo html_writer::table($table);
642816a6 419 echo $OUTPUT->confirm($strconfirm, $yesurl, $nourl);
aa9a6867 420 echo $OUTPUT->box_end();
7e0d6675 421 echo $OUTPUT->footer();
87fcac8d 422 return false;
423 }
424
425 /**
93dd2725
RW
426 * Processes the 'queueornext' stage of the export
427 *
428 * @return bool whether or not to process the next stage. this is important as the control function is called recursively.
429 */
87fcac8d 430 public function process_stage_queueorwait() {
87fcac8d 431 $wait = $this->instance->get_export_config('wait');
432 if (empty($wait)) {
433 events_trigger('portfolio_send', $this->id);
c95a6095 434 $this->queued = true;
87fcac8d 435 return $this->process_stage_finished(true);
436 }
437 return true;
438 }
439
440 /**
93dd2725
RW
441 * Processes the 'package' stage of the export
442 *
443 * @return bool whether or not to process the next stage. this is important as the control function is called recursively.
444 * @throws portfolio_export_exception
445 */
87fcac8d 446 public function process_stage_package() {
447 // now we've agreed on a format,
448 // the caller is given control to package it up however it wants
449 // and then the portfolio plugin is given control to do whatever it wants.
37f03ea0 450 try {
451 $this->caller->prepare_package();
452 } catch (portfolio_exception $e) {
453 throw new portfolio_export_exception($this, 'callercouldnotpackage', 'portfolio', null, $e->getMessage());
454 }
455 catch (file_exception $e) {
456 throw new portfolio_export_exception($this, 'callercouldnotpackage', 'portfolio', null, $e->getMessage());
457 }
458 try {
459 $this->instance->prepare_package();
87fcac8d 460 }
37f03ea0 461 catch (portfolio_exception $e) {
462 throw new portfolio_export_exception($this, 'plugincouldnotpackage', 'portfolio', null, $e->getMessage());
463 }
464 catch (file_exception $e) {
465 throw new portfolio_export_exception($this, 'plugincouldnotpackage', 'portfolio', null, $e->getMessage());
87fcac8d 466 }
467 return true;
468 }
469
470 /**
93dd2725
RW
471 * Processes the 'cleanup' stage of the export
472 *
473 * @param bool $pullok normally cleanup is deferred for pull plugins until after the file is requested from portfolio/file.php
474 * if you want to clean up earlier, pass true here (defaults to false)
475 * @return bool whether or not to process the next stage. this is important as the control function is called recursively.
476 */
87fcac8d 477 public function process_stage_cleanup($pullok=false) {
c95a6095 478 global $CFG, $DB;
87fcac8d 479
480 if (!$pullok && $this->get('instance') && !$this->get('instance')->is_push()) {
87fcac8d 481 return true;
482 }
483 if ($this->get('instance')) {
484 // might not be set - before export really starts
485 $this->get('instance')->cleanup();
486 }
487 $DB->delete_records('portfolio_tempdata', array('id' => $this->id));
488 $fs = get_file_storage();
64f93798 489 $fs->delete_area_files(SYSCONTEXTID, 'portfolio', 'exporter', $this->id);
5d0dbf13 490 $this->deleted = true;
87fcac8d 491 return true;
492 }
493
494 /**
93dd2725
RW
495 * Processes the 'send' stage of the export
496 *
497 * @return bool whether or not to process the next stage. this is important as the control function is called recursively.
498 */
87fcac8d 499 public function process_stage_send() {
500 // send the file
37f03ea0 501 try {
502 $this->instance->send_package();
503 }
504 catch (portfolio_plugin_exception $e) {
505 // not catching anything more general here. plugins with dependencies on other libraries that throw exceptions should catch and rethrow.
506 // eg curl exception
507 throw new portfolio_export_exception($this, 'failedtosendpackage', 'portfolio', null, $e->getMessage());
87fcac8d 508 }
de01a507 509 // only log push types, pull happens in send_file
510 if ($this->get('instance')->is_push()) {
511 $this->log_transfer();
512 }
513 return true;
514 }
515
516 /**
93dd2725
RW
517 * Log the transfer
518 *
519 * this should only be called after the file has been sent
520 * either via push, or sent from a pull request.
521 */
de01a507 522 public function log_transfer() {
87fcac8d 523 global $DB;
524 $l = array(
525 'userid' => $this->user->id,
526 'portfolio' => $this->instance->get('id'),
527 'caller_file' => $this->callerfile,
528 'caller_sha1' => $this->caller->get_sha1(),
529 'caller_class' => get_class($this->caller),
5d0dbf13
PL
530 'continueurl' => $this->instance->get_static_continue_url(),
531 'returnurl' => $this->caller->get_return_url(),
532 'tempdataid' => $this->id,
87fcac8d 533 'time' => time(),
534 );
535 $DB->insert_record('portfolio_log', $l);
87fcac8d 536 }
537
5d0dbf13 538 /**
93dd2725 539 * In some cases (mahara) we need to update this after the log has been done
5d0dbf13 540 * because of MDL-20872
93dd2725
RW
541 *
542 * @param string $url link to be recorded to portfolio log
5d0dbf13
PL
543 */
544 public function update_log_url($url) {
545 global $DB;
546 $DB->set_field('portfolio_log', 'continueurl', $url, array('tempdataid' => $this->id));
547 }
548
87fcac8d 549 /**
93dd2725
RW
550 * Processes the 'finish' stage of the export
551 *
552 * @param bool $queued let the process to be queued
553 * @return bool whether or not to process the next stage. this is important as the control function is called recursively.
554 */
87fcac8d 555 public function process_stage_finished($queued=false) {
7e0d6675 556 global $OUTPUT;
87fcac8d 557 $returnurl = $this->caller->get_return_url();
5d0dbf13 558 $continueurl = $this->instance->get_interactive_continue_url();
87fcac8d 559 $extras = $this->instance->get_extra_finish_options();
560
561 $key = 'exportcomplete';
37f03ea0 562 if ($queued || $this->forcequeue) {
87fcac8d 563 $key = 'exportqueued';
564 if ($this->forcequeue) {
565 $key = 'exportqueuedforced';
566 }
567 }
11fbe3fc 568 $this->print_header(get_string($key, 'portfolio'), false);
a67dc274 569 self::print_finish_info($returnurl, $continueurl, $extras);
7e0d6675 570 echo $OUTPUT->footer();
87fcac8d 571 return false;
572 }
573
574
575 /**
93dd2725
RW
576 * Local print header function to be reused across the export
577 *
578 * @param string $headingstr full language string
579 * @param bool $summary (optional) to print summary, default is set to true
580 * @return void
581 */
87fcac8d 582 public function print_header($headingstr, $summary=true) {
de6d81e6 583 global $OUTPUT, $PAGE;
87fcac8d 584 $titlestr = get_string('exporting', 'portfolio');
585 $headerstr = get_string('exporting', 'portfolio');
586
de6d81e6 587 $PAGE->set_title($titlestr);
588 $PAGE->set_heading($headerstr);
589 echo $OUTPUT->header();
11fbe3fc 590 echo $OUTPUT->heading($headingstr);
87fcac8d 591
592 if (!$summary) {
593 return;
594 }
595
11fbe3fc 596 echo $OUTPUT->box_start();
aa9a6867 597 echo $OUTPUT->box_start();
87fcac8d 598 echo $this->caller->heading_summary();
aa9a6867 599 echo $OUTPUT->box_end();
11fbe3fc
PL
600 if ($this->instance) {
601 echo $OUTPUT->box_start();
602 echo $this->instance->heading_summary();
603 echo $OUTPUT->box_end();
604 }
605 echo $OUTPUT->box_end();
87fcac8d 606 }
607
608 /**
93dd2725
RW
609 * Cancels a potfolio request and cleans up the tempdata
610 * and redirects the user back to where they started
611 *
612 * @param bool $logreturn options to return to porfolio log or caller return page
613 * @return void
614 * @uses exit
615 */
c95a6095
PL
616 public function cancel_request($logreturn=false) {
617 global $CFG;
87fcac8d 618 if (!isset($this)) {
619 return;
620 }
621 $this->process_stage_cleanup(true);
c95a6095
PL
622 if ($logreturn) {
623 redirect($CFG->wwwroot . '/user/portfoliologs.php');
624 }
87fcac8d 625 redirect($this->caller->get_return_url());
626 exit;
627 }
628
629 /**
93dd2725
RW
630 * Writes out the contents of this object and all its data to the portfolio_tempdata table and sets the 'id' field.
631 *
632 * @return void
633 */
87fcac8d 634 public function save() {
635 global $DB;
636 if (empty($this->id)) {
637 $r = (object)array(
638 'data' => base64_encode(serialize($this)),
639 'expirytime' => time() + (60*60*24),
640 'userid' => $this->user->id,
e1b9ac3e 641 'instance' => (empty($this->instance)) ? null : $this->instance->get('id'),
87fcac8d 642 );
643 $this->id = $DB->insert_record('portfolio_tempdata', $r);
c95a6095 644 $this->expirytime = $r->expirytime;
87fcac8d 645 $this->save(); // call again so that id gets added to the save data.
646 } else {
a67dc274 647 if (!$r = $DB->get_record('portfolio_tempdata', array('id' => $this->id))) {
5d0dbf13 648 if (!$this->deleted) {
9d429f0b 649 //debugging("tried to save current object, but failed - see MDL-20872");
5d0dbf13 650 }
a67dc274
PL
651 return;
652 }
e1b9ac3e
PL
653 $r->data = base64_encode(serialize($this));
654 $r->instance = (empty($this->instance)) ? null : $this->instance->get('id');
655 $DB->update_record('portfolio_tempdata', $r);
87fcac8d 656 }
657 }
658
659 /**
93dd2725
RW
660 * Rewakens the data from the database given the id.
661 * Makes sure to load the required files with the class definitions
662 *
663 * @param int $id id of data
664 * @return portfolio_exporter
665 */
87fcac8d 666 public static function rewaken_object($id) {
667 global $DB, $CFG;
668 require_once($CFG->libdir . '/filelib.php');
24ba58ee
PL
669 require_once($CFG->libdir . '/portfolio/exporter.php');
670 require_once($CFG->libdir . '/portfolio/caller.php');
671 require_once($CFG->libdir . '/portfolio/plugin.php');
87fcac8d 672 if (!$data = $DB->get_record('portfolio_tempdata', array('id' => $id))) {
a67dc274
PL
673 // maybe it's been finished already by a pull plugin
674 // so look in the logs
675 if ($log = $DB->get_record('portfolio_log', array('tempdataid' => $id))) {
676 self::print_cleaned_export($log);
677 }
87fcac8d 678 throw new portfolio_exception('invalidtempid', 'portfolio');
679 }
680 $exporter = unserialize(base64_decode($data->data));
681 if ($exporter->instancefile) {
682 require_once($CFG->dirroot . '/' . $exporter->instancefile);
683 }
684 require_once($CFG->dirroot . '/' . $exporter->callerfile);
685 $exporter = unserialize(serialize($exporter));
c95a6095
PL
686 if (!$exporter->get('id')) {
687 // workaround for weird case
688 // where the id doesn't get saved between a new insert
689 // and the subsequent call that sets this field in the serialised data
690 $exporter->set('id', $id);
691 $exporter->save();
692 }
87fcac8d 693 return $exporter;
694 }
695
696 /**
93dd2725
RW
697 * Helper function to create the beginnings of a file_record object
698 * to create a new file in the portfolio_temporary working directory.
18cdcdbf
SH
699 * Use write_new_file or copy_existing_file externally
700 * @see write_new_file
701 * @see copy_existing_file
93dd2725
RW
702 *
703 * @param string $name filename of new record
704 * @return object
705 */
87fcac8d 706 private function new_file_record_base($name) {
707 return (object)array_merge($this->get_base_filearea(), array(
708 'filepath' => '/',
709 'filename' => $name,
710 ));
711 }
712
713 /**
93dd2725
RW
714 * Verifies a rewoken object.
715 * Checks to make sure it belongs to the same user and session as is currently in use.
716 *
717 * @param bool $readonly if we're reawakening this for a user to just display in the log view, don't verify the sessionkey
718 * @throws portfolio_exception
719 */
de01a507 720 public function verify_rewaken($readonly=false) {
c95a6095
PL
721 global $USER, $CFG;
722 if ($this->get('user')->id != $USER->id) { // make sure it belongs to the right user
87fcac8d 723 throw new portfolio_exception('notyours', 'portfolio');
724 }
c95a6095 725 if (!$readonly && $this->get('instance') && !$this->get('instance')->allows_multiple_exports()
24ba58ee 726 && ($already = portfolio_existing_exports($this->get('user')->id, $this->get('instance')->get('plugin')))
c95a6095
PL
727 && array_shift(array_keys($already)) != $this->get('id')
728 ) {
729 $a = (object)array(
730 'plugin' => $this->get('instance')->get('plugin'),
731 'link' => $CFG->wwwroot . '/user/portfoliologs.php',
732 );
733 throw new portfolio_exception('nomultipleexports', 'portfolio', '', $a);
87fcac8d 734 }
c95a6095 735 if (!$this->caller->check_permissions()) { // recall the caller permission check
766d61cb 736 throw new portfolio_caller_exception('nopermissions', 'portfolio', $this->caller->get_return_url());
737 }
87fcac8d 738 }
739 /**
93dd2725
RW
740 * Copies a file from somewhere else in moodle
741 * to the portfolio temporary working directory
742 * associated with this export
743 *
744 * @param stored_file $oldfile existing stored file object
745 * @return stored_file|bool new file object
746 */
87fcac8d 747 public function copy_existing_file($oldfile) {
2ddd044a 748 if (array_key_exists($oldfile->get_contenthash(), $this->newfilehashes)) {
749 return $this->newfilehashes[$oldfile->get_contenthash()];
750 }
87fcac8d 751 $fs = get_file_storage();
752 $file_record = $this->new_file_record_base($oldfile->get_filename());
6be1dcae 753 if ($dir = $this->get('format')->get_file_directory()) {
754 $file_record->filepath = '/'. $dir . '/';
755 }
87fcac8d 756 try {
2ddd044a 757 $newfile = $fs->create_file_from_storedfile($file_record, $oldfile->get_id());
758 $this->newfilehashes[$newfile->get_contenthash()] = $newfile;
759 return $newfile;
87fcac8d 760 } catch (file_exception $e) {
761 return false;
762 }
763 }
764
765 /**
93dd2725
RW
766 * Writes out some content to a file
767 * in the portfolio temporary working directory
768 * associated with this export.
769 *
770 * @param string $content content to write
771 * @param string $name filename to use
772 * @param bool $manifest whether this is the main file or an secondary file (eg attachment)
773 * @return stored_file
774 */
6be1dcae 775 public function write_new_file($content, $name, $manifest=true) {
87fcac8d 776 $fs = get_file_storage();
777 $file_record = $this->new_file_record_base($name);
6be1dcae 778 if (empty($manifest) && ($dir = $this->get('format')->get_file_directory())) {
779 $file_record->filepath = '/' . $dir . '/';
780 }
87fcac8d 781 return $fs->create_file_from_string($file_record, $content);
782 }
783
de01a507 784 /**
93dd2725
RW
785 * Zips all files in the temporary directory
786 *
787 * @param string $filename name of resulting zipfile (optional, defaults to portfolio-export.zip)
788 * @param string $filepath subpath in the filearea (optional, defaults to final)
789 * @return stored_file|bool resulting stored_file object, or false
790 */
37f03ea0 791 public function zip_tempfiles($filename='portfolio-export.zip', $filepath='/final/') {
792 $zipper = new zip_packer();
793
dab61f9a
DM
794 list ($contextid, $component, $filearea, $itemid) = array_values($this->get_base_filearea());
795 if ($newfile = $zipper->archive_to_storage($this->get_tempfiles(), $contextid, $component, $filearea, $itemid, $filepath, $filename, $this->user->id)) {
37f03ea0 796 return $newfile;
797 }
798 return false;
799
800 }
801
87fcac8d 802 /**
93dd2725
RW
803 * Returns an arary of files in the temporary working directory
804 * for this export.
805 * Always use this instead of the files api directly
806 *
807 * @param string $skipfile name of the file to be skipped
808 * @return array of stored_file objects keyed by name
809 */
5d0dbf13 810 public function get_tempfiles($skipfile='portfolio-export.zip') {
87fcac8d 811 $fs = get_file_storage();
849b9a6a 812 $files = $fs->get_area_files(SYSCONTEXTID, 'portfolio', 'exporter', $this->id, 'sortorder, itemid, filepath, filename', false);
87fcac8d 813 if (empty($files)) {
814 return array();
815 }
816 $returnfiles = array();
817 foreach ($files as $f) {
5d0dbf13
PL
818 if ($f->get_filename() == $skipfile) {
819 continue;
820 }
d9c2628b 821 $returnfiles[$f->get_filepath() . '/' . $f->get_filename()] = $f;
87fcac8d 822 }
823 return $returnfiles;
824 }
825
826 /**
93dd2725
RW
827 * Returns the context, filearea, and itemid.
828 * Parts of a filearea (not filepath) to be used by
829 * plugins if they want to do things like zip up the contents of
830 * the temp area to here, or something that can't be done just using
831 * write_new_file, copy_existing_file or get_tempfiles
832 *
833 * @return array contextid, filearea, itemid are the keys.
834 */
87fcac8d 835 public function get_base_filearea() {
836 return array(
837 'contextid' => SYSCONTEXTID,
64f93798
PS
838 'component' => 'portfolio',
839 'filearea' => 'exporter',
840 'itemid' => $this->id,
87fcac8d 841 );
842 }
843
93dd2725
RW
844 /**
845 * Wrapper function to print a friendly error to users
846 * This is generally caused by them hitting an expired transfer
847 * through the usage of the backbutton
848 *
849 * @uses exit
850 */
c737eed8 851 public static function print_expired_export() {
de6d81e6 852 global $CFG, $OUTPUT, $PAGE;
c737eed8 853 $title = get_string('exportexpired', 'portfolio');
de6d81e6 854 $PAGE->navbar->add(get_string('exportexpired', 'portfolio'));
855 $PAGE->set_title($title);
856 $PAGE->set_heading($title);
857 echo $OUTPUT->header();
aa9a6867 858 echo $OUTPUT->notification(get_string('exportexpireddesc', 'portfolio'));
859 echo $OUTPUT->continue_button($CFG->wwwroot);
7e0d6675 860 echo $OUTPUT->footer();
c737eed8 861 exit;
862 }
863
93dd2725
RW
864 /**
865 * Wrapper function to print a friendly error to users
866 *
867 * @param stdClass $log portfolio_log object
868 * @param portfolio_plugin_base $instance portfolio instance
869 * @uses exit
870 */
5d0dbf13 871 public static function print_cleaned_export($log, $instance=null) {
a67dc274 872 global $CFG, $OUTPUT, $PAGE;
86fef776 873 if (empty($instance) || !$instance instanceof portfolio_plugin_base) {
5d0dbf13
PL
874 $instance = portfolio_instance($log->portfolio);
875 }
a67dc274
PL
876 $title = get_string('exportalreadyfinished', 'portfolio');
877 $PAGE->navbar->add($title);
878 $PAGE->set_title($title);
879 $PAGE->set_heading($title);
880 echo $OUTPUT->header();
881 echo $OUTPUT->notification(get_string('exportalreadyfinished', 'portfolio'));
5d0dbf13 882 self::print_finish_info($log->returnurl, $instance->resolve_static_continue_url($log->continueurl));
a67dc274
PL
883 echo $OUTPUT->continue_button($CFG->wwwroot);
884 echo $OUTPUT->footer();
885 exit;
886 }
887
93dd2725
RW
888 /**
889 * Wrapper function to print continue and/or return link
890 *
891 * @param string $returnurl link to previos page
892 * @param string $continueurl continue to next page
893 * @param array $extras (optional) other links to be display.
894 */
a67dc274
PL
895 public static function print_finish_info($returnurl, $continueurl, $extras=null) {
896 if ($returnurl) {
897 echo '<a href="' . $returnurl . '">' . get_string('returntowhereyouwere', 'portfolio') . '</a><br />';
898 }
899 if ($continueurl) {
900 echo '<a href="' . $continueurl . '">' . get_string('continuetoportfolio', 'portfolio') . '</a><br />';
901 }
902 if (is_array($extras)) {
903 foreach ($extras as $link => $string) {
904 echo '<a href="' . $link . '">' . $string . '</a><br />';
905 }
906 }
907 }
87fcac8d 908}