MDL-14591 - split portfoliolib into logical parts for better readability
[moodle.git] / lib / portfolio / plugin.php
CommitLineData
87fcac8d 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 moodle
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 base classes for portfolio plugins to inherit from:
27 * portfolio_plugin_pull_base and portfolio_plugin_push_base
28 * which both in turn inherit from portfolio_plugin_base.
29 * See http://docs.moodle.org/en/Development:Writing_a_Portfolio_Plugin
30 */
31
32/**
33* the base class for portfolio plugins
34* all plugins must subclass this
35* either via {@see portfolio_plugin_pull_base} or {@see portfolio_plugin_push_base}
36*/
37abstract class portfolio_plugin_base {
38
39 /**
40 * boolean
41 * whether this object needs writing out to the database
42 */
43 protected $dirty;
44
45 /**
46 * integer
47 * id of instance
48 */
49 protected $id;
50
51 /**
52 * string
53 * name of instance
54 */
55 protected $name;
56
57 /**
58 * string
59 * plugin this instance belongs to
60 */
61 protected $plugin;
62
63 /**
64 * boolean
65 * whether this instance is visible or not
66 */
67 protected $visible;
68
69 /**
70 * named array
71 * admin configured config
72 * use {@link set_config} and {@get_config} to access
73 */
74 protected $config;
75
76 /**
77 *
78 * user config cache
79 * named array of named arrays
80 * keyed on userid and then on config field => value
81 * use {@link get_user_config} and {@link set_user_config} to access.
82 */
83 protected $userconfig;
84
85 /**
86 * named array
87 * export config during export
88 * use {@link get_export_config} and {@link set export_config} to access.
89 */
90 protected $exportconfig;
91
92 /**
93 * stdclass object
94 * user currently exporting data
95 */
96 protected $user;
97
98 /**
99 * a reference to the exporter object
100 */
101 protected $exporter;
102
103 /**
104 * array of formats this portfolio supports
105 * the intersection of what this function returns
106 * and what the caller supports will be used
107 * use the constants PORTFOLIO_FORMAT_*
108 *
109 * @return array list of formats
110 */
111 public static function supported_formats() {
112 return array(PORTFOLIO_FORMAT_FILE);
113 }
114
115
116 /**
117 * how long does this reasonably expect to take..
118 * should we offer the user the option to wait..
119 * this is deliberately nonstatic so it can take filesize into account
120 *
121 * @param string $callertime - what the caller thinks
122 * the portfolio plugin instance
123 * is given the final say
124 * because it might be (for example) download.
125 * @return string (see PORTFOLIO_TIME_* constants)
126 */
127 public abstract function expected_time($callertime);
128
129 /**
130 * is this plugin push or pill.
131 * if push, cleanup will be called directly after send_package
132 * if not, cleanup will be called after portfolio/file.php is requested
133 *
134 * @return boolean
135 */
136 public abstract function is_push();
137
138 public static abstract function get_name();
139
140 /**
141 * check sanity of plugin
142 * if this function returns something non empty, ALL instances of your plugin
143 * will be set to invisble and not be able to be set back until it's fixed
144 *
145 * @return mixed - string = error string KEY (must be inside plugin_$yourplugin) or 0/false if you're ok
146 */
147 public static function plugin_sanity_check() {
148 return 0;
149 }
150
151 /**
152 * check sanity of instances
153 * if this function returns something non empty, the instance will be
154 * set to invislbe and not be able to be set back until it's fixed.
155 *
156 * @return mixed - string = error string KEY (must be inside plugin_$yourplugin) or 0/false if you're ok
157 */
158 public function instance_sanity_check() {
159 return 0;
160 }
161
162 /**
163 * does this plugin need any configuration by the administrator?
164 *
165 * if you override this to return true,
166 * you <b>must</b> implement {@see admin_config_form}
167 */
168 public static function has_admin_config() {
169 return false;
170 }
171
172 /**
173 * can this plugin be configured by the user in their profile?
174 *
175 * if you override this to return true,
176 * you <b>must</b> implement {@see user_config_form}
177 */
178 public function has_user_config() {
179 return false;
180 }
181
182 /**
183 * does this plugin need configuration during export time?
184 *
185 * if you override this to return true,
186 * you <b>must</b> implement {@see export_config_form}
187 */
188 public function has_export_config() {
189 return false;
190 }
191
192 /**
193 * just like the moodle form validation function
194 * this is passed in the data array from the form
195 * and if a non empty array is returned, form processing will stop.
196 *
197 * @param array $data data from form.
198 * @return array keyvalue pairs - form element => error string
199 */
200 public function export_config_validation() {}
201
202 /**
203 * just like the moodle form validation function
204 * this is passed in the data array from the form
205 * and if a non empty array is returned, form processing will stop.
206 *
207 * @param array $data data from form.
208 * @return array keyvalue pairs - form element => error string
209 */
210 public function user_config_validation() {}
211
212 /**
213 * sets the export time config from the moodle form.
214 * you can also use this to set export config that
215 * isn't actually controlled by the user
216 * eg things that your subclasses want to keep in state
217 * across the export.
218 * keys must be in {@see get_allowed_export_config}
219 *
220 * this is deliberately not final (see boxnet plugin)
221 *
222 * @param array $config named array of config items to set.
223 */
224 public function set_export_config($config) {
225 $allowed = array_merge(
226 array('wait', 'hidewait', 'format', 'hideformat'),
227 $this->get_allowed_export_config()
228 );
229 foreach ($config as $key => $value) {
230 if (!in_array($key, $allowed)) {
231 $a = (object)array('property' => $key, 'class' => get_class($this));
232 throw new portfolio_export_exception($this->get('exporter'), 'invalidexportproperty', 'portfolio', $this->get_return_url(), $a);
233 }
234 $this->exportconfig[$key] = $value;
235 }
236 }
237
238 /**
239 * gets an export time config value.
240 * subclasses should not override this.
241 *
242 * @param string key field to fetch
243 *
244 * @return string config value
245 *
246 */
247 public final function get_export_config($key) {
248 $allowed = array_merge(
249 array('hidewait', 'wait', 'format', 'hideformat'),
250 $this->get_allowed_export_config()
251 );
252 if (!in_array($key, $allowed)) {
253 $a = (object)array('property' => $key, 'class' => get_class($this));
254 throw new portfolio_export_exception($this->get('exporter'), 'invalidexportproperty', 'portfolio', $this->get_return_url(), $a);
255 }
256 if (!array_key_exists($key, $this->exportconfig)) {
257 return null;
258 }
259 return $this->exportconfig[$key];
260 }
261
262 /**
263 * after the user submits their config
264 * they're given a confirm screen
265 * summarising what they've chosen.
266 *
267 * this function should return a table of nice strings => values
268 * of what they've chosen
269 * to be displayed in a table.
270 *
271 * @return array array of config items.
272 */
273 public function get_export_summary() {
274 return false;
275 }
276
277 /**
278 * called after the caller has finished having control
279 * of its prepare_package function.
280 * this function should read all the files from the portfolio
281 * working file area and zip them and send them or whatever it wants.
282 * {@see get_tempfiles} to get the list of files.
283 *
284 */
285 public abstract function prepare_package();
286
287 /**
288 * this is the function that is responsible for sending
289 * the package to the remote system,
290 * or whatever request is necessary to initiate the transfer.
291 *
292 * @return boolean success
293 */
294 public abstract function send_package();
295
296
297 /**
298 * once everything is done and the user
299 * has the finish page displayed to them
300 * the base class takes care of printing them
301 * "return to where you are" or "continue to portfolio" links
302 * this function allows for exta finish options from the plugin
303 *
304 * @return array named array of links => titles
305 */
306 public function get_extra_finish_options() {
307 return false;
308 }
309
310 /**
311 * the url for the user to continue to their portfolio
312 *
313 * @return string url or false.
314 */
315 public abstract function get_continue_url();
316
317 /**
318 * mform to display to the user in their profile
319 * if your plugin can't be configured by the user,
320 * (see {@link has_user_config})
321 * don't bother overriding this function
322 *
323 * @param moodleform $mform passed by reference, add elements to it
324 */
325 public function user_config_form(&$mform) {}
326
327 /**
328 * mform to display to the admin configuring the plugin.
329 * if your plugin can't be configured by the admin,
330 * (see {@link} has_admin_config)
331 * don't bother overriding this function
332 *
333 * this function can be called statically or non statically,
334 * depending on whether it's creating a new instance (statically),
335 * or editing an existing one (non statically)
336 *
337 * @param moodleform $mform passed by reference, add elements to it.
338 */
339 public function admin_config_form(&$mform) {}
340
341 /**
342 * just like the moodle form validation function
343 * this is passed in the data array from the form
344 * and if a non empty array is returned, form processing will stop.
345 *
346 * @param array $data data from form.
347 * @return array keyvalue pairs - form element => error string
348 */
349 public function admin_config_validation($data) {}
350 /**
351 * mform to display to the user exporting data using this plugin.
352 * if your plugin doesn't need user input at this time,
353 * (see {@link has_export_config}
354 * don't bother overrideing this function
355 *
356 * @param moodleform $mform passed by reference, add elements to it.
357 */
358 public function export_config_form(&$mform) {}
359
360 /**
361 * override this if your plugin doesn't allow multiple instances
362 *
363 * @return boolean
364 */
365 public static function allows_multiple() {
366 return true;
367 }
368
369 /**
370 *
371 * If at any point the caller wants to steal control
372 * it can, by returning something that isn't false
373 * in this function
374 * The controller will redirect to whatever url
375 * this function returns.
376 * Afterwards, you can redirect back to portfolio/add.php?postcontrol=1
377 * and {@link post_control} is called before the rest of the processing
378 * for the stage is done
379 *
380 * @param int stage to steal control *before* (see constants PARAM_STAGE_*}
381 *
382 * @return boolean or string url
383 */
384 public function steal_control($stage) {
385 return false;
386 }
387
388 /**
389 * after a plugin has elected to steal control,
390 * and control returns to portfolio/add.php|postcontrol=1,
391 * this function is called, and passed the stage that was stolen control from
392 * and the request (get and post but not cookie) parameters
393 * this is useful for external systems that need to redirect the user back
394 * with some extra data in the url (like auth tokens etc)
395 * for an example implementation, see boxnet portfolio plugin.
396 *
397 * @param int $stage the stage before control was stolen
398 * @param array $params a merge of $_GET and $_POST
399 *
400 */
401
402 public function post_control($stage, $params) { }
403
404 /**
405 * this function creates a new instance of a plugin
406 * saves it in the database, saves the config
407 * and returns it.
408 * you shouldn't need to override it
409 * unless you're doing something really funky
410 *
411 * @param string $plugin portfolio plugin to create
412 * @param string $name name of new instance
413 * @param array $config what the admin config form returned
414 *
415 * @return object subclass of portfolio_plugin_base
416 */
417 public static function create_instance($plugin, $name, $config) {
418 global $DB, $CFG;
419 $new = (object)array(
420 'plugin' => $plugin,
421 'name' => $name,
422 );
423 if (!portfolio_static_function($plugin, 'allows_multiple')) {
424 // check we don't have one already
425 if ($DB->record_exists('portfolio_instance', array('plugin' => $plugin))) {
426 throw new portfolio_exception('multipledisallowed', 'portfolio', '', $plugin);
427 }
428 }
429 $newid = $DB->insert_record('portfolio_instance', $new);
430 require_once($CFG->dirroot . '/portfolio/type/' . $plugin . '/lib.php');
431 $classname = 'portfolio_plugin_' . $plugin;
432 $obj = new $classname($newid);
433 $obj->set_config($config);
434 return $obj;
435 }
436
437 /**
438 * construct a plugin instance
439 * subclasses should not need to override this unless they're doing something special
440 * and should call parent::__construct afterwards
441 *
442 * @param int $instanceid id of plugin instance to construct
443 * @param mixed $record stdclass object or named array - use this is you already have the record to avoid another query
444 *
445 * @return object subclass of portfolio_plugin_base
446 */
447 public function __construct($instanceid, $record=null) {
448 global $DB;
449 if (!$record) {
450 if (!$record = $DB->get_record('portfolio_instance', array('id' => $instanceid))) {
451 throw new portfolio_exception('invalidinstance', 'portfolio');
452 }
453 }
454 foreach ((array)$record as $key =>$value) {
455 if (property_exists($this, $key)) {
456 $this->{$key} = $value;
457 }
458 }
459 $this->config = new StdClass;
460 $this->userconfig = array();
461 $this->exportconfig = array();
462 foreach ($DB->get_records('portfolio_instance_config', array('instance' => $instanceid)) as $config) {
463 $this->config->{$config->name} = $config->value;
464 }
465 return $this;
466 }
467
468 /**
469 * a list of fields that can be configured per instance.
470 * this is used for the save handlers of the config form
471 * and as checks in set_config and get_config
472 *
473 * @return array array of strings (config item names)
474 */
475 public static function get_allowed_config() {
476 return array();
477 }
478
479 /**
480 * a list of fields that can be configured by the user.
481 * this is used for the save handlers in the config form
482 * and as checks in set_user_config and get_user_config.
483 *
484 * @return array array of strings (config field names)
485 */
486 public function get_allowed_user_config() {
487 return array();
488 }
489
490 /**
491 * a list of fields that can be configured by the user.
492 * this is used for the save handlers in the config form
493 * and as checks in set_export_config and get_export_config.
494 *
495 * @return array array of strings (config field names)
496 */
497 public function get_allowed_export_config() {
498 return array();
499 }
500
501 /**
502 * saves (or updates) the config stored in portfolio_instance_config.
503 * you shouldn't need to override this unless you're doing something funky.
504 *
505 * @param array $config array of config items.
506 */
507 public final function set_config($config) {
508 global $DB;
509 foreach ($config as $key => $value) {
510 // try set it in $this first
511 try {
512 $this->set($key, $value);
513 continue;
514 } catch (portfolio_exception $e) { }
515 if (!in_array($key, $this->get_allowed_config())) {
516 $a = (object)array('property' => $key, 'class' => get_class($this));
517 throw new portfolio_export_exception($this->get('exporter'), 'invalidconfigproperty', 'portfolio', null, $a);
518 }
519 if (!isset($this->config->{$key})) {
520 $DB->insert_record('portfolio_instance_config', (object)array(
521 'instance' => $this->id,
522 'name' => $key,
523 'value' => $value,
524 ));
525 } else if ($this->config->{$key} != $value) {
526 $DB->set_field('portfolio_instance_config', 'value', $value, array('name' => $key, 'instance' => $this->id));
527 }
528 $this->config->{$key} = $value;
529 }
530 }
531
532 /**
533 * gets the value of a particular config item
534 *
535 * @param string $key key to fetch
536 *
537 * @return string the corresponding value
538 */
539 public final function get_config($key) {
540 if (!in_array($key, $this->get_allowed_config())) {
541 $a = (object)array('property' => $key, 'class' => get_class($this));
542 throw new portfolio_export_exception($this->get('exporter'), 'invalidconfigproperty', 'portfolio', null, $a);
543 }
544 if (isset($this->config->{$key})) {
545 return $this->config->{$key};
546 }
547 return null;
548 }
549
550 /**
551 * get the value of a config item for a particular user
552 *
553 * @param string $key key to fetch
554 * @param integer $userid id of user (defaults to current)
555 *
556 * @return string the corresponding value
557 *
558 */
559 public final function get_user_config($key, $userid=0) {
560 global $DB;
561
562 if (empty($userid)) {
563 $userid = $this->user->id;
564 }
565
566 if ($key != 'visible') { // handled by the parent class
567 if (!in_array($key, $this->get_allowed_user_config())) {
568 $a = (object)array('property' => $key, 'class' => get_class($this));
569 throw new portfolio_export_exception($this->get('exporter'), 'invaliduserproperty', 'portfolio', null, $a);
570 }
571 }
572 if (!array_key_exists($userid, $this->userconfig)) {
573 $this->userconfig[$userid] = (object)array_fill_keys(array_merge(array('visible'), $this->get_allowed_user_config()), null);
574 foreach ($DB->get_records('portfolio_instance_user', array('instance' => $this->id, 'userid' => $userid)) as $config) {
575 $this->userconfig[$userid]->{$config->name} = $config->value;
576 }
577 }
578 if ($this->userconfig[$userid]->visible === null) {
579 $this->set_user_config(array('visible' => 1), $userid);
580 }
581 return $this->userconfig[$userid]->{$key};
582
583 }
584
585 /**
586 *
587 * sets config options for a given user
588 *
589 * @param mixed $config array or stdclass containing key/value pairs to set
590 * @param integer $userid userid to set config for (defaults to current)
591 *
592 */
593 public final function set_user_config($config, $userid=0) {
594 global $DB;
595
596 if (empty($userid)) {
597 $userid = $this->user->id;
598 }
599
600 foreach ($config as $key => $value) {
601 if ($key != 'visible' && !in_array($key, $this->get_allowed_user_config())) {
602 $a = (object)array('property' => $key, 'class' => get_class($this));
603 throw new portfolio_export_exception($this->get('exporter'), 'invaliduserproperty', 'portfolio', null, $a);
604 }
605 if (!$existing = $DB->get_record('portfolio_instance_user', array('instance'=> $this->id, 'userid' => $userid, 'name' => $key))) {
606 $DB->insert_record('portfolio_instance_user', (object)array(
607 'instance' => $this->id,
608 'name' => $key,
609 'value' => $value,
610 'userid' => $userid,
611 ));
612 } else if ($existing->value != $value) {
613 $DB->set_field('portfolio_instance_user', 'value', $value, array('name' => $key, 'instance' => $this->id, 'userid' => $userid));
614 }
615 $this->userconfig[$userid]->{$key} = $value;
616 }
617
618 }
619
620 /**
621 * generic getter for properties belonging to this instance
622 * <b>outside</b> the subclasses
623 * like name, visible etc.
624 *
625 */
626 public final function get($field) {
627 if (property_exists($this, $field)) {
628 return $this->{$field};
629 }
630 $a = (object)array('property' => $field, 'class' => get_class($this));
631 throw new portfolio_export_exception($this->get('exporter'), 'invalidproperty', 'portfolio', null, $a);
632 }
633
634 /**
635 * generic setter for properties belonging to this instance
636 * <b>outside</b> the subclass
637 * like name, visible, etc.
638 *
639 */
640 public final function set($field, $value) {
641 if (property_exists($this, $field)) {
642 $this->{$field} =& $value;
643 $this->dirty = true;
644 return true;
645 }
646 $a = (object)array('property' => $field, 'class' => get_class($this));
647 throw new portfolio_export_exception($this->get('exporter'), 'invalidproperty', 'portfolio', null, $a);
648
649 }
650
651 /**
652 * saves stuff that's been stored in the object to the database
653 * you shouldn't need to override this
654 * unless you're doing something really funky.
655 * and if so, call parent::save when you're done.
656 */
657 public function save() {
658 global $DB;
659 if (!$this->dirty) {
660 return true;
661 }
662 $fordb = new StdClass();
663 foreach (array('id', 'name', 'plugin', 'visible') as $field) {
664 $fordb->{$field} = $this->{$field};
665 }
666 $DB->update_record('portfolio_instance', $fordb);
667 $this->dirty = false;
668 return true;
669 }
670
671 /**
672 * deletes everything from the database about this plugin instance.
673 * you shouldn't need to override this unless you're storing stuff
674 * in your own tables. and if so, call parent::delete when you're done.
675 */
676 public function delete() {
677 global $DB;
678 $DB->delete_records('portfolio_instance_config', array('instance' => $this->get('id')));
679 $DB->delete_records('portfolio_instance_user', array('instance' => $this->get('id')));
680 $DB->delete_records('portfolio_instance', array('id' => $this->get('id')));
681 $this->dirty = false;
682 return true;
683 }
684
685 /**
686 * perform any required cleanup functions
687 */
688 public function cleanup() {
689 return true;
690 }
691
692 public static function mnet_publishes() {
693 return array();
694 }
695}
696
697/**
698* class to inherit from for 'push' type plugins
699* eg those that send the file via a HTTP post or whatever
700*/
701abstract class portfolio_plugin_push_base extends portfolio_plugin_base {
702
703 public function is_push() {
704 return true;
705 }
706}
707
708/**
709* class to inherit from for 'pull' type plugins
710* eg those that write a file and wait for the remote system to request it
711* from portfolio/file.php
712* if you're using this you must do $this->set('file', $file) so that it can be served.
713*/
714abstract class portfolio_plugin_pull_base extends portfolio_plugin_base {
715
716 protected $file;
717
718 public function is_push() {
719 return false;
720 }
721
722
723 /**
724 * before sending the file when the pull is requested, verify the request parameters
725 * these might include a token of some sort of whatever
726 *
727 * @param array request parameters (POST wins over GET)
728 */
729 public abstract function verify_file_request_params($params);
730
731 /**
732 * called from portfolio/file.php
733 * this function sends the stored file out to the browser
734 * the default is to just use send_stored_file,
735 * but other implementations might do something different
736 * for example, send back the file base64 encoded and encrypted
737 * mahara does this but in the response to an xmlrpc request
738 * rather than through file.php
739 */
740 public function send_file() {
741 $file = $this->get('file');
742 if (!($file instanceof stored_file)) {
743 throw new portfolio_export_exception($this->get('exporter'), 'filenotfound', 'portfolio');
744 }
745 send_stored_file($file, 0, 0, true, null, true);
746 }
747
748}
749
750?>