67a87e7d |
1 | <?php |
2 | // @TODO think about making some more of the functions final. |
3 | /** |
4 | * this file contains: |
5 | * {@link portfolio_add_button} -entry point for callers |
6 | * {@link class portfolio_plugin_base} - class plugins extend |
7 | * {@link class portfolio_caller_base} - class callers extend |
8 | * {@link class portfolio_admin_form} - base moodleform class for plugin administration |
9 | * {@link class portfolio_user_form} - base moodleform class for plugin instance user config |
10 | * {@link class portfolio_export_form} - base moodleform class for during-export configuration (eg metadata) |
11 | * {@link class portfolio_exporter} - class used during export process |
12 | * |
13 | * and some helper functions: |
14 | * {@link portfolio_instances - returns an array of all configured instances |
15 | * {@link portfolio_instance - returns an instance of the right class given an id |
16 | * {@link portfolio_instance_select} - returns a drop menu of available instances |
17 | * {@link portfolio_static_function - requires the file, and calls a static function on the given class |
18 | " {@link portfolio_plugin_sanity_check - polls given (or all) portfolio_plugins for sanity and disables insane ones |
19 | " {@link portfolio_instance_sanity_check - polls given (or all) portfolio instances for sanity and disables insane ones |
20 | * {@link portfolio_report_instane} - returns a table of insane plugins and the reasons (used for plugins or instances thereof) |
21 | * {@link portfolio_supported_formats - returns array of all available formats for plugins and callers to use |
22 | * {@link portfolio_handle_event} - event handler for queued transfers that get triggered on cron |
23 | * |
24 | */ |
25 | require_once ($CFG->libdir.'/formslib.php'); |
26 | |
27 | // **** EXPORT STAGE CONSTANTS **** // |
28 | |
29 | /** |
30 | * display a form to the user |
31 | * this one might not be used if neither |
32 | * the plugin, or the caller has any config. |
33 | */ |
34 | define('PORTFOLIO_STAGE_CONFIG', 1); |
35 | |
36 | /** |
37 | * summarise the form and ask for confirmation |
38 | * if we skipped PORTFOLIO_STAGE_CONFIG, |
39 | * just confirm the send. |
40 | */ |
41 | define('PORTFOLIO_STAGE_CONFIRM', 2); |
42 | |
43 | /** |
44 | * either queue the event and skip to PORTFOLIO_STAGE_FINISHED |
45 | * or continue to PORTFOLIO_STAGE_PACKAGE |
46 | */ |
47 | |
48 | define('PORTFOLIO_STAGE_QUEUEORWAIT', 3); |
49 | |
50 | /** |
51 | * package up the various bits |
52 | * during this stage both the caller |
53 | * and the plugin get their package methods called |
54 | */ |
55 | define('PORTFOLIO_STAGE_PACKAGE', 4); |
56 | |
57 | /* |
58 | * the portfolio plugin must send the file |
59 | */ |
60 | define('PORTFOLIO_STAGE_SEND', 5); |
61 | |
62 | /** |
63 | * cleanup the temporary area |
64 | */ |
65 | define('PORTFOLIO_STAGE_CLEANUP', 6); |
66 | |
67 | /** |
68 | * display the "finished notification" |
69 | */ |
70 | define('PORTFOLIO_STAGE_FINISHED', 7); |
71 | |
72 | |
73 | |
74 | // **** EXPORT FORMAT CONSTANTS **** // |
75 | // these should always correspond to a string |
76 | // in the portfolio module, called format_{$value} |
77 | // **** **** // |
78 | |
79 | /** |
80 | * html - this is the second most basic fallback |
81 | * after {@link PORTFOLIO_FORMAT_FILE} |
82 | * for export. This should always be supported |
83 | * in remote systems |
84 | */ |
85 | define('PORTFOLIO_FORMAT_HTML', 'html'); |
86 | |
87 | /** |
88 | * file - the most basic fallback format. |
89 | * this should always be supported |
90 | * in remote system.s |
91 | */ |
92 | define('PORTFOLIO_FORMAT_FILE', 'file'); |
93 | |
94 | /** |
95 | * moodle backup - the plugin needs to be able to write a complete backup |
96 | * the caller need to be able to export the particular XML bits to insert |
97 | * into moodle.xml (?and the file bits if necessary) |
98 | */ |
99 | define('PORTFOLIO_FORMAT_MBKP', 'mbkp'); |
100 | |
101 | |
102 | // **** EXPORT TIME LEVELS **** // |
103 | // these should correspond to a string |
104 | // in the portfolio module, called time_{$value} |
105 | |
106 | /** |
107 | * no delay. don't even offer the user the option |
108 | * of not waiting for the transfer |
109 | */ |
110 | define('PORTFOLIO_TIME_LOW', 'low'); |
111 | |
112 | /** |
113 | * a small delay. user can still easily opt to |
114 | * watch this transfer and wait. |
115 | */ |
116 | define('PORTFOLIO_TIME_MODERATE', 'moderate'); |
117 | |
118 | /** |
119 | * slow. the user really should not be given the option |
120 | * to choose this. |
121 | */ |
122 | define('PORTFOLIO_TIME_HIGH', 'high'); |
123 | |
124 | |
125 | /** |
126 | * entry point to add an 'add to portfolio' button somewhere in moodle |
127 | * this function does not check permissions. the caller must check permissions first. |
128 | * later, during the export process, the caller class is instantiated and the check_permissions method is called |
129 | * but not in this function. |
130 | * |
131 | * @param string $callbackclass name of the class containing the callback functions |
132 | * activity modules should ALWAYS use their name_portfolio_caller |
133 | * other locations must use something unique |
134 | * @param mixed $callbackargs this can be an array or hash of arguments to pass |
135 | * back to the callback functions (passed by reference) |
136 | * these MUST be primatives to be added as hidden form fields. |
137 | * and the values get cleaned to PARAM_ALPHAEXT or PARAM_NUMBER or PARAM_PATH |
138 | * @param boolean $fullform either display the fullform with the dropmenu of available instances |
139 | * or just a small icon (which will trigger instance selection in a new screen) |
140 | * optional, defaults to true. |
141 | * @param boolean $return whether to echo or return content (optional defaults to false (echo) |
142 | */ |
143 | function portfolio_add_button($callbackclass, $callbackargs, $fullform=true, $return=false) { |
144 | |
145 | global $SESSION, $CFG, $COURSE, $USER; |
146 | |
147 | if (!$instances = portfolio_instances()) { |
148 | return; |
149 | } |
150 | |
151 | $backtrace = debug_backtrace(); |
152 | if (!array_key_exists(0, $backtrace) || !array_key_exists('file', $backtrace[0]) || !is_readable($backtrace[0]['file'])) { |
153 | debugging(get_string('nocallbackfile', 'portfolio')); |
154 | return; |
155 | } |
156 | |
157 | $callbackfile = substr($backtrace[0]['file'], strlen($CFG->dirroot)); |
158 | |
159 | require_once($CFG->dirroot . $callbackfile); |
160 | |
161 | $callersupports = call_user_func(array($callbackclass, 'supported_formats')); |
162 | |
163 | if (isset($SESSION->portfolio)) { |
164 | return portfolio_exporter::raise_error('alreadyexporting', 'portfolio'); |
165 | } |
166 | |
167 | $output = '<form method="post" action="' . $CFG->wwwroot . '/portfolio/add.php" id="portfolio-add-button">' . "\n"; |
168 | foreach ($callbackargs as $key => $value) { |
169 | if (!empty($value) && !is_string($value) && !is_numeric($value)) { |
170 | $a->key = $key; |
171 | $a->value = print_r($value, true); |
172 | debugging(get_string('nonprimative', 'portfolio', $a)); |
173 | return; |
174 | } |
175 | $output .= "\n" . '<input type="hidden" name="ca_' . $key . '" value="' . $value . '" />'; |
176 | } |
177 | $output .= "\n" . '<input type="hidden" name="callbackfile" value="' . $callbackfile . '" />'; |
178 | $output .= "\n" . '<input type="hidden" name="callbackclass" value="' . $callbackclass . '" />'; |
179 | $output .= "\n" . '<input type="hidden" name="course" value="' . (!empty($COURSE) ? $COURSE->id : 0) . '" />'; |
180 | $selectoutput = ''; |
181 | if (count($instances) == 1) { |
182 | $instance = array_shift($instances); |
183 | if (count(array_intersect($callersupports, $instance->supported_formats())) == 0) { |
184 | // bail. no common formats. |
185 | debugging(get_string('nocommonformats', 'portfolio', $callbackclass)); |
186 | return; |
187 | } |
188 | if ($error = portfolio_instance_sanity_check($instance)) { |
189 | // bail, plugin is misconfigured |
190 | debugging(get_string('instancemisconfigured', 'portfolio', get_string($error[$instance->get('id')], 'portfolio_' . $instance->get('plugin')))); |
191 | return; |
192 | } |
193 | $output .= "\n" . '<input type="hidden" name="instance" value="' . $instance->get('id') . '" />'; |
194 | } |
195 | else { |
196 | $selectoutput = portfolio_instance_select($instances, $callersupports, $callbackclass); |
197 | } |
198 | |
199 | if ($fullform) { |
200 | $output .= $selectoutput; |
201 | $output .= "\n" . '<input type="submit" value="' . get_string('addtoportfolio', 'portfolio') .'" />'; |
202 | } else { |
203 | $output .= "\n" . '<input type="image" src="' . $CFG->pixpath . '/t/portfolio.gif" alt=' . get_string('addtoportfolio', 'portfolio') .'" />'; |
204 | //@todo replace this with a little icon |
205 | } |
206 | |
207 | $output .= "\n" . '</form>'; |
208 | |
209 | if ($return) { |
210 | return $output; |
211 | } else { |
212 | echo $output; |
213 | } |
214 | return true; |
215 | } |
216 | |
217 | /** |
218 | * returns a drop menu with a list of available instances. |
219 | * |
220 | * @param array $instances the instances to put in the menu |
221 | * @param array $callerformats the formats the caller supports |
222 | (this is used to filter plugins) |
223 | * @param array $callbackclass the callback class name |
224 | * |
225 | * @return string the html, from <select> to </select> inclusive. |
226 | */ |
227 | function portfolio_instance_select($instances, $callerformats, $callbackclass) { |
228 | $insane = portfolio_instance_sanity_check(); |
229 | $count = 0; |
230 | $selectoutput = "\n" . '<select name="instance">' . "\n"; |
231 | foreach ($instances as $instance) { |
232 | if (count(array_intersect($callerformats, $instance->supported_formats())) == 0) { |
233 | // bail. no common formats. |
234 | continue; |
235 | } |
236 | if (array_key_exists($instance->get('id'), $insane)) { |
237 | // bail, plugin is misconfigured |
238 | debugging(get_string('instancemisconfigured', 'portfolio', get_string($insane[$instance->get('id')], 'portfolio_' . $instance->get('plugin')))); |
239 | continue; |
240 | } |
241 | $count++; |
242 | $selectoutput .= "\n" . '<option value="' . $instance->get('id') . '">' . $instance->get('name') . '</a>' . "\n"; |
243 | } |
244 | if (empty($count)) { |
245 | // bail. no common formats. |
246 | debugging(get_string('nocommonformats', 'portfolio', $callbackclass)); |
247 | return; |
248 | } |
249 | $selectoutput .= "\n" . "</select>\n"; |
250 | return $selectoutput; |
251 | } |
252 | |
253 | /** |
254 | * return all portfolio instances |
255 | * |
256 | * @param boolean visibleonly don't include hidden instances (defaults to true and will be overridden to true if the next parameter is true) |
257 | * @param boolean useronly check the visibility preferences and permissions of the logged in user |
258 | * @return array of portfolio instances (full objects, not just database records) |
259 | */ |
260 | function portfolio_instances($visibleonly=true, $useronly=true) { |
261 | |
262 | global $DB, $USER; |
263 | |
264 | $values = array(); |
265 | $sql = 'SELECT * FROM {portfolio_instance}'; |
266 | |
267 | if ($visibleonly || $useronly) { |
268 | $values[] = 1; |
269 | $sql .= ' WHERE visible = ?'; |
270 | } |
271 | if ($useronly) { |
272 | $sql .= ' AND id NOT IN ( |
273 | SELECT instance FROM {portfolio_instance_user} |
274 | WHERE userid = ? AND name = ? AND value = ? |
275 | )'; |
276 | $values = array_merge($values, array($USER->id, 'visible', 0)); |
277 | } |
278 | $sql .= ' ORDER BY name'; |
279 | |
280 | $instances = array(); |
281 | foreach ($DB->get_records_sql($sql, $values) as $instance) { |
282 | $instances[] = portfolio_instance($instance->id, $instance); |
283 | } |
284 | // @todo check capabilities here - see MDL-15768 |
285 | return $instances; |
286 | } |
287 | |
288 | /** |
289 | * supported formats that portfolio plugins and callers |
290 | * can use for exporting content |
291 | * |
292 | * @return array of all the available export formats |
293 | */ |
294 | function portfolio_supported_formats() { |
295 | return array( |
296 | PORTFOLIO_FORMAT_FILE, |
297 | PORTFOLIO_FORMAT_HTML, |
298 | PORTFOLIO_FORMAT_MBKP, |
299 | ); |
300 | } |
301 | |
302 | /** |
303 | * helper function to return an instance of a plugin (with config loaded) |
304 | * |
305 | * @param int $instance id of instance |
306 | * @param array $record database row that corresponds to this instance |
307 | * this is passed to avoid unnecessary lookups |
308 | * |
309 | * @return subclass of portfolio_plugin_base |
310 | */ |
311 | function portfolio_instance($instanceid, $record=null) { |
312 | global $DB, $CFG; |
313 | |
314 | if ($record) { |
315 | $instance = $record; |
316 | } else { |
317 | if (!$instance = $DB->get_record('portfolio_instance', array('id' => $instanceid))) { |
318 | return false; // @todo throw exception? |
319 | } |
320 | } |
321 | require_once($CFG->dirroot . '/portfolio/type/'. $instance->plugin . '/lib.php'); |
322 | $classname = 'portfolio_plugin_' . $instance->plugin; |
323 | return new $classname($instanceid, $instance); |
324 | } |
325 | |
326 | /** |
327 | * helper function to call a static function on a portfolio plugin class |
328 | * this will figure out the classname and require the right file and call the function. |
329 | * you can send a variable number of arguments to this function after the first two |
330 | * and they will be passed on to the function you wish to call. |
331 | * |
332 | * @param string $plugin name of plugin |
333 | * @param string $function function to call |
334 | */ |
335 | function portfolio_static_function($plugin, $function) { |
336 | global $CFG; |
337 | |
338 | $pname = null; |
339 | if (is_object($plugin) || is_array($plugin)) { |
340 | $plugin = (object)$plugin; |
341 | $pname = $plugin->name; |
342 | } else { |
343 | $pname = $plugin; |
344 | } |
345 | |
346 | $args = func_get_args(); |
347 | if (count($args) <= 2) { |
348 | $args = array(); |
349 | } |
350 | else { |
351 | array_shift($args); |
352 | array_shift($args); |
353 | } |
354 | |
355 | require_once($CFG->dirroot . '/portfolio/type/' . $plugin . '/lib.php'); |
356 | return call_user_func_array(array('portfolio_plugin_' . $plugin, $function), $args); |
357 | } |
358 | |
359 | /** |
360 | * helper function to check all the plugins for sanity and set any insane ones to invisible. |
361 | * |
362 | * @param array $plugins to check (if null, defaults to all) |
363 | * one string will work too for a single plugin. |
364 | * |
365 | * @return array array of insane instances (keys= id, values = reasons (keys for plugin lang) |
366 | */ |
367 | function portfolio_plugin_sanity_check($plugins=null) { |
368 | global $DB; |
369 | if (is_string($plugins)) { |
370 | $plugins = array($plugins); |
371 | } else if (empty($plugins)) { |
372 | $plugins = get_list_of_plugins('portfolio/type'); |
373 | } |
374 | |
375 | $insane = array(); |
376 | foreach ($plugins as $plugin) { |
377 | if ($result = portfolio_static_function($plugin, 'plugin_sanity_check')) { |
378 | $insane[$plugin] = $result; |
379 | } |
380 | } |
381 | if (empty($insane)) { |
382 | return array(); |
383 | } |
384 | list($where, $params) = $DB->get_in_or_equal(array_keys($insane)); |
385 | $where = ' plugin ' . $where; |
386 | $DB->set_field_select('portfolio_instance', 'visible', 0, $where, $params); |
387 | return $insane; |
388 | } |
389 | |
390 | /** |
391 | * helper function to check all the instances for sanity and set any insane ones to invisible. |
392 | * |
393 | * @param array $instances to check (if null, defaults to all) |
394 | * one instance or id will work too |
395 | * |
396 | * @return array array of insane instances (keys= id, values = reasons (keys for plugin lang) |
397 | */ |
398 | function portfolio_instance_sanity_check($instances=null) { |
399 | global $DB; |
400 | if (empty($instances)) { |
401 | $instances = portfolio_instances(false); |
402 | } else if (!is_array($instances)) { |
403 | $instances = array($instances); |
404 | } |
405 | |
406 | $insane = array(); |
407 | foreach ($instances as $instance) { |
408 | if (is_object($instance) && !($instance instanceof portfolio_plugin_base)) { |
409 | $instance = portfolio_instance($instance->id, $instance); |
410 | } else if (is_numeric($instance)) { |
411 | $instance = portfolio_instance($instance); |
412 | } |
413 | if (!($instance instanceof portfolio_plugin_base)) { |
414 | debugging('something weird passed to portfolio_instance_sanity_check, not subclass or id'); |
415 | continue; |
416 | } |
417 | if ($result = $instance->instance_sanity_check()) { |
418 | $insane[$instance->get('id')] = $result; |
419 | } |
420 | } |
421 | if (empty($insane)) { |
422 | return array(); |
423 | } |
424 | list ($where, $params) = $DB->get_in_or_equal(array_keys($insane)); |
425 | $where = ' id ' . $where; |
426 | $DB->set_field_select('portfolio_instance', 'visible', 0, $where, $params); |
427 | return $insane; |
428 | } |
429 | |
430 | /** |
431 | * helper function to display a table of plugins (or instances) and reasons for disabling |
432 | * |
433 | * @param array $insane array of insane plugins (key = plugin (or instance id), value = reason) |
434 | * @param array $instances if reporting instances rather than whole plugins, pass the array (key = id, value = object) here |
435 | * |
436 | */ |
437 | function portfolio_report_insane($insane, $instances=false) { |
438 | if (empty($insane)) { |
439 | return; |
440 | } |
441 | |
442 | static $pluginstr; |
443 | if (empty($pluginstr)) { |
444 | $pluginstr = get_string('plugin', 'portfolio'); |
445 | } |
446 | if ($instances) { |
447 | $headerstr = get_string('someinstancesdisabled', 'portfolio'); |
448 | } else { |
449 | $headerstr = get_string('somepluginsdisabled', 'portfolio'); |
450 | } |
451 | |
452 | notify($headerstr); |
453 | $table = new StdClass; |
454 | $table->head = array($pluginstr, ''); |
455 | $table->data = array(); |
456 | foreach ($insane as $plugin => $reason) { |
457 | if ($instances) { |
458 | // @todo this isn't working |
459 | // because it seems the new recordset object |
460 | // doesn't implement the key correctly. |
0082ed89 |
461 | // see MDL-15798 |
67a87e7d |
462 | $instance = $instances[$plugin]; |
463 | $plugin = $instance->get('plugin'); |
464 | $name = $instance->get('name'); |
465 | } else { |
466 | $name = $plugin; |
467 | } |
468 | $table->data[] = array($name, get_string($reason, 'portfolio_' . $plugin)); |
469 | } |
470 | print_table($table); |
471 | echo '<br /><br /><br />'; |
472 | } |
473 | |
474 | /** |
475 | * temporary functions until the File API settles |
476 | * to do with moving files around |
477 | */ |
478 | function temp_portfolio_working_directory($unique) { |
479 | return make_upload_directory('temp/portfolio/export/' . $unique); |
480 | } |
481 | |
482 | function temp_portfolio_usertemp_directory($userid) { |
483 | return make_upload_directory('userdata/' . $userid . '/temp'); |
484 | } |
485 | |
486 | /** |
487 | *cleans up the working directory |
488 | */ |
489 | function temp_portfolio_cleanup($unique) { |
490 | $workdir = temp_portfolio_working_directory($unique); |
491 | return remove_dir($workdir); |
492 | } |
493 | |
494 | |
495 | /** |
496 | * base class for the caller |
497 | * places in moodle that want to display |
498 | * the add form should subclass this for their callback. |
499 | */ |
500 | abstract class portfolio_caller_base { |
501 | |
502 | /** |
503 | * stdclass object |
504 | * course that was active during the caller |
505 | */ |
506 | protected $course; |
507 | |
508 | /** |
509 | * named array of export config |
510 | * use{@link set_export_config} and {@link get_export_config} to access |
511 | */ |
512 | protected $exportconfig; |
513 | |
514 | /** |
515 | * stdclass object |
516 | * user currently exporting content |
517 | */ |
518 | protected $user; |
519 | |
520 | /** |
521 | * if this caller wants any additional config items |
522 | * they should be defined here. |
523 | * |
524 | * @param array $mform moodleform object (passed by reference) to add elements to |
525 | * @param object $instance subclass of portfolio_plugin_base |
526 | * @param integer $userid id of user exporting content |
527 | */ |
528 | public function export_config_form(&$mform, $instance) {} |
529 | |
530 | |
531 | /** |
532 | * whether this caller wants any additional |
533 | * config during export (eg options or metadata) |
534 | * |
535 | * @return boolean |
536 | */ |
537 | public function has_export_config() { |
538 | return false; |
539 | } |
540 | |
541 | /** |
542 | * just like the moodle form validation function |
543 | * this is passed in the data array from the form |
544 | * and if a non empty array is returned, form processing will stop. |
545 | * |
546 | * @param array $data data from form. |
547 | * @return array keyvalue pairs - form element => error string |
548 | */ |
549 | public function export_config_validation($data) {} |
550 | |
551 | /** |
552 | * how long does this reasonably expect to take.. |
553 | * should we offer the user the option to wait.. |
554 | * this is deliberately nonstatic so it can take filesize into account |
555 | * the portfolio plugin can override this. |
556 | * (so for exmaple even if a huge file is being sent, |
557 | * the download portfolio plugin doesn't care ) |
558 | * |
559 | * @return string (see PORTFOLIO_TIME_* constants) |
560 | */ |
561 | public abstract function expected_time(); |
562 | |
563 | /** |
564 | * used for displaying the navigation during the export screens. |
565 | * |
566 | * this function must be implemented, but can really return anything. |
567 | * an Exporting.. string will be added on the end. |
568 | * @return array of $extranav and $cm |
569 | * |
570 | * to pass to build_navigation |
571 | * |
572 | */ |
573 | public abstract function get_navigation(); |
574 | |
575 | /* |
576 | * generic getter for properties belonging to this instance |
577 | * <b>outside</b> the subclasses |
578 | * like name, visible etc. |
579 | * |
580 | * @todo determine what to return in the error case |
581 | */ |
582 | public final function get($field) { |
583 | if (property_exists($this, $field)) { |
584 | return $this->{$field}; |
585 | } |
586 | return false; // @todo throw exception? |
587 | } |
588 | |
589 | /** |
590 | * generic setter for properties belonging to this instance |
591 | * <b>outside</b> the subclass |
592 | * like name, visible, etc. |
593 | * |
594 | * @todo determine what to return in the error case |
595 | */ |
596 | public final function set($field, $value) { |
597 | if (property_exists($this, $field)) { |
598 | $this->{$field} = $value; |
599 | $this->dirty = true; |
600 | return true; |
601 | } |
602 | return false; // @todo throw exception? |
603 | |
604 | } |
605 | |
606 | /** |
607 | * stores the config generated at export time. |
608 | * subclasses can retrieve values using |
609 | * {@link get_export_config} |
610 | * |
611 | * @param array $config formdata |
612 | */ |
613 | public final function set_export_config($config) { |
614 | $allowed = array_merge( |
615 | array('wait', 'hidewait', 'format'), |
616 | $this->get_allowed_export_config() |
617 | ); |
618 | foreach ($config as $key => $value) { |
619 | if (!in_array($key, $allowed)) { |
620 | continue; // @ todo throw exception |
621 | } |
622 | $this->exportconfig[$key] = $value; |
623 | } |
624 | } |
625 | |
626 | /** |
627 | * returns a particular export config value. |
628 | * subclasses shouldn't need to override this |
629 | * |
630 | * @param string key the config item to fetch |
631 | * @todo figure out the error cases (item not found or not allowed) |
632 | */ |
633 | public final function get_export_config($key) { |
634 | $allowed = array_merge( |
635 | array('wait', 'hidewait', 'format'), |
636 | $this->get_allowed_export_config() |
637 | ); |
638 | if (!in_array($key, $allowed)) { |
639 | return false; // @todo throw exception? |
640 | } |
641 | if (!array_key_exists($key, $this->exportconfig)) { |
642 | return null; // @todo what to return| |
643 | } |
644 | return $this->exportconfig[$key]; |
645 | } |
646 | |
647 | /** |
648 | * Similar to the other allowed_config functions |
649 | * if you need export config, you must provide |
650 | * a list of what the fields are. |
651 | * |
652 | * even if you want to store stuff during export |
653 | * without displaying a form to the user, |
654 | * you can use this. |
655 | * |
656 | * @return array array of allowed keys |
657 | */ |
658 | public function get_allowed_export_config() { |
659 | return array(); |
660 | } |
661 | |
662 | /** |
663 | * after the user submits their config |
664 | * they're given a confirm screen |
665 | * summarising what they've chosen. |
666 | * |
667 | * this function should return a table of nice strings => values |
668 | * of what they've chosen |
669 | * to be displayed in a table. |
670 | * |
671 | * @return array array of config items. |
672 | */ |
673 | public function get_export_summary() { |
674 | return false; |
675 | } |
676 | |
677 | /** |
678 | * called before the portfolio plugin gets control |
679 | * this function should copy all the files it wants to |
680 | * the temporary directory. |
681 | * |
682 | * @param string $tempdir path to tempdir to put files in |
683 | */ |
684 | public abstract function prepare_package($tempdir); |
685 | |
686 | /** |
687 | * array of formats this caller supports |
688 | * the intersection of what this function returns |
689 | * and what the selected portfolio plugin supports |
690 | * will be used |
691 | * use the constants PORTFOLIO_FORMAT_* |
692 | * |
693 | * @return array list of formats |
694 | */ |
695 | public abstract static function supported_formats(); |
696 | |
697 | /** |
698 | * this is the "return to where you were" url |
699 | * |
700 | * @return string url |
701 | */ |
702 | public abstract function get_return_url(); |
703 | |
704 | /** |
705 | * callback to do whatever capability checks required |
706 | * in the caller (called during the export process |
707 | */ |
708 | public abstract function check_permissions(); |
709 | } |
710 | |
711 | /** |
712 | * the base class for portfolio plguins |
713 | * all plugins must subclass this. |
714 | */ |
715 | abstract class portfolio_plugin_base { |
716 | |
717 | /** |
718 | * boolean |
719 | * whether this object needs writing out to the database |
720 | */ |
721 | protected $dirty; |
722 | |
723 | /** |
724 | * integer |
725 | * id of instance |
726 | */ |
727 | protected $id; |
728 | |
729 | /** |
730 | * string |
731 | * name of instance |
732 | */ |
733 | protected $name; |
734 | |
735 | /** |
736 | * string |
737 | * plugin this instance belongs to |
738 | */ |
739 | protected $plugin; |
740 | |
741 | /** |
742 | * boolean |
743 | * whether this instance is visible or not |
744 | */ |
745 | protected $visible; |
746 | |
747 | /** |
748 | * named array |
749 | * admin configured config |
750 | * use {@link set_config} and {@get_config} to access |
751 | */ |
752 | protected $config; |
753 | |
754 | /** |
755 | * |
756 | * user config cache |
757 | * named array of named arrays |
758 | * keyed on userid and then on config field => value |
759 | * use {@link get_user_config} and {@link set_user_config} to access. |
760 | */ |
761 | protected $userconfig; |
762 | |
763 | /** |
764 | * named array |
765 | * export config during export |
766 | * use {@link get_export_config} and {@link set export_config} to access. |
767 | */ |
768 | protected $exportconfig; |
769 | |
770 | /** |
771 | * stdclass object |
772 | * user currently exporting data |
773 | */ |
774 | protected $user; |
775 | |
776 | |
777 | /** |
778 | * array of formats this portfolio supports |
779 | * the intersection of what this function returns |
780 | * and what the caller supports will be used |
781 | * use the constants PORTFOLIO_FORMAT_* |
782 | * |
783 | * @return array list of formats |
784 | */ |
785 | public abstract static function supported_formats(); |
786 | |
787 | |
788 | /** |
789 | * how long does this reasonably expect to take.. |
790 | * should we offer the user the option to wait.. |
791 | * this is deliberately nonstatic so it can take filesize into account |
792 | * |
793 | * @param string $callertime - what the caller thinks |
794 | * the portfolio plugin instance |
795 | * is given the final say |
796 | * because it might be (for example) download. |
797 | * @return string (see PORTFOLIO_TIME_* constants) |
798 | */ |
799 | public abstract function expected_time($callertime); |
800 | |
801 | /** |
802 | * check sanity of plugin |
803 | * if this function returns something non empty, ALL instances of your plugin |
804 | * will be set to invisble and not be able to be set back until it's fixed |
805 | * |
806 | * @return mixed - string = error string KEY (must be inside plugin_$yourplugin) or 0/false if you're ok |
807 | */ |
808 | public static function plugin_sanity_check() { |
809 | return 0; |
810 | } |
811 | |
812 | /** |
813 | * check sanity of instances |
814 | * if this function returns something non empty, the instance will be |
815 | * set to invislbe and not be able to be set back until it's fixed. |
816 | * |
817 | * @return mixed - string = error string KEY (must be inside plugin_$yourplugin) or 0/false if you're ok |
818 | */ |
819 | public function instance_sanity_check() { |
820 | return 0; |
821 | } |
822 | |
823 | /** |
824 | * does this plugin need any configuration by the administrator? |
825 | * |
826 | * if you override this to return true, |
827 | * you <b>must</b> implement {@link} admin_config_form |
828 | */ |
829 | public static function has_admin_config() { |
830 | return false; |
831 | } |
832 | |
833 | /** |
834 | * can this plugin be configured by the user in their profile? |
835 | * |
836 | * if you override this to return true, |
837 | * you <b>must</b> implement {@link user_config_form |
838 | */ |
839 | public function has_user_config() { |
840 | return false; |
841 | } |
842 | |
843 | /** |
844 | * does this plugin need configuration during export time? |
845 | * |
846 | * if you override this to return true, |
847 | * you <b>must</b> implement {@link export_config_form} |
848 | */ |
849 | public function has_export_config() { |
850 | return false; |
851 | } |
852 | |
853 | /** |
854 | * just like the moodle form validation function |
855 | * this is passed in the data array from the form |
856 | * and if a non empty array is returned, form processing will stop. |
857 | * |
858 | * @param array $data data from form. |
859 | * @return array keyvalue pairs - form element => error string |
860 | */ |
861 | public function export_config_validation() {} |
862 | |
863 | /** |
864 | * just like the moodle form validation function |
865 | * this is passed in the data array from the form |
866 | * and if a non empty array is returned, form processing will stop. |
867 | * |
868 | * @param array $data data from form. |
869 | * @return array keyvalue pairs - form element => error string |
870 | */ |
871 | public function user_config_validation() {} |
872 | |
873 | /** |
874 | * sets the export time config from the moodle form. |
875 | * you can also use this to set export config that |
876 | * isn't actually controlled by the user |
877 | * eg things that your subclasses want to keep in state |
878 | * across the export. |
879 | * keys must be in {@link get_allowed_export_config} |
880 | * |
881 | * this is deliberately not final (see boxnet plugin) |
882 | * |
883 | * @param array $config named array of config items to set. |
884 | */ |
885 | public function set_export_config($config) { |
886 | $allowed = array_merge( |
887 | array('wait', 'format'), |
888 | $this->get_allowed_export_config() |
889 | ); |
890 | foreach ($config as $key => $value) { |
891 | if (!in_array($key, $allowed)) { |
892 | continue; // @ todo throw exception |
893 | } |
894 | $this->exportconfig[$key] = $value; |
895 | } |
896 | } |
897 | |
898 | /** |
899 | * gets an export time config value. |
900 | * subclasses should not override this. |
901 | * |
902 | * @param string key field to fetch |
903 | * |
904 | * @return string config value |
905 | * |
906 | * @todo figure out the error cases |
907 | */ |
908 | public final function get_export_config($key) { |
909 | $allowed = array_merge( |
910 | array('wait', 'format'), |
911 | $this->get_allowed_export_config() |
912 | ); |
913 | if (!in_array($key, $allowed)) { |
914 | return false; // @todo throw exception? |
915 | } |
916 | if (!array_key_exists($key, $this->exportconfig)) { |
917 | return null; // @todo what to return| |
918 | } |
919 | return $this->exportconfig[$key]; |
920 | } |
921 | |
922 | /** |
923 | * after the user submits their config |
924 | * they're given a confirm screen |
925 | * summarising what they've chosen. |
926 | * |
927 | * this function should return a table of nice strings => values |
928 | * of what they've chosen |
929 | * to be displayed in a table. |
930 | * |
931 | * @return array array of config items. |
932 | */ |
933 | public function get_export_summary() { |
934 | return false; |
935 | } |
936 | |
937 | /** |
938 | * called before the portfolio plugin gets control |
939 | * this function should copy all the files it wants to |
940 | * the temporary directory. |
941 | * |
942 | * @param string $tempdir path to temporary directory |
943 | */ |
944 | public abstract function prepare_package($tempdir); |
945 | |
946 | /** |
947 | * this is the function that is responsible for sending |
948 | * the package to the remote system, |
949 | * or whatever request is necessary to initiate the transfer. |
950 | * |
951 | * @return boolean success |
952 | */ |
953 | public abstract function send_package(); |
954 | |
955 | |
956 | /** |
957 | * once everything is done and the user |
958 | * has the finish page displayed to them |
959 | * the base class takes care of printing them |
960 | * "return to where you are" or "continue to portfolio" links |
961 | * this function allows for exta finish options from the plugin |
962 | * |
963 | * @return array named array of links => titles |
964 | */ |
965 | public function get_extra_finish_options() { |
966 | return false; |
967 | } |
968 | |
969 | /** |
970 | * the url for the user to continue to their portfolio |
971 | * |
972 | * @return string url or false. |
973 | */ |
974 | public abstract function get_continue_url(); |
975 | |
976 | /** |
977 | * mform to display to the user in their profile |
978 | * if your plugin can't be configured by the user, |
979 | * (see {@link has_user_config}) |
980 | * don't bother overriding this function |
981 | * |
982 | * @param moodleform $mform passed by reference, add elements to it |
983 | */ |
984 | public function user_config_form(&$mform) {} |
985 | |
986 | /** |
987 | * mform to display to the admin configuring the plugin. |
988 | * if your plugin can't be configured by the admin, |
989 | * (see {@link} has_admin_config) |
990 | * don't bother overriding this function |
991 | * |
992 | * this function can be called statically or non statically, |
993 | * depending on whether it's creating a new instance (statically), |
994 | * or editing an existing one (non statically) |
995 | * |
996 | * @param moodleform $mform passed by reference, add elements to it. |
997 | * @return mixed - if a string is returned, it means the plugin cannot create an instance |
998 | * and the string is an error code |
999 | */ |
1000 | public function admin_config_form(&$mform) {} |
1001 | |
1002 | /** |
1003 | * just like the moodle form validation function |
1004 | * this is passed in the data array from the form |
1005 | * and if a non empty array is returned, form processing will stop. |
1006 | * |
1007 | * @param array $data data from form. |
1008 | * @return array keyvalue pairs - form element => error string |
1009 | */ |
1010 | public static function admin_config_validation($data) {} |
1011 | /** |
1012 | * mform to display to the user exporting data using this plugin. |
1013 | * if your plugin doesn't need user input at this time, |
1014 | * (see {@link has_export_config} |
1015 | * don't bother overrideing this function |
1016 | * |
1017 | * @param moodleform $mform passed by reference, add elements to it. |
1018 | */ |
1019 | public function export_config_form(&$mform) {} |
1020 | |
1021 | /** |
1022 | * override this if your plugin doesn't allow multiple instances |
1023 | * |
1024 | * @return boolean |
1025 | */ |
1026 | public static function allows_multiple() { |
1027 | return true; |
1028 | } |
1029 | |
1030 | /** |
1031 | * |
1032 | * If at any point the caller wants to steal control |
1033 | * it can, by returning something that isn't false |
1034 | * in this function |
1035 | * The controller will redirect to whatever url |
1036 | * this function returns. |
1037 | * Afterwards, you can redirect back to portfolio/add.php?postcontrol=1 |
1038 | * and {@link post_control} is called before the rest of the processing |
1039 | * for the stage is done |
1040 | * |
1041 | * @param int stage to steal control *before* (see constants PARAM_STAGE_*} |
1042 | * |
1043 | * @return boolean or string url |
1044 | */ |
1045 | public function steal_control($stage) { |
1046 | return false; |
1047 | } |
1048 | |
1049 | /** |
1050 | * after a plugin has elected to steal control, |
1051 | * and control returns to portfolio/add.php|postcontrol=1, |
1052 | * this function is called, and passed the stage that was stolen control from |
1053 | * and the request (get and post but not cookie) parameters |
1054 | * this is useful for external systems that need to redirect the user back |
1055 | * with some extra data in the url (like auth tokens etc) |
1056 | * for an example implementation, see boxnet portfolio plugin. |
1057 | * |
1058 | * @param int $stage the stage before control was stolen |
1059 | * @param array $params a merge of $_GET and $_POST |
1060 | * |
1061 | */ |
1062 | |
1063 | public function post_control($stage, $params) { } |
1064 | |
1065 | /** |
1066 | * this function creates a new instance of a plugin |
1067 | * saves it in the database, saves the config |
1068 | * and returns it. |
1069 | * you shouldn't need to override it |
1070 | * unless you're doing something really funky |
1071 | * |
1072 | * @return object subclass of portfolio_plugin_base |
1073 | */ |
1074 | public static function create_instance($plugin, $name, $config) { |
1075 | global $DB, $CFG; |
1076 | $new = (object)array( |
1077 | 'plugin' => $plugin, |
1078 | 'name' => $name, |
1079 | ); |
1080 | $newid = $DB->insert_record('portfolio_instance', $new); |
1081 | require_once($CFG->dirroot . '/portfolio/type/' . $plugin . '/lib.php'); |
1082 | $classname = 'portfolio_plugin_' . $plugin; |
1083 | $obj = new $classname($newid); |
1084 | $obj->set_config($config); |
1085 | return $obj; |
1086 | } |
1087 | |
1088 | /** |
1089 | * construct a plugin instance |
1090 | * subclasses should not need to override this unless they're doing something special |
1091 | * and should call parent::__construct afterwards |
1092 | * |
1093 | * @param int $instanceid id of plugin instance to construct |
1094 | * @param mixed $record stdclass object or named array - use this is you already have the record to avoid another query |
1095 | * |
1096 | * @return object subclass of portfolio_plugin_base |
1097 | */ |
1098 | public function __construct($instanceid, $record=null) { |
1099 | global $DB; |
1100 | if (!$record) { |
1101 | if (!$record = $DB->get_record('portfolio_instance', array('id' => $instanceid))) { |
1102 | return false; // @todo throw exception? |
1103 | } |
1104 | } |
1105 | foreach ((array)$record as $key =>$value) { |
1106 | if (property_exists($this, $key)) { |
1107 | $this->{$key} = $value; |
1108 | } |
1109 | } |
1110 | $this->config = new StdClass; |
1111 | $this->userconfig = array(); |
1112 | $this->exportconfig = array(); |
1113 | foreach ($DB->get_records('portfolio_instance_config', array('instance' => $instanceid)) as $config) { |
1114 | $this->config->{$config->name} = $config->value; |
1115 | } |
1116 | return $this; |
1117 | } |
1118 | |
1119 | /** |
1120 | * a list of fields that can be configured per instance. |
1121 | * this is used for the save handlers of the config form |
1122 | * and as checks in set_config and get_config |
1123 | * |
1124 | * @return array array of strings (config item names) |
1125 | */ |
1126 | public static function get_allowed_config() { |
1127 | return array(); |
1128 | } |
1129 | |
1130 | /** |
1131 | * a list of fields that can be configured by the user. |
1132 | * this is used for the save handlers in the config form |
1133 | * and as checks in set_user_config and get_user_config. |
1134 | * |
1135 | * @return array array of strings (config field names) |
1136 | */ |
1137 | public function get_allowed_user_config() { |
1138 | return array(); |
1139 | } |
1140 | |
1141 | /** |
1142 | * a list of fields that can be configured by the user. |
1143 | * this is used for the save handlers in the config form |
1144 | * and as checks in set_export_config and get_export_config. |
1145 | * |
1146 | * @return array array of strings (config field names) |
1147 | */ |
1148 | public function get_allowed_export_config() { |
1149 | return array(); |
1150 | } |
1151 | |
1152 | /** |
1153 | * saves (or updates) the config stored in portfolio_instance_config. |
1154 | * you shouldn't need to override this unless you're doing something funky. |
1155 | * |
1156 | * @param array $config array of config items. |
1157 | */ |
1158 | public final function set_config($config) { |
1159 | global $DB; |
1160 | foreach ($config as $key => $value) { |
1161 | // try set it in $this first |
1162 | if ($this->set($key, $value)) { |
1163 | continue; |
1164 | } |
1165 | if (!in_array($key, $this->get_allowed_config())) { |
1166 | continue; // @todo throw exception? |
1167 | } |
1168 | if (!isset($this->config->{$key})) { |
1169 | $DB->insert_record('portfolio_instance_config', (object)array( |
1170 | 'instance' => $this->id, |
1171 | 'name' => $key, |
1172 | 'value' => $value, |
1173 | )); |
1174 | } else if ($this->config->{$key} != $value) { |
1175 | $DB->set_field('portfolio_instance_config', 'value', $value, array('name' => $key, 'instance' => $this->id)); |
1176 | } |
1177 | $this->config->{$key} = $value; |
1178 | } |
1179 | return true; // @todo - if we're going to change here to throw exceptions, this can change |
1180 | } |
1181 | |
1182 | /** |
1183 | * gets the value of a particular config item |
1184 | * |
1185 | * @param string $key key to fetch |
1186 | * |
1187 | * @return string the corresponding value |
1188 | * |
1189 | * @todo determine what to return in the error case. |
1190 | */ |
1191 | public final function get_config($key) { |
1192 | if (!in_array($key, $this->get_allowed_config())) { |
1193 | return false; // @todo throw exception? |
1194 | } |
1195 | if (isset($this->config->{$key})) { |
1196 | return $this->config->{$key}; |
1197 | } |
1198 | return false; // @todo null? |
1199 | } |
1200 | |
1201 | /** |
1202 | * get the value of a config item for a particular user |
1203 | * |
1204 | * @param string $key key to fetch |
1205 | * @param integer $userid id of user (defaults to current) |
1206 | * |
1207 | * @return string the corresponding value |
1208 | * |
1209 | * @todo determine what to return in the error case |
1210 | */ |
1211 | public final function get_user_config($key, $userid=0) { |
1212 | global $DB; |
1213 | |
1214 | if (empty($userid)) { |
1215 | $userid = $this->user->id; |
1216 | } |
1217 | |
1218 | if ($key != 'visible') { // handled by the parent class |
1219 | if (!in_array($key, $this->get_allowed_user_config())) { |
1220 | return false; // @todo throw exception? |
1221 | } |
1222 | } |
1223 | if (!array_key_exists($userid, $this->userconfig)) { |
1224 | $this->userconfig[$userid] = (object)array_fill_keys(array_merge(array('visible'), $this->get_allowed_user_config()), null); |
1225 | foreach ($DB->get_records('portfolio_instance_user', array('instance' => $this->id, 'userid' => $userid)) as $config) { |
1226 | $this->userconfig[$userid]->{$config->name} = $config->value; |
1227 | } |
1228 | } |
1229 | if ($this->userconfig[$userid]->visible === null) { |
1230 | $this->set_user_config(array('visible' => 1), $userid); |
1231 | } |
1232 | return $this->userconfig[$userid]->{$key}; |
1233 | |
1234 | } |
1235 | |
1236 | /** |
1237 | * |
1238 | * sets config options for a given user |
1239 | * |
1240 | * @param mixed $config array or stdclass containing key/value pairs to set |
1241 | * @param integer $userid userid to set config for (defaults to current) |
1242 | * |
1243 | * @todo determine what to return in the error case |
1244 | */ |
1245 | public final function set_user_config($config, $userid=0) { |
1246 | global $DB; |
1247 | |
1248 | if (empty($userid)) { |
1249 | $userid = $this->user->id; |
1250 | } |
1251 | |
1252 | foreach ($config as $key => $value) { |
1253 | if ($key != 'visible' && !in_array($key, $this->get_allowed_user_config())) { |
1254 | continue; // @todo throw exception? |
1255 | } |
1256 | if (!$existing = $DB->get_record('portfolio_instance_user', array('instance'=> $this->id, 'userid' => $userid, 'name' => $key))) { |
1257 | $DB->insert_record('portfolio_instance_user', (object)array( |
1258 | 'instance' => $this->id, |
1259 | 'name' => $key, |
1260 | 'value' => $value, |
1261 | 'userid' => $userid, |
1262 | )); |
1263 | } else if ($existing->value != $value) { |
1264 | $DB->set_field('portfolio_instance_user', 'value', $value, array('name' => $key, 'instance' => $this->id, 'userid' => $userid)); |
1265 | } |
1266 | $this->userconfig[$userid]->{$key} = $value; |
1267 | } |
1268 | return true; // @todo |
1269 | |
1270 | } |
1271 | |
1272 | /** |
1273 | * generic getter for properties belonging to this instance |
1274 | * <b>outside</b> the subclasses |
1275 | * like name, visible etc. |
1276 | * |
1277 | * @todo determine what to return in the error case |
1278 | */ |
1279 | public final function get($field) { |
1280 | if (property_exists($this, $field)) { |
1281 | return $this->{$field}; |
1282 | } |
1283 | return false; // @todo throw exception? |
1284 | } |
1285 | |
1286 | /** |
1287 | * generic setter for properties belonging to this instance |
1288 | * <b>outside</b> the subclass |
1289 | * like name, visible, etc. |
1290 | * |
1291 | * @todo determine what to return in the error case |
1292 | */ |
1293 | public final function set($field, $value) { |
1294 | if (property_exists($this, $field)) { |
1295 | $this->{$field} = $value; |
1296 | $this->dirty = true; |
1297 | return true; |
1298 | } |
1299 | return false; // @todo throw exception? |
1300 | |
1301 | } |
1302 | |
1303 | /** |
1304 | * saves stuff that's been stored in the object to the database |
1305 | * you shouldn't need to override this |
1306 | * unless you're doing something really funky. |
1307 | * and if so, call parent::save when you're done. |
1308 | */ |
1309 | public function save() { |
1310 | global $DB; |
1311 | if (!$this->dirty) { |
1312 | return true; |
1313 | } |
1314 | $fordb = new StdClass(); |
1315 | foreach (array('id', 'name', 'plugin', 'visible') as $field) { |
1316 | $fordb->{$field} = $this->{$field}; |
1317 | } |
1318 | $DB->update_record('portfolio_instance', $fordb); |
1319 | $this->dirty = false; |
1320 | return true; |
1321 | } |
1322 | |
1323 | /** |
1324 | * deletes everything from the database about this plugin instance. |
1325 | * you shouldn't need to override this unless you're storing stuff |
1326 | * in your own tables. and if so, call parent::delete when you're done. |
1327 | */ |
1328 | public function delete() { |
1329 | global $DB; |
1330 | $DB->delete_records('portfolio_instance_config', array('instance' => $this->get('id'))); |
1331 | $DB->delete_records('portfolio_instance_user', array('instance' => $this->get('id'))); |
1332 | $DB->delete_records('portfolio_instance', array('id' => $this->get('id'))); |
1333 | $this->dirty = false; |
1334 | return true; |
1335 | } |
1336 | } |
1337 | |
1338 | /** |
1339 | * this is the form that is actually used while exporting. |
1340 | * plugins and callers don't get to define their own class |
1341 | * as we have to handle form elements from both places |
1342 | * see the docs for portfolio_plugin_base and portfolio_caller for more information |
1343 | */ |
1344 | final class portfolio_export_form extends moodleform { |
1345 | |
1346 | public function definition() { |
1347 | |
1348 | $mform =& $this->_form; |
1349 | $mform->addElement('hidden', 'stage', PORTFOLIO_STAGE_CONFIG); |
1350 | $mform->addElement('hidden', 'instance', $this->_customdata['instance']->get('id')); |
1351 | |
1352 | if (array_key_exists('formats', $this->_customdata) && is_array($this->_customdata['formats'])) { |
1353 | if (count($this->_customdata['formats']) > 1) { |
1354 | $options = array(); |
1355 | foreach ($this->_customdata['formats'] as $key) { |
1356 | $options[$key] = get_string('format_' . $key, 'portfolio'); |
1357 | } |
1358 | $mform->addElement('select', 'format', get_string('availableformats', 'portfolio'), $options); |
1359 | } else { |
1360 | $f = array_shift($this->_customdata['formats']); |
1361 | $mform->addElement('hidden', 'format', $f); |
1362 | } |
1363 | } |
1364 | |
1365 | if (array_key_exists('expectedtime', $this->_customdata) && $this->_customdata['expectedtime'] != PORTFOLIO_TIME_LOW) { |
1366 | //$mform->addElement('select', 'wait', get_string('waitlevel_' . $this->_customdata['expectedtime'], 'portfolio'), $options); |
1367 | |
1368 | |
1369 | $radioarray = array(); |
1370 | $radioarray[] = &MoodleQuickForm::createElement('radio', 'wait', '', get_string('wait', 'portfolio'), 1); |
1371 | $radioarray[] = &MoodleQuickForm::createElement('radio', 'wait', '', get_string('dontwait', 'portfolio'), 0); |
1372 | $mform->addGroup($radioarray, 'radioar', get_string('wanttowait_' . $this->_customdata['expectedtime'], 'portfolio') , array(' '), false); |
1373 | |
1374 | $mform->setDefault('wait', 0); |
1375 | } |
1376 | else { |
1377 | $mform->addElement('hidden', 'wait', 1); |
1378 | } |
1379 | |
1380 | if (array_key_exists('plugin', $this->_customdata) && is_object($this->_customdata['plugin'])) { |
1381 | $this->_customdata['plugin']->export_config_form($mform, $this->_customdata['userid']); |
1382 | } |
1383 | |
1384 | if (array_key_exists('caller', $this->_customdata) && is_object($this->_customdata['caller'])) { |
1385 | $this->_customdata['caller']->export_config_form($mform, $this->_customdata['instance'], $this->_customdata['userid']); |
1386 | } |
1387 | |
1388 | $this->add_action_buttons(true, get_string('next')); |
1389 | } |
1390 | |
1391 | public function validation($data) { |
1392 | |
1393 | $errors = array(); |
1394 | |
1395 | if (array_key_exists('plugin', $this->_customdata) && is_object($this->_customdata['plugin'])) { |
1396 | $pluginerrors = $this->_customdata['plugin']->export_config_validation($data); |
1397 | if (is_array($pluginerrors)) { |
1398 | $errors = $pluginerrors; |
1399 | } |
1400 | } |
1401 | if (array_key_exists('caller', $this->_customdata) && is_object($this->_customdata['caller'])) { |
1402 | $callererrors = $this->_customdata['caller']->export_config_validation($data); |
1403 | if (is_array($callererrors)) { |
1404 | $errors = array_merge($errors, $callererrors); |
1405 | } |
1406 | } |
1407 | return $errors; |
1408 | } |
1409 | } |
1410 | |
1411 | /** |
1412 | * this form is extendable by plugins |
1413 | * who want the admin to be able to configure |
1414 | * more than just the name of the instance. |
1415 | * this is NOT done by subclassing this class, |
1416 | * see the docs for portfolio_plugin_base for more information |
1417 | */ |
1418 | final class portfolio_admin_form extends moodleform { |
1419 | |
1420 | protected $instance; |
1421 | protected $plugin; |
1422 | |
1423 | public function definition() { |
1424 | global $CFG; |
1425 | $this->plugin = $this->_customdata['plugin']; |
1426 | $this->instance = (isset($this->_customdata['instance']) |
1427 | && is_subclass_of($this->_customdata['instance'], 'portfolio_plugin_base')) |
1428 | ? $this->_customdata['instance'] : null; |
1429 | |
1430 | $mform =& $this->_form; |
1431 | $strrequired = get_string('required'); |
1432 | |
1433 | $mform->addElement('hidden', 'edit', ($this->instance) ? $this->instance->get('id') : 0); |
1434 | $mform->addElement('hidden', 'new', $this->plugin); |
1435 | $mform->addElement('hidden', 'plugin', $this->plugin); |
1436 | |
1437 | $mform->addElement('text', 'name', get_string('name'), 'maxlength="100" size="30"'); |
1438 | $mform->addRule('name', $strrequired, 'required', null, 'client'); |
1439 | |
1440 | // let the plugin add the fields they want (either statically or not) |
1441 | if (portfolio_static_function($this->plugin, 'has_admin_config')) { |
1442 | if (!$this->instance) { |
1443 | $result = portfolio_static_function($this->plugin, 'admin_config_form', $mform); |
1444 | } else { |
1445 | $result = $this->instance->admin_config_form($mform); |
1446 | } |
1447 | } |
1448 | |
1449 | if (isset($result) && is_string($result)) { // something went wrong, stop |
1450 | return $this->raise_error($result, 'portfolio_' . $this->plugin, $CFG->wwwroot . '/' . $CFG->admin . '/portfolio.php'); |
1451 | } |
1452 | |
1453 | // and set the data if we have some. |
1454 | if ($this->instance) { |
1455 | $data = array('name' => $this->instance->get('name')); |
1456 | foreach ($this->instance->get_allowed_config() as $config) { |
1457 | $data[$config] = $this->instance->get_config($config); |
1458 | } |
1459 | $this->set_data($data); |
1460 | } |
1461 | $this->add_action_buttons(true, get_string('save', 'portfolio')); |
1462 | } |
1463 | |
1464 | public function validation($data) { |
1465 | global $DB; |
1466 | |
1467 | $errors = array(); |
1468 | if ($DB->count_records('portfolio_instance', array('name' => $data['name'], 'plugin' => $data['plugin'])) > 1) { |
1469 | $errors = array('name' => get_string('err_uniquename', 'portfolio')); |
1470 | } |
1471 | |
1472 | $pluginerrors = array(); |
1473 | if ($this->instance) { |
1474 | $pluginerrors = $this->instance->admin_config_validation($data); |
1475 | } |
1476 | else { |
1477 | $pluginerrors = portfolio_static_function($this->plugin, 'admin_config_validation', $data); |
1478 | } |
1479 | if (is_array($pluginerrors)) { |
1480 | $errors = array_merge($errors, $pluginerrors); |
1481 | } |
1482 | return $errors; |
1483 | } |
1484 | } |
1485 | |
1486 | /** |
1487 | * this is the form for letting the user configure an instance of a plugin. |
1488 | * in order to extend this, you don't subclass this in the plugin.. |
1489 | * see the docs in portfolio_plugin_base for more information |
1490 | */ |
1491 | final class portfolio_user_form extends moodleform { |
1492 | |
1493 | protected $instance; |
1494 | protected $userid; |
1495 | |
1496 | public function definition() { |
1497 | $this->instance = $this->_customdata['instance']; |
1498 | $this->userid = $this->_customdata['userid']; |
1499 | |
1500 | $this->_form->addElement('hidden', 'config', $this->instance->get('id')); |
1501 | |
1502 | $this->instance->user_config_form($this->_form, $this->userid); |
1503 | |
1504 | $data = array(); |
1505 | foreach ($this->instance->get_allowed_user_config() as $config) { |
1506 | $data[$config] = $this->instance->get_user_config($config, $this->userid); |
1507 | } |
1508 | $this->set_data($data); |
1509 | $this->add_action_buttons(true, get_string('save', 'portfolio')); |
1510 | } |
1511 | |
1512 | public function validation($data) { |
1513 | |
1514 | $errors = $this->instance->user_config_validation($data); |
1515 | |
1516 | } |
1517 | } |
1518 | |
1519 | /** |
1520 | * |
1521 | * Class that handles the various stages of the actual export |
1522 | */ |
1523 | final class portfolio_exporter { |
1524 | |
1525 | private $currentstage; |
1526 | private $caller; |
1527 | private $instance; |
1528 | private $noconfig; |
1529 | private $navigation; |
1530 | private $uniquekey; |
1531 | private $tempdir; |
1532 | private $user; |
1533 | |
1534 | public $instancefile; |
1535 | public $callerfile; |
1536 | |
1537 | /** |
1538 | * construct a new exporter for use |
1539 | * |
1540 | * @param portfolio_plugin_base subclass $instance portfolio instance (passed by reference) |
1541 | * @param portfolio_caller_base subclass $caller portfolio caller (passed by reference) |
1542 | * @param string $navigation result of build_navigation (passed to print_header) |
1543 | */ |
1544 | public function __construct(&$instance, &$caller, $callerfile, $navigation) { |
1545 | $this->instance =& $instance; |
1546 | $this->caller =& $caller; |
1547 | if ($instance) { |
1548 | $this->instancefile = 'portfolio/type/' . $instance->get('plugin') . '/lib.php'; |
1549 | } |
1550 | $this->callerfile = $callerfile; |
1551 | $this->stage = PORTFOLIO_STAGE_CONFIG; |
1552 | $this->navigation = $navigation; |
1553 | } |
1554 | |
1555 | /* |
1556 | * generic getter for properties belonging to this instance |
1557 | * <b>outside</b> the subclasses |
1558 | * like name, visible etc. |
1559 | * |
1560 | * @todo determine what to return in the error case |
1561 | */ |
1562 | public function get($field) { |
1563 | if (property_exists($this, $field)) { |
1564 | return $this->{$field}; |
1565 | } |
1566 | return false; // @todo throw exception? |
1567 | } |
1568 | |
1569 | /** |
1570 | * generic setter for properties belonging to this instance |
1571 | * <b>outside</b> the subclass |
1572 | * like name, visible, etc. |
1573 | * |
1574 | * @todo determine what to return in the error case |
1575 | */ |
1576 | |
1577 | public function set($field, $value) { |
1578 | if (property_exists($this, $field)) { |
1579 | $this->{$field} = $value; |
1580 | if ($field == 'instance') { |
1581 | $this->instancefile = 'portfolio/type/' . $this->instance->get('plugin') . '/lib.php'; |
1582 | } |
1583 | $this->dirty = true; |
1584 | return true; |
1585 | } |
1586 | return false; // @todo throw exception? |
1587 | |
1588 | } |
1589 | /** |
1590 | * process the given stage calling whatever functions are necessary |
1591 | * |
1592 | * @param int $stage (see PORTFOLIO_STAGE_* constants) |
1593 | * @param boolean $alreadystolen used to avoid letting plugins steal control twice. |
1594 | * |
1595 | * @return boolean whether or not to process the next stage. this is important as the function is called recursively. |
1596 | */ |
1597 | public function process_stage($stage, $alreadystolen=false) { |
1598 | global $SESSION; |
1599 | if (!$alreadystolen && $url = $this->instance->steal_control($stage)) { |
1600 | $SESSION->portfolio->stagepresteal = $stage; |
1601 | redirect($url); |
1602 | break; |
1603 | } |
1604 | |
1605 | $waiting = $this->instance->get_export_config('wait'); |
1606 | if ($stage > PORTFOLIO_STAGE_QUEUEORWAIT && empty($waiting)) { |
1607 | $stage = PORTFOLIO_STAGE_FINISHED; |
1608 | } |
1609 | $functionmap = array( |
1610 | PORTFOLIO_STAGE_CONFIG => 'config', |
1611 | PORTFOLIO_STAGE_CONFIRM => 'confirm', |
1612 | PORTFOLIO_STAGE_QUEUEORWAIT => 'queueorwait', |
1613 | PORTFOLIO_STAGE_PACKAGE => 'package', |
1614 | PORTFOLIO_STAGE_CLEANUP => 'cleanup', |
1615 | PORTFOLIO_STAGE_SEND => 'send', |
1616 | PORTFOLIO_STAGE_FINISHED => 'finished' |
1617 | ); |
1618 | |
1619 | $function = 'process_stage_' . $functionmap[$stage]; |
1620 | if ($this->$function()) { |
1621 | // if we get through here it means control was returned |
1622 | // as opposed to wanting to stop processing |
1623 | // eg to wait for user input. |
1624 | $stage++; |
1625 | return $this->process_stage($stage); |
1626 | } |
1627 | return false; |
1628 | } |
1629 | |
1630 | /** |
1631 | * helper function to return the portfolio instance |
1632 | * |
1633 | * @return portfolio_plugin_base subclass |
1634 | */ |
1635 | public function instance() { |
1636 | return $this->instance; |
1637 | } |
1638 | |
1639 | /** |
1640 | * helper function to return the caller object |
1641 | * |
1642 | * @return portfolio_caller_base subclass |
1643 | */ |
1644 | public function caller() { |
1645 | return $this->caller; |
1646 | } |
1647 | |
1648 | /** |
1649 | * processes the 'config' stage of the export |
1650 | * |
1651 | * @return boolean whether or not to process the next stage. this is important as the control function is called recursively. |
1652 | */ |
1653 | public function process_stage_config() { |
1654 | |
1655 | global $SESSION; |
1656 | |
1657 | $pluginobj = $callerobj = null; |
1658 | if ($this->instance->has_export_config()) { |
1659 | $pluginobj = $this->instance; |
1660 | } |
1661 | if ($this->caller->has_export_config()) { |
1662 | $callerobj = $this->caller; |
1663 | } |
1664 | $formats = array_intersect($this->instance->supported_formats(), $this->caller->supported_formats()); |
1665 | $expectedtime = $this->instance->expected_time($this->caller->expected_time()); |
1666 | if (count($formats) == 0) { |
1667 | // something went wrong, we should not have gotten this far. |
1668 | return $this->raise_error('nocommonformats', 'portfolio', get_class($caller)); |
1669 | } |
1670 | // even if neither plugin or caller wants any config, we have to let the user choose their format, and decide to wait. |
1671 | if ($pluginobj || $callerobj || count($formats) > 1 || $expectedtime != PORTFOLIO_TIME_LOW) { |
1672 | $customdata = array( |
1673 | 'instance' => $this->instance, |
1674 | 'plugin' => $pluginobj, |
1675 | 'caller' => $callerobj, |
1676 | 'userid' => $this->user->id, |
1677 | 'formats' => $formats, |
1678 | 'expectedtime' => $expectedtime, |
1679 | ); |
1680 | $mform = new portfolio_export_form('', $customdata); |
1681 | if ($mform->is_cancelled()){ |
1682 | unset($SESSION->portfolio); |
1683 | redirect($this->caller->get_return_url()); |
1684 | exit; |
1685 | } else if ($fromform = $mform->get_data()){ |
1686 | if (!confirm_sesskey()) { |
1687 | return $this->raise_error('confirmsesskeybad', '', $caller->get_return_url()); |
1688 | } |
1689 | $pluginbits = array(); |
1690 | $callerbits = array(); |
1691 | foreach ($fromform as $key => $value) { |
1692 | if (strpos($key, 'plugin_') === 0) { |
1693 | $pluginbits[substr($key, 7)] = $value; |
1694 | } else if (strpos($key, 'caller_') === 0) { |
1695 | $callerbits[substr($key, 7)] = $value; |
1696 | } |
1697 | } |
1698 | $callerbits['format'] = $pluginbits['format'] = $fromform->format; |
1699 | $pluginbits['wait'] = $fromform->wait; |
1700 | if ($expectedtime = PORTFOLIO_TIME_LOW) { |
1701 | $pluginbits['wait'] = 1; |
1702 | $pluginbits['hidewait'] = 1; |
1703 | } |
1704 | $this->caller->set_export_config($callerbits); |
1705 | $this->instance->set_export_config($pluginbits); |
1706 | return true; |
1707 | } else { |
1708 | $this->print_header(); |
1709 | print_heading(get_string('configexport' ,'portfolio')); |
1710 | print_simple_box_start(); |
1711 | $mform->display(); |
1712 | print_simple_box_end(); |
1713 | print_footer(); |
1714 | return false;; |
1715 | } |
1716 | } else { |
1717 | $this->noexportconfig = true; |
1718 | $this->instance->set_export_config(array('wait' => 1)); |
1719 | return true; |
1720 | // do not break - fall through to confirm |
1721 | } |
1722 | } |
1723 | |
1724 | |
1725 | /** |
1726 | * processes the 'confirm' stage of the export |
1727 | * |
1728 | * @return boolean whether or not to process the next stage. this is important as the control function is called recursively. |
1729 | */ |
1730 | public function process_stage_confirm() { |
1731 | global $CFG; |
1732 | if ($this->noexportconfig) { |
1733 | return true; |
1734 | } |
1735 | $strconfirm = get_string('confirmexport', 'portfolio'); |
1736 | $yesurl = $CFG->wwwroot . '/portfolio/add.php?stage=' . PORTFOLIO_STAGE_QUEUEORWAIT; |
1737 | $nourl = $this->caller->get_return_url(); |
1738 | $this->print_header(); |
1739 | print_heading($strconfirm); |
1740 | print_simple_box_start(); |
1741 | print_heading(get_string('confirmsummary', 'portfolio'), '', 4); |
1742 | $mainsummary = array( |
1743 | // @todo do something cleverer about wait |
1744 | get_string('selectedformat', 'portfolio') => get_string('format_' . $this->instance->get_export_config('format'), 'portfolio'), |
1745 | ); |
1746 | if (!$this->instance->get_export_config('hidewait')) { |
1747 | $mainsummary[get_string('selectedwait', 'portfolio')] = get_string($this->instance->get_export_config('wait') ? 'yes' : 'no'); |
1748 | } |
1749 | if (!$csummary = $this->caller->get_export_summary()) { |
1750 | $csummary = array(); |
1751 | } |
1752 | if (!$isummary = $this->instance->get_export_summary()) { |
1753 | $isummary = array(); |
1754 | } |
1755 | $mainsummary = array_merge($mainsummary, $csummary, $isummary); |
1756 | foreach ($mainsummary as $string => $value) { |
1757 | echo '<b>' . $string . '</b>:' . $value . '<br />' . "\n"; |
1758 | } |
1759 | notice_yesno($strconfirm, $yesurl, $nourl); |
1760 | print_simple_box_end(); |
1761 | print_footer(); |
1762 | return false; |
1763 | } |
1764 | |
1765 | /** |
1766 | * processes the 'queueornext' stage of the export |
1767 | * |
1768 | * @return boolean whether or not to process the next stage. this is important as the control function is called recursively. |
1769 | */ |
1770 | public function process_stage_queueorwait() { |
1771 | global $SESSION; |
1772 | $wait = $this->instance->get_export_config('wait'); |
1773 | if (empty($wait)) { |
1774 | error_log(print_r(serialize($this), true)); |
1775 | events_trigger('portfolio_send', $this); |
1776 | unset($SESSION->portfolio); |
1777 | return $this->process_stage_finished(); |
1778 | } |
1779 | return true; |
1780 | } |
1781 | |
1782 | /** |
1783 | * processes the 'package' stage of the export |
1784 | * |
1785 | * @return boolean whether or not to process the next stage. this is important as the control function is called recursively. |
1786 | */ |
1787 | public function process_stage_package() { |
1788 | // now we've agreed on a format, |
1789 | // the caller is given control to package it up however it wants |
1790 | // and then the portfolio plugin is given control to do whatever it wants. |
1791 | $unique = $this->user->id . '-' . time(); |
1792 | $tempdir = temp_portfolio_working_directory($unique); |
1793 | $this->uniquekey = $unique; |
1794 | $this->tempdir = $tempdir; |
1795 | if (!$this->caller->prepare_package($tempdir)) { |
1796 | return $this->raise_error('callercouldnotpackage', 'portfolio'); |
1797 | } |
1798 | if (!$package = $this->instance->prepare_package($tempdir)) { |
1799 | return $this->raise_error('plugincouldnotpackage', 'portfolio'); |
1800 | } |
1801 | return true; |
1802 | } |
1803 | |
1804 | /** |
1805 | * processes the 'cleanup' stage of the export |
1806 | * |
1807 | * @return boolean whether or not to process the next stage. this is important as the control function is called recursively. |
1808 | */ |
1809 | public function process_stage_cleanup() { |
1810 | global $CFG; |
1811 | // @todo this is unpleasant. fix it. |
1812 | require_once($CFG->dirroot . '/backup/lib.php'); |
1813 | delete_dir_contents($this->tempdir); |
1814 | // @todo maybe add a hook in the plugin(s) |
1815 | return true; |
1816 | } |
1817 | |
1818 | /** |
1819 | * processes the 'send' stage of the export |
1820 | * |
1821 | * @return boolean whether or not to process the next stage. this is important as the control function is called recursively. |
1822 | */ |
1823 | public function process_stage_send() { |
1824 | // send the file |
1825 | if (!$this->instance->send_package()) { |
1826 | return $this->raise_error('failedtosendpackage', 'portfolio'); |
1827 | } |
1828 | return true; |
1829 | } |
1830 | |
1831 | /** |
1832 | * processes the 'finish' stage of the export |
1833 | * |
1834 | * @return boolean whether or not to process the next stage. this is important as the control function is called recursively. |
1835 | */ |
1836 | public function process_stage_finished() { |
1837 | global $SESSION; |
1838 | $returnurl = $this->caller->get_return_url(); |
1839 | $continueurl = $this->instance->get_continue_url(); |
1840 | $extras = $this->instance->get_extra_finish_options(); |
1841 | |
1842 | $this->print_header(); |
1843 | //@todo do something different here if we're queueing. |
1844 | print_heading(get_string('exportcomplete', 'portfolio')); |
1845 | if ($returnurl) { |
1846 | echo '<a href="' . $returnurl . '">' . get_string('returntowhereyouwere', 'portfolio') . '</a><br />'; |
1847 | } |
1848 | if ($continueurl) { |
1849 | echo '<a href="' . $continueurl . '">' . get_string('continuetoportfolio', 'portfolio') . '</a><br />'; |
1850 | } |
1851 | if (is_array($extras)) { |
1852 | foreach ($extras as $link => $string) { |
1853 | echo '<a href="' . $link . '">' . $string . '</a><br />'; |
1854 | } |
1855 | } |
1856 | print_footer(); |
1857 | unset($SESSION->portfolio); |
1858 | return false; |
1859 | } |
1860 | |
1861 | |
1862 | /** |
1863 | * local print header function to be reused across the export |
1864 | * |
1865 | * @param string $titlestring key for a portfolio language string |
1866 | * @param string $headerstring key for a portfolio language string |
1867 | */ |
1868 | public function print_header($titlestr='exporting', $headerstr='exporting') { |
1869 | $titlestr = get_string($titlestr, 'portfolio'); |
1870 | $headerstr = get_string($headerstr, 'portfolio'); |
1871 | |
1872 | print_header($titlestr, $headerstr, $this->navigation); |
1873 | } |
1874 | |
1875 | /** |
1876 | * error handler - decides whether we're running interactively or not |
1877 | * and behaves accordingly |
1878 | */ |
1879 | public static function raise_error($string, $module, $continue=null) { |
1880 | if (defined('FULLME') && FULLME == 'cron') { |
1881 | debugging(get_string($string, $module)); |
1882 | return false; |
1883 | } |
1884 | global $SESSION; |
1885 | unset($SESSION->portfolio); |
1886 | print_error($string, $module, $continue); |
1887 | } |
1888 | } |
1889 | |
1890 | /** |
1891 | * event handler for the portfolio_send event |
1892 | */ |
1893 | function portfolio_handle_event($eventdata) { |
1894 | global $CFG; |
1895 | require_once($CFG->dirroot . '/' . $eventdata->instancefile); |
1896 | require_once($CFG->dirroot . '/' . $eventdata->callerfile); |
1897 | $exporter = unserialize(serialize($eventdata)); |
1898 | $exporter->process_stage_package(); |
1899 | $exporter->process_stage_send(); |
1900 | $exporter->process_stage_cleanup(); |
1901 | return true; |
1902 | } |
1903 | |
1904 | ?> |
1905 | |