MDL-30989 portfolio: Fixed up phpdocs
[moodle.git] / lib / portfolio / caller.php
1 <?php
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/>.
17 /**
18  * This file contains the base classes that are extended to create portfolio export functionality.
19  *
20  * For places in moodle that want to
21  * add export functionality to subclass from {@link http://docs.moodle.org/dev/Adding_a_Portfolio_Button_to_a_page}
22  *
23  * @package core_portfolio
24  * @copyright 2008 Penny Leach <penny@catalyst.net.nz>, Martin Dougiamas
25  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26  */
28 defined('MOODLE_INTERNAL') || die();
30 /**
31  * Base class for callers
32  *
33  * @link See http://docs.moodle.org/dev/Adding_a_Portfolio_Button_to_a_page
34  * @see also portfolio_module_caller_base
35  *
36  * @package core_portfolio
37  * @category portfolio
38  * @copyright 2008 Penny Leach <penny@catalyst.net.nz>
39  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40  */
41 abstract class portfolio_caller_base {
43     /** @var stdClass course active during the call */
44     protected $course;
46     /** @var array configuration used for export. Use set_export_config and get_export_config to access */
47     protected $exportconfig = array();
49     /** @var stdclass user currently exporting content */
50     protected $user;
52     /** @var stdClass a reference to the exporter object */
53     protected $exporter;
55     /** @var array can be optionally overridden by subclass constructors */
56     protected $supportedformats;
58     /** @var stored_file single file exports configuration*/
59     protected $singlefile;
61     /** @var stored_file|object set this for multi file exports */
62     protected $multifiles;
64     /** @var string set this for generated-file exports */
65     protected $intendedmimetype;
67     /**
68      * Create portfolio_caller object
69      *
70      * @param array $callbackargs argument properties
71      */
72     public function __construct($callbackargs) {
73         $expected = call_user_func(array(get_class($this), 'expected_callbackargs'));
74         foreach ($expected as $key => $required) {
75             if (!array_key_exists($key, $callbackargs)) {
76                 if ($required) {
77                     $a = (object)array('arg' => $key, 'class' => get_class($this));
78                     throw new portfolio_caller_exception('missingcallbackarg', 'portfolio', null, $a);
79                 }
80                 continue;
81             }
82             $this->{$key} = $callbackargs[$key];
83         }
84     }
86     /**
87      * If this caller wants any additional config items,
88      * they should be defined here.
89      *
90      * @param moodleform $mform passed by reference, add elements to it.
91      * @param portfolio_plugin_base $instance subclass of portfolio_plugin_base
92      */
93     public function export_config_form(&$mform, $instance) {}
96     /**
97      * Whether this caller wants any additional
98      * config during export (eg options or metadata)
99      *
100      * @return bool
101      */
102     public function has_export_config() {
103         return false;
104     }
106     /**
107      * Just like the moodle form validation function,
108      * this is passed in the data array from the form
109      * and if a non empty array is returned, form processing will stop.
110      *
111      * @param array $data data from form.
112      */
113     public function export_config_validation($data) {}
115     /**
116      * How long does this reasonably expect to take..
117      * Should we offer the user the option to wait..?
118      * This is deliberately nonstatic so it can take filesize into account
119      * the portfolio plugin can override this.
120      * (so for example even if a huge file is being sent,
121      * the download portfolio plugin doesn't care )
122      */
123     public abstract function expected_time();
125     /**
126      * Helper method to calculate expected time for multi or single file exports
127      *
128      * @return string file time expectation
129      */
130     public function expected_time_file() {
131         if ($this->multifiles) {
132             return portfolio_expected_time_file($this->multifiles);
133         }
134         else if ($this->singlefile) {
135             return portfolio_expected_time_file($this->singlefile);
136         }
137         return PORTFOLIO_TIME_LOW;
138     }
140     /**
141      * Function to build navigation
142      */
143     public abstract function get_navigation();
145     /**
146      * Helper function to get sha1
147      */
148     public abstract function get_sha1();
150     /**
151      * Helper function to calculate the sha1 for multi or single file exports
152      *
153      * @return string sha1 file exports
154      */
155     public function get_sha1_file() {
156         if (empty($this->singlefile) && empty($this->multifiles)) {
157             throw new portfolio_caller_exception('invalidsha1file', 'portfolio', $this->get_return_url());
158         }
159         if ($this->singlefile) {
160             return $this->singlefile->get_contenthash();
161         }
162         $sha1s = array();
163         foreach ($this->multifiles as $file) {
164             $sha1s[] = $file->get_contenthash();
165         }
166         asort($sha1s);
167         return sha1(implode('', $sha1s));
168     }
170     /**
171      * Generic getter for properties belonging to this instance
172      * <b>outside</b> the subclasses
173      * like name, visible etc.
174      *
175      * @param string $field property's name
176      * @return mixed
177      * @throws portfolio_export_exception
178      */
179     public function get($field) {
180         if (property_exists($this, $field)) {
181             return $this->{$field};
182         }
183         $a = (object)array('property' => $field, 'class' => get_class($this));
184         throw new portfolio_export_exception($this->get('exporter'), 'invalidproperty', 'portfolio', $this->get_return_url(), $a);
185     }
187     /**
188      * Generic setter for properties belonging to this instance
189      * <b>outside</b> the subclass
190      * like name, visible, etc.
191      *
192      * @param string $field property's name
193      * @param mixed $value property's value
194      * @return bool
195      * @throws moodle_exception
196      */
197     public final function set($field, &$value) {
198         if (property_exists($this, $field)) {
199             $this->{$field} =& $value;
200             $this->dirty = true;
201             return true;
202         }
203         $a = (object)array('property' => $field, 'class' => get_class($this));
204         throw new portfolio_export_exception($this->get('exporter'), 'invalidproperty', 'portfolio', $this->get_return_url(), $a);
205     }
207     /**
208      * Stores the config generated at export time.
209      * Subclasses can retrieve values using
210      * @see get_export_config
211      *
212      * @param array $config formdata
213      */
214     public final function set_export_config($config) {
215         $allowed = array_merge(
216             array('wait', 'hidewait', 'format', 'hideformat'),
217             $this->get_allowed_export_config()
218         );
219         foreach ($config as $key => $value) {
220             if (!in_array($key, $allowed)) {
221                 $a = (object)array('property' => $key, 'class' => get_class($this));
222                 throw new portfolio_export_exception($this->get('exporter'), 'invalidexportproperty', 'portfolio', $this->get_return_url(), $a);
223             }
224             $this->exportconfig[$key] = $value;
225         }
226     }
228     /**
229      * Returns a particular export config value.
230      * Subclasses shouldn't need to override this
231      *
232      * @param string $key the config item to fetch
233      * @return null|mixed of export configuration
234      */
235     public final function get_export_config($key) {
236         $allowed = array_merge(
237             array('wait', 'hidewait', 'format', 'hideformat'),
238             $this->get_allowed_export_config()
239         );
240         if (!in_array($key, $allowed)) {
241             $a = (object)array('property' => $key, 'class' => get_class($this));
242             throw new portfolio_export_exception($this->get('exporter'), 'invalidexportproperty', 'portfolio', $this->get_return_url(), $a);
243         }
244         if (!array_key_exists($key, $this->exportconfig)) {
245             return null;
246         }
247         return $this->exportconfig[$key];
248     }
250     /**
251      * Similar to the other allowed_config functions
252      * if you need export config, you must provide
253      * a list of what the fields are.
254      * Even if you want to store stuff during export
255      * without displaying a form to the user,
256      * you can use this.
257      *
258      * @return array array of allowed keys
259      */
260     public function get_allowed_export_config() {
261         return array();
262     }
264     /**
265      * After the user submits their config,
266      * they're given a confirm screen
267      * summarising what they've chosen.
268      * This function should return a table of nice strings => values
269      * of what they've chosen
270      * to be displayed in a table.
271      *
272      * @return bool
273      */
274     public function get_export_summary() {
275         return false;
276     }
278     /**
279      * Called before the portfolio plugin gets control.
280      * This function should copy all the files it wants to
281      * the temporary directory, using copy_existing_file
282      * or write_new_file
283      *
284      * @see copy_existing_file()
285      * @see write_new_file()
286      */
287     public abstract function prepare_package();
289     /**
290      * Helper function to copy files into the temp area
291      * for single or multi file exports.
292      *
293      * @return stored_file|bool
294      */
295     public function prepare_package_file() {
296         if (empty($this->singlefile) && empty($this->multifiles)) {
297             throw new portfolio_caller_exception('invalidpreparepackagefile', 'portfolio', $this->get_return_url());
298         }
299         if ($this->singlefile) {
300             return $this->exporter->copy_existing_file($this->singlefile);
301         }
302         foreach ($this->multifiles as $file) {
303             $this->exporter->copy_existing_file($file);
304         }
305     }
307     /**
308      * Array of formats this caller supports.
309      *
310      * @return array list of formats
311      */
312     public final function supported_formats() {
313         $basic = $this->base_supported_formats();
314         if (empty($this->supportedformats)) {
315             $specific = array();
316         } else if (!is_array($this->supportedformats)) {
317             debugging(get_class($this) . ' has set a non array value of member variable supported formats - working around but should be fixed in code');
318             $specific = array($this->supportedformats);
319         } else {
320             $specific = $this->supportedformats;
321         }
322         return portfolio_most_specific_formats($specific, $basic);
323     }
325     /**
326      * Base supported formats
327      *
328      * @throws coding_exception
329      */
330     public static function base_supported_formats() {
331         throw new coding_exception('base_supported_formats() method needs to be overridden in each subclass of portfolio_caller_base');
332     }
334     /**
335      * This is the "return to where you were" url
336      */
337     public abstract function get_return_url();
339     /**
340      * Callback to do whatever capability checks required
341      * in the caller (called during the export process
342      */
343     public abstract function check_permissions();
345     /**
346      * Clean name to display to the user about this caller location
347      */
348     public static function display_name() {
349         throw new coding_exception('display_name() method needs to be overridden in each subclass of portfolio_caller_base');
350     }
352     /**
353      * Return a string to put at the header summarising this export.
354      * By default, it just display the name (usually just 'assignment' or something unhelpful
355      *
356      * @return string
357      */
358     public function heading_summary() {
359         return get_string('exportingcontentfrom', 'portfolio', $this->display_name());
360     }
362     /**
363      * Load data
364      */
365     public abstract function load_data();
367     /**
368      * Set up the required files for this export.
369      * This supports either passing files directly
370      * or passing area arguments directly through
371      * to the files api using file_storage::get_area_files
372      *
373      * @param mixed $ids one of:
374      *                   - single file id
375      *                   - single stored_file object
376      *                   - array of file ids or stored_file objects
377      *                   - null
378      * @return void
379      */
380     public function set_file_and_format_data($ids=null /* ..pass arguments to area files here. */) {
381         $args = func_get_args();
382         array_shift($args); // shift off $ids
383         if (empty($ids) && count($args) == 0) {
384             return;
385         }
386         $files = array();
387         $fs = get_file_storage();
388         if (!empty($ids)) {
389             if (is_numeric($ids) || $ids instanceof stored_file) {
390                 $ids = array($ids);
391             }
392             foreach ($ids as $id) {
393                 if ($id instanceof stored_file) {
394                     $files[] = $id;
395                 } else {
396                     $files[] = $fs->get_file_by_id($id);
397                 }
398             }
399         } else if (count($args) != 0) {
400             if (count($args) < 4) {
401                 throw new portfolio_caller_exception('invalidfileareaargs', 'portfolio');
402             }
403             $files = array_values(call_user_func_array(array($fs, 'get_area_files'), $args));
404         }
405         switch (count($files)) {
406             case 0: return;
407             case 1: {
408                 $this->singlefile = $files[0];
409                 return;
410             }
411             default: {
412                 $this->multifiles = $files;
413             }
414         }
415     }
417     /**
418      * The button-location always knows best
419      * what the formats are... so it should be trusted.
420      *
421      * @todo MDL-31298 - re-analyze set_formats_from_button comment
422      * @param array $formats array of PORTFOLIO_FORMAT_XX
423      * @return void
424      */
425     public function set_formats_from_button($formats) {
426         $base = $this->base_supported_formats();
427         if (count($base) != count($formats)
428                 || count($base) != count(array_intersect($base, $formats))) {
429                 $this->supportedformats = portfolio_most_specific_formats($formats, $base);
430                 return;
431         }
432         // in the case where the button hasn't actually set anything,
433         // we need to run through again and resolve conflicts
434         // TODO revisit this comment - it looks to me like it's lying
435         $this->supportedformats = portfolio_most_specific_formats($formats, $formats);
436     }
438     /**
439      * Adds a new format to the list of supported formats.
440      * This functions also handles removing conflicting and less specific
441      * formats at the same time.
442      *
443      * @param string $format one of PORTFOLIO_FORMAT_XX
444      * @return void
445      */
446     protected function add_format($format) {
447         if (in_array($format, $this->supportedformats)) {
448             return;
449         }
450         $this->supportedformats = portfolio_most_specific_formats(array($format), $this->supportedformats);
451     }
453     /**
454      * Gets mimetype
455      *
456      * @return string
457      */
458     public function get_mimetype() {
459         if ($this->singlefile instanceof stored_file) {
460             return $this->singlefile->get_mimetype();
461         } else if (!empty($this->intendedmimetype)) {
462             return $this->intendedmimetype;
463         }
464     }
466     /**
467      * Array of arguments the caller expects to be passed through to it.
468      * This must be keyed on the argument name, and the array value is a boolean,
469      * whether it is required, or just optional
470      * eg array(
471      *     id            => true,
472      *     somethingelse => false
473      * )
474      */
475     public static function expected_callbackargs() {
476         throw new coding_exception('expected_callbackargs() method needs to be overridden in each subclass of portfolio_caller_base');
477     }
480     /**
481      * Return the context for this export. used for $PAGE->set_context
482      *
483      * @param moodle_page $PAGE global page object
484      */
485     public abstract function set_context($PAGE);
488 /**
489  * Base class for module callers.
490  *
491  * This just implements a few of the abstract functions
492  * from portfolio_caller_base so that caller authors
493  * don't need to.
494  * {@link http://docs.moodle.org/dev/Adding_a_Portfolio_Button_to_a_page}
495  * @see also portfolio_caller_base
496  *
497  * @package core_portfolio
498  * @category portfolio
499  * @copyright 2008 Penny Leach <penny@catalyst.net.nz>
500  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
501  */
502 abstract class portfolio_module_caller_base extends portfolio_caller_base {
504     /** @var object coursemodule object. set this in the constructor like $this->cm = get_coursemodule_from_instance('forum', $this->forum->id); */
505     protected $cm;
507     /** @var int cmid */
508     protected $id;
510     /** @var stdclass course object */
511     protected $course;
513     /**
514      * Navigation passed to print_header.
515      * Override this to do something more specific than the module view page
516      *
517      * @return array
518      */
519     public function get_navigation() {
520         $extranav = array('name' => $this->cm->name, 'link' => $this->get_return_url());
521         return array($extranav, $this->cm);
522     }
524     /**
525      * The url to return to after export or on cancel.
526      * Defaults value is set to the module 'view' page.
527      * Override this if it's deeper inside the module.
528      *
529      * @return string
530      */
531     public function get_return_url() {
532         global $CFG;
533         return $CFG->wwwroot . '/mod/' . $this->cm->modname . '/view.php?id=' . $this->cm->id;
534     }
536     /**
537      * Override the parent get function
538      * to make sure when we're asked for a course,
539      * We retrieve the object from the database as needed.
540      *
541      * @param string $key the name of get function
542      * @return stdClass
543      */
544     public function get($key) {
545         if ($key != 'course') {
546             return parent::get($key);
547         }
548         global $DB;
549         if (empty($this->course)) {
550             $this->course = $DB->get_record('course', array('id' => $this->cm->course));
551         }
552         return $this->course;
553     }
555     /**
556      * Return a string to put at the header summarising this export.
557      * by default, this function just display the name and module instance name.
558      * Override this to do something more specific
559      *
560      * @return string
561      */
562     public function heading_summary() {
563         return get_string('exportingcontentfrom', 'portfolio', $this->display_name() . ': ' . $this->cm->name);
564     }
566     /**
567      * Overridden to return the course module context
568      *
569      * @param moodle_page $PAGE global PAGE
570      */
571     public function set_context($PAGE) {
572         $PAGE->set_cm($this->cm);
573     }