3 * Moodle - Modular Object-Oriented Dynamic Learning Environment
5 * Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com
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.
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.
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/>.
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
26 * This file contains the base classes for places in moodle that want to
27 * add export functionality to subclass from.
28 * See http://docs.moodle.org/en/Development:Adding_a_Portfolio_Button_to_a_page
31 defined('MOODLE_INTERNAL') ||
die();
34 * base class for callers
36 * See http://docs.moodle.org/en/Development:Adding_a_Portfolio_Button_to_a_page
37 * {@see also portfolio_module_caller_base}
39 abstract class portfolio_caller_base
{
43 * course that was active during the caller
48 * named array of export config
49 * use{@link set_export_config} and {@link get_export_config} to access
51 protected $exportconfig = array();
55 * user currently exporting content
60 * a reference to the exporter object
65 * this can be overridden in subclasses constructors if they want
67 protected $supportedformats;
70 * set this for single file exports
72 protected $singlefile;
75 * set this for multi file exports
77 protected $multifiles;
80 * set this for generated-file exports
82 protected $intendedmimetype;
84 public function __construct($callbackargs) {
85 $expected = call_user_func(array(get_class($this), 'expected_callbackargs'));
86 foreach ($expected as $key => $required) {
87 if (!array_key_exists($key, $callbackargs)) {
89 $a = (object)array('arg' => $key, 'class' => get_class($this));
90 throw new portfolio_caller_exception('missingcallbackarg', 'portfolio', null, $a);
94 $this->{$key} = $callbackargs[$key];
99 * if this caller wants any additional config items
100 * they should be defined here.
102 * @param array $mform moodleform object (passed by reference) to add elements to
103 * @param object $instance subclass of portfolio_plugin_base
104 * @param integer $userid id of user exporting content
106 public function export_config_form(&$mform, $instance) {}
110 * whether this caller wants any additional
111 * config during export (eg options or metadata)
115 public function has_export_config() {
120 * just like the moodle form validation function
121 * this is passed in the data array from the form
122 * and if a non empty array is returned, form processing will stop.
124 * @param array $data data from form.
125 * @return array keyvalue pairs - form element => error string
127 public function export_config_validation($data) {}
130 * how long does this reasonably expect to take..
131 * should we offer the user the option to wait..
132 * this is deliberately nonstatic so it can take filesize into account
133 * the portfolio plugin can override this.
134 * (so for example even if a huge file is being sent,
135 * the download portfolio plugin doesn't care )
137 * @return string (see PORTFOLIO_TIME_* constants)
139 public abstract function expected_time();
142 * helper method to calculate expected time for multi or single file exports
144 public function expected_time_file() {
145 if ($this->multifiles
) {
146 return portfolio_expected_time_file($this->multifiles
);
148 else if ($this->singlefile
) {
149 return portfolio_expected_time_file($this->singlefile
);
151 return PORTFOLIO_TIME_LOW
;
155 * used for displaying the navigation during the export screens.
157 * this function must be implemented, but can really return anything.
158 * an Exporting.. string will be added on the end.
159 * @return array of $extranav and $cm
161 * to pass to build_navigation
164 public abstract function get_navigation();
169 public abstract function get_sha1();
172 * helper function to calculate the sha1 for multi or single file exports
174 public function get_sha1_file() {
175 if (empty($this->singlefile
) && empty($this->multifiles
)) {
176 throw new portfolio_caller_exception('invalidsha1file', 'portfolio', $this->get_return_url());
178 if ($this->singlefile
) {
179 return $this->singlefile
->get_contenthash();
182 foreach ($this->multifiles
as $file) {
183 $sha1s[] = $file->get_contenthash();
186 return sha1(implode('', $sha1s));
190 * generic getter for properties belonging to this instance
191 * <b>outside</b> the subclasses
192 * like name, visible etc.
194 public function get($field) {
195 if (property_exists($this, $field)) {
196 return $this->{$field};
198 $a = (object)array('property' => $field, 'class' => get_class($this));
199 throw new portfolio_export_exception($this->get('exporter'), 'invalidproperty', 'portfolio', $this->get_return_url(), $a);
203 * generic setter for properties belonging to this instance
204 * <b>outside</b> the subclass
205 * like name, visible, etc.
208 public final function set($field, &$value) {
209 if (property_exists($this, $field)) {
210 $this->{$field} =& $value;
214 $a = (object)array('property' => $field, 'class' => get_class($this));
215 throw new portfolio_export_exception($this->get('exporter'), 'invalidproperty', 'portfolio', $this->get_return_url(), $a);
219 * stores the config generated at export time.
220 * subclasses can retrieve values using
221 * {@link get_export_config}
223 * @param array $config formdata
225 public final function set_export_config($config) {
226 $allowed = array_merge(
227 array('wait', 'hidewait', 'format', 'hideformat'),
228 $this->get_allowed_export_config()
230 foreach ($config as $key => $value) {
231 if (!in_array($key, $allowed)) {
232 $a = (object)array('property' => $key, 'class' => get_class($this));
233 throw new portfolio_export_exception($this->get('exporter'), 'invalidexportproperty', 'portfolio', $this->get_return_url(), $a);
235 $this->exportconfig
[$key] = $value;
240 * returns a particular export config value.
241 * subclasses shouldn't need to override this
243 * @param string key the config item to fetch
245 public final function get_export_config($key) {
246 $allowed = array_merge(
247 array('wait', 'hidewait', 'format', 'hideformat'),
248 $this->get_allowed_export_config()
250 if (!in_array($key, $allowed)) {
251 $a = (object)array('property' => $key, 'class' => get_class($this));
252 throw new portfolio_export_exception($this->get('exporter'), 'invalidexportproperty', 'portfolio', $this->get_return_url(), $a);
254 if (!array_key_exists($key, $this->exportconfig
)) {
257 return $this->exportconfig
[$key];
261 * Similar to the other allowed_config functions
262 * if you need export config, you must provide
263 * a list of what the fields are.
265 * even if you want to store stuff during export
266 * without displaying a form to the user,
269 * @return array array of allowed keys
271 public function get_allowed_export_config() {
276 * after the user submits their config
277 * they're given a confirm screen
278 * summarising what they've chosen.
280 * this function should return a table of nice strings => values
281 * of what they've chosen
282 * to be displayed in a table.
284 * @return array array of config items.
286 public function get_export_summary() {
291 * called before the portfolio plugin gets control
292 * this function should copy all the files it wants to
293 * the temporary directory, using {@see copy_existing_file}
294 * or {@see write_new_file}
296 public abstract function prepare_package();
299 * helper function to copy files into the temp area
300 * for single or multi file exports.
302 public function prepare_package_file() {
303 if (empty($this->singlefile
) && empty($this->multifiles
)) {
304 throw new portfolio_caller_exception('invalidpreparepackagefile', 'portfolio', $this->get_return_url());
306 if ($this->singlefile
) {
307 return $this->exporter
->copy_existing_file($this->singlefile
);
309 foreach ($this->multifiles
as $file) {
310 $this->exporter
->copy_existing_file($file);
315 * array of formats this caller supports
316 * the intersection of what this function returns
317 * and what the selected portfolio plugin supports
319 * use the constants PORTFOLIO_FORMAT_*
321 * @return array list of formats
324 public final function supported_formats() {
325 $basic = $this->base_supported_formats();
326 if (empty($this->supportedformats
)) {
328 } else if (!is_array($this->supportedformats
)) {
329 debugging(get_class($this) . ' has set a non array value of member variable supported formats - working around but should be fixed in code');
330 $specific = array($this->supportedformats
);
332 $specific = $this->supportedformats
;
334 return portfolio_most_specific_formats($specific, $basic);
337 public static function base_supported_formats() {
338 throw new coding_exception('base_supported_formats() method needs to be overridden in each subclass of portfolio_caller_base');
342 * this is the "return to where you were" url
346 public abstract function get_return_url();
349 * callback to do whatever capability checks required
350 * in the caller (called during the export process
352 public abstract function check_permissions();
355 * nice name to display to the user about this caller location
357 public static function display_name() {
358 throw new coding_exception('display_name() method needs to be overridden in each subclass of portfolio_caller_base');
362 * return a string to put at the header summarising this export
363 * by default, just the display name (usually just 'assignment' or something unhelpful
367 public function heading_summary() {
368 return get_string('exportingcontentfrom', 'portfolio', $this->display_name());
371 public abstract function load_data();
374 * set up the required files for this export.
375 * this supports either passing files directly
376 * or passing area arguments directly through
377 * to the files api using file_storage::get_area_files
379 * @param mixed $ids one of:
381 * - single stored_file object
382 * - array of file ids or stored_file objects
384 * @param int $contextid (optional), passed to {@link see file_storage::get_area_files}
385 * @param string $component (optional), passed to {@link see file_storage::get_area_files}
386 * @param string $filearea (optional), passed to {@link see file_storage::get_area_files}
387 * @param int $itemid (optional), passed to {@link see file_storage::get_area_files}
388 * @param string $sort (optional), passed to {@link see file_storage::get_area_files}
389 * @param bool $includedirs (optional), passed to {@link see file_storage::get_area_files}
391 public function set_file_and_format_data($ids=null /* ..pass arguments to area files here. */) {
392 $args = func_get_args();
393 array_shift($args); // shift off $ids
394 if (empty($ids) && count($args) == 0) {
398 $fs = get_file_storage();
400 if (is_numeric($ids) ||
$ids instanceof stored_file
) {
403 foreach ($ids as $id) {
404 if ($id instanceof stored_file
) {
407 $files[] = $fs->get_file_by_id($id);
410 } else if (count($args) != 0) {
411 if (count($args) < 4) {
412 throw new portfolio_caller_exception('invalidfileareaargs', 'portfolio');
414 $files = array_values(call_user_func_array(array($fs, 'get_area_files'), $args));
416 switch (count($files)) {
419 $this->singlefile
= $files[0];
423 $this->multifiles
= $files;
429 * the button-location always knows best
430 * what the formats are... so it should be trusted.
432 * @param array $formats array of PORTFOLIO_FORMAT_XX
434 public function set_formats_from_button($formats) {
435 $base = $this->base_supported_formats();
436 if (count($base) != count($formats)
437 ||
count($base) != count(array_intersect($base, $formats))) {
438 $this->supportedformats
= portfolio_most_specific_formats($formats, $base);
441 // in the case where the button hasn't actually set anything,
442 // we need to run through again and resolve conflicts
443 // TODO revisit this comment - it looks to me like it's lying
444 $this->supportedformats
= portfolio_most_specific_formats($formats, $formats);
448 * adds a new format to the list of supported formats.
449 * handles removing conflicting and less specific
450 * formats at the same time.
452 * @param string $format one of PORTFOLIO_FORMAT_XX
456 protected function add_format($format) {
457 if (in_array($format, $this->supportedformats
)) {
460 $this->supportedformats
= portfolio_most_specific_formats(array($format), $this->supportedformats
);
463 public function get_mimetype() {
464 if ($this->singlefile
instanceof stored_file
) {
465 return $this->singlefile
->get_mimetype();
466 } else if (!empty($this->intendedmimetype
)) {
467 return $this->intendedmimetype
;
472 * array of arguments the caller expects to be passed through to it
473 * this must be keyed on the argument name, and the array value is a boolean,
474 * whether it is required, or just optional
477 * somethingelse => false,
482 public static function expected_callbackargs() {
483 throw new coding_exception('expected_callbackargs() method needs to be overridden in each subclass of portfolio_caller_base');
488 * return the context for this export. used for $PAGE->set_context
492 public abstract function set_context($PAGE);
496 * base class for module callers
497 * this just implements a few of the abstract functions
498 * from portfolio_caller_base so that caller authors
501 * See http://docs.moodle.org/en/Development:Adding_a_Portfolio_Button_to_a_page
502 * {@see also portfolio_caller_base}
504 abstract class portfolio_module_caller_base
extends portfolio_caller_base
{
507 * coursemodule object
508 * set this in the constructor like
509 * $this->cm = get_coursemodule_from_instance('forum', $this->forum->id);
520 * stdclass course object
525 * navigation passed to print_header
526 * override this to do something more specific than the module view page
528 public function get_navigation() {
529 $extranav = array('name' => $this->cm
->name
, 'link' => $this->get_return_url());
530 return array($extranav, $this->cm
);
534 * the url to return to after export or on cancel
535 * defaults to the module 'view' page
536 * override this if it's deeper inside the module
538 public function get_return_url() {
540 return $CFG->wwwroot
. '/mod/' . $this->cm
->modname
. '/view.php?id=' . $this->cm
->id
;
544 * override the parent get function
545 * to make sure when we're asked for a course
546 * we retrieve the object from the database as needed
548 public function get($key) {
549 if ($key != 'course') {
550 return parent
::get($key);
553 if (empty($this->course
)) {
554 $this->course
= $DB->get_record('course', array('id' => $this->cm
->course
));
556 return $this->course
;
560 * return a string to put at the header summarising this export
561 * by default, just the display name and the module instance name
562 * override this to do something more specific
564 public function heading_summary() {
565 return get_string('exportingcontentfrom', 'portfolio', $this->display_name() . ': ' . $this->cm
->name
);
569 * overridden to return the course module context
571 public function set_context($PAGE) {
572 $PAGE->set_cm($this->cm
);