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