MDL-16520 - print info about current export
[moodle.git] / lib / portfoliolib.php
CommitLineData
67a87e7d 1<?php
67a87e7d 2/**
87fcac8d 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 all global functions to do with manipulating portfolios
27 * everything else that is logically namespaced by class is in its own file
28 * in lib/portfolio/ directory.
29 */
30
31// require all the sublibraries first.
32require_once($CFG->libdir . '/portfolio/constants.php'); // all the constants for time, export format etc.
33require_once($CFG->libdir . '/portfolio/exceptions.php'); // exception classes used by portfolio code
34require_once($CFG->libdir . '/portfolio/formats.php'); // the export format hierarchy
35require_once($CFG->libdir . '/portfolio/forms.php'); // the form classes that subclass moodleform
36require_once($CFG->libdir . '/portfolio/exporter.php'); // the exporter class
37require_once($CFG->libdir . '/portfolio/plugin.php'); // the base classes for plugins
38require_once($CFG->libdir . '/portfolio/caller.php'); // the base classes for calling code
39
40/**
ce09fecc 41* use this to add a portfolio button or icon or form to a page
67a87e7d 42*
ce09fecc 43* These class methods do not check permissions. the caller must check permissions first.
87fcac8d 44* Later, during the export process, the caller class is instantiated and the check_permissions method is called
67a87e7d 45*
ce09fecc 46* This class can be used like this:
47* $button = new portfolio_add_button();
48* $button->set_callback_options('name_of_caller_class', array('id' => 6), '/your/mod/lib.php');
49* $button->render(PORTFOLIO_ADD_FULL_FORM, get_string('addeverythingtoportfolio', 'yourmodule'));
50*
51* or like this:
52* $button = new portfolio_add_button(array('callbackclass' => 'name_of_caller_class', 'callbackargs' => array('id' => 6), 'callbackfile' => '/your/mod/lib.php'));
53* $somehtml .= $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
54*
55* See http://docs.moodle.org/en/Development:Adding_a_Portfolio_Button_to_a_page for more information
67a87e7d 56*/
ce09fecc 57class portfolio_add_button {
67a87e7d 58
a4763136 59 private $alreadyexporting;
ce09fecc 60 private $callbackclass;
61 private $callbackargs;
62 private $callbackfile;
63 private $formats;
64 private $instances;
67a87e7d 65
ce09fecc 66 /**
67 * constructor. either pass the options here or set them using the helper methods.
68 * generally the code will be clearer if you use the helper methods.
69 *
70 * @param array $options keyed array of options:
71 * key 'callbackclass': name of the caller class (eg forum_portfolio_caller')
72 * key 'callbackargs': the array of callback arguments your caller class wants passed to it in the constructor
73 * key 'callbackfile': the file containing the class definition of your caller class.
74 * See set_callback_options for more information on these three.
75 * key 'formats': an array of PORTFOLIO_FORMATS this caller will support
76 * See set_formats for more information on this.
77 */
78 public function __construct($options=null) {
11ae365c 79 global $SESSION, $CFG;
ce09fecc 80 if (isset($SESSION->portfolioexport)) {
a4763136 81 $this->alreadyexporting = true;
82 return;
ce09fecc 83 }
380a251f 84 $this->instances = portfolio_instances();
ce09fecc 85 if (empty($options)) {
86 return true;
87 }
88 foreach ((array)$options as $key => $value) {
89 if (!in_array($key, $constructoroptions)) {
90 throw new portfolio_button_exception('invalidbuttonproperty', 'portfolio', $key);
91 }
92 $this->{$key} = $value;
93 }
a239f01e 94 }
95
ce09fecc 96 /*
97 * @param string $class name of the class containing the callback functions
98 * activity modules should ALWAYS use their name_portfolio_caller
99 * other locations must use something unique
100 * @param mixed $argarray this can be an array or hash of arguments to pass
101 * back to the callback functions (passed by reference)
102 * these MUST be primatives to be added as hidden form fields.
103 * and the values get cleaned to PARAM_ALPHAEXT or PARAM_NUMBER or PARAM_PATH
104 * @param string $file this can be autodetected if it's in the same file as your caller,
105 * but often, the caller is a script.php and the class in a lib.php
106 * so you can pass it here if necessary.
107 * this path should be relative (ie, not include) dirroot, eg '/mod/forum/lib.php'
108 */
109 public function set_callback_options($class, array $argarray, $file=null) {
a4763136 110 if ($this->alreadyexporting) {
111 return;
112 }
ce09fecc 113 global $CFG;
114 if (empty($file)) {
115 $backtrace = debug_backtrace();
116 if (!array_key_exists(0, $backtrace) || !array_key_exists('file', $backtrace[0]) || !is_readable($backtrace[0]['file'])) {
117 throw new portfolio_button_exception('nocallbackfile', 'portfolio');
118 }
119
120 $file = substr($backtrace[0]['file'], strlen($CFG->dirroot));
121 } else if (!is_readable($CFG->dirroot . $file)) {
122 throw new portfolio_button_exception('nocallbackfile', 'portfolio', $file);
123 }
124 $this->callbackfile = $file;
125 require_once($CFG->dirroot . $file);
126 if (!class_exists($class)) {
127 throw new portfolio_button_exception('nocallbackclass', 'portfolio', $class);
128 }
0d06b6fd 129
130 // this will throw exceptions
131 // but should not actually do anything other than verify callbackargs
132 $test = new $class($argarray);
133 unset($test);
134
ce09fecc 135 $this->callbackclass = $class;
136 $this->callbackargs = $argarray;
67a87e7d 137 }
138
ce09fecc 139 /*
9d7432f6 140 * sets the available export formats for this content
141 * this function will also poll the static function in the caller class
142 * and make sure we're not overriding a format that has nothing to do with mimetypes
143 * eg if you pass IMAGE here but the caller can export LEAP it will keep LEAP as well.
144 * see portfolio_most_specific_formats for more information
145 *
ce09fecc 146 * @param array $formats if the calling code knows better than the static method on the calling class (supported_formats)
147 * eg, if it's going to be a single file, or if you know it's HTML, you can pass it here instead
148 * this is almost always the case so you should always use this.
149 * {@see portfolio_format_from_file} for how to get the appropriate formats to pass here for uploaded files.
150 */
151 public function set_formats($formats=null) {
a4763136 152 if ($this->alreadyexporting) {
153 return;
154 }
ce09fecc 155 if (is_string($formats)) {
156 $formats = array($formats);
157 }
158 if (empty($formats)) {
9d7432f6 159 $formats = array();
ce09fecc 160 }
9d7432f6 161 if (empty($this->callbackclass)) {
162 throw new portfolio_button_exception('noclassbeforeformats', 'portfolio');
d1581fc5 163 }
9d7432f6 164 $callerformats = call_user_func(array($this->callbackclass, 'supported_formats'));
165 $this->formats = portfolio_most_specific_formats($formats, $callerformats);
6fdd8fa7 166 }
167
ce09fecc 168 /*
169 * echo the form/button/icon/text link to the page
170 *
171 * @param int $format format to display the button or form or icon or link.
172 * See constants PORTFOLIO_ADD_XXX for more info.
173 * optional, defaults to PORTFOLI_ADD_FULL_FORM
174 * @param str $addstr string to use for the button or icon alt text or link text.
175 * this is whole string, not key. optional, defaults to 'Add to portfolio';
176 */
177 public function render($format=null, $addstr=null) {
380a251f 178 echo $this->to_html($format, $addstr);
84a44985 179 }
180
ce09fecc 181 /*
182 * returns the form/button/icon/text link as html
183 *
184 * @param int $format format to display the button or form or icon or link.
185 * See constants PORTFOLIO_ADD_XXX for more info.
186 * optional, defaults to PORTFOLI_ADD_FULL_FORM
187 * @param str $addstr string to use for the button or icon alt text or link text.
188 * this is whole string, not key. optional, defaults to 'Add to portfolio';
189 */
190 public function to_html($format=null, $addstr=null) {
a4763136 191 if ($this->alreadyexporting) {
6e6cf8a3 192 return $this->already_exporting($format, $addstr);
a4763136 193 }
ce09fecc 194 global $CFG, $COURSE;
195 if (!$this->is_renderable()) {
ed1fcf79 196 return;
197 }
380a251f 198 if (empty($this->callbackclass) || empty($this->callbackfile)) {
199 throw new portfolio_button_exception('mustsetcallbackoptions', 'portfolio');
ce09fecc 200 }
201 if (empty($this->formats)) {
202 // use the caller defaults
203 $this->set_formats();
204 }
205 $formoutput = '<form method="post" action="' . $CFG->wwwroot . '/portfolio/add.php" id="portfolio-add-button">' . "\n";
206 $linkoutput = '<a href="' . $CFG->wwwroot . '/portfolio/add.php?';
207 foreach ($this->callbackargs as $key => $value) {
208 if (!empty($value) && !is_string($value) && !is_numeric($value)) {
209 $a->key = $key;
210 $a->value = print_r($value, true);
211 debugging(get_string('nonprimative', 'portfolio', $a));
212 return;
213 }
214 $linkoutput .= 'ca_' . $key . '=' . $value . '&amp;';
215 $formoutput .= "\n" . '<input type="hidden" name="ca_' . $key . '" value="' . $value . '" />';
216 }
217 $formoutput .= "\n" . '<input type="hidden" name="callbackfile" value="' . $this->callbackfile . '" />';
218 $formoutput .= "\n" . '<input type="hidden" name="callbackclass" value="' . $this->callbackclass . '" />';
219 $formoutput .= "\n" . '<input type="hidden" name="course" value="' . (!empty($COURSE) ? $COURSE->id : 0) . '" />';
220 $linkoutput .= 'callbackfile=' . $this->callbackfile . '&amp;callbackclass='
221 . $this->callbackclass . '&amp;course=' . (!empty($COURSE) ? $COURSE->id : 0);
222 $selectoutput = '';
223 if (count($this->instances) == 1) {
224 $instance = array_shift($this->instances);
225 $formats = portfolio_supported_formats_intersect($this->formats, $instance->supported_formats());
226 if (count($formats) == 0) {
227 // bail. no common formats.
228 debugging(get_string('nocommonformats', 'portfolio', $this->callbackclass));
229 return;
230 }
231 if ($error = portfolio_instance_sanity_check($instance)) {
232 // bail, plugin is misconfigured
233 debugging(get_string('instancemisconfigured', 'portfolio', get_string($error[$instance->get('id')], 'portfolio_' . $instance->get('plugin'))));
234 return;
235 }
236 $formoutput .= "\n" . '<input type="hidden" name="instance" value="' . $instance->get('id') . '" />';
237 $linkoutput .= '&amp;instance=' . $instance->get('id');
238 }
239 else {
240 $selectoutput = portfolio_instance_select($this->instances, $this->formats, $this->callbackclass, 'instance', true);
ed1fcf79 241 }
67a87e7d 242
ce09fecc 243 if (empty($addstr)) {
244 $addstr = get_string('addtoportfolio', 'portfolio');
245 }
246 if (empty($format)) {
247 $format = PORTFOLIO_ADD_FULL_FORM;
248 }
249 switch ($format) {
250 case PORTFOLIO_ADD_FULL_FORM:
251 $formoutput .= $selectoutput;
252 $formoutput .= "\n" . '<input type="submit" value="' . $addstr .'" />';
253 $formoutput .= "\n" . '</form>';
254 break;
255 case PORTFOLIO_ADD_ICON_FORM:
256 $formoutput .= $selectoutput;
257 $formoutput .= "\n" . '<input type="image" src="' . $CFG->pixpath . '/t/portfolio.gif" alt=' . $addstr .'" />';
258 $formoutput .= "\n" . '</form>';
259 break;
260 case PORTFOLIO_ADD_ICON_LINK:
261 $linkoutput .= '"><img src="' . $CFG->pixpath . '/t/portfolio.gif" alt=' . $addstr .'" /></a>';
262 break;
263 case PORTFOLIO_ADD_TEXT_LINK:
264 $linkoutput .= '">' . $addstr .'</a>';
265 break;
266 default:
267 debugging(get_string('invalidaddformat', 'portfolio', $format));
268 }
269 $output = (in_array($format, array(PORTFOLIO_ADD_FULL_FORM, PORTFOLIO_ADD_ICON_FORM)) ? $formoutput : $linkoutput);
270 return $output;
349242a3 271 }
67a87e7d 272
ce09fecc 273 /**
274 * does some internal checks
275 * these are not errors, just situations
276 * where it's not appropriate to add the button
277 */
278 private function is_renderable() {
279 global $CFG;
280 if (empty($CFG->enableportfolios)) {
281 return false;
67a87e7d 282 }
ce09fecc 283 if (defined('PORTFOLIO_INTERNAL')) {
284 // something somewhere has detected a risk of this being called during inside the preparation
285 // eg forum_print_attachments
286 return false;
67a87e7d 287 }
380a251f 288 if (empty($this->instances) || count($this->instances) == 0) {
ce09fecc 289 return false;
67a87e7d 290 }
ce09fecc 291 return true;
67a87e7d 292 }
ef6f0f60 293
294 /**
295 * Getter for $format property
296 * @return array
297 */
298 public function get_formats() {
299 return $this->formats;
300 }
301
302 /**
303 * Getter for $callbackargs property
304 * @return array
305 */
306 public function get_callbackargs() {
307 return $this->callbackargs;
308 }
309
310 /**
311 * Getter for $callbackfile property
312 * @return array
313 */
314 public function get_callbackfile() {
315 return $this->callbackfile;
316 }
317
318 /**
319 * Getter for $callbackclass property
320 * @return array
321 */
322 public function get_callbackclass() {
323 return $this->callbackclass;
324 }
a4763136 325
6e6cf8a3 326 private function already_exporting($format, $addstr) {
a4763136 327 global $CFG;
328 $url = $CFG->wwwroot . '/portfolio/already.php';
329 $icon = $CFG->pixpath . '/t/portfoliono.gif';
330 $alt = get_string('alreadyalt', 'portfolio');
a4763136 331 if (empty($format)) {
332 $format = PORTFOLIO_ADD_FULL_FORM;
333 }
6e6cf8a3 334 if (empty($addstr)) {
335 $addstr = get_string('addtoportfolio', 'portfolio');
336 }
a4763136 337 switch ($format) {
338 case PORTFOLIO_ADD_FULL_FORM:
339 return '<form action="' . $url . '">' . "\n"
6e6cf8a3 340 . '<input type="submit" value="' . $addstr . '" />' . "\n"
341 . '<img src="' . $icon . '" alt="' . $alt . '" />' . "\n"
a4763136 342 . ' </form>';
343 case PORTFOLIO_ADD_ICON_FORM:
344 case PORTFOLIO_ADD_ICON_LINK:
345 return '<a href="' . $url . '"><img src="' . $icon . '" alt="' . $alt . '" /></a>';
346 case PORTFOLIO_ADD_TEXT_LINK:
6e6cf8a3 347 return '<a href="' . $url . '">' . $addstr . '(!) </a>';
a4763136 348 default:
349 debugging(get_string('invalidaddformat', 'portfolio', $format));
350 }
351 }
ce09fecc 352}
67a87e7d 353
67a87e7d 354/**
355* returns a drop menu with a list of available instances.
356*
87fcac8d 357* @param array $instances array of portfolio plugin instance objects - the instances to put in the menu
358* @param array $callerformats array of PORTFOLIO_FORMAT_XXX constants - the formats the caller supports (this is used to filter plugins)
359* @param array $callbackclass the callback class name - used for debugging only for when there are no common formats
360* @param string $selectname the name of the select element. Optional, defaults to instance.
361* @param boolean $return whether to print or return the output. Optional, defaults to print.
362* @param booealn $returnarray if returning, whether to return the HTML or the array of options. Optional, defaults to HTML.
67a87e7d 363*
364* @return string the html, from <select> to </select> inclusive.
365*/
9eb0a772 366function portfolio_instance_select($instances, $callerformats, $callbackclass, $selectname='instance', $return=false, $returnarray=false) {
367 global $CFG;
368
90658eef 369 if (empty($CFG->enableportfolios)) {
9eb0a772 370 return;
371 }
372
67a87e7d 373 $insane = portfolio_instance_sanity_check();
374 $count = 0;
9eb0a772 375 $selectoutput = "\n" . '<select name="' . $selectname . '">' . "\n";
67a87e7d 376 foreach ($instances as $instance) {
349242a3 377 $formats = portfolio_supported_formats_intersect($callerformats, $instance->supported_formats());
378 if (count($formats) == 0) {
67a87e7d 379 // bail. no common formats.
380 continue;
381 }
382 if (array_key_exists($instance->get('id'), $insane)) {
383 // bail, plugin is misconfigured
384 debugging(get_string('instancemisconfigured', 'portfolio', get_string($insane[$instance->get('id')], 'portfolio_' . $instance->get('plugin'))));
385 continue;
386 }
387 $count++;
9eb0a772 388 $selectoutput .= "\n" . '<option value="' . $instance->get('id') . '">' . $instance->get('name') . '</option>' . "\n";
389 $options[$instance->get('id')] = $instance->get('name');
67a87e7d 390 }
391 if (empty($count)) {
392 // bail. no common formats.
393 debugging(get_string('nocommonformats', 'portfolio', $callbackclass));
394 return;
395 }
396 $selectoutput .= "\n" . "</select>\n";
9eb0a772 397 if (!empty($returnarray)) {
398 return $options;
399 }
400 if (!empty($return)) {
401 return $selectoutput;
402 }
403 echo $selectoutput;
67a87e7d 404}
405
406/**
407* return all portfolio instances
408*
87fcac8d 409* @todo check capabilities here - see MDL-15768
410*
411* @param boolean visibleonly Don't include hidden instances. Defaults to true and will be overridden to true if the next parameter is true
412* @param boolean useronly Check the visibility preferences and permissions of the logged in user. Defaults to true.
413*
67a87e7d 414* @return array of portfolio instances (full objects, not just database records)
415*/
416function portfolio_instances($visibleonly=true, $useronly=true) {
417
418 global $DB, $USER;
419
420 $values = array();
421 $sql = 'SELECT * FROM {portfolio_instance}';
422
423 if ($visibleonly || $useronly) {
424 $values[] = 1;
425 $sql .= ' WHERE visible = ?';
426 }
427 if ($useronly) {
428 $sql .= ' AND id NOT IN (
429 SELECT instance FROM {portfolio_instance_user}
430 WHERE userid = ? AND name = ? AND value = ?
431 )';
432 $values = array_merge($values, array($USER->id, 'visible', 0));
433 }
434 $sql .= ' ORDER BY name';
435
436 $instances = array();
437 foreach ($DB->get_records_sql($sql, $values) as $instance) {
a50ef3d3 438 $instances[$instance->id] = portfolio_instance($instance->id, $instance);
67a87e7d 439 }
67a87e7d 440 return $instances;
441}
442
443/**
87fcac8d 444* Supported formats currently in use.
445*
446* Canonical place for a list of all formats
447* that portfolio plugins and callers
67a87e7d 448* can use for exporting content
449*
87fcac8d 450* @return keyed array of all the available export formats (constant => classname)
67a87e7d 451*/
452function portfolio_supported_formats() {
453 return array(
349242a3 454 PORTFOLIO_FORMAT_FILE => 'portfolio_format_file',
455 PORTFOLIO_FORMAT_IMAGE => 'portfolio_format_image',
456 PORTFOLIO_FORMAT_HTML => 'portfolio_format_html',
ea0de12f 457 PORTFOLIO_FORMAT_TEXT => 'portfolio_format_text',
458 PORTFOLIO_FORMAT_VIDEO => 'portfolio_format_video',
5071079c 459 /*PORTFOLIO_FORMAT_MBKP, */ // later
460 /*PORTFOLIO_FORMAT_PIOP, */ // also later
67a87e7d 461 );
462}
463
ea0de12f 464/**
87fcac8d 465* Deduce export format from file mimetype
466*
467* This function returns the revelant portfolio export format
ea0de12f 468* which is used to determine which portfolio plugins can be used
469* for exporting this content
470* according to the mime type of the given file
471* this only works when exporting exactly <b>one</b> file
472*
473* @param stored_file $file file to check mime type for
87fcac8d 474*
ea0de12f 475* @return string the format constant (see PORTFOLIO_FORMAT_XXX constants)
476*/
87fcac8d 477function portfolio_format_from_file(stored_file $file) {
ea0de12f 478 static $alreadymatched;
479 if (empty($alreadymatched)) {
480 $alreadymatched = array();
481 }
482 if (!($file instanceof stored_file)) {
483 throw new portfolio_exception('invalidfileargument', 'portfolio');
484 }
485 $mimetype = $file->get_mimetype();
486 if (array_key_exists($mimetype, $alreadymatched)) {
487 return $alreadymatched[$mimetype];
488 }
489 $allformats = portfolio_supported_formats();
490 foreach ($allformats as $format => $classname) {
491 $supportedmimetypes = call_user_func(array($classname, 'mimetypes'));
492 if (!is_array($supportedmimetypes)) {
493 debugging("one of the portfolio format classes, $classname, said it supported something funny for mimetypes, should have been array...");
494 debugging(print_r($supportedmimetypes, true));
495 continue;
496 }
497 if (in_array($mimetype, $supportedmimetypes)) {
498 $alreadymatched[$mimetype] = $format;
499 return $format;
500 }
501 }
502 return PORTFOLIO_FORMAT_FILE; // base case for files...
503}
504
505/**
87fcac8d 506* Intersection of plugin formats and caller formats
507*
508* Walks both the caller formats and portfolio plugin formats
ea0de12f 509* and looks for matches (walking the hierarchy as well)
510* and returns the intersection
511*
512* @param array $callerformats formats the caller supports
513* @param array $pluginformats formats the portfolio plugin supports
514*/
7812e882 515function portfolio_supported_formats_intersect($callerformats, $pluginformats) {
516 $allformats = portfolio_supported_formats();
517 $intersection = array();
518 foreach ($callerformats as $cf) {
519 if (!array_key_exists($cf, $allformats)) {
520 debugging(get_string('invalidformat', 'portfolio', $cf));
521 continue;
522 }
34035201 523 $cfobj = new $allformats[$cf]();
7812e882 524 foreach ($pluginformats as $p => $pf) {
525 if (!array_key_exists($pf, $allformats)) {
526 debugging(get_string('invalidformat', 'portfolio', $pf));
527 unset($pluginformats[$p]); // to avoid the same warning over and over
528 continue;
529 }
34035201 530 if ($cfobj instanceof $allformats[$pf]) {
7812e882 531 $intersection[] = $cf;
532 }
533 }
534 }
535 return $intersection;
536}
537
9d7432f6 538/**
539* return the combination of the two arrays of formats with duplicates in terms of specificity removed
540* use case: a module is exporting a single file, so the general formats would be FILE and MBKP
541* while the specific formats would be the specific subclass of FILE based on mime (say IMAGE)
542* and this function would return IMAGE and MBKP
543*
544* @param array $specificformats array of more specific formats (eg based on mime detection)
545* @param array $generalformats array of more general formats (usually more supported)
546*
547* @return array merged formats with dups removed
548*/
549function portfolio_most_specific_formats($specificformats, $generalformats) {
550 $allformats = portfolio_supported_formats();
551 foreach ($specificformats as $f) {
552 // look for something less specific and remove it, ie outside of the inheritance tree of the current formats.
553 if (!array_key_exists($f, $allformats)) {
554 throw new portfolio_button_exception('invalidformat', 'portfolio', $f);
555 }
556 $fobj = new $allformats[$f];
557 foreach ($generalformats as $key => $cf) {
558 $cfclass = $allformats[$cf];
559 if ($fobj instanceof $cfclass) {
560 unset($generalformats[$cf]);
561 }
562 }
563 }
564 return array_merge(array_values($specificformats), array_values($generalformats));
565}
566
67a87e7d 567/**
568* helper function to return an instance of a plugin (with config loaded)
569*
87fcac8d 570* @param int $instance id of instance
571* @param array $record database row that corresponds to this instance
572* this is passed to avoid unnecessary lookups
573* Optional, and the record will be retrieved if null.
67a87e7d 574*
575* @return subclass of portfolio_plugin_base
576*/
577function portfolio_instance($instanceid, $record=null) {
578 global $DB, $CFG;
579
580 if ($record) {
581 $instance = $record;
582 } else {
583 if (!$instance = $DB->get_record('portfolio_instance', array('id' => $instanceid))) {
34035201 584 throw new portfolio_exception('invalidinstance', 'portfolio');
67a87e7d 585 }
586 }
587 require_once($CFG->dirroot . '/portfolio/type/'. $instance->plugin . '/lib.php');
588 $classname = 'portfolio_plugin_' . $instance->plugin;
589 return new $classname($instanceid, $instance);
590}
591
592/**
87fcac8d 593* Helper function to call a static function on a portfolio plugin class
594*
595* This will figure out the classname and require the right file and call the function.
67a87e7d 596* you can send a variable number of arguments to this function after the first two
597* and they will be passed on to the function you wish to call.
598*
87fcac8d 599* @param string $plugin name of plugin
67a87e7d 600* @param string $function function to call
601*/
602function portfolio_static_function($plugin, $function) {
603 global $CFG;
604
605 $pname = null;
606 if (is_object($plugin) || is_array($plugin)) {
607 $plugin = (object)$plugin;
608 $pname = $plugin->name;
609 } else {
610 $pname = $plugin;
611 }
612
613 $args = func_get_args();
614 if (count($args) <= 2) {
615 $args = array();
616 }
617 else {
618 array_shift($args);
619 array_shift($args);
620 }
621
622 require_once($CFG->dirroot . '/portfolio/type/' . $plugin . '/lib.php');
623 return call_user_func_array(array('portfolio_plugin_' . $plugin, $function), $args);
624}
625
626/**
627* helper function to check all the plugins for sanity and set any insane ones to invisible.
628*
629* @param array $plugins to check (if null, defaults to all)
630* one string will work too for a single plugin.
631*
632* @return array array of insane instances (keys= id, values = reasons (keys for plugin lang)
633*/
634function portfolio_plugin_sanity_check($plugins=null) {
635 global $DB;
636 if (is_string($plugins)) {
637 $plugins = array($plugins);
638 } else if (empty($plugins)) {
639 $plugins = get_list_of_plugins('portfolio/type');
640 }
641
642 $insane = array();
643 foreach ($plugins as $plugin) {
644 if ($result = portfolio_static_function($plugin, 'plugin_sanity_check')) {
645 $insane[$plugin] = $result;
646 }
647 }
648 if (empty($insane)) {
649 return array();
650 }
651 list($where, $params) = $DB->get_in_or_equal(array_keys($insane));
652 $where = ' plugin ' . $where;
653 $DB->set_field_select('portfolio_instance', 'visible', 0, $where, $params);
654 return $insane;
655}
656
657/**
658* helper function to check all the instances for sanity and set any insane ones to invisible.
659*
660* @param array $instances to check (if null, defaults to all)
661* one instance or id will work too
662*
663* @return array array of insane instances (keys= id, values = reasons (keys for plugin lang)
664*/
665function portfolio_instance_sanity_check($instances=null) {
666 global $DB;
667 if (empty($instances)) {
668 $instances = portfolio_instances(false);
669 } else if (!is_array($instances)) {
670 $instances = array($instances);
671 }
672
673 $insane = array();
674 foreach ($instances as $instance) {
675 if (is_object($instance) && !($instance instanceof portfolio_plugin_base)) {
676 $instance = portfolio_instance($instance->id, $instance);
677 } else if (is_numeric($instance)) {
678 $instance = portfolio_instance($instance);
679 }
680 if (!($instance instanceof portfolio_plugin_base)) {
681 debugging('something weird passed to portfolio_instance_sanity_check, not subclass or id');
682 continue;
683 }
684 if ($result = $instance->instance_sanity_check()) {
685 $insane[$instance->get('id')] = $result;
686 }
687 }
688 if (empty($insane)) {
689 return array();
690 }
691 list ($where, $params) = $DB->get_in_or_equal(array_keys($insane));
692 $where = ' id ' . $where;
693 $DB->set_field_select('portfolio_instance', 'visible', 0, $where, $params);
694 return $insane;
695}
696
697/**
698* helper function to display a table of plugins (or instances) and reasons for disabling
699*
700* @param array $insane array of insane plugins (key = plugin (or instance id), value = reason)
701* @param array $instances if reporting instances rather than whole plugins, pass the array (key = id, value = object) here
702*
703*/
a50ef3d3 704function portfolio_report_insane($insane, $instances=false, $return=false) {
67a87e7d 705 if (empty($insane)) {
706 return;
707 }
708
709 static $pluginstr;
710 if (empty($pluginstr)) {
711 $pluginstr = get_string('plugin', 'portfolio');
712 }
713 if ($instances) {
714 $headerstr = get_string('someinstancesdisabled', 'portfolio');
715 } else {
716 $headerstr = get_string('somepluginsdisabled', 'portfolio');
717 }
718
a50ef3d3 719 $output = notify($headerstr, 'notifyproblem', 'center', true);
67a87e7d 720 $table = new StdClass;
721 $table->head = array($pluginstr, '');
722 $table->data = array();
723 foreach ($insane as $plugin => $reason) {
724 if ($instances) {
67a87e7d 725 $instance = $instances[$plugin];
726 $plugin = $instance->get('plugin');
727 $name = $instance->get('name');
728 } else {
729 $name = $plugin;
730 }
731 $table->data[] = array($name, get_string($reason, 'portfolio_' . $plugin));
732 }
a50ef3d3 733 $output .= print_table($table, true);
734 $output .= '<br /><br /><br />';
735
736 if ($return) {
737 return $output;
738 }
739 echo $output;
67a87e7d 740}
741
9eb0a772 742/**
743* fake the url to portfolio/add.php from data from somewhere else
744* you should use portfolio_add_button instead 99% of the time
745*
87fcac8d 746* @param int $instanceid instanceid (optional, will force a new screen if not specified)
747* @param string $classname callback classname
748* @param string $classfile file containing the callback class definition
749* @param array $callbackargs arguments to pass to the callback class
9eb0a772 750*/
751function portfolio_fake_add_url($instanceid, $classname, $classfile, $callbackargs) {
752 global $CFG;
753 $url = $CFG->wwwroot . '/portfolio/add.php?instance=' . $instanceid . '&amp;callbackclass=' . $classname . '&amp;callbackfile=' . $classfile;
754
755 if (is_object($callbackargs)) {
756 $callbackargs = (array)$callbackargs;
757 }
758 if (!is_array($callbackargs) || empty($callbackargs)) {
759 return $url;
760 }
761 foreach ($callbackargs as $key => $value) {
762 $url .= '&amp;ca_' . $key . '=' . urlencode($value);
763 }
764 return $url;
765}
67a87e7d 766
67a87e7d 767
67a87e7d 768
87fcac8d 769/**
770* event handler for the portfolio_send event
771*/
772function portfolio_handle_event($eventdata) {
773 global $CFG;
774 $exporter = portfolio_exporter::rewaken_object($eventdata);
775 $exporter->process_stage_package();
776 $exporter->process_stage_send();
777 $exporter->save();
778 $exporter->process_stage_cleanup();
779 return true;
67a87e7d 780}
781
87fcac8d 782/**
783* main portfolio cronjob
784* currently just cleans up expired transfer records.
785*
786* @todo add hooks in the plugins - either per instance or per plugin
787*/
788function portfolio_cron() {
789 global $DB;
9eb0a772 790
87fcac8d 791 if ($expired = $DB->get_records_select('portfolio_tempdata', 'expirytime < ?', array(time()), '', 'id')) {
792 foreach ($expired as $d) {
793 $e = portfolio_exporter::rewaken_object($d);
794 $e->process_stage_cleanup(true);
9eb0a772 795 }
192ce92b 796 }
5071079c 797}
798
67a87e7d 799/**
87fcac8d 800* helper function to rethrow a caught portfolio_exception as an export exception
801*
802* used because when a portfolio_export exception is thrown the export is cancelled
803*
804* @param portfolio_exporter $exporter current exporter object
805* @param exception $exception exception to rethrow
806*
807* @return void
808* @throws portfolio_export_exceptiog
67a87e7d 809*/
87fcac8d 810function portfolio_export_rethrow_exception($exporter, $exception) {
811 throw new portfolio_export_exception($exporter, $exception->errorcode, $exception->module, $exception->link, $exception->a);
812}
67a87e7d 813
bee4bce2 814/**
815* try and determine expected_time for purely file based exports
816* or exports that might include large file attachments.
817*
818* @param mixed $totest - either an array of stored_file objects or a single stored_file object
819*
820* @return constant PORTFOLIO_TIME_XXX
821*/
822function portfolio_expected_time_file($totest) {
823 global $CFG;
824 if ($totest instanceof stored_file) {
825 $totest = array($totest);
826 }
827 $size = 0;
828 foreach ($totest as $file) {
829 if (!($file instanceof stored_file)) {
830 debugging('something weird passed to portfolio_expected_time_file - not stored_file object');
831 debugging(print_r($file, true));
832 continue;
833 }
834 $size += $file->get_filesize();
835 }
836
837 $fileinfo = portfolio_filesize_info();
838
839 $moderate = $high = 0; // avoid warnings
840
841 foreach (array('moderate', 'high') as $setting) {
842 $settingname = 'portfolio_' . $setting . '_filesize_threshold';
843 if (empty($CFG->{$settingname}) || !array_key_exists($CFG->{$settingname}, $fileinfo['options'])) {
844 debugging("weird or unset admin value for $settingname, using default instead");
845 $$setting = $fileinfo[$setting];
846 } else {
847 $$setting = $CFG->{$settingname};
848 }
849 }
850
851 if ($size < $moderate) {
852 return PORTFOLIO_TIME_LOW;
853 } else if ($size < $high) {
854 return PORTFOLIO_TIME_MODERATE;
855 }
856 return PORTFOLIO_TIME_HIGH;
857}
858
859
860/**
861* the default filesizes and threshold information for file based transfers
862* this shouldn't need to be used outside the admin pages and the portfolio code
863*/
864function portfolio_filesize_info() {
865 $filesizes = array();
866 $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152, 5242880, 10485760, 20971520, 52428800);
867 foreach ($sizelist as $size) {
868 $filesizes[$size] = display_size($size);
869 }
870 return array(
871 'options' => $filesizes,
872 'moderate' => 1048576,
873 'high' => 5242880,
874 );
875}
876
877/**
878* try and determine expected_time for purely database based exports
879* or exports that might include large parts of a database
880*
881* @param integer $recordcount - number of records trying to export
882*
883* @return constant PORTFOLIO_TIME_XXX
884*/
885function portfolio_expected_time_db($recordcount) {
886 global $CFG;
887
888 if (empty($CFG->portfolio_moderate_dbsize_threshold)) {
889 set_config('portfolio_moderate_dbsize_threshold', 10);
890 }
891 if (empty($CFG->portfolio_high_dbsize_threshold)) {
892 set_config('portfolio_high_dbsize_threshold', 50);
893 }
894 if ($recordcount < $CFG->portfolio_moderate_dbsize_threshold) {
895 return PORTFOLIO_TIME_LOW;
896 } else if ($recordcount < $CFG->portfolio_high_dbsize_threshold) {
897 return PORTFOLIO_TIME_MODERATE;
898 }
899 return PORTFOLIO_TIME_HIGH;
900}
67a87e7d 901
67a87e7d 902?>