67a87e7d |
1 | <?php |
67a87e7d |
2 | /** |
3 | * this file contains: |
4 | * {@link portfolio_add_button} -entry point for callers |
5 | * {@link class portfolio_plugin_base} - class plugins extend |
6 | * {@link class portfolio_caller_base} - class callers extend |
7 | * {@link class portfolio_admin_form} - base moodleform class for plugin administration |
8 | * {@link class portfolio_user_form} - base moodleform class for plugin instance user config |
9 | * {@link class portfolio_export_form} - base moodleform class for during-export configuration (eg metadata) |
10 | * {@link class portfolio_exporter} - class used during export process |
11 | * |
12 | * and some helper functions: |
13 | * {@link portfolio_instances - returns an array of all configured instances |
14 | * {@link portfolio_instance - returns an instance of the right class given an id |
15 | * {@link portfolio_instance_select} - returns a drop menu of available instances |
16 | * {@link portfolio_static_function - requires the file, and calls a static function on the given class |
17 | " {@link portfolio_plugin_sanity_check - polls given (or all) portfolio_plugins for sanity and disables insane ones |
18 | " {@link portfolio_instance_sanity_check - polls given (or all) portfolio instances for sanity and disables insane ones |
19 | * {@link portfolio_report_instane} - returns a table of insane plugins and the reasons (used for plugins or instances thereof) |
20 | * {@link portfolio_supported_formats - returns array of all available formats for plugins and callers to use |
21 | * {@link portfolio_handle_event} - event handler for queued transfers that get triggered on cron |
22 | * |
23 | */ |
24 | require_once ($CFG->libdir.'/formslib.php'); |
25 | |
26 | // **** EXPORT STAGE CONSTANTS **** // |
27 | |
28 | /** |
29 | * display a form to the user |
30 | * this one might not be used if neither |
31 | * the plugin, or the caller has any config. |
32 | */ |
33 | define('PORTFOLIO_STAGE_CONFIG', 1); |
34 | |
35 | /** |
36 | * summarise the form and ask for confirmation |
37 | * if we skipped PORTFOLIO_STAGE_CONFIG, |
38 | * just confirm the send. |
39 | */ |
40 | define('PORTFOLIO_STAGE_CONFIRM', 2); |
41 | |
42 | /** |
43 | * either queue the event and skip to PORTFOLIO_STAGE_FINISHED |
44 | * or continue to PORTFOLIO_STAGE_PACKAGE |
45 | */ |
46 | |
47 | define('PORTFOLIO_STAGE_QUEUEORWAIT', 3); |
48 | |
49 | /** |
50 | * package up the various bits |
51 | * during this stage both the caller |
52 | * and the plugin get their package methods called |
53 | */ |
54 | define('PORTFOLIO_STAGE_PACKAGE', 4); |
55 | |
56 | /* |
57 | * the portfolio plugin must send the file |
58 | */ |
59 | define('PORTFOLIO_STAGE_SEND', 5); |
60 | |
61 | /** |
62 | * cleanup the temporary area |
63 | */ |
64 | define('PORTFOLIO_STAGE_CLEANUP', 6); |
65 | |
66 | /** |
67 | * display the "finished notification" |
68 | */ |
69 | define('PORTFOLIO_STAGE_FINISHED', 7); |
70 | |
71 | |
72 | |
73 | // **** EXPORT FORMAT CONSTANTS **** // |
74 | // these should always correspond to a string |
75 | // in the portfolio module, called format_{$value} |
76 | // **** **** // |
77 | |
67a87e7d |
78 | |
79 | /** |
80 | * file - the most basic fallback format. |
81 | * this should always be supported |
82 | * in remote system.s |
83 | */ |
84 | define('PORTFOLIO_FORMAT_FILE', 'file'); |
85 | |
86 | /** |
87 | * moodle backup - the plugin needs to be able to write a complete backup |
88 | * the caller need to be able to export the particular XML bits to insert |
89 | * into moodle.xml (?and the file bits if necessary) |
90 | */ |
91 | define('PORTFOLIO_FORMAT_MBKP', 'mbkp'); |
92 | |
7812e882 |
93 | /** |
94 | * html - subtype of file |
95 | */ |
96 | define('PORTFOLIO_FORMAT_HTML', 'html'); |
97 | |
98 | /** |
99 | * image - subtype of file |
100 | */ |
101 | define('PORTFOLIO_FORMAT_IMAGE', 'image'); |
102 | |
ea0de12f |
103 | /** |
104 | * video - subtype of file |
105 | */ |
106 | define('PORTFOLIO_FORMAT_VIDEO', 'video'); |
107 | |
108 | /** |
109 | * text - subtype of file |
110 | */ |
111 | define('PORTFOLIO_FORMAT_TEXT', 'text'); |
7812e882 |
112 | |
67a87e7d |
113 | |
114 | // **** EXPORT TIME LEVELS **** // |
115 | // these should correspond to a string |
116 | // in the portfolio module, called time_{$value} |
117 | |
118 | /** |
119 | * no delay. don't even offer the user the option |
120 | * of not waiting for the transfer |
121 | */ |
122 | define('PORTFOLIO_TIME_LOW', 'low'); |
123 | |
124 | /** |
125 | * a small delay. user can still easily opt to |
126 | * watch this transfer and wait. |
127 | */ |
128 | define('PORTFOLIO_TIME_MODERATE', 'moderate'); |
129 | |
130 | /** |
131 | * slow. the user really should not be given the option |
132 | * to choose this. |
133 | */ |
134 | define('PORTFOLIO_TIME_HIGH', 'high'); |
135 | |
83f60058 |
136 | /** |
137 | * very slow, or immediate transfers not supported |
138 | */ |
139 | define('PORTFOLIO_TIME_FORCEQUEUE', 'queue'); |
140 | |
866d543f |
141 | // ************************************************** // |
142 | // available ways to add the portfolio export to a page |
143 | // ************************************************** // |
144 | |
145 | /** |
146 | * a whole form, containing a drop down menu (where necessary) |
147 | * and a submit button |
148 | */ |
149 | define('PORTFOLIO_ADD_FULL_FORM', 1); |
150 | |
151 | |
152 | /** |
153 | * a whole form, containing a drop down menu (where necessary) |
154 | * but has an icon instead of a button to submit |
155 | */ |
156 | define('PORTFOLIO_ADD_ICON_FORM', 2); |
157 | |
158 | /** |
159 | * just an icon with a link around it (yuk, as will result in a long url |
160 | * only use where necessary) |
161 | */ |
162 | define('PORTFOLIO_ADD_ICON_LINK', 3); |
163 | |
164 | /** |
165 | * just some text with a link around it (yuk, as will result in a long url |
166 | * only use where necessary) |
167 | */ |
168 | define('PORTFOLIO_ADD_TEXT_LINK', 4); |
67a87e7d |
169 | |
170 | /** |
171 | * entry point to add an 'add to portfolio' button somewhere in moodle |
172 | * this function does not check permissions. the caller must check permissions first. |
173 | * later, during the export process, the caller class is instantiated and the check_permissions method is called |
174 | * but not in this function. |
175 | * |
176 | * @param string $callbackclass name of the class containing the callback functions |
177 | * activity modules should ALWAYS use their name_portfolio_caller |
178 | * other locations must use something unique |
179 | * @param mixed $callbackargs this can be an array or hash of arguments to pass |
180 | * back to the callback functions (passed by reference) |
181 | * these MUST be primatives to be added as hidden form fields. |
182 | * and the values get cleaned to PARAM_ALPHAEXT or PARAM_NUMBER or PARAM_PATH |
ed1fcf79 |
183 | * @param string $callbackfile this can be autodetected if it's in the same file as your caller, |
184 | * but more often, the caller is a script.php and the class in a lib.php |
185 | * so you can pass it here if necessary. |
186 | * this path should be relative (ie, not include) dirroot |
866d543f |
187 | * @param int $format format to display the button or form or icon or link. |
188 | * See constants PORTFOLIO_ADD_XXX for more info. |
189 | * optional, defaults to PORTFOLI_ADD_FULL_FORM |
190 | * @param str $addstr string to use for the button or icon alt text or link text. |
191 | * this is whole string, not key. optional, defaults to 'Add to portfolio'; |
67a87e7d |
192 | * @param boolean $return whether to echo or return content (optional defaults to false (echo) |
349242a3 |
193 | * @param array $callersupports if the calling code knows better than the static method on the calling class (supported_formats) |
194 | * eg, if there's a file that might be an image, you can pass it here instead |
67a87e7d |
195 | */ |
349242a3 |
196 | function portfolio_add_button($callbackclass, $callbackargs, $callbackfile=null, $format=PORTFOLIO_ADD_FULL_FORM, $addstr=null, $return=false, $callersupports=null) { |
67a87e7d |
197 | |
198 | global $SESSION, $CFG, $COURSE, $USER; |
199 | |
90658eef |
200 | if (empty($CFG->enableportfolios)) { |
a239f01e |
201 | return; |
202 | } |
203 | |
67a87e7d |
204 | if (!$instances = portfolio_instances()) { |
205 | return; |
206 | } |
207 | |
84a44985 |
208 | if (defined('PORTFOLIO_INTERNAL')) { |
6fdd8fa7 |
209 | // something somewhere has detected a risk of this being called during inside the preparation |
210 | // eg forum_print_attachments |
211 | return; |
212 | } |
213 | |
84a44985 |
214 | if (isset($SESSION->portfolioexport)) { |
ac6a5492 |
215 | $a = new StdClass; |
216 | $a->cancel = $CFG->wwwroot . '/portfolio/add.php?cancel=1'; |
217 | $a->finish = $CFG->wwwroot . '/portfolio/add.php?id=' . $SESSION->portfolioexport; |
3bb8a2c7 |
218 | throw new portfolio_exception('alreadyexporting', 'portfolio', null, $a); |
84a44985 |
219 | } |
220 | |
ed1fcf79 |
221 | if (empty($callbackfile)) { |
222 | $backtrace = debug_backtrace(); |
223 | if (!array_key_exists(0, $backtrace) || !array_key_exists('file', $backtrace[0]) || !is_readable($backtrace[0]['file'])) { |
224 | debugging(get_string('nocallbackfile', 'portfolio')); |
225 | return; |
226 | } |
227 | |
228 | $callbackfile = substr($backtrace[0]['file'], strlen($CFG->dirroot)); |
229 | } else { |
230 | if (!is_readable($CFG->dirroot . $callbackfile)) { |
231 | debugging(get_string('nocallbackfile', 'portfolio')); |
232 | return; |
233 | } |
67a87e7d |
234 | } |
235 | |
67a87e7d |
236 | require_once($CFG->dirroot . $callbackfile); |
237 | |
349242a3 |
238 | if (empty($callersupports)) { |
239 | $callersupports = call_user_func(array($callbackclass, 'supported_formats')); |
240 | } |
67a87e7d |
241 | |
866d543f |
242 | $formoutput = '<form method="post" action="' . $CFG->wwwroot . '/portfolio/add.php" id="portfolio-add-button">' . "\n"; |
243 | $linkoutput = '<a href="' . $CFG->wwwroot . '/portfolio/add.php?'; |
67a87e7d |
244 | foreach ($callbackargs as $key => $value) { |
245 | if (!empty($value) && !is_string($value) && !is_numeric($value)) { |
246 | $a->key = $key; |
247 | $a->value = print_r($value, true); |
248 | debugging(get_string('nonprimative', 'portfolio', $a)); |
249 | return; |
250 | } |
866d543f |
251 | $linkoutput .= 'ca_' . $key . '=' . $value . '&'; |
252 | $formoutput .= "\n" . '<input type="hidden" name="ca_' . $key . '" value="' . $value . '" />'; |
67a87e7d |
253 | } |
866d543f |
254 | $formoutput .= "\n" . '<input type="hidden" name="callbackfile" value="' . $callbackfile . '" />'; |
255 | $formoutput .= "\n" . '<input type="hidden" name="callbackclass" value="' . $callbackclass . '" />'; |
256 | $formoutput .= "\n" . '<input type="hidden" name="course" value="' . (!empty($COURSE) ? $COURSE->id : 0) . '" />'; |
257 | $linkoutput .= 'callbackfile=' . $callbackfile . '&callbackclass=' |
258 | . $callbackclass . '&course=' . (!empty($COURSE) ? $COURSE->id : 0); |
67a87e7d |
259 | $selectoutput = ''; |
260 | if (count($instances) == 1) { |
261 | $instance = array_shift($instances); |
349242a3 |
262 | $formats = portfolio_supported_formats_intersect($callersupports, $instance->supported_formats()); |
263 | if (count($formats) == 0) { |
67a87e7d |
264 | // bail. no common formats. |
265 | debugging(get_string('nocommonformats', 'portfolio', $callbackclass)); |
266 | return; |
267 | } |
268 | if ($error = portfolio_instance_sanity_check($instance)) { |
269 | // bail, plugin is misconfigured |
270 | debugging(get_string('instancemisconfigured', 'portfolio', get_string($error[$instance->get('id')], 'portfolio_' . $instance->get('plugin')))); |
271 | return; |
272 | } |
866d543f |
273 | $formoutput .= "\n" . '<input type="hidden" name="instance" value="' . $instance->get('id') . '" />'; |
274 | $linkoutput .= '&instance=' . $instance->get('id'); |
67a87e7d |
275 | } |
276 | else { |
7c61a8f0 |
277 | $selectoutput = portfolio_instance_select($instances, $callersupports, $callbackclass, 'instance', true); |
67a87e7d |
278 | } |
279 | |
866d543f |
280 | if (empty($addstr)) { |
281 | $addstr = get_string('addtoportfolio', 'portfolio'); |
67a87e7d |
282 | } |
866d543f |
283 | if (empty($format)) { |
284 | $format = PORTFOLIO_ADD_FULL_FORM; |
285 | } |
286 | switch ($format) { |
287 | case PORTFOLIO_ADD_FULL_FORM: |
288 | $formoutput .= $selectoutput; |
289 | $formoutput .= "\n" . '<input type="submit" value="' . $addstr .'" />'; |
290 | $formoutput .= "\n" . '</form>'; |
291 | break; |
292 | case PORTFOLIO_ADD_ICON_FORM: |
293 | $formoutput .= $selectoutput; |
294 | $formoutput .= "\n" . '<input type="image" src="' . $CFG->pixpath . '/t/portfolio.gif" alt=' . $addstr .'" />'; |
295 | $formoutput .= "\n" . '</form>'; |
296 | break; |
297 | case PORTFOLIO_ADD_ICON_LINK: |
298 | $linkoutput .= '"><img src="' . $CFG->pixpath . '/t/portfolio.gif" alt=' . $addstr .'" /></a>'; |
299 | break; |
300 | case PORTFOLIO_ADD_TEXT_LINK: |
301 | $linkoutput .= '">' . $addstr .'</a>'; |
302 | break; |
303 | default: |
3bb8a2c7 |
304 | debugging(get_string('invalidaddformat', 'portfolio', $format)); |
866d543f |
305 | } |
306 | $output = (in_array($format, array(PORTFOLIO_ADD_FULL_FORM, PORTFOLIO_ADD_ICON_FORM)) ? $formoutput : $linkoutput); |
67a87e7d |
307 | if ($return) { |
308 | return $output; |
309 | } else { |
310 | echo $output; |
311 | } |
312 | return true; |
313 | } |
314 | |
315 | /** |
316 | * returns a drop menu with a list of available instances. |
317 | * |
318 | * @param array $instances the instances to put in the menu |
319 | * @param array $callerformats the formats the caller supports |
320 | (this is used to filter plugins) |
321 | * @param array $callbackclass the callback class name |
322 | * |
323 | * @return string the html, from <select> to </select> inclusive. |
324 | */ |
9eb0a772 |
325 | function portfolio_instance_select($instances, $callerformats, $callbackclass, $selectname='instance', $return=false, $returnarray=false) { |
326 | global $CFG; |
327 | |
90658eef |
328 | if (empty($CFG->enableportfolios)) { |
9eb0a772 |
329 | return; |
330 | } |
331 | |
67a87e7d |
332 | $insane = portfolio_instance_sanity_check(); |
333 | $count = 0; |
9eb0a772 |
334 | $selectoutput = "\n" . '<select name="' . $selectname . '">' . "\n"; |
67a87e7d |
335 | foreach ($instances as $instance) { |
349242a3 |
336 | $formats = portfolio_supported_formats_intersect($callerformats, $instance->supported_formats()); |
337 | if (count($formats) == 0) { |
67a87e7d |
338 | // bail. no common formats. |
339 | continue; |
340 | } |
341 | if (array_key_exists($instance->get('id'), $insane)) { |
342 | // bail, plugin is misconfigured |
343 | debugging(get_string('instancemisconfigured', 'portfolio', get_string($insane[$instance->get('id')], 'portfolio_' . $instance->get('plugin')))); |
344 | continue; |
345 | } |
346 | $count++; |
9eb0a772 |
347 | $selectoutput .= "\n" . '<option value="' . $instance->get('id') . '">' . $instance->get('name') . '</option>' . "\n"; |
348 | $options[$instance->get('id')] = $instance->get('name'); |
67a87e7d |
349 | } |
350 | if (empty($count)) { |
351 | // bail. no common formats. |
352 | debugging(get_string('nocommonformats', 'portfolio', $callbackclass)); |
353 | return; |
354 | } |
355 | $selectoutput .= "\n" . "</select>\n"; |
9eb0a772 |
356 | if (!empty($returnarray)) { |
357 | return $options; |
358 | } |
359 | if (!empty($return)) { |
360 | return $selectoutput; |
361 | } |
362 | echo $selectoutput; |
67a87e7d |
363 | } |
364 | |
365 | /** |
366 | * return all portfolio instances |
367 | * |
368 | * @param boolean visibleonly don't include hidden instances (defaults to true and will be overridden to true if the next parameter is true) |
369 | * @param boolean useronly check the visibility preferences and permissions of the logged in user |
370 | * @return array of portfolio instances (full objects, not just database records) |
371 | */ |
372 | function portfolio_instances($visibleonly=true, $useronly=true) { |
373 | |
374 | global $DB, $USER; |
375 | |
376 | $values = array(); |
377 | $sql = 'SELECT * FROM {portfolio_instance}'; |
378 | |
379 | if ($visibleonly || $useronly) { |
380 | $values[] = 1; |
381 | $sql .= ' WHERE visible = ?'; |
382 | } |
383 | if ($useronly) { |
384 | $sql .= ' AND id NOT IN ( |
385 | SELECT instance FROM {portfolio_instance_user} |
386 | WHERE userid = ? AND name = ? AND value = ? |
387 | )'; |
388 | $values = array_merge($values, array($USER->id, 'visible', 0)); |
389 | } |
390 | $sql .= ' ORDER BY name'; |
391 | |
392 | $instances = array(); |
393 | foreach ($DB->get_records_sql($sql, $values) as $instance) { |
a50ef3d3 |
394 | $instances[$instance->id] = portfolio_instance($instance->id, $instance); |
67a87e7d |
395 | } |
396 | // @todo check capabilities here - see MDL-15768 |
397 | return $instances; |
398 | } |
399 | |
400 | /** |
401 | * supported formats that portfolio plugins and callers |
402 | * can use for exporting content |
403 | * |
404 | * @return array of all the available export formats |
405 | */ |
406 | function portfolio_supported_formats() { |
407 | return array( |
349242a3 |
408 | PORTFOLIO_FORMAT_FILE => 'portfolio_format_file', |
409 | PORTFOLIO_FORMAT_IMAGE => 'portfolio_format_image', |
410 | PORTFOLIO_FORMAT_HTML => 'portfolio_format_html', |
ea0de12f |
411 | PORTFOLIO_FORMAT_TEXT => 'portfolio_format_text', |
412 | PORTFOLIO_FORMAT_VIDEO => 'portfolio_format_video', |
5071079c |
413 | /*PORTFOLIO_FORMAT_MBKP, */ // later |
414 | /*PORTFOLIO_FORMAT_PIOP, */ // also later |
67a87e7d |
415 | ); |
416 | } |
417 | |
ea0de12f |
418 | /** |
419 | * this function returns the revelant portfolio export format |
420 | * which is used to determine which portfolio plugins can be used |
421 | * for exporting this content |
422 | * according to the mime type of the given file |
423 | * this only works when exporting exactly <b>one</b> file |
424 | * |
425 | * @param stored_file $file file to check mime type for |
426 | * @return string the format constant (see PORTFOLIO_FORMAT_XXX constants) |
427 | */ |
428 | function portfolio_format_from_file($file) { |
429 | static $alreadymatched; |
430 | if (empty($alreadymatched)) { |
431 | $alreadymatched = array(); |
432 | } |
433 | if (!($file instanceof stored_file)) { |
434 | throw new portfolio_exception('invalidfileargument', 'portfolio'); |
435 | } |
436 | $mimetype = $file->get_mimetype(); |
437 | if (array_key_exists($mimetype, $alreadymatched)) { |
438 | return $alreadymatched[$mimetype]; |
439 | } |
440 | $allformats = portfolio_supported_formats(); |
441 | foreach ($allformats as $format => $classname) { |
442 | $supportedmimetypes = call_user_func(array($classname, 'mimetypes')); |
443 | if (!is_array($supportedmimetypes)) { |
444 | debugging("one of the portfolio format classes, $classname, said it supported something funny for mimetypes, should have been array..."); |
445 | debugging(print_r($supportedmimetypes, true)); |
446 | continue; |
447 | } |
448 | if (in_array($mimetype, $supportedmimetypes)) { |
449 | $alreadymatched[$mimetype] = $format; |
450 | return $format; |
451 | } |
452 | } |
453 | return PORTFOLIO_FORMAT_FILE; // base case for files... |
454 | } |
455 | |
456 | /** |
457 | * walks both the caller formats and portfolio plugin formats |
458 | * and looks for matches (walking the hierarchy as well) |
459 | * and returns the intersection |
460 | * |
461 | * @param array $callerformats formats the caller supports |
462 | * @param array $pluginformats formats the portfolio plugin supports |
463 | */ |
7812e882 |
464 | function portfolio_supported_formats_intersect($callerformats, $pluginformats) { |
465 | $allformats = portfolio_supported_formats(); |
466 | $intersection = array(); |
467 | foreach ($callerformats as $cf) { |
468 | if (!array_key_exists($cf, $allformats)) { |
469 | debugging(get_string('invalidformat', 'portfolio', $cf)); |
470 | continue; |
471 | } |
34035201 |
472 | $cfobj = new $allformats[$cf](); |
7812e882 |
473 | foreach ($pluginformats as $p => $pf) { |
474 | if (!array_key_exists($pf, $allformats)) { |
475 | debugging(get_string('invalidformat', 'portfolio', $pf)); |
476 | unset($pluginformats[$p]); // to avoid the same warning over and over |
477 | continue; |
478 | } |
34035201 |
479 | if ($cfobj instanceof $allformats[$pf]) { |
7812e882 |
480 | $intersection[] = $cf; |
481 | } |
482 | } |
483 | } |
484 | return $intersection; |
485 | } |
486 | |
67a87e7d |
487 | /** |
488 | * helper function to return an instance of a plugin (with config loaded) |
489 | * |
490 | * @param int $instance id of instance |
491 | * @param array $record database row that corresponds to this instance |
492 | * this is passed to avoid unnecessary lookups |
493 | * |
494 | * @return subclass of portfolio_plugin_base |
495 | */ |
496 | function portfolio_instance($instanceid, $record=null) { |
497 | global $DB, $CFG; |
498 | |
499 | if ($record) { |
500 | $instance = $record; |
501 | } else { |
502 | if (!$instance = $DB->get_record('portfolio_instance', array('id' => $instanceid))) { |
34035201 |
503 | throw new portfolio_exception('invalidinstance', 'portfolio'); |
67a87e7d |
504 | } |
505 | } |
506 | require_once($CFG->dirroot . '/portfolio/type/'. $instance->plugin . '/lib.php'); |
507 | $classname = 'portfolio_plugin_' . $instance->plugin; |
508 | return new $classname($instanceid, $instance); |
509 | } |
510 | |
511 | /** |
512 | * helper function to call a static function on a portfolio plugin class |
513 | * this will figure out the classname and require the right file and call the function. |
514 | * you can send a variable number of arguments to this function after the first two |
515 | * and they will be passed on to the function you wish to call. |
516 | * |
517 | * @param string $plugin name of plugin |
518 | * @param string $function function to call |
519 | */ |
520 | function portfolio_static_function($plugin, $function) { |
521 | global $CFG; |
522 | |
523 | $pname = null; |
524 | if (is_object($plugin) || is_array($plugin)) { |
525 | $plugin = (object)$plugin; |
526 | $pname = $plugin->name; |
527 | } else { |
528 | $pname = $plugin; |
529 | } |
530 | |
531 | $args = func_get_args(); |
532 | if (count($args) <= 2) { |
533 | $args = array(); |
534 | } |
535 | else { |
536 | array_shift($args); |
537 | array_shift($args); |
538 | } |
539 | |
540 | require_once($CFG->dirroot . '/portfolio/type/' . $plugin . '/lib.php'); |
541 | return call_user_func_array(array('portfolio_plugin_' . $plugin, $function), $args); |
542 | } |
543 | |
544 | /** |
545 | * helper function to check all the plugins for sanity and set any insane ones to invisible. |
546 | * |
547 | * @param array $plugins to check (if null, defaults to all) |
548 | * one string will work too for a single plugin. |
549 | * |
550 | * @return array array of insane instances (keys= id, values = reasons (keys for plugin lang) |
551 | */ |
552 | function portfolio_plugin_sanity_check($plugins=null) { |
553 | global $DB; |
554 | if (is_string($plugins)) { |
555 | $plugins = array($plugins); |
556 | } else if (empty($plugins)) { |
557 | $plugins = get_list_of_plugins('portfolio/type'); |
558 | } |
559 | |
560 | $insane = array(); |
561 | foreach ($plugins as $plugin) { |
562 | if ($result = portfolio_static_function($plugin, 'plugin_sanity_check')) { |
563 | $insane[$plugin] = $result; |
564 | } |
565 | } |
566 | if (empty($insane)) { |
567 | return array(); |
568 | } |
569 | list($where, $params) = $DB->get_in_or_equal(array_keys($insane)); |
570 | $where = ' plugin ' . $where; |
571 | $DB->set_field_select('portfolio_instance', 'visible', 0, $where, $params); |
572 | return $insane; |
573 | } |
574 | |
575 | /** |
576 | * helper function to check all the instances for sanity and set any insane ones to invisible. |
577 | * |
578 | * @param array $instances to check (if null, defaults to all) |
579 | * one instance or id will work too |
580 | * |
581 | * @return array array of insane instances (keys= id, values = reasons (keys for plugin lang) |
582 | */ |
583 | function portfolio_instance_sanity_check($instances=null) { |
584 | global $DB; |
585 | if (empty($instances)) { |
586 | $instances = portfolio_instances(false); |
587 | } else if (!is_array($instances)) { |
588 | $instances = array($instances); |
589 | } |
590 | |
591 | $insane = array(); |
592 | foreach ($instances as $instance) { |
593 | if (is_object($instance) && !($instance instanceof portfolio_plugin_base)) { |
594 | $instance = portfolio_instance($instance->id, $instance); |
595 | } else if (is_numeric($instance)) { |
596 | $instance = portfolio_instance($instance); |
597 | } |
598 | if (!($instance instanceof portfolio_plugin_base)) { |
599 | debugging('something weird passed to portfolio_instance_sanity_check, not subclass or id'); |
600 | continue; |
601 | } |
602 | if ($result = $instance->instance_sanity_check()) { |
603 | $insane[$instance->get('id')] = $result; |
604 | } |
605 | } |
606 | if (empty($insane)) { |
607 | return array(); |
608 | } |
609 | list ($where, $params) = $DB->get_in_or_equal(array_keys($insane)); |
610 | $where = ' id ' . $where; |
611 | $DB->set_field_select('portfolio_instance', 'visible', 0, $where, $params); |
612 | return $insane; |
613 | } |
614 | |
615 | /** |
616 | * helper function to display a table of plugins (or instances) and reasons for disabling |
617 | * |
618 | * @param array $insane array of insane plugins (key = plugin (or instance id), value = reason) |
619 | * @param array $instances if reporting instances rather than whole plugins, pass the array (key = id, value = object) here |
620 | * |
621 | */ |
a50ef3d3 |
622 | function portfolio_report_insane($insane, $instances=false, $return=false) { |
67a87e7d |
623 | if (empty($insane)) { |
624 | return; |
625 | } |
626 | |
627 | static $pluginstr; |
628 | if (empty($pluginstr)) { |
629 | $pluginstr = get_string('plugin', 'portfolio'); |
630 | } |
631 | if ($instances) { |
632 | $headerstr = get_string('someinstancesdisabled', 'portfolio'); |
633 | } else { |
634 | $headerstr = get_string('somepluginsdisabled', 'portfolio'); |
635 | } |
636 | |
a50ef3d3 |
637 | $output = notify($headerstr, 'notifyproblem', 'center', true); |
67a87e7d |
638 | $table = new StdClass; |
639 | $table->head = array($pluginstr, ''); |
640 | $table->data = array(); |
641 | foreach ($insane as $plugin => $reason) { |
642 | if ($instances) { |
643 | // @todo this isn't working |
644 | // because it seems the new recordset object |
645 | // doesn't implement the key correctly. |
0082ed89 |
646 | // see MDL-15798 |
67a87e7d |
647 | $instance = $instances[$plugin]; |
648 | $plugin = $instance->get('plugin'); |
649 | $name = $instance->get('name'); |
650 | } else { |
651 | $name = $plugin; |
652 | } |
653 | $table->data[] = array($name, get_string($reason, 'portfolio_' . $plugin)); |
654 | } |
a50ef3d3 |
655 | $output .= print_table($table, true); |
656 | $output .= '<br /><br /><br />'; |
657 | |
658 | if ($return) { |
659 | return $output; |
660 | } |
661 | echo $output; |
67a87e7d |
662 | } |
663 | |
9eb0a772 |
664 | /** |
665 | * fake the url to portfolio/add.php from data from somewhere else |
666 | * you should use portfolio_add_button instead 99% of the time |
667 | * |
668 | * @param int $instanceid instanceid (optional, will force a new screen if not specified) |
669 | * @param string $classname callback classname |
670 | * @param string $classfile file containing the callback class definition |
671 | * @param array $callbackargs arguments to pass to the callback class |
672 | */ |
673 | function portfolio_fake_add_url($instanceid, $classname, $classfile, $callbackargs) { |
674 | global $CFG; |
675 | $url = $CFG->wwwroot . '/portfolio/add.php?instance=' . $instanceid . '&callbackclass=' . $classname . '&callbackfile=' . $classfile; |
676 | |
677 | if (is_object($callbackargs)) { |
678 | $callbackargs = (array)$callbackargs; |
679 | } |
680 | if (!is_array($callbackargs) || empty($callbackargs)) { |
681 | return $url; |
682 | } |
683 | foreach ($callbackargs as $key => $value) { |
684 | $url .= '&ca_' . $key . '=' . urlencode($value); |
685 | } |
686 | return $url; |
687 | } |
67a87e7d |
688 | |
689 | /** |
690 | * base class for the caller |
691 | * places in moodle that want to display |
692 | * the add form should subclass this for their callback. |
693 | */ |
694 | abstract class portfolio_caller_base { |
695 | |
696 | /** |
697 | * stdclass object |
698 | * course that was active during the caller |
699 | */ |
700 | protected $course; |
701 | |
702 | /** |
703 | * named array of export config |
704 | * use{@link set_export_config} and {@link get_export_config} to access |
705 | */ |
706 | protected $exportconfig; |
707 | |
708 | /** |
709 | * stdclass object |
710 | * user currently exporting content |
711 | */ |
712 | protected $user; |
713 | |
d67bfc32 |
714 | /** |
715 | * a reference to the exporter object |
716 | */ |
717 | protected $exporter; |
84a44985 |
718 | |
04f35360 |
719 | /** |
349242a3 |
720 | * this can be overridden in subclasses constructors if they want |
04f35360 |
721 | */ |
349242a3 |
722 | protected $supportedformats; |
04f35360 |
723 | |
67a87e7d |
724 | /** |
725 | * if this caller wants any additional config items |
726 | * they should be defined here. |
727 | * |
728 | * @param array $mform moodleform object (passed by reference) to add elements to |
729 | * @param object $instance subclass of portfolio_plugin_base |
730 | * @param integer $userid id of user exporting content |
731 | */ |
732 | public function export_config_form(&$mform, $instance) {} |
733 | |
734 | |
735 | /** |
736 | * whether this caller wants any additional |
737 | * config during export (eg options or metadata) |
738 | * |
739 | * @return boolean |
740 | */ |
741 | public function has_export_config() { |
742 | return false; |
743 | } |
744 | |
745 | /** |
746 | * just like the moodle form validation function |
747 | * this is passed in the data array from the form |
748 | * and if a non empty array is returned, form processing will stop. |
749 | * |
750 | * @param array $data data from form. |
751 | * @return array keyvalue pairs - form element => error string |
752 | */ |
753 | public function export_config_validation($data) {} |
754 | |
755 | /** |
756 | * how long does this reasonably expect to take.. |
757 | * should we offer the user the option to wait.. |
758 | * this is deliberately nonstatic so it can take filesize into account |
759 | * the portfolio plugin can override this. |
760 | * (so for exmaple even if a huge file is being sent, |
761 | * the download portfolio plugin doesn't care ) |
762 | * |
763 | * @return string (see PORTFOLIO_TIME_* constants) |
764 | */ |
765 | public abstract function expected_time(); |
766 | |
767 | /** |
768 | * used for displaying the navigation during the export screens. |
769 | * |
770 | * this function must be implemented, but can really return anything. |
771 | * an Exporting.. string will be added on the end. |
772 | * @return array of $extranav and $cm |
773 | * |
774 | * to pass to build_navigation |
775 | * |
776 | */ |
777 | public abstract function get_navigation(); |
778 | |
ffcfd8a7 |
779 | /** |
780 | * |
781 | */ |
782 | public abstract function get_sha1(); |
783 | |
67a87e7d |
784 | /* |
785 | * generic getter for properties belonging to this instance |
786 | * <b>outside</b> the subclasses |
787 | * like name, visible etc. |
67a87e7d |
788 | */ |
9eb0a772 |
789 | public function get($field) { |
67a87e7d |
790 | if (property_exists($this, $field)) { |
791 | return $this->{$field}; |
792 | } |
34035201 |
793 | $a = (object)array('property' => $field, 'class' => get_class($this)); |
794 | throw new portfolio_export_exception($this->get('exporter'), 'invalidproperty', 'portfolio', $this->get_return_url(), $a); |
67a87e7d |
795 | } |
796 | |
797 | /** |
798 | * generic setter for properties belonging to this instance |
799 | * <b>outside</b> the subclass |
800 | * like name, visible, etc. |
801 | * |
67a87e7d |
802 | */ |
d67bfc32 |
803 | public final function set($field, &$value) { |
67a87e7d |
804 | if (property_exists($this, $field)) { |
d67bfc32 |
805 | $this->{$field} =& $value; |
67a87e7d |
806 | $this->dirty = true; |
807 | return true; |
808 | } |
34035201 |
809 | $a = (object)array('property' => $field, 'class' => get_class($this)); |
810 | throw new portfolio_export_exception($this->get('exporter'), 'invalidproperty', 'portfolio', $this->get_return_url(), $a); |
67a87e7d |
811 | } |
812 | |
813 | /** |
814 | * stores the config generated at export time. |
815 | * subclasses can retrieve values using |
816 | * {@link get_export_config} |
817 | * |
818 | * @param array $config formdata |
819 | */ |
820 | public final function set_export_config($config) { |
821 | $allowed = array_merge( |
ffcfd8a7 |
822 | array('wait', 'hidewait', 'format', 'hideformat'), |
67a87e7d |
823 | $this->get_allowed_export_config() |
824 | ); |
825 | foreach ($config as $key => $value) { |
826 | if (!in_array($key, $allowed)) { |
34035201 |
827 | $a = (object)array('property' => $key, 'class' => get_class($this)); |
828 | throw new portfolio_export_exception($this->get('exporter'), 'invalidexportproperty', 'portfolio', $this->get_return_url(), $a); |
67a87e7d |
829 | } |
830 | $this->exportconfig[$key] = $value; |
831 | } |
832 | } |
833 | |
834 | /** |
835 | * returns a particular export config value. |
836 | * subclasses shouldn't need to override this |
837 | * |
838 | * @param string key the config item to fetch |
67a87e7d |
839 | */ |
840 | public final function get_export_config($key) { |
841 | $allowed = array_merge( |
ffcfd8a7 |
842 | array('wait', 'hidewait', 'format', 'hideformat'), |
67a87e7d |
843 | $this->get_allowed_export_config() |
844 | ); |
845 | if (!in_array($key, $allowed)) { |
34035201 |
846 | $a = (object)array('property' => $key, 'class' => get_class($this)); |
847 | throw new portfolio_export_exception($this->get('exporter'), 'invalidexportproperty', 'portfolio', $this->get_return_url(), $a); |
67a87e7d |
848 | } |
849 | if (!array_key_exists($key, $this->exportconfig)) { |
34035201 |
850 | return null; |
67a87e7d |
851 | } |
852 | return $this->exportconfig[$key]; |
853 | } |
854 | |
04f35360 |
855 | |
04f35360 |
856 | |
67a87e7d |
857 | /** |
858 | * Similar to the other allowed_config functions |
859 | * if you need export config, you must provide |
860 | * a list of what the fields are. |
861 | * |
862 | * even if you want to store stuff during export |
863 | * without displaying a form to the user, |
864 | * you can use this. |
865 | * |
866 | * @return array array of allowed keys |
867 | */ |
868 | public function get_allowed_export_config() { |
869 | return array(); |
870 | } |
871 | |
872 | /** |
873 | * after the user submits their config |
874 | * they're given a confirm screen |
875 | * summarising what they've chosen. |
876 | * |
877 | * this function should return a table of nice strings => values |
878 | * of what they've chosen |
879 | * to be displayed in a table. |
880 | * |
881 | * @return array array of config items. |
882 | */ |
883 | public function get_export_summary() { |
884 | return false; |
885 | } |
886 | |
887 | /** |
888 | * called before the portfolio plugin gets control |
889 | * this function should copy all the files it wants to |
d67bfc32 |
890 | * the temporary directory, using {@see copy_existing_file} |
891 | * or {@see write_new_file} |
67a87e7d |
892 | */ |
d67bfc32 |
893 | public abstract function prepare_package(); |
67a87e7d |
894 | |
895 | /** |
896 | * array of formats this caller supports |
897 | * the intersection of what this function returns |
898 | * and what the selected portfolio plugin supports |
899 | * will be used |
900 | * use the constants PORTFOLIO_FORMAT_* |
349242a3 |
901 | * if $caller is passed, that can be used for more specific guesses |
902 | * as this function <b>must</b> be called statically. |
67a87e7d |
903 | * |
904 | * @return array list of formats |
905 | */ |
349242a3 |
906 | public static function supported_formats($caller=null) { |
ea0de12f |
907 | if ($caller && $formats = $caller->get('supportedformats')) { |
908 | if (is_array($formats)) { |
909 | return $formats; |
910 | } |
911 | debugging(get_class($caller) . ' has set a non array value of member variable supported formats - working around but should be fixed in code'); |
912 | return array($formats); |
349242a3 |
913 | } |
bb63fc3e |
914 | return array(PORTFOLIO_FORMAT_FILE); |
915 | } |
67a87e7d |
916 | |
ea0de12f |
917 | |
67a87e7d |
918 | /** |
919 | * this is the "return to where you were" url |
920 | * |
921 | * @return string url |
922 | */ |
923 | public abstract function get_return_url(); |
924 | |
925 | /** |
926 | * callback to do whatever capability checks required |
927 | * in the caller (called during the export process |
928 | */ |
929 | public abstract function check_permissions(); |
ffcfd8a7 |
930 | |
931 | /** |
932 | * nice name to display to the user about this caller location |
933 | */ |
934 | public abstract static function display_name(); |
192ce92b |
935 | |
936 | /** |
937 | * return a string to put at the header summarising this export |
938 | * by default, just the display name (usually just 'assignment' or something unhelpful |
939 | */ |
940 | public function heading_summary() { |
941 | return get_string('exportingcontentfrom', 'portfolio', $this->display_name()); |
942 | } |
67a87e7d |
943 | } |
944 | |
5071079c |
945 | abstract class portfolio_module_caller_base extends portfolio_caller_base { |
946 | |
947 | protected $cm; |
9eb0a772 |
948 | protected $course; |
5071079c |
949 | |
950 | public function get_navigation() { |
951 | $extranav = array('name' => $this->cm->name, 'link' => $this->get_return_url()); |
952 | return array($extranav, $this->cm); |
953 | } |
954 | |
955 | public function get_return_url() { |
956 | global $CFG; |
957 | return $CFG->wwwroot . '/mod/' . $this->cm->modname . '/view.php?id=' . $this->cm->id; |
958 | } |
9eb0a772 |
959 | |
960 | public function get($key) { |
961 | if ($key != 'course') { |
962 | return parent::get($key); |
963 | } |
964 | global $DB; |
965 | if (empty($this->course)) { |
966 | $this->course = $DB->get_record('course', array('id' => $this->cm->course)); |
967 | } |
968 | return $this->course; |
969 | } |
192ce92b |
970 | |
971 | public function heading_summary() { |
972 | return get_string('exportingcontentfrom', 'portfolio', $this->display_name() . ': ' . $this->cm->name); |
973 | } |
5071079c |
974 | } |
975 | |
67a87e7d |
976 | /** |
2876d73b |
977 | * the base class for portfolio plugins |
67a87e7d |
978 | * all plugins must subclass this. |
979 | */ |
980 | abstract class portfolio_plugin_base { |
981 | |
982 | /** |
983 | * boolean |
984 | * whether this object needs writing out to the database |
985 | */ |
986 | protected $dirty; |
987 | |
988 | /** |
989 | * integer |
990 | * id of instance |
991 | */ |
992 | protected $id; |
993 | |
994 | /** |
995 | * string |
996 | * name of instance |
997 | */ |
998 | protected $name; |
999 | |
1000 | /** |
1001 | * string |
1002 | * plugin this instance belongs to |
1003 | */ |
1004 | protected $plugin; |
1005 | |
1006 | /** |
1007 | * boolean |
1008 | * whether this instance is visible or not |
1009 | */ |
1010 | protected $visible; |
1011 | |
1012 | /** |
1013 | * named array |
1014 | * admin configured config |
1015 | * use {@link set_config} and {@get_config} to access |
1016 | */ |
1017 | protected $config; |
1018 | |
1019 | /** |
1020 | * |
1021 | * user config cache |
1022 | * named array of named arrays |
1023 | * keyed on userid and then on config field => value |
1024 | * use {@link get_user_config} and {@link set_user_config} to access. |
1025 | */ |
1026 | protected $userconfig; |
1027 | |
1028 | /** |
1029 | * named array |
1030 | * export config during export |
1031 | * use {@link get_export_config} and {@link set export_config} to access. |
1032 | */ |
1033 | protected $exportconfig; |
1034 | |
1035 | /** |
1036 | * stdclass object |
1037 | * user currently exporting data |
1038 | */ |
1039 | protected $user; |
1040 | |
d67bfc32 |
1041 | /** |
1042 | * a reference to the exporter object |
1043 | */ |
1044 | protected $exporter; |
67a87e7d |
1045 | |
1046 | /** |
1047 | * array of formats this portfolio supports |
1048 | * the intersection of what this function returns |
1049 | * and what the caller supports will be used |
1050 | * use the constants PORTFOLIO_FORMAT_* |
1051 | * |
1052 | * @return array list of formats |
1053 | */ |
bb63fc3e |
1054 | public static function supported_formats() { |
1055 | return array(PORTFOLIO_FORMAT_FILE); |
1056 | } |
67a87e7d |
1057 | |
1058 | |
1059 | /** |
1060 | * how long does this reasonably expect to take.. |
1061 | * should we offer the user the option to wait.. |
1062 | * this is deliberately nonstatic so it can take filesize into account |
1063 | * |
1064 | * @param string $callertime - what the caller thinks |
1065 | * the portfolio plugin instance |
1066 | * is given the final say |
1067 | * because it might be (for example) download. |
1068 | * @return string (see PORTFOLIO_TIME_* constants) |
1069 | */ |
1070 | public abstract function expected_time($callertime); |
1071 | |
d96a1acc |
1072 | /** |
1073 | * is this plugin push or pill. |
1074 | * if push, cleanup will be called directly after send_package |
1075 | * if not, cleanup will be called after portfolio/file.php is requested |
1076 | * |
1077 | * @return boolean |
1078 | */ |
1079 | public abstract function is_push(); |
1080 | |
0f71f48b |
1081 | public static abstract function get_name(); |
1082 | |
67a87e7d |
1083 | /** |
1084 | * check sanity of plugin |
1085 | * if this function returns something non empty, ALL instances of your plugin |
1086 | * will be set to invisble and not be able to be set back until it's fixed |
1087 | * |
1088 | * @return mixed - string = error string KEY (must be inside plugin_$yourplugin) or 0/false if you're ok |
1089 | */ |
1090 | public static function plugin_sanity_check() { |
1091 | return 0; |
1092 | } |
1093 | |
1094 | /** |
1095 | * check sanity of instances |
1096 | * if this function returns something non empty, the instance will be |
1097 | * set to invislbe and not be able to be set back until it's fixed. |
1098 | * |
1099 | * @return mixed - string = error string KEY (must be inside plugin_$yourplugin) or 0/false if you're ok |
1100 | */ |
1101 | public function instance_sanity_check() { |
1102 | return 0; |
1103 | } |
1104 | |
1105 | /** |
1106 | * does this plugin need any configuration by the administrator? |
1107 | * |
1108 | * if you override this to return true, |
1109 | * you <b>must</b> implement {@link} admin_config_form |
1110 | */ |
1111 | public static function has_admin_config() { |
1112 | return false; |
1113 | } |
1114 | |
1115 | /** |
1116 | * can this plugin be configured by the user in their profile? |
1117 | * |
1118 | * if you override this to return true, |
1119 | * you <b>must</b> implement {@link user_config_form |
1120 | */ |
1121 | public function has_user_config() { |
1122 | return false; |
1123 | } |
1124 | |
1125 | /** |
1126 | * does this plugin need configuration during export time? |
1127 | * |
1128 | * if you override this to return true, |
1129 | * you <b>must</b> implement {@link export_config_form} |
1130 | */ |
1131 | public function has_export_config() { |
1132 | return false; |
1133 | } |
1134 | |
1135 | /** |
1136 | * just like the moodle form validation function |
1137 | * this is passed in the data array from the form |
1138 | * and if a non empty array is returned, form processing will stop. |
1139 | * |
1140 | * @param array $data data from form. |
1141 | * @return array keyvalue pairs - form element => error string |
1142 | */ |
1143 | public function export_config_validation() {} |
1144 | |
1145 | /** |
1146 | * just like the moodle form validation function |
1147 | * this is passed in the data array from the form |
1148 | * and if a non empty array is returned, form processing will stop. |
1149 | * |
1150 | * @param array $data data from form. |
1151 | * @return array keyvalue pairs - form element => error string |
1152 | */ |
1153 | public function user_config_validation() {} |
1154 | |
1155 | /** |
1156 | * sets the export time config from the moodle form. |
1157 | * you can also use this to set export config that |
1158 | * isn't actually controlled by the user |
1159 | * eg things that your subclasses want to keep in state |
1160 | * across the export. |
1161 | * keys must be in {@link get_allowed_export_config} |
1162 | * |
1163 | * this is deliberately not final (see boxnet plugin) |
1164 | * |
1165 | * @param array $config named array of config items to set. |
1166 | */ |
1167 | public function set_export_config($config) { |
1168 | $allowed = array_merge( |
ffcfd8a7 |
1169 | array('wait', 'hidewait', 'format', 'hideformat'), |
67a87e7d |
1170 | $this->get_allowed_export_config() |
1171 | ); |
1172 | foreach ($config as $key => $value) { |
1173 | if (!in_array($key, $allowed)) { |
34035201 |
1174 | $a = (object)array('property' => $key, 'class' => get_class($this)); |
1175 | throw new portfolio_export_exception($this->get('exporter'), 'invalidexportproperty', 'portfolio', $this->get_return_url(), $a); |
67a87e7d |
1176 | } |
1177 | $this->exportconfig[$key] = $value; |
1178 | } |
1179 | } |
1180 | |
1181 | /** |
1182 | * gets an export time config value. |
1183 | * subclasses should not override this. |
1184 | * |
1185 | * @param string key field to fetch |
1186 | * |
1187 | * @return string config value |
1188 | * |
67a87e7d |
1189 | */ |
1190 | public final function get_export_config($key) { |
1191 | $allowed = array_merge( |
ffcfd8a7 |
1192 | array('hidewait', 'wait', 'format', 'hideformat'), |
67a87e7d |
1193 | $this->get_allowed_export_config() |
1194 | ); |
1195 | if (!in_array($key, $allowed)) { |
34035201 |
1196 | $a = (object)array('property' => $key, 'class' => get_class($this)); |
1197 | throw new portfolio_export_exception($this->get('exporter'), 'invalidexportproperty', 'portfolio', $this->get_return_url(), $a); |
67a87e7d |
1198 | } |
1199 | if (!array_key_exists($key, $this->exportconfig)) { |
34035201 |
1200 | return null; |
67a87e7d |
1201 | } |
1202 | return $this->exportconfig[$key]; |
1203 | } |
1204 | |
1205 | /** |
1206 | * after the user submits their config |
1207 | * they're given a confirm screen |
1208 | * summarising what they've chosen. |
1209 | * |
1210 | * this function should return a table of nice strings => values |
1211 | * of what they've chosen |
1212 | * to be displayed in a table. |
1213 | * |
1214 | * @return array array of config items. |
1215 | */ |
1216 | public function get_export_summary() { |
1217 | return false; |
1218 | } |
1219 | |
1220 | /** |
d67bfc32 |
1221 | * called after the caller has finished having control |
1222 | * of its prepare_package function. |
1223 | * this function should read all the files from the portfolio |
1224 | * working file area and zip them and send them or whatever it wants. |
1225 | * {@see get_tempfiles} to get the list of files. |
67a87e7d |
1226 | * |
67a87e7d |
1227 | */ |
d67bfc32 |
1228 | public abstract function prepare_package(); |
67a87e7d |
1229 | |
1230 | /** |
1231 | * this is the function that is responsible for sending |
1232 | * the package to the remote system, |
1233 | * or whatever request is necessary to initiate the transfer. |
1234 | * |
1235 | * @return boolean success |
1236 | */ |
1237 | public abstract function send_package(); |
1238 | |
1239 | |
1240 | /** |
1241 | * once everything is done and the user |
1242 | * has the finish page displayed to them |
1243 | * the base class takes care of printing them |
1244 | * "return to where you are" or "continue to portfolio" links |
1245 | * this function allows for exta finish options from the plugin |
1246 | * |
1247 | * @return array named array of links => titles |
1248 | */ |
1249 | public function get_extra_finish_options() { |
1250 | return false; |
1251 | } |
1252 | |
1253 | /** |
1254 | * the url for the user to continue to their portfolio |
1255 | * |
1256 | * @return string url or false. |
1257 | */ |
1258 | public abstract function get_continue_url(); |
1259 | |
1260 | /** |
1261 | * mform to display to the user in their profile |
1262 | * if your plugin can't be configured by the user, |
1263 | * (see {@link has_user_config}) |
1264 | * don't bother overriding this function |
1265 | * |
1266 | * @param moodleform $mform passed by reference, add elements to it |
1267 | */ |
1268 | public function user_config_form(&$mform) {} |
1269 | |
1270 | /** |
1271 | * mform to display to the admin configuring the plugin. |
1272 | * if your plugin can't be configured by the admin, |
1273 | * (see {@link} has_admin_config) |
1274 | * don't bother overriding this function |
1275 | * |
1276 | * this function can be called statically or non statically, |
1277 | * depending on whether it's creating a new instance (statically), |
1278 | * or editing an existing one (non statically) |
1279 | * |
1280 | * @param moodleform $mform passed by reference, add elements to it. |
67a87e7d |
1281 | */ |
1282 | public function admin_config_form(&$mform) {} |
1283 | |
1284 | /** |
1285 | * just like the moodle form validation function |
1286 | * this is passed in the data array from the form |
1287 | * and if a non empty array is returned, form processing will stop. |
1288 | * |
1289 | * @param array $data data from form. |
1290 | * @return array keyvalue pairs - form element => error string |
1291 | */ |
d67bfc32 |
1292 | public function admin_config_validation($data) {} |
67a87e7d |
1293 | /** |
1294 | * mform to display to the user exporting data using this plugin. |
1295 | * if your plugin doesn't need user input at this time, |
1296 | * (see {@link has_export_config} |
1297 | * don't bother overrideing this function |
1298 | * |
1299 | * @param moodleform $mform passed by reference, add elements to it. |
1300 | */ |
1301 | public function export_config_form(&$mform) {} |
1302 | |
1303 | /** |
1304 | * override this if your plugin doesn't allow multiple instances |
1305 | * |
1306 | * @return boolean |
1307 | */ |
1308 | public static function allows_multiple() { |
1309 | return true; |
1310 | } |
1311 | |
1312 | /** |
1313 | * |
1314 | * If at any point the caller wants to steal control |
1315 | * it can, by returning something that isn't false |
1316 | * in this function |
1317 | * The controller will redirect to whatever url |
1318 | * this function returns. |
1319 | * Afterwards, you can redirect back to portfolio/add.php?postcontrol=1 |
1320 | * and {@link post_control} is called before the rest of the processing |
1321 | * for the stage is done |
1322 | * |
1323 | * @param int stage to steal control *before* (see constants PARAM_STAGE_*} |
1324 | * |
1325 | * @return boolean or string url |
1326 | */ |
1327 | public function steal_control($stage) { |
1328 | return false; |
1329 | } |
1330 | |
1331 | /** |
1332 | * after a plugin has elected to steal control, |
1333 | * and control returns to portfolio/add.php|postcontrol=1, |
1334 | * this function is called, and passed the stage that was stolen control from |
1335 | * and the request (get and post but not cookie) parameters |
1336 | * this is useful for external systems that need to redirect the user back |
1337 | * with some extra data in the url (like auth tokens etc) |
1338 | * for an example implementation, see boxnet portfolio plugin. |
1339 | * |
1340 | * @param int $stage the stage before control was stolen |
1341 | * @param array $params a merge of $_GET and $_POST |
1342 | * |
1343 | */ |
1344 | |
1345 | public function post_control($stage, $params) { } |
1346 | |
1347 | /** |
1348 | * this function creates a new instance of a plugin |
1349 | * saves it in the database, saves the config |
1350 | * and returns it. |
1351 | * you shouldn't need to override it |
1352 | * unless you're doing something really funky |
1353 | * |
cc8696b2 |
1354 | * @param string $plugin portfolio plugin to create |
1355 | * @param string $name name of new instance |
1356 | * @param array $config what the admin config form returned |
1357 | * |
67a87e7d |
1358 | * @return object subclass of portfolio_plugin_base |
1359 | */ |
1360 | public static function create_instance($plugin, $name, $config) { |
1361 | global $DB, $CFG; |
1362 | $new = (object)array( |
1363 | 'plugin' => $plugin, |
1364 | 'name' => $name, |
1365 | ); |
cc8696b2 |
1366 | if (!portfolio_static_function($plugin, 'allows_multiple')) { |
1367 | // check we don't have one already |
1368 | if ($DB->record_exists('portfolio_instance', array('plugin' => $plugin))) { |
f240dc70 |
1369 | throw new portfolio_exception('multipledisallowed', 'portfolio', '', $plugin); |
cc8696b2 |
1370 | } |
1371 | } |
67a87e7d |
1372 | $newid = $DB->insert_record('portfolio_instance', $new); |
1373 | require_once($CFG->dirroot . '/portfolio/type/' . $plugin . '/lib.php'); |
1374 | $classname = 'portfolio_plugin_' . $plugin; |
1375 | $obj = new $classname($newid); |
1376 | $obj->set_config($config); |
1377 | return $obj; |
1378 | } |
1379 | |
1380 | /** |
1381 | * construct a plugin instance |
1382 | * subclasses should not need to override this unless they're doing something special |
1383 | * and should call parent::__construct afterwards |
1384 | * |
1385 | * @param int $instanceid id of plugin instance to construct |
1386 | * @param mixed $record stdclass object or named array - use this is you already have the record to avoid another query |
1387 | * |
1388 | * @return object subclass of portfolio_plugin_base |
1389 | */ |
1390 | public function __construct($instanceid, $record=null) { |
1391 | global $DB; |
1392 | if (!$record) { |
1393 | if (!$record = $DB->get_record('portfolio_instance', array('id' => $instanceid))) { |
34035201 |
1394 | throw new portfolio_exception('invalidinstance', 'portfolio'); |
67a87e7d |
1395 | } |
1396 | } |
1397 | foreach ((array)$record as $key =>$value) { |
1398 | if (property_exists($this, $key)) { |
1399 | $this->{$key} = $value; |
1400 | } |
1401 | } |
1402 | $this->config = new StdClass; |
1403 | $this->userconfig = array(); |
1404 | $this->exportconfig = array(); |
1405 | foreach ($DB->get_records('portfolio_instance_config', array('instance' => $instanceid)) as $config) { |
1406 | $this->config->{$config->name} = $config->value; |
1407 | } |
1408 | return $this; |
1409 | } |
1410 | |
1411 | /** |
1412 | * a list of fields that can be configured per instance. |
1413 | * this is used for the save handlers of the config form |
1414 | * and as checks in set_config and get_config |
1415 | * |
1416 | * @return array array of strings (config item names) |
1417 | */ |
1418 | public static function get_allowed_config() { |
1419 | return array(); |
1420 | } |
1421 | |
1422 | /** |
1423 | * a list of fields that can be configured by the user. |
1424 | * this is used for the save handlers in the config form |
1425 | * and as checks in set_user_config and get_user_config. |
1426 | * |
1427 | * @return array array of strings (config field names) |
1428 | */ |
1429 | public function get_allowed_user_config() { |
1430 | return array(); |
1431 | } |
1432 | |
1433 | /** |
1434 | * a list of fields that can be configured by the user. |
1435 | * this is used for the save handlers in the config form |
1436 | * and as checks in set_export_config and get_export_config. |
1437 | * |
1438 | * @return array array of strings (config field names) |
1439 | */ |
1440 | public function get_allowed_export_config() { |
1441 | return array(); |
1442 | } |
1443 | |
1444 | /** |
1445 | * saves (or updates) the config stored in portfolio_instance_config. |
1446 | * you shouldn't need to override this unless you're doing something funky. |
1447 | * |
1448 | * @param array $config array of config items. |
1449 | */ |
1450 | public final function set_config($config) { |
1451 | global $DB; |
1452 | foreach ($config as $key => $value) { |
1453 | // try set it in $this first |
23cbde0a |
1454 | try { |
1455 | $this->set($key, $value); |
67a87e7d |
1456 | continue; |
23cbde0a |
1457 | } catch (portfolio_exception $e) { } |
67a87e7d |
1458 | if (!in_array($key, $this->get_allowed_config())) { |
34035201 |
1459 | $a = (object)array('property' => $key, 'class' => get_class($this)); |
1460 | throw new portfolio_export_exception($this->get('exporter'), 'invalidconfigproperty', 'portfolio', null, $a); |
67a87e7d |
1461 | } |
1462 | if (!isset($this->config->{$key})) { |
1463 | $DB->insert_record('portfolio_instance_config', (object)array( |
1464 | 'instance' => $this->id, |
1465 | 'name' => $key, |
1466 | 'value' => $value, |
1467 | )); |
1468 | } else if ($this->config->{$key} != $value) { |
1469 | $DB->set_field('portfolio_instance_config', 'value', $value, array('name' => $key, 'instance' => $this->id)); |
1470 | } |
1471 | $this->config->{$key} = $value; |
1472 | } |
67a87e7d |
1473 | } |
1474 | |
1475 | /** |
1476 | * gets the value of a particular config item |
1477 | * |
1478 | * @param string $key key to fetch |
1479 | * |
1480 | * @return string the corresponding value |
67a87e7d |
1481 | */ |
1482 | public final function get_config($key) { |
1483 | if (!in_array($key, $this->get_allowed_config())) { |
34035201 |
1484 | $a = (object)array('property' => $key, 'class' => get_class($this)); |
1485 | throw new portfolio_export_exception($this->get('exporter'), 'invalidconfigproperty', 'portfolio', null, $a); |
67a87e7d |
1486 | } |
1487 | if (isset($this->config->{$key})) { |
1488 | return $this->config->{$key}; |
1489 | } |
34035201 |
1490 | return null; |
67a87e7d |
1491 | } |
1492 | |
1493 | /** |
1494 | * get the value of a config item for a particular user |
1495 | * |
1496 | * @param string $key key to fetch |
1497 | * @param integer $userid id of user (defaults to current) |
1498 | * |
1499 | * @return string the corresponding value |
1500 | * |
67a87e7d |
1501 | */ |
1502 | public final function get_user_config($key, $userid=0) { |
1503 | global $DB; |
1504 | |
1505 | if (empty($userid)) { |
1506 | $userid = $this->user->id; |
1507 | } |
1508 | |
1509 | if ($key != 'visible') { // handled by the parent class |
1510 | if (!in_array($key, $this->get_allowed_user_config())) { |
34035201 |
1511 | $a = (object)array('property' => $key, 'class' => get_class($this)); |
1512 | throw new portfolio_export_exception($this->get('exporter'), 'invaliduserproperty', 'portfolio', null, $a); |
67a87e7d |
1513 | } |
1514 | } |
1515 | if (!array_key_exists($userid, $this->userconfig)) { |
1516 | $this->userconfig[$userid] = (object)array_fill_keys(array_merge(array('visible'), $this->get_allowed_user_config()), null); |
1517 | foreach ($DB->get_records('portfolio_instance_user', array('instance' => $this->id, 'userid' => $userid)) as $config) { |
1518 | $this->userconfig[$userid]->{$config->name} = $config->value; |
1519 | } |
1520 | } |
1521 | if ($this->userconfig[$userid]->visible === null) { |
1522 | $this->set_user_config(array('visible' => 1), $userid); |
1523 | } |
1524 | return $this->userconfig[$userid]->{$key}; |
1525 | |
1526 | } |
1527 | |
1528 | /** |
1529 | * |
1530 | * sets config options for a given user |
1531 | * |
1532 | * @param mixed $config array or stdclass containing key/value pairs to set |
1533 | * @param integer $userid userid to set config for (defaults to current) |
1534 | * |
67a87e7d |
1535 | */ |
1536 | public final function set_user_config($config, $userid=0) { |
1537 | global $DB; |
1538 | |
1539 | if (empty($userid)) { |
1540 | $userid = $this->user->id; |
1541 | } |
1542 | |
1543 | foreach ($config as $key => $value) { |
1544 | if ($key != 'visible' && !in_array($key, $this->get_allowed_user_config())) { |
34035201 |
1545 | $a = (object)array('property' => $key, 'class' => get_class($this)); |
1546 | throw new portfolio_export_exception($this->get('exporter'), 'invaliduserproperty', 'portfolio', null, $a); |
67a87e7d |
1547 | } |
1548 | if (!$existing = $DB->get_record('portfolio_instance_user', array('instance'=> $this->id, 'userid' => $userid, 'name' => $key))) { |
1549 | $DB->insert_record('portfolio_instance_user', (object)array( |
1550 | 'instance' => $this->id, |
1551 | 'name' => $key, |
1552 | 'value' => $value, |
1553 | 'userid' => $userid, |
1554 | )); |
1555 | } else if ($existing->value != $value) { |
1556 | $DB->set_field('portfolio_instance_user', 'value', $value, array('name' => $key, 'instance' => $this->id, 'userid' => $userid)); |
1557 | } |
1558 | $this->userconfig[$userid]->{$key} = $value; |
1559 | } |
67a87e7d |
1560 | |
1561 | } |
1562 | |
1563 | /** |
1564 | * generic getter for properties belonging to this instance |
1565 | * <b>outside</b> the subclasses |
1566 | * like name, visible etc. |
1567 | * |
67a87e7d |
1568 | */ |
1569 | public final function get($field) { |
1570 | if (property_exists($this, $field)) { |
1571 | return $this->{$field}; |
1572 | } |
34035201 |
1573 | $a = (object)array('property' => $field, 'class' => get_class($this)); |
e3569156 |
1574 | throw new portfolio_export_exception($this->get('exporter'), 'invalidproperty', 'portfolio', null, $a); |
67a87e7d |
1575 | } |
1576 | |
1577 | /** |
1578 | * generic setter for properties belonging to this instance |
1579 | * <b>outside</b> the subclass |
1580 | * like name, visible, etc. |
1581 | * |
67a87e7d |
1582 | */ |
1583 | public final function set($field, $value) { |
1584 | if (property_exists($this, $field)) { |
d67bfc32 |
1585 | $this->{$field} =& $value; |
67a87e7d |
1586 | $this->dirty = true; |
1587 | return true; |
1588 | } |
34035201 |
1589 | $a = (object)array('property' => $field, 'class' => get_class($this)); |
e3569156 |
1590 | throw new portfolio_export_exception($this->get('exporter'), 'invalidproperty', 'portfolio', null, $a); |
67a87e7d |
1591 | |
1592 | } |
1593 | |
1594 | /** |
1595 | * saves stuff that's been stored in the object to the database |
1596 | * you shouldn't need to override this |
1597 | * unless you're doing something really funky. |
1598 | * and if so, call parent::save when you're done. |
1599 | */ |
1600 | public function save() { |
1601 | global $DB; |
1602 | if (!$this->dirty) { |
1603 | return true; |
1604 | } |
1605 | $fordb = new StdClass(); |
1606 | foreach (array('id', 'name', 'plugin', 'visible') as $field) { |
1607 | $fordb->{$field} = $this->{$field}; |
1608 | } |
1609 | $DB->update_record('portfolio_instance', $fordb); |
1610 | $this->dirty = false; |
1611 | return true; |
1612 | } |
1613 | |
1614 | /** |
1615 | * deletes everything from the database about this plugin instance. |
1616 | * you shouldn't need to override this unless you're storing stuff |
1617 | * in your own tables. and if so, call parent::delete when you're done. |
1618 | */ |
1619 | public function delete() { |
1620 | global $DB; |
1621 | $DB->delete_records('portfolio_instance_config', array('instance' => $this->get('id'))); |
1622 | $DB->delete_records('portfolio_instance_user', array('instance' => $this->get('id'))); |
1623 | $DB->delete_records('portfolio_instance', array('id' => $this->get('id'))); |
1624 | $this->dirty = false; |
1625 | return true; |
1626 | } |
83f60058 |
1627 | |
2eb07e79 |
1628 | /** |
1629 | * perform any required cleanup functions |
1630 | */ |
1631 | public function cleanup() { |
1632 | return true; |
1633 | } |
1634 | |
83f60058 |
1635 | public static function mnet_publishes() { |
1636 | return array(); |
1637 | } |
67a87e7d |
1638 | } |
1639 | |
d96a1acc |
1640 | /** |
1641 | * class to inherit from for 'push' type plugins |
1642 | */ |
1643 | abstract class portfolio_plugin_push_base extends portfolio_plugin_base { |
1644 | |
1645 | public function is_push() { |
1646 | return true; |
1647 | } |
1648 | } |
1649 | |
1650 | /** |
1651 | * class to inherit from for 'pull' type plugins |
1652 | */ |
1653 | abstract class portfolio_plugin_pull_base extends portfolio_plugin_base { |
1654 | |
2eb07e79 |
1655 | protected $file; |
d96a1acc |
1656 | |
1657 | public function is_push() { |
1658 | return false; |
1659 | } |
1660 | |
1661 | |
1662 | /** |
1663 | * before sending the file when the pull is requested, verify the request parameters |
1664 | * these might include a token of some sort of whatever |
1665 | * |
1666 | * @param array request parameters (POST wins over GET) |
1667 | */ |
1668 | public abstract function verify_file_request_params($params); |
1669 | |
2eb07e79 |
1670 | /** |
1671 | * called from portfolio/file.php |
1672 | * this function sends the stored file out to the browser |
1673 | * the default is to just use send_stored_file, |
1674 | * but other implementations might do something different |
1675 | * for example, send back the file base64 encoded and encrypted |
1676 | * mahara does this but in the response to an xmlrpc request |
1677 | * rather than through file.php |
1678 | */ |
1679 | public function send_file() { |
1680 | $file = $this->get('file'); |
1681 | if (!($file instanceof stored_file)) { |
1682 | throw new portfolio_export_exception($this->get('exporter'), 'filenotfound', 'portfolio'); |
1683 | } |
1684 | send_stored_file($file, 0, 0, true, null, true); |
1685 | } |
1686 | |
d96a1acc |
1687 | } |
1688 | |
67a87e7d |
1689 | /** |
1690 | * this is the form that is actually used while exporting. |
1691 | * plugins and callers don't get to define their own class |
1692 | * as we have to handle form elements from both places |
1693 | * see the docs for portfolio_plugin_base and portfolio_caller for more information |
1694 | */ |
1695 | final class portfolio_export_form extends moodleform { |
1696 | |
1697 | public function definition() { |
1698 | |
1699 | $mform =& $this->_form; |
1700 | $mform->addElement('hidden', 'stage', PORTFOLIO_STAGE_CONFIG); |
1701 | $mform->addElement('hidden', 'instance', $this->_customdata['instance']->get('id')); |
1702 | |
1703 | if (array_key_exists('formats', $this->_customdata) && is_array($this->_customdata['formats'])) { |
1704 | if (count($this->_customdata['formats']) > 1) { |
1705 | $options = array(); |
1706 | foreach ($this->_customdata['formats'] as $key) { |
1707 | $options[$key] = get_string('format_' . $key, 'portfolio'); |
1708 | } |
1709 | $mform->addElement('select', 'format', get_string('availableformats', 'portfolio'), $options); |
1710 | } else { |
1711 | $f = array_shift($this->_customdata['formats']); |
1712 | $mform->addElement('hidden', 'format', $f); |
1713 | } |
1714 | } |
1715 | |
83f60058 |
1716 | if (array_key_exists('expectedtime', $this->_customdata) && $this->_customdata['expectedtime'] != PORTFOLIO_TIME_LOW && $this->_customdata['expectedtime'] != PORTFOLIO_TIME_FORCEQUEUE) { |
67a87e7d |
1717 | $radioarray = array(); |
1718 | $radioarray[] = &MoodleQuickForm::createElement('radio', 'wait', '', get_string('wait', 'portfolio'), 1); |
1719 | $radioarray[] = &MoodleQuickForm::createElement('radio', 'wait', '', get_string('dontwait', 'portfolio'), 0); |
1720 | $mform->addGroup($radioarray, 'radioar', get_string('wanttowait_' . $this->_customdata['expectedtime'], 'portfolio') , array(' '), false); |
1721 | |
1722 | $mform->setDefault('wait', 0); |
1723 | } |
1724 | else { |
83f60058 |
1725 | if ($this->_customdata['expectedtime'] == PORTFOLIO_TIME_LOW) { |
1726 | $mform->addElement('hidden', 'wait', 1); |
1727 | } else { |
1728 | $mform->addElement('hidden', 'wait', 0); |
1729 | } |
67a87e7d |
1730 | } |
1731 | |
1732 | if (array_key_exists('plugin', $this->_customdata) && is_object($this->_customdata['plugin'])) { |
1733 | $this->_customdata['plugin']->export_config_form($mform, $this->_customdata['userid']); |
1734 | } |
1735 | |
1736 | if (array_key_exists('caller', $this->_customdata) && is_object($this->_customdata['caller'])) { |
1737 | $this->_customdata['caller']->export_config_form($mform, $this->_customdata['instance'], $this->_customdata['userid']); |
1738 | } |
1739 | |
1740 | $this->add_action_buttons(true, get_string('next')); |
1741 | } |
1742 | |
1743 | public function validation($data) { |
1744 | |
1745 | $errors = array(); |
1746 | |
1747 | if (array_key_exists('plugin', $this->_customdata) && is_object($this->_customdata['plugin'])) { |
1748 | $pluginerrors = $this->_customdata['plugin']->export_config_validation($data); |
1749 | if (is_array($pluginerrors)) { |
1750 | $errors = $pluginerrors; |
1751 | } |
1752 | } |
1753 | if (array_key_exists('caller', $this->_customdata) && is_object($this->_customdata['caller'])) { |
1754 | $callererrors = $this->_customdata['caller']->export_config_validation($data); |
1755 | if (is_array($callererrors)) { |
1756 | $errors = array_merge($errors, $callererrors); |
1757 | } |
1758 | } |
1759 | return $errors; |
1760 | } |
1761 | } |
1762 | |
1763 | /** |
1764 | * this form is extendable by plugins |
1765 | * who want the admin to be able to configure |
1766 | * more than just the name of the instance. |
1767 | * this is NOT done by subclassing this class, |
1768 | * see the docs for portfolio_plugin_base for more information |
1769 | */ |
1770 | final class portfolio_admin_form extends moodleform { |
1771 | |
1772 | protected $instance; |
1773 | protected $plugin; |
1774 | |
1775 | public function definition() { |
1776 | global $CFG; |
1777 | $this->plugin = $this->_customdata['plugin']; |
1778 | $this->instance = (isset($this->_customdata['instance']) |
1779 | && is_subclass_of($this->_customdata['instance'], 'portfolio_plugin_base')) |
1780 | ? $this->_customdata['instance'] : null; |
1781 | |
1782 | $mform =& $this->_form; |
1783 | $strrequired = get_string('required'); |
1784 | |
1785 | $mform->addElement('hidden', 'edit', ($this->instance) ? $this->instance->get('id') : 0); |
1786 | $mform->addElement('hidden', 'new', $this->plugin); |
1787 | $mform->addElement('hidden', 'plugin', $this->plugin); |
1788 | |
67a87e7d |
1789 | // let the plugin add the fields they want (either statically or not) |
1790 | if (portfolio_static_function($this->plugin, 'has_admin_config')) { |
1791 | if (!$this->instance) { |
3304eb44 |
1792 | $insane = portfolio_instance_sanity_check($this->instance); |
1793 | portfolio_static_function($this->plugin, 'admin_config_form', $mform); |
67a87e7d |
1794 | } else { |
3304eb44 |
1795 | $insane = portfolio_plugin_sanity_check($this->plugin); |
1796 | $this->instance->admin_config_form($mform); |
67a87e7d |
1797 | } |
1798 | } |
1799 | |
0f71f48b |
1800 | if (isset($insane) && is_array($insane)) { |
3304eb44 |
1801 | $insane = array_shift($insane); |
1802 | } |
1803 | if (isset($insane) && is_string($insane)) { // something went wrong, warn... |
1804 | $mform->addElement('warning', 'insane', null, get_string($insane, 'portfolio_' . $this->plugin)); |
67a87e7d |
1805 | } |
1806 | |
36facf2e |
1807 | $mform->addElement('text', 'name', get_string('name'), 'maxlength="100" size="30"'); |
1808 | $mform->addRule('name', $strrequired, 'required', null, 'client'); |
1809 | |
1810 | |
67a87e7d |
1811 | // and set the data if we have some. |
1812 | if ($this->instance) { |
1813 | $data = array('name' => $this->instance->get('name')); |
1814 | foreach ($this->instance->get_allowed_config() as $config) { |
1815 | $data[$config] = $this->instance->get_config($config); |
1816 | } |
1817 | $this->set_data($data); |
0f71f48b |
1818 | } else { |
1819 | $this->set_data(array('name' => portfolio_static_function($this->plugin, 'get_name'))); |
67a87e7d |
1820 | } |
0f71f48b |
1821 | |
67a87e7d |
1822 | $this->add_action_buttons(true, get_string('save', 'portfolio')); |
1823 | } |
1824 | |
1825 | public function validation($data) { |
1826 | global $DB; |
1827 | |
1828 | $errors = array(); |
1829 | if ($DB->count_records('portfolio_instance', array('name' => $data['name'], 'plugin' => $data['plugin'])) > 1) { |
1830 | $errors = array('name' => get_string('err_uniquename', 'portfolio')); |
1831 | } |
1832 | |
1833 | $pluginerrors = array(); |
1834 | if ($this->instance) { |
1835 | $pluginerrors = $this->instance->admin_config_validation($data); |
1836 | } |
1837 | else { |
1838 | $pluginerrors = portfolio_static_function($this->plugin, 'admin_config_validation', $data); |
1839 | } |
1840 | if (is_array($pluginerrors)) { |
1841 | $errors = array_merge($errors, $pluginerrors); |
1842 | } |
1843 | return $errors; |
1844 | } |
1845 | } |
1846 | |
1847 | /** |
1848 | * this is the form for letting the user configure an instance of a plugin. |
1849 | * in order to extend this, you don't subclass this in the plugin.. |
1850 | * see the docs in portfolio_plugin_base for more information |
1851 | */ |
1852 | final class portfolio_user_form extends moodleform { |
1853 | |
1854 | protected $instance; |
1855 | protected $userid; |
1856 | |
1857 | public function definition() { |
1858 | $this->instance = $this->_customdata['instance']; |
1859 | $this->userid = $this->_customdata['userid']; |
1860 | |
1861 | $this->_form->addElement('hidden', 'config', $this->instance->get('id')); |
1862 | |
1863 | $this->instance->user_config_form($this->_form, $this->userid); |
1864 | |
1865 | $data = array(); |
1866 | foreach ($this->instance->get_allowed_user_config() as $config) { |
1867 | $data[$config] = $this->instance->get_user_config($config, $this->userid); |
1868 | } |
1869 | $this->set_data($data); |
1870 | $this->add_action_buttons(true, get_string('save', 'portfolio')); |
1871 | } |
1872 | |
1873 | public function validation($data) { |
1874 | |
1875 | $errors = $this->instance->user_config_validation($data); |
1876 | |
1877 | } |
1878 | } |
1879 | |
1880 | /** |
1881 | * |
1882 | * Class that handles the various stages of the actual export |
1883 | */ |
77ac22a5 |
1884 | class portfolio_exporter { |
67a87e7d |
1885 | |
15053330 |
1886 | /** |
1887 | * the caller object used during the export |
1888 | */ |
67a87e7d |
1889 | private $caller; |
15053330 |
1890 | |
1891 | /** the portfolio plugin instanced used during the export |
1892 | */ |
67a87e7d |
1893 | private $instance; |
15053330 |
1894 | |
1895 | /** |
1896 | * if there has been no config form displayed to the user |
1897 | */ |
67a87e7d |
1898 | private $noconfig; |
15053330 |
1899 | |
1900 | /** |
1901 | * the navigation to display on the wizard screens |
1902 | * built from build_navigation |
1903 | */ |
67a87e7d |
1904 | private $navigation; |
15053330 |
1905 | |
1906 | /** |
1907 | * the user currently exporting content |
1908 | * always $USER, but more conveniently placed here |
1909 | */ |
67a87e7d |
1910 | private $user; |
1911 | |
15053330 |
1912 | /** the file to include that contains the class defintion |
1913 | * of the portfolio instance plugin |
1914 | * used to re-waken the object after sleep |
1915 | */ |
67a87e7d |
1916 | public $instancefile; |
15053330 |
1917 | |
1918 | /** |
1919 | * the file to include that contains the class definition |
1920 | * of the caller object |
1921 | * used to re-waken the object after sleep |
1922 | */ |
67a87e7d |
1923 | public $callerfile; |
1924 | |
15053330 |
1925 | /** |
1926 | * the current stage of the export |
1927 | */ |
ac6a5492 |
1928 | private $stage; |
1929 | |
15053330 |
1930 | /** |
1931 | * whether something (usually the portfolio plugin) |
1932 | * has forced queuing |
1933 | */ |
f1ebc192 |
1934 | private $forcequeue; |
1935 | |
84a44985 |
1936 | /** |
d67bfc32 |
1937 | * id of this export |
1938 | * matches record in portfolio_tempdata table |
1939 | * and used for itemid for file storage. |
84a44985 |
1940 | */ |
1941 | private $id; |
1942 | |
15053330 |
1943 | /** |
1944 | * the session key during the export |
1945 | * used to avoid hijacking transfers |
1946 | */ |
beb4ac1a |
1947 | private $sesskey; |
1948 | |
15053330 |
1949 | /** |
1950 | * array of stages that have had the portfolio plugin already steal control from them |
1951 | */ |
1952 | private $alreadystolen; |
1953 | |
67a87e7d |
1954 | /** |
1955 | * construct a new exporter for use |
1956 | * |
1957 | * @param portfolio_plugin_base subclass $instance portfolio instance (passed by reference) |
1958 | * @param portfolio_caller_base subclass $caller portfolio caller (passed by reference) |
34035201 |
1959 | * @param string $callerfile path to callerfile (relative to dataroot) |
67a87e7d |
1960 | * @param string $navigation result of build_navigation (passed to print_header) |
1961 | */ |
1962 | public function __construct(&$instance, &$caller, $callerfile, $navigation) { |
1963 | $this->instance =& $instance; |
1964 | $this->caller =& $caller; |
1965 | if ($instance) { |
1966 | $this->instancefile = 'portfolio/type/' . $instance->get('plugin') . '/lib.php'; |
d67bfc32 |
1967 | $this->instance->set('exporter', $this); |
67a87e7d |
1968 | } |
1969 | $this->callerfile = $callerfile; |
1970 | $this->stage = PORTFOLIO_STAGE_CONFIG; |
1971 | $this->navigation = $navigation; |
d67bfc32 |
1972 | $this->caller->set('exporter', $this); |
15053330 |
1973 | $this->alreadystolen = array(); |
67a87e7d |
1974 | } |
1975 | |
1976 | /* |
1977 | * generic getter for properties belonging to this instance |
1978 | * <b>outside</b> the subclasses |
1979 | * like name, visible etc. |
67a87e7d |
1980 | */ |
1981 | public function get($field) { |
1982 | if (property_exists($this, $field)) { |
1983 | return $this->{$field}; |
1984 | } |
34035201 |
1985 | $a = (object)array('property' => $field, 'class' => get_class($this)); |
e3569156 |
1986 | throw new portfolio_export_exception($this, 'invalidproperty', 'portfolio', null, $a); |
67a87e7d |
1987 | } |
1988 | |
1989 | /** |
1990 | * generic setter for properties belonging to this instance |
1991 | * <b>outside</b> the subclass |
1992 | * like name, visible, etc. |
67a87e7d |
1993 | */ |
d67bfc32 |
1994 | public function set($field, &$value) { |
67a87e7d |
1995 | if (property_exists($this, $field)) { |
d67bfc32 |
1996 | $this->{$field} =& $value; |
67a87e7d |
1997 | if ($field == 'instance') { |
1998 | $this->instancefile = 'portfolio/type/' . $this->instance->get('plugin') . '/lib.php'; |
d67bfc32 |
1999 | $this->instance->set('exporter', $this); |
67a87e7d |
2000 | } |
2001 | $this->dirty = true; |
2002 | return true; |
2003 | } |
34035201 |
2004 | $a = (object)array('property' => $field, 'class' => get_class($this)); |
e3569156 |
2005 | throw new portfolio_export_exception($this, 'invalidproperty', 'portfolio', null, $a); |
67a87e7d |
2006 | |
2007 | } |
2008 | /** |
2009 | * process the given stage calling whatever functions are necessary |
2010 | * |
2011 | * @param int $stage (see PORTFOLIO_STAGE_* constants) |
2012 | * @param boolean $alreadystolen used to avoid letting plugins steal control twice. |
2013 | * |
2014 | * @return boolean whether or not to process the next stage. this is important as the function is called recursively. |
2015 | */ |
2016 | public function process_stage($stage, $alreadystolen=false) { |
ac6a5492 |
2017 | $this->set('stage', $stage); |
15053330 |
2018 | if ($alreadystolen) { |
2019 | $this->alreadystolen[$stage] = true; |
2020 | } else { |
2021 | if (!array_key_exists($stage, $this->alreadystolen)) { |
2022 | $this->alreadystolen[$stage] = false; |
2023 | } |
2024 | } |
ac6a5492 |
2025 | $this->save(); |
15053330 |
2026 | if (!$this->alreadystolen[$stage] && $url = $this->instance->steal_control($stage)) { |
67a87e7d |
2027 | redirect($url); |
2028 | break; |
2029 | } |
2030 | |
2031 | $waiting = $this->instance->get_export_config('wait'); |
2032 | if ($stage > PORTFOLIO_STAGE_QUEUEORWAIT && empty($waiting)) { |
2033 | $stage = PORTFOLIO_STAGE_FINISHED; |
2034 | } |
2035 | $functionmap = array( |
2036 | PORTFOLIO_STAGE_CONFIG => 'config', |
2037 | PORTFOLIO_STAGE_CONFIRM => 'confirm', |
2038 | PORTFOLIO_STAGE_QUEUEORWAIT => 'queueorwait', |
2039 | PORTFOLIO_STAGE_PACKAGE => 'package', |
2040 | PORTFOLIO_STAGE_CLEANUP => 'cleanup', |
2041 | PORTFOLIO_STAGE_SEND => 'send', |
2042 | PORTFOLIO_STAGE_FINISHED => 'finished' |
2043 | ); |
2044 | |
2045 | $function = 'process_stage_' . $functionmap[$stage]; |
34035201 |
2046 | try { |
2047 | if ($this->$function()) { |
2048 | // if we get through here it means control was returned |
2049 | // as opposed to wanting to stop processing |
2050 | // eg to wait for user input. |
2eb07e79 |
2051 | $this->save(); |
34035201 |
2052 | $stage++; |
2053 | return $this->process_stage($stage); |
2eb07e79 |
2054 | } else { |
2055 | $this->save(); |
2056 | return false; |
34035201 |
2057 | } |
2058 | } catch (portfolio_caller_exception $e) { |
2059 | portfolio_export_rethrow_exception($this, $e); |
2060 | } catch (portfolio_plugin_exception $e) { |
2061 | portfolio_export_rethrow_exception($this, $e); |
83f60058 |
2062 | } catch (portfolio_export_exception $e) { |
2063 | throw $e; |
34035201 |
2064 | } catch (Exception $e) { |
2065 | debugging(get_string('thirdpartyexception', 'portfolio', get_class($e))); |
2066 | portfolio_export_rethrow_exception($this, $e); |
67a87e7d |
2067 | } |
67a87e7d |
2068 | } |
2069 | |
2070 | /** |
2071 | * helper function to return the portfolio instance |
2072 | * |
2073 | * @return portfolio_plugin_base subclass |
2074 | */ |
2075 | public function instance() { |
2076 | return $this->instance; |
2077 | } |
2078 | |
2079 | /** |
2080 | * helper function to return the caller object |
2081 | * |
2082 | * @return portfolio_caller_base subclass |
2083 | */ |
2084 | public function caller() { |
2085 | return $this->caller; |
2086 | } |
2087 | |
2088 | /** |
2089 | * processes the 'config' stage of the export |
2090 | * |
2091 | * @return boolean whether or not to process the next stage. this is important as the control function is called recursively. |
2092 | */ |
2093 | public function process_stage_config() { |
2094 | |
67a87e7d |
2095 | $pluginobj = $callerobj = null; |
2096 | if ($this->instance->has_export_config()) { |
2097 | $pluginobj = $this->instance; |
2098 | } |
2099 | if ($this->caller->has_export_config()) { |
2100 | $callerobj = $this->caller; |
2101 | } |
349242a3 |
2102 | $formats = portfolio_supported_formats_intersect($this->caller->supported_formats($this->caller), $this->instance->supported_formats()); |
67a87e7d |
2103 | $expectedtime = $this->instance->expected_time($this->caller->expected_time()); |
2104 | if (count($formats) == 0) { |
2105 | // something went wrong, we should not have gotten this far. |
e3569156 |
2106 | throw new portfolio_export_exception($this, 'nocommonformats', 'portfolio', null, get_class($this->caller)); |
67a87e7d |
2107 | } |
2108 | // even if neither plugin or caller wants any config, we have to let the user choose their format, and decide to wait. |
83f60058 |
2109 | if ($pluginobj || $callerobj || count($formats) > 1 || ($expectedtime != PORTFOLIO_TIME_LOW && $expectedtime != PORTFOLIO_TIME_FORCEQUEUE)) { |
67a87e7d |
2110 | $customdata = array( |
2111 | 'instance' => $this->instance, |
2112 | 'plugin' => $pluginobj, |
2113 | 'caller' => $callerobj, |
2114 | 'userid' => $this->user->id, |
2115 | 'formats' => $formats, |
2116 | 'expectedtime' => $expectedtime, |
2117 | ); |
2118 | $mform = new portfolio_export_form('', $customdata); |
2119 | if ($mform->is_cancelled()){ |
84a44985 |
2120 | $this->cancel_request(); |
67a87e7d |
2121 | } else if ($fromform = $mform->get_data()){ |
2122 | if (!confirm_sesskey()) { |
e3569156 |
2123 | throw new portfolio_export_exception($this, 'confirmsesskeybad'); |
67a87e7d |
2124 | } |
2125 | $pluginbits = array(); |
2126 | $callerbits = array(); |
2127 | foreach ($fromform as $key => $value) { |
2128 | if (strpos($key, 'plugin_') === 0) { |
2129 | $pluginbits[substr($key, 7)] = $value; |
2130 | } else if (strpos($key, 'caller_') === 0) { |
2131 | $callerbits[substr($key, 7)] = $value; |
2132 | } |
2133 | } |
2134 | $callerbits['format'] = $pluginbits['format'] = $fromform->format; |
2135 | $pluginbits['wait'] = $fromform->wait; |
294b4928 |
2136 | if ($expectedtime == PORTFOLIO_TIME_LOW) { |
67a87e7d |
2137 | $pluginbits['wait'] = 1; |
2138 | $pluginbits['hidewait'] = 1; |
83f60058 |
2139 | } else if ($expectedtime == PORTFOLIO_TIME_FORCEQUEUE) { |
2140 | $pluginbits['wait'] = 0; |
2141 | $pluginbits['hidewait'] = 1; |
f1ebc192 |
2142 | $this->forcequeue = true; |
67a87e7d |
2143 | } |
ffcfd8a7 |
2144 | $callerbits['hideformat'] = $pluginbits['hideformat'] = (count($formats) == 1); |
67a87e7d |
2145 | $this->caller->set_export_config($callerbits); |
2146 | $this->instance->set_export_config($pluginbits); |
2147 | return true; |
2148 | } else { |
192ce92b |
2149 | $this->print_header('configexport'); |
67a87e7d |
2150 | print_simple_box_start(); |
2151 | $mform->display(); |
2152 | print_simple_box_end(); |
2153 | print_footer(); |
2154 | return false;; |
2155 | } |
2156 | } else { |
2157 | $this->noexportconfig = true; |
ffcfd8a7 |
2158 | $format = array_shift($formats); |
83f60058 |
2159 | $config = array( |
2160 | 'hidewait' => 1, |
2161 | 'wait' => (($expectedtime == PORTFOLIO_TIME_LOW) ? 1 : 0), |
2162 | 'format' => $format, |
2163 | 'hideformat' => 1 |
2164 | ); |
2165 | $this->instance->set_export_config($config); |
ffcfd8a7 |
2166 | $this->caller->set_export_config(array('format' => $format, 'hideformat' => 1)); |
f1ebc192 |
2167 | if ($expectedtime == PORTFOLIO_TIME_FORCEQUEUE) { |
2168 | $this->forcequeue = true; |
2169 | } |
67a87e7d |
2170 | return true; |
2171 | // do not break - fall through to confirm |
2172 | } |
2173 | } |
2174 | |
67a87e7d |
2175 | /** |
2176 | * processes the 'confirm' stage of the export |
2177 | * |
2178 | * @return boolean whether or not to process the next stage. this is important as the control function is called recursively. |
2179 | */ |
2180 | public function process_stage_confirm() { |
ffcfd8a7 |
2181 | global $CFG, $DB; |
2182 | |
2183 | $previous = $DB->get_records( |
2184 | 'portfolio_log', |
2185 | array( |
2186 | 'userid' => $this->user->id, |
2187 | 'portfolio' => $this->instance->get('id'), |
2188 | 'caller_sha1' => $this->caller->get_sha1(), |
2189 | ) |
2190 | ); |
2191 | if (isset($this->noexportconfig) && empty($previous)) { |
67a87e7d |
2192 | return true; |
2193 | } |
2194 | $strconfirm = get_string('confirmexport', 'portfolio'); |
2195 | $yesurl = $CFG->wwwroot . '/portfolio/add.php?stage=' . PORTFOLIO_STAGE_QUEUEORWAIT; |
ffcfd8a7 |
2196 | $nourl = $CFG->wwwroot . '/portfolio/add.php?cancel=1'; |
192ce92b |
2197 | $this->print_header('confirmexport'); |
67a87e7d |
2198 | print_simple_box_start(); |
2199 | print_heading(get_string('confirmsummary', 'portfolio'), '', 4); |
ffcfd8a7 |
2200 | $mainsummary = array(); |
2201 | if (!$this->instance->get_export_config('hideformat')) { |
2202 | $mainsummary[get_string('selectedformat', 'portfolio')] = get_string('format_' . $this->instance->get_export_config('format'), 'portfolio'); |
2203 | } |
67a87e7d |
2204 | if (!$this->instance->get_export_config('hidewait')) { |
ffcfd8a7 |
2205 | $mainsummary[get_string('selectedwait', 'portfolio')] = get_string(($this->instance->get_export_config('wait') ? 'yes' : 'no')); |
2206 | } |
2207 | if ($previous) { |
2208 | $previousstr = ''; |
2209 | foreach ($previous as $row) { |
2210 | $previousstr .= userdate($row->time); |
2211 | if ($row->caller_class != get_class($this->caller)) { |
2212 | require_once($CFG->dirroot . '/' . $row->caller_file); |
2213 | $previousstr .= ' (' . call_user_func(array($row->caller_class, 'display_name')) . ')'; |
2214 | } |
2215 | $previousstr .= '<br />'; |
2216 | } |
2217 | $mainsummary[get_string('exportedpreviously', 'portfolio')] = $previousstr; |
67a87e7d |
2218 | } |
2219 | if (!$csummary = $this->caller->get_export_summary()) { |
2220 | $csummary = array(); |
2221 | } |
2222 | if (!$isummary = $this->instance->get_export_summary()) { |
2223 | $isummary = array(); |
2224 | } |
2225 | $mainsummary = array_merge($mainsummary, $csummary, $isummary); |
ffcfd8a7 |
2226 | $table = new StdClass; |
2227 | $table->data = array(); |
67a87e7d |
2228 | foreach ($mainsummary as $string => $value) { |
ffcfd8a7 |
2229 | $table->data[] = array($string, $value); |
67a87e7d |
2230 | } |
ffcfd8a7 |
2231 | print_table($table); |
67a87e7d |
2232 | notice_yesno($strconfirm, $yesurl, $nourl); |
2233 | print_simple_box_end(); |
2234 | print_footer(); |
2235 | return false; |
2236 | } |
2237 | |
2238 | /** |
2239 | * processes the 'queueornext' stage of the export |
2240 | * |
2241 | * @return boolean whether or not to process the next stage. this is important as the control function is called recursively. |
2242 | */ |
2243 | public function process_stage_queueorwait() { |
2244 | global $SESSION; |
2245 | $wait = $this->instance->get_export_config('wait'); |
2246 | if (empty($wait)) { |
84a44985 |
2247 | events_trigger('portfolio_send', $this->id); |
2248 | unset($SESSION->portfolioexport); |
8f182eef |
2249 | return $this->process_stage_finished(true); |
67a87e7d |
2250 | } |
2251 | return true; |
2252 | } |
2253 | |
2254 | /** |
2255 | * processes the 'package' stage of the export |
2256 | * |
2257 | * @return boolean whether or not to process the next stage. this is important as the control function is called recursively. |
2258 | */ |
2259 | public function process_stage_package() { |
2260 | // now we've agreed on a format, |
2261 | // the caller is given control to package it up however it wants |
2262 | // and then the portfolio plugin is given control to do whatever it wants. |
d67bfc32 |
2263 | if (!$this->caller->prepare_package()) { |
34035201 |
2264 | throw new portfolio_export_exception($this, 'callercouldnotpackage', 'portfolio'); |
67a87e7d |
2265 | } |
d67bfc32 |
2266 | if (!$package = $this->instance->prepare_package()) { |
34035201 |
2267 | throw new portfolio_export_exception($this, 'plugincouldnotpackage', 'portfolio'); |
67a87e7d |
2268 | } |
2269 | return true; |
2270 | } |
2271 | |
2272 | /** |
2273 | * processes the 'cleanup' stage of the export |
2274 | * |
2275 | * @return boolean whether or not to process the next stage. this is important as the control function is called recursively. |
2276 | */ |
d96a1acc |
2277 | public function process_stage_cleanup($pullok=false) { |
84a44985 |
2278 | global $CFG, $DB, $SESSION; |
d96a1acc |
2279 | |
349242a3 |
2280 | if (!$pullok && $this->get('instance') && !$this->get('instance')->is_push()) { |
d96a1acc |
2281 | unset($SESSION->portfolioexport); |
2282 | return true; |
2283 | } |
0f71f48b |
2284 | if ($this->get('instance')) { |
2285 | // might not be set - before export really starts |
2286 | $this->get('instance')->cleanup(); |
2287 | } |
84a44985 |
2288 | $DB->delete_records('portfolio_tempdata', array('id' => $this->id)); |
d67bfc32 |
2289 | $fs = get_file_storage(); |
2290 | $fs->delete_area_files(SYSCONTEXTID, 'portfolio_exporter', $this->id); |
84a44985 |
2291 | unset($SESSION->portfolioexport); |
67a87e7d |
2292 | return true; |
2293 | } |
2294 | |
2295 | /** |
2296 | * processes the 'send' stage of the export |
2297 | * |
2298 | * @return boolean whether or not to process the next stage. this is important as the control function is called recursively. |
2299 | */ |
2300 | public function process_stage_send() { |
2301 | // send the file |
2302 | if (!$this->instance->send_package()) { |
34035201 |
2303 | throw new portfolio_export_exception($this, 'failedtosendpackage', 'portfolio'); |
67a87e7d |
2304 | } |
ffcfd8a7 |
2305 | // log the transfer |
2306 | global $DB; |
2307 | $l = array( |
2308 | 'userid' => $this->user->id, |
2309 | 'portfolio' => $this->instance->get('id'), |
2310 | 'caller_file' => $this->callerfile, |
2311 | 'caller_sha1' => $this->caller->get_sha1(), |
2312 | 'caller_class' => get_class($this->caller), |
2313 | 'time' => time(), |
2314 | ); |
2315 | $DB->insert_record('portfolio_log', $l); |
67a87e7d |
2316 | return true; |
2317 | } |
2318 | |
2319 | /** |
2320 | * processes the 'finish' stage of the export |
2321 | * |
2322 | * @return boolean whether or not to process the next stage. this is important as the control function is called recursively. |
2323 | */ |
8f182eef |
2324 | public function process_stage_finished($queued=false) { |
67a87e7d |
2325 | $returnurl = $this->caller->get_return_url(); |
2326 | $continueurl = $this->instance->get_continue_url(); |
2327 | $extras = $this->instance->get_extra_finish_options(); |
2328 | |
192ce92b |
2329 | $key = 'exportcomplete'; |
8f182eef |
2330 | if ($queued) { |
192ce92b |
2331 | $key = 'exportqueued'; |
f1ebc192 |
2332 | if ($this->forcequeue) { |
2333 | $key = 'exportqueuedforced'; |
2334 | } |
8f182eef |
2335 | } |
192ce92b |
2336 | $this->print_header($key, false); |
67a87e7d |
2337 | if ($returnurl) { |
2338 | echo '<a href="' . $returnurl . '">' . get_string('returntowhereyouwere', 'portfolio') . '</a><br />'; |
2339 | } |
2340 | if ($continueurl) { |
2341 | echo '<a href="' . $continueurl . '">' . get_string('continuetoportfolio', 'portfolio') . '</a><br />'; |
2342 | } |
2343 | if (is_array($extras)) { |
2344 | foreach ($extras as $link => $string) { |
2345 | echo '<a href="' . $link . '">' . $string . '</a><br />'; |
2346 | } |
2347 | } |
2348 | print_footer(); |
67a87e7d |
2349 | return false; |
2350 | } |
2351 | |
2352 | |
2353 | /** |
2354 | * local print header function to be reused across the export |
2355 | * |
2356 | * @param string $titlestring key for a portfolio language string |
2357 | * @param string $headerstring key for a portfolio language string |
2358 | */ |
192ce92b |
2359 | public function print_header($headingstr, $summary=true) { |
2360 | $titlestr = get_string('exporting', 'portfolio'); |
2361 | $headerstr = get_string('exporting', 'portfolio'); |
67a87e7d |
2362 | |
2363 | print_header($titlestr, $headerstr, $this->navigation); |
192ce92b |
2364 | print_heading(get_string($headingstr, 'portfolio')); |
2365 | |
2366 | if (!$summary) { |
2367 | return; |
2368 | } |
2369 | |
2370 | print_simple_box_start(); |
2371 | echo $this->caller->heading_summary(); |
2372 | print_simple_box_end(); |
67a87e7d |
2373 | } |
2374 | |
62a99dea |
2375 | /** |
2376 | * cancels a potfolio request and cleans up the tempdata |
2377 | * and redirects the user back to where they started |
2378 | */ |
84a44985 |
2379 | public function cancel_request() { |
2380 | if (!isset($this)) { |
2381 | return; |
2382 | } |
d96a1acc |
2383 | $this->process_stage_cleanup(true); |
84a44985 |
2384 | redirect($this->caller->get_return_url()); |
2385 | exit; |
2386 | } |
2387 | |
2388 | /** |
2389 | * writes out the contents of this object and all its data to the portfolio_tempdata table and sets the 'id' field. |
2390 | */ |
2391 | public function save() { |
2392 | global $DB; |
2393 | if (empty($this->id)) { |
2394 | $r = (object)array( |
2395 | 'data' => base64_encode(serialize($this)), |
2396 | 'expirytime' => time() + (60*60*24), |
192ce92b |
2397 | 'userid' => $this->user->id, |
84a44985 |
2398 | ); |
2399 | $this->id = $DB->insert_record('portfolio_tempdata', $r); |
d67bfc32 |
2400 | $this->save(); // call again so that id gets added to the save data. |
84a44985 |
2401 | } else { |
2402 | $DB->set_field('portfolio_tempdata', 'data', base64_encode(serialize($this)), array('id' => $this->id)); |
2403 | } |
2404 | } |
2405 | |
62a99dea |
2406 | /** |
2407 | * rewakens the data from the database given the id |
2876d73b |
2408 | * makes sure to load the required files with the class definitions |
62a99dea |
2409 | * |
2410 | * @param int $id id of data |
2411 | * |
2412 | * @return portfolio_exporter |
2413 | */ |
84a44985 |
2414 | public static function rewaken_object($id) { |
2415 | global $DB, $CFG; |
15053330 |
2416 | require_once($CFG->libdir . '/filelib.php'); |
84a44985 |
2417 | if (!$data = $DB->get_record('portfolio_tempdata', array('id' => $id))) { |
34035201 |
2418 | throw new portfolio_exception('invalidtempid', 'portfolio'); |
84a44985 |
2419 | } |
2420 | $exporter = unserialize(base64_decode($data->data)); |
2421 | if ($exporter->instancefile) { |
2422 | require_once($CFG->dirroot . '/' . $exporter->instancefile); |
2423 | } |
2424 | require_once($CFG->dirroot . '/' . $exporter->callerfile); |
2425 | $exporter = unserialize(serialize($exporter)); |
2426 | return $exporter; |
2427 | } |
d67bfc32 |
2428 | |
2429 | /** |
2430 | * copies a file from somewhere else in moodle |
2431 | * to the portfolio temporary working directory |
2432 | * associated with this export |
2433 | * |
2434 | * @param $oldfile stored_file object |
2435 | */ |
2436 | public function copy_existing_file($oldfile) { |
2437 | $fs = get_file_storage(); |
2438 | $file_record = $this->new_file_record_base($oldfile->get_filename()); |
2439 | try { |
2440 | return $fs->create_file_from_storedfile($file_record, $oldfile->get_id()); |
2441 | } catch (file_exception $e) { |
2442 | return false; |
2443 | } |
2444 | } |
2445 | |
2446 | /** |
2447 | * writes out some content to a file in the |
2448 | * portfolio temporary working directory |
2449 | * associated with this export |
2450 | * |
2451 | * @param string $content content to write |
2452 | * @param string $name filename to use |
2453 | */ |
2454 | public function write_new_file($content, $name) { |
2455 | $fs = get_file_storage(); |
2456 | $file_record = $this->new_file_record_base($name); |
2457 | return $fs->create_file_from_string($file_record, $content); |
2458 | } |
2459 | |
2460 | /** |
2461 | * returns an arary of files in the temporary working directory |
2462 | * for this export |
2463 | * always use this instead of the files api directly |
2464 | * |
2465 | * @return arary |
2466 | */ |
2467 | public function get_tempfiles() { |
2468 | $fs = get_file_storage(); |
2469 | $files = $fs->get_area_files(SYSCONTEXTID, 'portfolio_exporter', $this->id, '', false); |
2470 | if (empty($files)) { |
2471 | return array(); |
2472 | } |
d96a1acc |
2473 | $returnfiles = array(); |
2474 | foreach ($files as $f) { |
2475 | $returnfiles[$f->get_filename()] = $f; |
2476 | } |
2477 | return $returnfiles; |
d67bfc32 |
2478 | } |
2479 | |
2480 | /** |
2481 | * helper function to create the beginnings of a file_record object |
2482 | * to create a new file in the portfolio_temporary working directory |
2483 | * use {@see write_new_file} or {@see copy_existing_file} externally |
2484 | * |
2485 | * @param string $name filename of new record |
2486 | */ |
2487 | private function new_file_record_base($name) { |
2488 | return (object)array( |
2489 | 'contextid' => SYSCONTEXTID, |
2490 | 'filearea' => 'portfolio_exporter', |
2491 | 'itemid' => $this->id, |
2492 | 'filepath' => '/', |
2493 | 'filename' => $name, |
2494 | ); |
2495 | } |
2496 | |
beb4ac1a |
2497 | public function verify_rewaken() { |
2498 | global $USER; |
2499 | if ($this->get('user')->id != $USER->id) { |
2500 | throw new portfolio_exception('notyours', 'portfolio'); |
2501 | } |
2502 | if (!confirm_sesskey($this->get('sesskey'))) { |
2503 | throw new portfolio_exception('confirmsesskeybad'); |
2504 | } |
2505 | } |
67a87e7d |
2506 | } |
2507 | |
62a99dea |
2508 | /** |
2509 | * form that just contains the dropdown menu of available instances |
2510 | */ |
6fdd8fa7 |
2511 | class portfolio_instance_select extends moodleform { |
2512 | |
2513 | private $caller; |
2514 | |
2515 | function definition() { |
2516 | $this->caller = $this->_customdata['caller']; |
2517 | $options = portfolio_instance_select( |
2518 | portfolio_instances(), |
349242a3 |
2519 | $this->caller->supported_formats($this->caller), |
6fdd8fa7 |
2520 | get_class($this->caller), |
2521 | 'instance', |
2522 | true, |
2523 | true |
2524 | ); |
2525 | if (empty($options)) { |
34035201 |
2526 | debugging('noavailableplugins', 'portfolio'); |
2527 | return false; |
6fdd8fa7 |
2528 | } |
2529 | $mform =& $this->_form; |
2530 | $mform->addElement('select', 'instance', get_string('selectplugin', 'portfolio'), $options); |
2531 | $this->add_action_buttons(true, get_string('next')); |
2532 | } |
2533 | } |
2534 | |
67a87e7d |
2535 | /** |
2536 | * event handler for the portfolio_send event |
2537 | */ |
2538 | function portfolio_handle_event($eventdata) { |
2539 | global $CFG; |
84a44985 |
2540 | $exporter = portfolio_exporter::rewaken_object($eventdata); |
67a87e7d |
2541 | $exporter->process_stage_package(); |
2542 | $exporter->process_stage_send(); |
d5dfe1b3 |
2543 | $exporter->save(); |
31ca68c4 |
2544 | $exporter->process_stage_cleanup(); |
67a87e7d |
2545 | return true; |
2546 | } |
2547 | |
7e51bbce |
2548 | /** |
2549 | * main portfolio cronjob |
2550 | * |
2551 | */ |
2552 | function portfolio_cron() { |
2553 | global $DB; |
2554 | |
2eb07e79 |
2555 | if ($expired = $DB->get_records_select('portfolio_tempdata', 'expirytime < ?', array(time()), '', 'id')) { |
7e51bbce |
2556 | foreach ($expired as $d) { |
2eb07e79 |
2557 | $e = portfolio_exporter::rewaken_object($d); |
2558 | $e->process_stage_cleanup(true); |
7e51bbce |
2559 | } |
2560 | } |
2eb07e79 |
2561 | // @todo add hooks in the plugins - either per instance or per plugin |
7e51bbce |
2562 | } |
2563 | |
7812e882 |
2564 | /** |
2565 | * this is just used to find an intersection of supported formats |
2566 | * between the caller and portfolio plugins |
2567 | * |
2568 | * the most basic type - pretty much everything is a subtype |
2569 | */ |
ea0de12f |
2570 | class portfolio_format_file { |
2571 | public static function mimetypes() { |
2572 | return array(null); |
2573 | } |
2574 | } |
7812e882 |
2575 | |
2576 | /** |
2577 | * this is just used to find an intersection of supported formats |
2578 | * between the caller and portfolio plugins |
2579 | * |
2580 | * added for potential flickr plugin |
2581 | */ |
ea0de12f |
2582 | class portfolio_format_image extends portfolio_format_file { |
2583 | public static function mimetypes() { |
2584 | return mimeinfo_from_icon('type', 'image.gif', true); |
2585 | } |
2586 | } |
7812e882 |
2587 | |
2588 | /** |
2589 | * this is just used to find an intersection of supported formats |
2590 | * between the caller and portfolio plugins |
2591 | * |
2592 | * in case we want to be really specific. |
2593 | */ |
ea0de12f |
2594 | class portfolio_format_html extends portfolio_format_file { |
2595 | public static function mimetypes() { |
2596 | return array('text/html'); |
2597 | } |
2598 | } |
2599 | |
2600 | /** |
2601 | * I guess there could be a youtube/google video plugin |
2602 | * and anyway, the flickr plugin can support it already |
2603 | */ |
2604 | class portfolio_format_video extends portfolio_format_file { |
2605 | public static function mimetypes() { |
2606 | return mimeinfo_from_icon('type', 'video.gif', true); |
2607 | } |
2608 | } |
2609 | |
2610 | /** |
2611 | * class for plain text format.. not sure why we would need this yet |
2612 | * but since resource module wants to export it... we can |
2613 | */ |
2614 | class portfolio_format_text extends portfolio_format_file { |
2615 | public static function mimetypes() { |
2616 | return array('text/plain'); |
2617 | } |
2618 | } |
7812e882 |
2619 | |
2620 | /** |
2621 | * this is just used to find an intersection of supported formats |
2622 | * between the caller and portfolio plugins |
2623 | * |
2624 | * later.... a moodle plugin might support this. |
2625 | */ |
2626 | class portfolio_format_mbkp extends portfolio_format_file {} |
7e51bbce |
2627 | |
ebdf8957 |
2628 | /** |
2629 | * top level portfolio exception. |
2630 | * sometimes caught and rethrown as {@see portfolio_export_exception} |
2631 | */ |
2632 | class portfolio_exception extends moodle_exception {} |
2633 | |
34035201 |
2634 | /** |
2635 | * exception to throw during an export - will clean up session and tempdata |
2636 | */ |
2637 | class portfolio_export_exception extends portfolio_exception { |
2638 | |
2639 | /** |
2640 | * constructor. |
2641 | * @param object $exporter instance of portfolio_exporter (will handle null case) |
2642 | * @param string $errorcode language string key |
2643 | * @param string $module language string module (optional, defaults to moodle) |
2644 | * @param string $continue url to continue to (optional, defaults to wwwroot) |
2645 | * @param mixed $a language string data (optional, defaults to null) |
2646 | */ |
2647 | public function __construct($exporter, $errorcode, $module=null, $continue=null, $a=null) { |
2648 | if (!empty($exporter) && $exporter instanceof portfolio_exporter) { |
2649 | if (empty($continue)) { |
2650 | $caller = $exporter->get('caller'); |
2651 | if (!empty($caller) && $caller instanceof portfolio_caller_base) { |
2652 | $continue = $exporter->get('caller')->get_return_url(); |
2653 | } |
2654 | } |
2655 | if (!defined('FULLME') || FULLME != 'cron') { |
2656 | $exporter->process_stage_cleanup(); |
2657 | } |
2658 | } else { |
2659 | global $SESSION; |
2660 | if (!empty($SESSION->portfolioexport)) { |
2661 | debugging(get_string('exportexceptionnoexporter', 'portfolio')); |
2662 | } |
2663 | } |
2664 | parent::__construct($errorcode, $module, $continue, $a); |
2665 | } |
2666 | } |
2667 | |
2668 | /** |
2669 | * exception for callers to throw when they have a problem. |
2670 | * usually caught and rethrown as {@see portfolio_export_exception} |
2671 | */ |
2672 | class portfolio_caller_exception extends portfolio_exception {} |
2673 | |
34035201 |
2674 | /** |
2675 | * exception for portfolio plugins to throw when they have a problem. |
2676 | * usually caught and rethrown as {@see portfolio_export_exception} |
2677 | */ |
2678 | class portfolio_plugin_exception extends portfolio_exception {} |
2679 | |
2680 | /** |
2681 | * helper function to rethrow a caught portfolio_exception as an export exception |
2682 | */ |
2683 | function portfolio_export_rethrow_exception($exporter, $e) { |
2684 | throw new portfolio_export_exception($exporter, $e->errorcode, $e->module, $e->link, $e->a); |
2685 | } |
67a87e7d |
2686 | ?> |